@d5techs/3dgs-lib 1.4.24 → 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 +216 -32
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +216 -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.cjs
CHANGED
|
@@ -653,6 +653,8 @@ const _OrbitControls = class _OrbitControls {
|
|
|
653
653
|
__publicField(this, "lastTouchCenter", { x: 0, y: 0 });
|
|
654
654
|
// 启用/禁用
|
|
655
655
|
__publicField(this, "enabled", true);
|
|
656
|
+
// 双击拾取回调:返回世界坐标则聚焦,返回 null 则忽略
|
|
657
|
+
__publicField(this, "pickWorldPosition", null);
|
|
656
658
|
// 绑定的事件处理函数(用于移除监听器)
|
|
657
659
|
__publicField(this, "boundOnMouseDown");
|
|
658
660
|
__publicField(this, "boundOnMouseMove");
|
|
@@ -839,6 +841,13 @@ const _OrbitControls = class _OrbitControls {
|
|
|
839
841
|
}
|
|
840
842
|
onDblClick(e) {
|
|
841
843
|
if (!this.enabled) return;
|
|
844
|
+
if (this.pickWorldPosition) {
|
|
845
|
+
const hit = this.pickWorldPosition(e.clientX, e.clientY);
|
|
846
|
+
if (hit) {
|
|
847
|
+
this.animateToTarget(hit);
|
|
848
|
+
}
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
842
851
|
const rect = this.canvas.getBoundingClientRect();
|
|
843
852
|
const ndcX = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
844
853
|
const ndcY = -((e.clientY - rect.top) / rect.height * 2 - 1);
|
|
@@ -6214,7 +6223,10 @@ function buildCodebook(values, k = 256) {
|
|
|
6214
6223
|
for (let i = 0; i < N; i++) {
|
|
6215
6224
|
const uniformPos = (sorted[i] - vMin) / vRange;
|
|
6216
6225
|
const quantilePos = i / N;
|
|
6217
|
-
const bin = Math.min(
|
|
6226
|
+
const bin = Math.min(
|
|
6227
|
+
H - 1,
|
|
6228
|
+
Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos))
|
|
6229
|
+
);
|
|
6218
6230
|
counts[bin]++;
|
|
6219
6231
|
sums[bin] += sorted[i];
|
|
6220
6232
|
}
|
|
@@ -6291,7 +6303,8 @@ function buildCodebook(values, k = 256) {
|
|
|
6291
6303
|
centroidValues.sort();
|
|
6292
6304
|
const result = new Array(k);
|
|
6293
6305
|
for (let i = 0; i < effectiveK; i++) result[i] = centroidValues[i];
|
|
6294
|
-
for (let i = effectiveK; i < k; i++)
|
|
6306
|
+
for (let i = effectiveK; i < k; i++)
|
|
6307
|
+
result[i] = centroidValues[effectiveK - 1];
|
|
6295
6308
|
return result;
|
|
6296
6309
|
}
|
|
6297
6310
|
function findNearest(sortedCB, value) {
|
|
@@ -6419,27 +6432,66 @@ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, num
|
|
|
6419
6432
|
const WORKGROUP_SIZE2 = 64;
|
|
6420
6433
|
const BATCH_SIZE = 1024 * WORKGROUP_SIZE2;
|
|
6421
6434
|
const numBatches = Math.ceil(numPoints / BATCH_SIZE);
|
|
6422
|
-
const shaderModule = device.createShaderModule({
|
|
6435
|
+
const shaderModule = device.createShaderModule({
|
|
6436
|
+
code: getGpuClusterShader(dim)
|
|
6437
|
+
});
|
|
6423
6438
|
const bindGroupLayout = device.createBindGroupLayout({
|
|
6424
6439
|
entries: [
|
|
6425
|
-
{
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6440
|
+
{
|
|
6441
|
+
binding: 0,
|
|
6442
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6443
|
+
buffer: { type: "uniform" }
|
|
6444
|
+
},
|
|
6445
|
+
{
|
|
6446
|
+
binding: 1,
|
|
6447
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6448
|
+
buffer: { type: "read-only-storage" }
|
|
6449
|
+
},
|
|
6450
|
+
{
|
|
6451
|
+
binding: 2,
|
|
6452
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6453
|
+
buffer: { type: "read-only-storage" }
|
|
6454
|
+
},
|
|
6455
|
+
{
|
|
6456
|
+
binding: 3,
|
|
6457
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
6458
|
+
buffer: { type: "storage" }
|
|
6459
|
+
}
|
|
6429
6460
|
]
|
|
6430
6461
|
});
|
|
6431
6462
|
const pipeline = device.createComputePipeline({
|
|
6432
|
-
layout: device.createPipelineLayout({
|
|
6463
|
+
layout: device.createPipelineLayout({
|
|
6464
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
6465
|
+
}),
|
|
6433
6466
|
compute: { module: shaderModule, entryPoint: "main" }
|
|
6434
6467
|
});
|
|
6435
|
-
const uniformBuf = device.createBuffer({
|
|
6436
|
-
|
|
6437
|
-
|
|
6468
|
+
const uniformBuf = device.createBuffer({
|
|
6469
|
+
size: 8,
|
|
6470
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
6471
|
+
});
|
|
6472
|
+
const centroidsBuf = device.createBuffer({
|
|
6473
|
+
size: centroidsFlat.byteLength,
|
|
6474
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6475
|
+
});
|
|
6476
|
+
device.queue.writeBuffer(
|
|
6477
|
+
centroidsBuf,
|
|
6478
|
+
0,
|
|
6479
|
+
centroidsFlat.buffer
|
|
6480
|
+
);
|
|
6438
6481
|
const pointsBufSize = BATCH_SIZE * dim * 4;
|
|
6439
|
-
const pointsBuf = device.createBuffer({
|
|
6482
|
+
const pointsBuf = device.createBuffer({
|
|
6483
|
+
size: pointsBufSize,
|
|
6484
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6485
|
+
});
|
|
6440
6486
|
const resultsBufSize = BATCH_SIZE * 4;
|
|
6441
|
-
const resultsBuf = device.createBuffer({
|
|
6442
|
-
|
|
6487
|
+
const resultsBuf = device.createBuffer({
|
|
6488
|
+
size: resultsBufSize,
|
|
6489
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
6490
|
+
});
|
|
6491
|
+
const readbackBuf = device.createBuffer({
|
|
6492
|
+
size: resultsBufSize,
|
|
6493
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
6494
|
+
});
|
|
6443
6495
|
const labels = new Uint32Array(numPoints);
|
|
6444
6496
|
const bindGroup = device.createBindGroup({
|
|
6445
6497
|
layout: bindGroupLayout,
|
|
@@ -6454,18 +6506,36 @@ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, num
|
|
|
6454
6506
|
const offset = batch * BATCH_SIZE;
|
|
6455
6507
|
const currentBatchSize = Math.min(numPoints - offset, BATCH_SIZE);
|
|
6456
6508
|
const groups = Math.ceil(currentBatchSize / WORKGROUP_SIZE2);
|
|
6457
|
-
device.queue.writeBuffer(
|
|
6458
|
-
|
|
6509
|
+
device.queue.writeBuffer(
|
|
6510
|
+
uniformBuf,
|
|
6511
|
+
0,
|
|
6512
|
+
new Uint32Array([currentBatchSize, numCentroids])
|
|
6513
|
+
);
|
|
6514
|
+
device.queue.writeBuffer(
|
|
6515
|
+
pointsBuf,
|
|
6516
|
+
0,
|
|
6517
|
+
pointsFlat.buffer,
|
|
6518
|
+
offset * dim * 4,
|
|
6519
|
+
currentBatchSize * dim * 4
|
|
6520
|
+
);
|
|
6459
6521
|
const encoder = device.createCommandEncoder();
|
|
6460
6522
|
const pass = encoder.beginComputePass();
|
|
6461
6523
|
pass.setPipeline(pipeline);
|
|
6462
6524
|
pass.setBindGroup(0, bindGroup);
|
|
6463
6525
|
pass.dispatchWorkgroups(groups);
|
|
6464
6526
|
pass.end();
|
|
6465
|
-
encoder.copyBufferToBuffer(
|
|
6527
|
+
encoder.copyBufferToBuffer(
|
|
6528
|
+
resultsBuf,
|
|
6529
|
+
0,
|
|
6530
|
+
readbackBuf,
|
|
6531
|
+
0,
|
|
6532
|
+
currentBatchSize * 4
|
|
6533
|
+
);
|
|
6466
6534
|
device.queue.submit([encoder.finish()]);
|
|
6467
6535
|
await readbackBuf.mapAsync(GPUMapMode.READ);
|
|
6468
|
-
const mapped = new Uint32Array(
|
|
6536
|
+
const mapped = new Uint32Array(
|
|
6537
|
+
readbackBuf.getMappedRange(0, currentBatchSize * 4)
|
|
6538
|
+
);
|
|
6469
6539
|
labels.set(mapped, offset);
|
|
6470
6540
|
readbackBuf.unmap();
|
|
6471
6541
|
}
|
|
@@ -6489,13 +6559,23 @@ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
|
|
|
6489
6559
|
const centroids = new Float32Array(k * dim);
|
|
6490
6560
|
const indices = pickRandomIndices(n, k);
|
|
6491
6561
|
for (let i = 0; i < k; i++) {
|
|
6492
|
-
centroids.set(
|
|
6562
|
+
centroids.set(
|
|
6563
|
+
pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim),
|
|
6564
|
+
i * dim
|
|
6565
|
+
);
|
|
6493
6566
|
}
|
|
6494
6567
|
const device = await getGpuDevice();
|
|
6495
6568
|
for (let iter = 0; iter < iterations; iter++) {
|
|
6496
6569
|
onProgress == null ? void 0 : onProgress(iter, iterations);
|
|
6497
6570
|
if (device) {
|
|
6498
|
-
const newLabels = await gpuAssignLabels(
|
|
6571
|
+
const newLabels = await gpuAssignLabels(
|
|
6572
|
+
device,
|
|
6573
|
+
pointsFlat,
|
|
6574
|
+
centroids,
|
|
6575
|
+
n,
|
|
6576
|
+
k,
|
|
6577
|
+
dim
|
|
6578
|
+
);
|
|
6499
6579
|
labels.set(newLabels);
|
|
6500
6580
|
} else {
|
|
6501
6581
|
assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
|
|
@@ -6504,7 +6584,14 @@ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
|
|
|
6504
6584
|
}
|
|
6505
6585
|
onProgress == null ? void 0 : onProgress(iterations, iterations);
|
|
6506
6586
|
if (device) {
|
|
6507
|
-
const finalLabels = await gpuAssignLabels(
|
|
6587
|
+
const finalLabels = await gpuAssignLabels(
|
|
6588
|
+
device,
|
|
6589
|
+
pointsFlat,
|
|
6590
|
+
centroids,
|
|
6591
|
+
n,
|
|
6592
|
+
k,
|
|
6593
|
+
dim
|
|
6594
|
+
);
|
|
6508
6595
|
labels.set(finalLabels);
|
|
6509
6596
|
} else {
|
|
6510
6597
|
assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
|
|
@@ -6550,7 +6637,8 @@ function updateCentroids(points, centroids, labels, n, k, dim) {
|
|
|
6550
6637
|
for (let c = 0; c < k; c++) {
|
|
6551
6638
|
if (counts[c] > 0) {
|
|
6552
6639
|
const off = c * dim;
|
|
6553
|
-
for (let d = 0; d < dim; d++)
|
|
6640
|
+
for (let d = 0; d < dim; d++)
|
|
6641
|
+
centroids[off + d] = sums[off + d] / counts[c];
|
|
6554
6642
|
} else {
|
|
6555
6643
|
const idx = Math.floor(Math.random() * n);
|
|
6556
6644
|
centroids.set(points.subarray(idx * dim, idx * dim + dim), c * dim);
|
|
@@ -6583,9 +6671,18 @@ function mortonSort(positions, count) {
|
|
|
6583
6671
|
const morton = new Uint32Array(count);
|
|
6584
6672
|
const indices = new Uint32Array(count);
|
|
6585
6673
|
for (let i = 0; i < count; i++) {
|
|
6586
|
-
const ix = Math.min(
|
|
6587
|
-
|
|
6588
|
-
|
|
6674
|
+
const ix = Math.min(
|
|
6675
|
+
1023,
|
|
6676
|
+
Math.floor(1024 * (positions[i * 3] - minx) / xlen)
|
|
6677
|
+
);
|
|
6678
|
+
const iy = Math.min(
|
|
6679
|
+
1023,
|
|
6680
|
+
Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen)
|
|
6681
|
+
);
|
|
6682
|
+
const iz = Math.min(
|
|
6683
|
+
1023,
|
|
6684
|
+
Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen)
|
|
6685
|
+
);
|
|
6589
6686
|
morton[i] = (part1By2(iz) << 2) + (part1By2(iy) << 1) + part1By2(ix);
|
|
6590
6687
|
indices[i] = i;
|
|
6591
6688
|
}
|
|
@@ -6626,12 +6723,14 @@ async function encodeWebP(width, height, pixelData) {
|
|
|
6626
6723
|
}
|
|
6627
6724
|
return new Uint8Array(await blob.arrayBuffer());
|
|
6628
6725
|
}
|
|
6726
|
+
const yieldToUI = () => new Promise((r) => setTimeout(r, 0));
|
|
6629
6727
|
async function serializeSOG(data, coordinateSystem = "blender", options = {}, onProgress) {
|
|
6630
6728
|
const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
|
|
6631
6729
|
const { count, colors, opacities } = data;
|
|
6632
6730
|
let { positions, scales, rotations } = data;
|
|
6633
6731
|
let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
|
|
6634
6732
|
onProgress == null ? void 0 : onProgress("coord_transform", 0);
|
|
6733
|
+
await yieldToUI();
|
|
6635
6734
|
if (coordinateSystem === "blender") {
|
|
6636
6735
|
positions = new Float32Array(positions);
|
|
6637
6736
|
scales = new Float32Array(scales);
|
|
@@ -6655,6 +6754,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6655
6754
|
}
|
|
6656
6755
|
}
|
|
6657
6756
|
onProgress == null ? void 0 : onProgress("morton_sort", 0.05);
|
|
6757
|
+
await yieldToUI();
|
|
6658
6758
|
const sortOrder = mortonSort(positions, count);
|
|
6659
6759
|
const sPos = new Float32Array(count * 3);
|
|
6660
6760
|
const sSca = new Float32Array(count * 3);
|
|
@@ -6688,6 +6788,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6688
6788
|
const { width, height } = calcImageDimensions(count);
|
|
6689
6789
|
const totalPixels = width * height;
|
|
6690
6790
|
onProgress == null ? void 0 : onProgress("encode_position", 0.1);
|
|
6791
|
+
await yieldToUI();
|
|
6691
6792
|
const logPos = new Float32Array(count * 3);
|
|
6692
6793
|
for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
|
|
6693
6794
|
const mins = [Infinity, Infinity, Infinity];
|
|
@@ -6716,8 +6817,10 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6716
6817
|
meansUData[off + 3] = 255;
|
|
6717
6818
|
}
|
|
6718
6819
|
onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
|
|
6820
|
+
await yieldToUI();
|
|
6719
6821
|
const logScales = new Float32Array(count * 3);
|
|
6720
|
-
for (let i = 0; i < count * 3; i++)
|
|
6822
|
+
for (let i = 0; i < count * 3; i++)
|
|
6823
|
+
logScales[i] = Math.log(Math.max(1e-8, sSca[i]));
|
|
6721
6824
|
const scaleCodebook = buildCodebook(logScales, 256);
|
|
6722
6825
|
const scalesData = new Uint8ClampedArray(totalPixels * 4);
|
|
6723
6826
|
scalesData.fill(255);
|
|
@@ -6729,6 +6832,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6729
6832
|
scalesData[off + 3] = 255;
|
|
6730
6833
|
}
|
|
6731
6834
|
onProgress == null ? void 0 : onProgress("encode_quaternion", 0.3);
|
|
6835
|
+
await yieldToUI();
|
|
6732
6836
|
const quatsData = new Uint8ClampedArray(totalPixels * 4);
|
|
6733
6837
|
quatsData.fill(255);
|
|
6734
6838
|
const SQRT2 = Math.SQRT2;
|
|
@@ -6776,12 +6880,19 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6776
6880
|
r1 = qx;
|
|
6777
6881
|
r2 = qy;
|
|
6778
6882
|
}
|
|
6779
|
-
quatsData[off] = Math.round(
|
|
6780
|
-
|
|
6781
|
-
|
|
6883
|
+
quatsData[off] = Math.round(
|
|
6884
|
+
Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255))
|
|
6885
|
+
);
|
|
6886
|
+
quatsData[off + 1] = Math.round(
|
|
6887
|
+
Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255))
|
|
6888
|
+
);
|
|
6889
|
+
quatsData[off + 2] = Math.round(
|
|
6890
|
+
Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255))
|
|
6891
|
+
);
|
|
6782
6892
|
quatsData[off + 3] = largest + 252;
|
|
6783
6893
|
}
|
|
6784
6894
|
onProgress == null ? void 0 : onProgress("encode_color", 0.4);
|
|
6895
|
+
await yieldToUI();
|
|
6785
6896
|
const dcValues = new Float32Array(count * 3);
|
|
6786
6897
|
for (let i = 0; i < count; i++) {
|
|
6787
6898
|
dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
|
|
@@ -6811,6 +6922,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6811
6922
|
];
|
|
6812
6923
|
if (actualBands > 0 && sSH) {
|
|
6813
6924
|
onProgress == null ? void 0 : onProgress("sh_compress", 0.5);
|
|
6925
|
+
await yieldToUI();
|
|
6814
6926
|
const pointsFlat = new Float32Array(count * shDim);
|
|
6815
6927
|
for (let i = 0; i < count; i++) {
|
|
6816
6928
|
const b = i * 45;
|
|
@@ -6829,9 +6941,11 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6829
6941
|
}
|
|
6830
6942
|
);
|
|
6831
6943
|
onProgress == null ? void 0 : onProgress("sh_quantize", 0.88);
|
|
6944
|
+
await yieldToUI();
|
|
6832
6945
|
const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
|
|
6833
6946
|
for (let c = 0; c < effectivePaletteSize; c++) {
|
|
6834
|
-
for (let d = 0; d < shDim; d++)
|
|
6947
|
+
for (let d = 0; d < shDim; d++)
|
|
6948
|
+
allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
|
|
6835
6949
|
}
|
|
6836
6950
|
const shCodebook = buildCodebook(allCoeffValues, 256);
|
|
6837
6951
|
const centW = 64 * numCoeffs;
|
|
@@ -6845,8 +6959,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6845
6959
|
const off = (v * centW + u) * 4;
|
|
6846
6960
|
const cOff = n * shDim;
|
|
6847
6961
|
centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
|
|
6848
|
-
centData[off + 1] = findNearest(
|
|
6849
|
-
|
|
6962
|
+
centData[off + 1] = findNearest(
|
|
6963
|
+
shCodebook,
|
|
6964
|
+
centroids[cOff + c * 3 + 1]
|
|
6965
|
+
);
|
|
6966
|
+
centData[off + 2] = findNearest(
|
|
6967
|
+
shCodebook,
|
|
6968
|
+
centroids[cOff + c * 3 + 2]
|
|
6969
|
+
);
|
|
6850
6970
|
centData[off + 3] = 255;
|
|
6851
6971
|
}
|
|
6852
6972
|
}
|
|
@@ -6871,6 +6991,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6871
6991
|
};
|
|
6872
6992
|
}
|
|
6873
6993
|
onProgress == null ? void 0 : onProgress("encode_webp", 0.92);
|
|
6994
|
+
await yieldToUI();
|
|
6874
6995
|
const webps = await Promise.all(webpPromises);
|
|
6875
6996
|
const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
|
|
6876
6997
|
const meta = {
|
|
@@ -6897,6 +7018,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6897
7018
|
zipEntries["sh_labels.webp"] = webps[6];
|
|
6898
7019
|
}
|
|
6899
7020
|
onProgress == null ? void 0 : onProgress("pack_zip", 0.98);
|
|
7021
|
+
await yieldToUI();
|
|
6900
7022
|
const zipData = zipSync(zipEntries, { level: 0 });
|
|
6901
7023
|
onProgress == null ? void 0 : onProgress("done", 1);
|
|
6902
7024
|
return zipData.buffer;
|
|
@@ -18102,6 +18224,7 @@ class App {
|
|
|
18102
18224
|
this.camera = new Camera();
|
|
18103
18225
|
this.camera.setAspect(this.renderer.getAspectRatio());
|
|
18104
18226
|
this.controls = new OrbitControls(this.camera, this.canvas);
|
|
18227
|
+
this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
|
|
18105
18228
|
this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
|
|
18106
18229
|
this.glbLoader = new GLBLoader(this.renderer.device);
|
|
18107
18230
|
this.objLoader = new OBJLoader(this.renderer.device);
|
|
@@ -18722,6 +18845,67 @@ class App {
|
|
|
18722
18845
|
getRenderer() {
|
|
18723
18846
|
return this.renderer;
|
|
18724
18847
|
}
|
|
18848
|
+
/**
|
|
18849
|
+
* CPU splat 拾取:在屏幕坐标 (clientX, clientY) 处找最近的 splat,返回世界坐标或 null
|
|
18850
|
+
*/
|
|
18851
|
+
pickSplatPosition(clientX, clientY) {
|
|
18852
|
+
const gsRenderer = this.sceneManager.getGSRenderer();
|
|
18853
|
+
if (!gsRenderer) return null;
|
|
18854
|
+
const positions = gsRenderer.getCPUPositions();
|
|
18855
|
+
if (!positions) return null;
|
|
18856
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
18857
|
+
const ndcX = (clientX - rect.left) / rect.width * 2 - 1;
|
|
18858
|
+
const ndcY = -((clientY - rect.top) / rect.height * 2 - 1);
|
|
18859
|
+
const modelMatrix = gsRenderer.getModelMatrix();
|
|
18860
|
+
const vp = this.camera.viewProjectionMatrix;
|
|
18861
|
+
const count = positions.length / 3;
|
|
18862
|
+
const step = count > 3e5 ? Math.ceil(count / 3e5) : 1;
|
|
18863
|
+
const halfW = rect.width / 2;
|
|
18864
|
+
const halfH = rect.height / 2;
|
|
18865
|
+
const pickRadiusPx = 20;
|
|
18866
|
+
const pickRadSq = pickRadiusPx * pickRadiusPx;
|
|
18867
|
+
let bestCamDistSq = Infinity;
|
|
18868
|
+
let bestIdx = -1;
|
|
18869
|
+
let bestScreenDistSq = Infinity;
|
|
18870
|
+
const m0 = modelMatrix[0], m1 = modelMatrix[1], m2 = modelMatrix[2];
|
|
18871
|
+
const m4 = modelMatrix[4], m5 = modelMatrix[5], m6 = modelMatrix[6];
|
|
18872
|
+
const m8 = modelMatrix[8], m9 = modelMatrix[9], m10 = modelMatrix[10];
|
|
18873
|
+
const m12 = modelMatrix[12], m13 = modelMatrix[13], m14 = modelMatrix[14];
|
|
18874
|
+
const v0 = vp[0], v1 = vp[1], v3 = vp[3];
|
|
18875
|
+
const v4 = vp[4], v5 = vp[5], v7 = vp[7];
|
|
18876
|
+
const v8 = vp[8], v9 = vp[9], v11 = vp[11];
|
|
18877
|
+
const v12 = vp[12], v13 = vp[13], v15 = vp[15];
|
|
18878
|
+
const rox = this.camera.position[0], roy = this.camera.position[1], roz = this.camera.position[2];
|
|
18879
|
+
for (let i = 0; i < count; i += step) {
|
|
18880
|
+
const i3 = i * 3;
|
|
18881
|
+
const lx = positions[i3], ly = positions[i3 + 1], lz = positions[i3 + 2];
|
|
18882
|
+
const tx = m0 * lx + m4 * ly + m8 * lz + m12;
|
|
18883
|
+
const ty = m1 * lx + m5 * ly + m9 * lz + m13;
|
|
18884
|
+
const tz = m2 * lx + m6 * ly + m10 * lz + m14;
|
|
18885
|
+
const cw = v3 * tx + v7 * ty + v11 * tz + v15;
|
|
18886
|
+
if (cw <= 0) continue;
|
|
18887
|
+
const invCw = 1 / cw;
|
|
18888
|
+
const pixX = ((v0 * tx + v4 * ty + v8 * tz + v12) * invCw - ndcX) * halfW;
|
|
18889
|
+
const pixY = ((v1 * tx + v5 * ty + v9 * tz + v13) * invCw - ndcY) * halfH;
|
|
18890
|
+
const screenDistSq = pixX * pixX + pixY * pixY;
|
|
18891
|
+
if (screenDistSq < pickRadSq) {
|
|
18892
|
+
const dx = tx - rox, dy = ty - roy, dz = tz - roz;
|
|
18893
|
+
const camDistSq = dx * dx + dy * dy + dz * dz;
|
|
18894
|
+
if (camDistSq < bestCamDistSq * 0.98 || camDistSq < bestCamDistSq * 1.02 && screenDistSq < bestScreenDistSq) {
|
|
18895
|
+
bestCamDistSq = camDistSq;
|
|
18896
|
+
bestScreenDistSq = screenDistSq;
|
|
18897
|
+
bestIdx = i;
|
|
18898
|
+
}
|
|
18899
|
+
}
|
|
18900
|
+
}
|
|
18901
|
+
if (bestIdx < 0) return null;
|
|
18902
|
+
const hx = positions[bestIdx * 3], hy = positions[bestIdx * 3 + 1], hz = positions[bestIdx * 3 + 2];
|
|
18903
|
+
return [
|
|
18904
|
+
m0 * hx + m4 * hy + m8 * hz + m12,
|
|
18905
|
+
m1 * hx + m5 * hy + m9 * hz + m13,
|
|
18906
|
+
m2 * hx + m6 * hy + m10 * hz + m14
|
|
18907
|
+
];
|
|
18908
|
+
}
|
|
18725
18909
|
getCamera() {
|
|
18726
18910
|
return this.camera;
|
|
18727
18911
|
}
|