@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.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
|
}
|
|
@@ -6624,12 +6721,14 @@ async function encodeWebP(width, height, pixelData) {
|
|
|
6624
6721
|
}
|
|
6625
6722
|
return new Uint8Array(await blob.arrayBuffer());
|
|
6626
6723
|
}
|
|
6724
|
+
const yieldToUI = () => new Promise((r) => setTimeout(r, 0));
|
|
6627
6725
|
async function serializeSOG(data, coordinateSystem = "blender", options = {}, onProgress) {
|
|
6628
6726
|
const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
|
|
6629
6727
|
const { count, colors, opacities } = data;
|
|
6630
6728
|
let { positions, scales, rotations } = data;
|
|
6631
6729
|
let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
|
|
6632
6730
|
onProgress == null ? void 0 : onProgress("coord_transform", 0);
|
|
6731
|
+
await yieldToUI();
|
|
6633
6732
|
if (coordinateSystem === "blender") {
|
|
6634
6733
|
positions = new Float32Array(positions);
|
|
6635
6734
|
scales = new Float32Array(scales);
|
|
@@ -6653,6 +6752,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6653
6752
|
}
|
|
6654
6753
|
}
|
|
6655
6754
|
onProgress == null ? void 0 : onProgress("morton_sort", 0.05);
|
|
6755
|
+
await yieldToUI();
|
|
6656
6756
|
const sortOrder = mortonSort(positions, count);
|
|
6657
6757
|
const sPos = new Float32Array(count * 3);
|
|
6658
6758
|
const sSca = new Float32Array(count * 3);
|
|
@@ -6686,6 +6786,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6686
6786
|
const { width, height } = calcImageDimensions(count);
|
|
6687
6787
|
const totalPixels = width * height;
|
|
6688
6788
|
onProgress == null ? void 0 : onProgress("encode_position", 0.1);
|
|
6789
|
+
await yieldToUI();
|
|
6689
6790
|
const logPos = new Float32Array(count * 3);
|
|
6690
6791
|
for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
|
|
6691
6792
|
const mins = [Infinity, Infinity, Infinity];
|
|
@@ -6714,8 +6815,10 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6714
6815
|
meansUData[off + 3] = 255;
|
|
6715
6816
|
}
|
|
6716
6817
|
onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
|
|
6818
|
+
await yieldToUI();
|
|
6717
6819
|
const logScales = new Float32Array(count * 3);
|
|
6718
|
-
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]));
|
|
6719
6822
|
const scaleCodebook = buildCodebook(logScales, 256);
|
|
6720
6823
|
const scalesData = new Uint8ClampedArray(totalPixels * 4);
|
|
6721
6824
|
scalesData.fill(255);
|
|
@@ -6727,6 +6830,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6727
6830
|
scalesData[off + 3] = 255;
|
|
6728
6831
|
}
|
|
6729
6832
|
onProgress == null ? void 0 : onProgress("encode_quaternion", 0.3);
|
|
6833
|
+
await yieldToUI();
|
|
6730
6834
|
const quatsData = new Uint8ClampedArray(totalPixels * 4);
|
|
6731
6835
|
quatsData.fill(255);
|
|
6732
6836
|
const SQRT2 = Math.SQRT2;
|
|
@@ -6774,12 +6878,19 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6774
6878
|
r1 = qx;
|
|
6775
6879
|
r2 = qy;
|
|
6776
6880
|
}
|
|
6777
|
-
quatsData[off] = Math.round(
|
|
6778
|
-
|
|
6779
|
-
|
|
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
|
+
);
|
|
6780
6890
|
quatsData[off + 3] = largest + 252;
|
|
6781
6891
|
}
|
|
6782
6892
|
onProgress == null ? void 0 : onProgress("encode_color", 0.4);
|
|
6893
|
+
await yieldToUI();
|
|
6783
6894
|
const dcValues = new Float32Array(count * 3);
|
|
6784
6895
|
for (let i = 0; i < count; i++) {
|
|
6785
6896
|
dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
|
|
@@ -6809,6 +6920,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6809
6920
|
];
|
|
6810
6921
|
if (actualBands > 0 && sSH) {
|
|
6811
6922
|
onProgress == null ? void 0 : onProgress("sh_compress", 0.5);
|
|
6923
|
+
await yieldToUI();
|
|
6812
6924
|
const pointsFlat = new Float32Array(count * shDim);
|
|
6813
6925
|
for (let i = 0; i < count; i++) {
|
|
6814
6926
|
const b = i * 45;
|
|
@@ -6827,9 +6939,11 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6827
6939
|
}
|
|
6828
6940
|
);
|
|
6829
6941
|
onProgress == null ? void 0 : onProgress("sh_quantize", 0.88);
|
|
6942
|
+
await yieldToUI();
|
|
6830
6943
|
const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
|
|
6831
6944
|
for (let c = 0; c < effectivePaletteSize; c++) {
|
|
6832
|
-
for (let d = 0; d < shDim; d++)
|
|
6945
|
+
for (let d = 0; d < shDim; d++)
|
|
6946
|
+
allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
|
|
6833
6947
|
}
|
|
6834
6948
|
const shCodebook = buildCodebook(allCoeffValues, 256);
|
|
6835
6949
|
const centW = 64 * numCoeffs;
|
|
@@ -6843,8 +6957,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6843
6957
|
const off = (v * centW + u) * 4;
|
|
6844
6958
|
const cOff = n * shDim;
|
|
6845
6959
|
centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
|
|
6846
|
-
centData[off + 1] = findNearest(
|
|
6847
|
-
|
|
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
|
+
);
|
|
6848
6968
|
centData[off + 3] = 255;
|
|
6849
6969
|
}
|
|
6850
6970
|
}
|
|
@@ -6869,6 +6989,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6869
6989
|
};
|
|
6870
6990
|
}
|
|
6871
6991
|
onProgress == null ? void 0 : onProgress("encode_webp", 0.92);
|
|
6992
|
+
await yieldToUI();
|
|
6872
6993
|
const webps = await Promise.all(webpPromises);
|
|
6873
6994
|
const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
|
|
6874
6995
|
const meta = {
|
|
@@ -6895,6 +7016,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
|
|
|
6895
7016
|
zipEntries["sh_labels.webp"] = webps[6];
|
|
6896
7017
|
}
|
|
6897
7018
|
onProgress == null ? void 0 : onProgress("pack_zip", 0.98);
|
|
7019
|
+
await yieldToUI();
|
|
6898
7020
|
const zipData = zipSync(zipEntries, { level: 0 });
|
|
6899
7021
|
onProgress == null ? void 0 : onProgress("done", 1);
|
|
6900
7022
|
return zipData.buffer;
|
|
@@ -18100,6 +18222,7 @@ class App {
|
|
|
18100
18222
|
this.camera = new Camera();
|
|
18101
18223
|
this.camera.setAspect(this.renderer.getAspectRatio());
|
|
18102
18224
|
this.controls = new OrbitControls(this.camera, this.canvas);
|
|
18225
|
+
this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
|
|
18103
18226
|
this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
|
|
18104
18227
|
this.glbLoader = new GLBLoader(this.renderer.device);
|
|
18105
18228
|
this.objLoader = new OBJLoader(this.renderer.device);
|
|
@@ -18720,6 +18843,67 @@ class App {
|
|
|
18720
18843
|
getRenderer() {
|
|
18721
18844
|
return this.renderer;
|
|
18722
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
|
+
}
|
|
18723
18907
|
getCamera() {
|
|
18724
18908
|
return this.camera;
|
|
18725
18909
|
}
|