@d5techs/3dgs-lib 1.4.25 → 1.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3dgs-lib.cjs +205 -32
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +205 -32
- package/dist/3dgs-lib.js.map +1 -1
- package/dist/App.d.ts +4 -0
- package/dist/core/OrbitControls.d.ts +1 -0
- package/dist/gs/SOGEncoder.d.ts +1 -1
- package/package.json +1 -1
package/dist/3dgs-lib.js
CHANGED
|
@@ -651,6 +651,8 @@ const _OrbitControls = class _OrbitControls {
|
|
|
651
651
|
__publicField(this, "lastTouchCenter", { x: 0, y: 0 });
|
|
652
652
|
// 启用/禁用
|
|
653
653
|
__publicField(this, "enabled", true);
|
|
654
|
+
// 双击拾取回调:返回世界坐标则聚焦,返回 null 则忽略
|
|
655
|
+
__publicField(this, "pickWorldPosition", null);
|
|
654
656
|
// 绑定的事件处理函数(用于移除监听器)
|
|
655
657
|
__publicField(this, "boundOnMouseDown");
|
|
656
658
|
__publicField(this, "boundOnMouseMove");
|
|
@@ -837,6 +839,13 @@ const _OrbitControls = class _OrbitControls {
|
|
|
837
839
|
}
|
|
838
840
|
onDblClick(e) {
|
|
839
841
|
if (!this.enabled) return;
|
|
842
|
+
if (this.pickWorldPosition) {
|
|
843
|
+
const hit = this.pickWorldPosition(e.clientX, e.clientY);
|
|
844
|
+
if (hit) {
|
|
845
|
+
this.animateToTarget(hit);
|
|
846
|
+
}
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
840
849
|
const rect = this.canvas.getBoundingClientRect();
|
|
841
850
|
const ndcX = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
842
851
|
const ndcY = -((e.clientY - rect.top) / rect.height * 2 - 1);
|
|
@@ -6212,7 +6221,10 @@ function buildCodebook(values, k = 256) {
|
|
|
6212
6221
|
for (let i = 0; i < N; i++) {
|
|
6213
6222
|
const uniformPos = (sorted[i] - vMin) / vRange;
|
|
6214
6223
|
const quantilePos = i / N;
|
|
6215
|
-
const bin = Math.min(
|
|
6224
|
+
const bin = Math.min(
|
|
6225
|
+
H - 1,
|
|
6226
|
+
Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos))
|
|
6227
|
+
);
|
|
6216
6228
|
counts[bin]++;
|
|
6217
6229
|
sums[bin] += sorted[i];
|
|
6218
6230
|
}
|
|
@@ -6289,7 +6301,8 @@ function buildCodebook(values, k = 256) {
|
|
|
6289
6301
|
centroidValues.sort();
|
|
6290
6302
|
const result = new Array(k);
|
|
6291
6303
|
for (let i = 0; i < effectiveK; i++) result[i] = centroidValues[i];
|
|
6292
|
-
for (let i = effectiveK; i < k; i++)
|
|
6304
|
+
for (let i = effectiveK; i < k; i++)
|
|
6305
|
+
result[i] = centroidValues[effectiveK - 1];
|
|
6293
6306
|
return result;
|
|
6294
6307
|
}
|
|
6295
6308
|
function findNearest(sortedCB, value) {
|
|
@@ -6417,27 +6430,66 @@ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, num
|
|
|
6417
6430
|
const WORKGROUP_SIZE2 = 64;
|
|
6418
6431
|
const BATCH_SIZE = 1024 * WORKGROUP_SIZE2;
|
|
6419
6432
|
const numBatches = Math.ceil(numPoints / BATCH_SIZE);
|
|
6420
|
-
const shaderModule = device.createShaderModule({
|
|
6433
|
+
const shaderModule = device.createShaderModule({
|
|
6434
|
+
code: getGpuClusterShader(dim)
|
|
6435
|
+
});
|
|
6421
6436
|
const bindGroupLayout = device.createBindGroupLayout({
|
|
6422
6437
|
entries: [
|
|
6423
|
-
{
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6438
|
+
{
|
|
6439
|
+
binding: 0,
|
|
6440
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6441
|
+
buffer: { type: "uniform" }
|
|
6442
|
+
},
|
|
6443
|
+
{
|
|
6444
|
+
binding: 1,
|
|
6445
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6446
|
+
buffer: { type: "read-only-storage" }
|
|
6447
|
+
},
|
|
6448
|
+
{
|
|
6449
|
+
binding: 2,
|
|
6450
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6451
|
+
buffer: { type: "read-only-storage" }
|
|
6452
|
+
},
|
|
6453
|
+
{
|
|
6454
|
+
binding: 3,
|
|
6455
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6456
|
+
buffer: { type: "storage" }
|
|
6457
|
+
}
|
|
6427
6458
|
]
|
|
6428
6459
|
});
|
|
6429
6460
|
const pipeline = device.createComputePipeline({
|
|
6430
|
-
layout: device.createPipelineLayout({
|
|
6461
|
+
layout: device.createPipelineLayout({
|
|
6462
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
6463
|
+
}),
|
|
6431
6464
|
compute: { module: shaderModule, entryPoint: "main" }
|
|
6432
6465
|
});
|
|
6433
|
-
const uniformBuf = device.createBuffer({
|
|
6434
|
-
|
|
6435
|
-
|
|
6466
|
+
const uniformBuf = device.createBuffer({
|
|
6467
|
+
size: 8,
|
|
6468
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
6469
|
+
});
|
|
6470
|
+
const centroidsBuf = device.createBuffer({
|
|
6471
|
+
size: centroidsFlat.byteLength,
|
|
6472
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6473
|
+
});
|
|
6474
|
+
device.queue.writeBuffer(
|
|
6475
|
+
centroidsBuf,
|
|
6476
|
+
0,
|
|
6477
|
+
centroidsFlat.buffer
|
|
6478
|
+
);
|
|
6436
6479
|
const pointsBufSize = BATCH_SIZE * dim * 4;
|
|
6437
|
-
const pointsBuf = device.createBuffer({
|
|
6480
|
+
const pointsBuf = device.createBuffer({
|
|
6481
|
+
size: pointsBufSize,
|
|
6482
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6483
|
+
});
|
|
6438
6484
|
const resultsBufSize = BATCH_SIZE * 4;
|
|
6439
|
-
const resultsBuf = device.createBuffer({
|
|
6440
|
-
|
|
6485
|
+
const resultsBuf = device.createBuffer({
|
|
6486
|
+
size: resultsBufSize,
|
|
6487
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
6488
|
+
});
|
|
6489
|
+
const readbackBuf = device.createBuffer({
|
|
6490
|
+
size: resultsBufSize,
|
|
6491
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
6492
|
+
});
|
|
6441
6493
|
const labels = new Uint32Array(numPoints);
|
|
6442
6494
|
const bindGroup = device.createBindGroup({
|
|
6443
6495
|
layout: bindGroupLayout,
|
|
@@ -6452,18 +6504,36 @@ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, num
|
|
|
6452
6504
|
const offset = batch * BATCH_SIZE;
|
|
6453
6505
|
const currentBatchSize = Math.min(numPoints - offset, BATCH_SIZE);
|
|
6454
6506
|
const groups = Math.ceil(currentBatchSize / WORKGROUP_SIZE2);
|
|
6455
|
-
device.queue.writeBuffer(
|
|
6456
|
-
|
|
6507
|
+
device.queue.writeBuffer(
|
|
6508
|
+
uniformBuf,
|
|
6509
|
+
0,
|
|
6510
|
+
new Uint32Array([currentBatchSize, numCentroids])
|
|
6511
|
+
);
|
|
6512
|
+
device.queue.writeBuffer(
|
|
6513
|
+
pointsBuf,
|
|
6514
|
+
0,
|
|
6515
|
+
pointsFlat.buffer,
|
|
6516
|
+
offset * dim * 4,
|
|
6517
|
+
currentBatchSize * dim * 4
|
|
6518
|
+
);
|
|
6457
6519
|
const encoder = device.createCommandEncoder();
|
|
6458
6520
|
const pass = encoder.beginComputePass();
|
|
6459
6521
|
pass.setPipeline(pipeline);
|
|
6460
6522
|
pass.setBindGroup(0, bindGroup);
|
|
6461
6523
|
pass.dispatchWorkgroups(groups);
|
|
6462
6524
|
pass.end();
|
|
6463
|
-
encoder.copyBufferToBuffer(
|
|
6525
|
+
encoder.copyBufferToBuffer(
|
|
6526
|
+
resultsBuf,
|
|
6527
|
+
0,
|
|
6528
|
+
readbackBuf,
|
|
6529
|
+
0,
|
|
6530
|
+
currentBatchSize * 4
|
|
6531
|
+
);
|
|
6464
6532
|
device.queue.submit([encoder.finish()]);
|
|
6465
6533
|
await readbackBuf.mapAsync(GPUMapMode.READ);
|
|
6466
|
-
const mapped = new Uint32Array(
|
|
6534
|
+
const mapped = new Uint32Array(
|
|
6535
|
+
readbackBuf.getMappedRange(0, currentBatchSize * 4)
|
|
6536
|
+
);
|
|
6467
6537
|
labels.set(mapped, offset);
|
|
6468
6538
|
readbackBuf.unmap();
|
|
6469
6539
|
}
|
|
@@ -6487,13 +6557,23 @@ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
|
|
|
6487
6557
|
const centroids = new Float32Array(k * dim);
|
|
6488
6558
|
const indices = pickRandomIndices(n, k);
|
|
6489
6559
|
for (let i = 0; i < k; i++) {
|
|
6490
|
-
centroids.set(
|
|
6560
|
+
centroids.set(
|
|
6561
|
+
pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim),
|
|
6562
|
+
i * dim
|
|
6563
|
+
);
|
|
6491
6564
|
}
|
|
6492
6565
|
const device = await getGpuDevice();
|
|
6493
6566
|
for (let iter = 0; iter < iterations; iter++) {
|
|
6494
6567
|
onProgress == null ? void 0 : onProgress(iter, iterations);
|
|
6495
6568
|
if (device) {
|
|
6496
|
-
const newLabels = await gpuAssignLabels(
|
|
6569
|
+
const newLabels = await gpuAssignLabels(
|
|
6570
|
+
device,
|
|
6571
|
+
pointsFlat,
|
|
6572
|
+
centroids,
|
|
6573
|
+
n,
|
|
6574
|
+
k,
|
|
6575
|
+
dim
|
|
6576
|
+
);
|
|
6497
6577
|
labels.set(newLabels);
|
|
6498
6578
|
} else {
|
|
6499
6579
|
assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
|
|
@@ -6502,7 +6582,14 @@ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
|
|
|
6502
6582
|
}
|
|
6503
6583
|
onProgress == null ? void 0 : onProgress(iterations, iterations);
|
|
6504
6584
|
if (device) {
|
|
6505
|
-
const finalLabels = await gpuAssignLabels(
|
|
6585
|
+
const finalLabels = await gpuAssignLabels(
|
|
6586
|
+
device,
|
|
6587
|
+
pointsFlat,
|
|
6588
|
+
centroids,
|
|
6589
|
+
n,
|
|
6590
|
+
k,
|
|
6591
|
+
dim
|
|
6592
|
+
);
|
|
6506
6593
|
labels.set(finalLabels);
|
|
6507
6594
|
} else {
|
|
6508
6595
|
assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
|
|
@@ -6548,7 +6635,8 @@ function updateCentroids(points, centroids, labels, n, k, dim) {
|
|
|
6548
6635
|
for (let c = 0; c < k; c++) {
|
|
6549
6636
|
if (counts[c] > 0) {
|
|
6550
6637
|
const off = c * dim;
|
|
6551
|
-
for (let d = 0; d < dim; d++)
|
|
6638
|
+
for (let d = 0; d < dim; d++)
|
|
6639
|
+
centroids[off + d] = sums[off + d] / counts[c];
|
|
6552
6640
|
} else {
|
|
6553
6641
|
const idx = Math.floor(Math.random() * n);
|
|
6554
6642
|
centroids.set(points.subarray(idx * dim, idx * dim + dim), c * dim);
|
|
@@ -6581,9 +6669,18 @@ function mortonSort(positions, count) {
|
|
|
6581
6669
|
const morton = new Uint32Array(count);
|
|
6582
6670
|
const indices = new Uint32Array(count);
|
|
6583
6671
|
for (let i = 0; i < count; i++) {
|
|
6584
|
-
const ix = Math.min(
|
|
6585
|
-
|
|
6586
|
-
|
|
6672
|
+
const ix = Math.min(
|
|
6673
|
+
1023,
|
|
6674
|
+
Math.floor(1024 * (positions[i * 3] - minx) / xlen)
|
|
6675
|
+
);
|
|
6676
|
+
const iy = Math.min(
|
|
6677
|
+
1023,
|
|
6678
|
+
Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen)
|
|
6679
|
+
);
|
|
6680
|
+
const iz = Math.min(
|
|
6681
|
+
1023,
|
|
6682
|
+
Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen)
|
|
6683
|
+
);
|
|
6587
6684
|
morton[i] = (part1By2(iz) << 2) + (part1By2(iy) << 1) + part1By2(ix);
|
|
6588
6685
|
indices[i] = i;
|
|
6589
6686
|
}
|
|
@@ -6720,7 +6817,8 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6720
6817
|
onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
|
|
6721
6818
|
await yieldToUI();
|
|
6722
6819
|
const logScales = new Float32Array(count * 3);
|
|
6723
|
-
for (let i = 0; i < count * 3; i++)
|
|
6820
|
+
for (let i = 0; i < count * 3; i++)
|
|
6821
|
+
logScales[i] = Math.log(Math.max(1e-8, sSca[i]));
|
|
6724
6822
|
const scaleCodebook = buildCodebook(logScales, 256);
|
|
6725
6823
|
const scalesData = new Uint8ClampedArray(totalPixels * 4);
|
|
6726
6824
|
scalesData.fill(255);
|
|
@@ -6780,9 +6878,15 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6780
6878
|
r1 = qx;
|
|
6781
6879
|
r2 = qy;
|
|
6782
6880
|
}
|
|
6783
|
-
quatsData[off] = Math.round(
|
|
6784
|
-
|
|
6785
|
-
|
|
6881
|
+
quatsData[off] = Math.round(
|
|
6882
|
+
Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255))
|
|
6883
|
+
);
|
|
6884
|
+
quatsData[off + 1] = Math.round(
|
|
6885
|
+
Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255))
|
|
6886
|
+
);
|
|
6887
|
+
quatsData[off + 2] = Math.round(
|
|
6888
|
+
Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255))
|
|
6889
|
+
);
|
|
6786
6890
|
quatsData[off + 3] = largest + 252;
|
|
6787
6891
|
}
|
|
6788
6892
|
onProgress == null ? void 0 : onProgress("encode_color", 0.4);
|
|
@@ -6838,7 +6942,8 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6838
6942
|
await yieldToUI();
|
|
6839
6943
|
const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
|
|
6840
6944
|
for (let c = 0; c < effectivePaletteSize; c++) {
|
|
6841
|
-
for (let d = 0; d < shDim; d++)
|
|
6945
|
+
for (let d = 0; d < shDim; d++)
|
|
6946
|
+
allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
|
|
6842
6947
|
}
|
|
6843
6948
|
const shCodebook = buildCodebook(allCoeffValues, 256);
|
|
6844
6949
|
const centW = 64 * numCoeffs;
|
|
@@ -6852,8 +6957,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6852
6957
|
const off = (v * centW + u) * 4;
|
|
6853
6958
|
const cOff = n * shDim;
|
|
6854
6959
|
centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
|
|
6855
|
-
centData[off + 1] = findNearest(
|
|
6856
|
-
|
|
6960
|
+
centData[off + 1] = findNearest(
|
|
6961
|
+
shCodebook,
|
|
6962
|
+
centroids[cOff + c * 3 + 1]
|
|
6963
|
+
);
|
|
6964
|
+
centData[off + 2] = findNearest(
|
|
6965
|
+
shCodebook,
|
|
6966
|
+
centroids[cOff + c * 3 + 2]
|
|
6967
|
+
);
|
|
6857
6968
|
centData[off + 3] = 255;
|
|
6858
6969
|
}
|
|
6859
6970
|
}
|
|
@@ -18111,6 +18222,7 @@ class App {
|
|
|
18111
18222
|
this.camera = new Camera();
|
|
18112
18223
|
this.camera.setAspect(this.renderer.getAspectRatio());
|
|
18113
18224
|
this.controls = new OrbitControls(this.camera, this.canvas);
|
|
18225
|
+
this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
|
|
18114
18226
|
this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
|
|
18115
18227
|
this.glbLoader = new GLBLoader(this.renderer.device);
|
|
18116
18228
|
this.objLoader = new OBJLoader(this.renderer.device);
|
|
@@ -18731,6 +18843,67 @@ class App {
|
|
|
18731
18843
|
getRenderer() {
|
|
18732
18844
|
return this.renderer;
|
|
18733
18845
|
}
|
|
18846
|
+
/**
|
|
18847
|
+
* CPU splat 拾取:在屏幕坐标 (clientX, clientY) 处找最近的 splat,返回世界坐标或 null
|
|
18848
|
+
*/
|
|
18849
|
+
pickSplatPosition(clientX, clientY) {
|
|
18850
|
+
const gsRenderer = this.sceneManager.getGSRenderer();
|
|
18851
|
+
if (!gsRenderer) return null;
|
|
18852
|
+
const positions = gsRenderer.getCPUPositions();
|
|
18853
|
+
if (!positions) return null;
|
|
18854
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
18855
|
+
const ndcX = (clientX - rect.left) / rect.width * 2 - 1;
|
|
18856
|
+
const ndcY = -((clientY - rect.top) / rect.height * 2 - 1);
|
|
18857
|
+
const modelMatrix = gsRenderer.getModelMatrix();
|
|
18858
|
+
const vp = this.camera.viewProjectionMatrix;
|
|
18859
|
+
const count = positions.length / 3;
|
|
18860
|
+
const step = count > 3e5 ? Math.ceil(count / 3e5) : 1;
|
|
18861
|
+
const halfW = rect.width / 2;
|
|
18862
|
+
const halfH = rect.height / 2;
|
|
18863
|
+
const pickRadiusPx = 20;
|
|
18864
|
+
const pickRadSq = pickRadiusPx * pickRadiusPx;
|
|
18865
|
+
let bestCamDistSq = Infinity;
|
|
18866
|
+
let bestIdx = -1;
|
|
18867
|
+
let bestScreenDistSq = Infinity;
|
|
18868
|
+
const m0 = modelMatrix[0], m1 = modelMatrix[1], m2 = modelMatrix[2];
|
|
18869
|
+
const m4 = modelMatrix[4], m5 = modelMatrix[5], m6 = modelMatrix[6];
|
|
18870
|
+
const m8 = modelMatrix[8], m9 = modelMatrix[9], m10 = modelMatrix[10];
|
|
18871
|
+
const m12 = modelMatrix[12], m13 = modelMatrix[13], m14 = modelMatrix[14];
|
|
18872
|
+
const v0 = vp[0], v1 = vp[1], v3 = vp[3];
|
|
18873
|
+
const v4 = vp[4], v5 = vp[5], v7 = vp[7];
|
|
18874
|
+
const v8 = vp[8], v9 = vp[9], v11 = vp[11];
|
|
18875
|
+
const v12 = vp[12], v13 = vp[13], v15 = vp[15];
|
|
18876
|
+
const rox = this.camera.position[0], roy = this.camera.position[1], roz = this.camera.position[2];
|
|
18877
|
+
for (let i = 0; i < count; i += step) {
|
|
18878
|
+
const i3 = i * 3;
|
|
18879
|
+
const lx = positions[i3], ly = positions[i3 + 1], lz = positions[i3 + 2];
|
|
18880
|
+
const tx = m0 * lx + m4 * ly + m8 * lz + m12;
|
|
18881
|
+
const ty = m1 * lx + m5 * ly + m9 * lz + m13;
|
|
18882
|
+
const tz = m2 * lx + m6 * ly + m10 * lz + m14;
|
|
18883
|
+
const cw = v3 * tx + v7 * ty + v11 * tz + v15;
|
|
18884
|
+
if (cw <= 0) continue;
|
|
18885
|
+
const invCw = 1 / cw;
|
|
18886
|
+
const pixX = ((v0 * tx + v4 * ty + v8 * tz + v12) * invCw - ndcX) * halfW;
|
|
18887
|
+
const pixY = ((v1 * tx + v5 * ty + v9 * tz + v13) * invCw - ndcY) * halfH;
|
|
18888
|
+
const screenDistSq = pixX * pixX + pixY * pixY;
|
|
18889
|
+
if (screenDistSq < pickRadSq) {
|
|
18890
|
+
const dx = tx - rox, dy = ty - roy, dz = tz - roz;
|
|
18891
|
+
const camDistSq = dx * dx + dy * dy + dz * dz;
|
|
18892
|
+
if (camDistSq < bestCamDistSq * 0.98 || camDistSq < bestCamDistSq * 1.02 && screenDistSq < bestScreenDistSq) {
|
|
18893
|
+
bestCamDistSq = camDistSq;
|
|
18894
|
+
bestScreenDistSq = screenDistSq;
|
|
18895
|
+
bestIdx = i;
|
|
18896
|
+
}
|
|
18897
|
+
}
|
|
18898
|
+
}
|
|
18899
|
+
if (bestIdx < 0) return null;
|
|
18900
|
+
const hx = positions[bestIdx * 3], hy = positions[bestIdx * 3 + 1], hz = positions[bestIdx * 3 + 2];
|
|
18901
|
+
return [
|
|
18902
|
+
m0 * hx + m4 * hy + m8 * hz + m12,
|
|
18903
|
+
m1 * hx + m5 * hy + m9 * hz + m13,
|
|
18904
|
+
m2 * hx + m6 * hy + m10 * hz + m14
|
|
18905
|
+
];
|
|
18906
|
+
}
|
|
18734
18907
|
getCamera() {
|
|
18735
18908
|
return this.camera;
|
|
18736
18909
|
}
|