@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 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(H - 1, Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos)));
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++) result[i] = centroidValues[effectiveK - 1];
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({ code: getGpuClusterShader(dim) });
6435
+ const shaderModule = device.createShaderModule({
6436
+ code: getGpuClusterShader(dim)
6437
+ });
6423
6438
  const bindGroupLayout = device.createBindGroupLayout({
6424
6439
  entries: [
6425
- { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
6426
- { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
6427
- { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
6428
- { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } }
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({ bindGroupLayouts: [bindGroupLayout] }),
6463
+ layout: device.createPipelineLayout({
6464
+ bindGroupLayouts: [bindGroupLayout]
6465
+ }),
6433
6466
  compute: { module: shaderModule, entryPoint: "main" }
6434
6467
  });
6435
- const uniformBuf = device.createBuffer({ size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
6436
- const centroidsBuf = device.createBuffer({ size: centroidsFlat.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
6437
- device.queue.writeBuffer(centroidsBuf, 0, centroidsFlat.buffer);
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({ size: pointsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
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({ size: resultsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
6442
- const readbackBuf = device.createBuffer({ size: resultsBufSize, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
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(uniformBuf, 0, new Uint32Array([currentBatchSize, numCentroids]));
6458
- device.queue.writeBuffer(pointsBuf, 0, pointsFlat.buffer, offset * dim * 4, currentBatchSize * dim * 4);
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(resultsBuf, 0, readbackBuf, 0, currentBatchSize * 4);
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(readbackBuf.getMappedRange(0, currentBatchSize * 4));
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(pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim), i * dim);
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(device, pointsFlat, centroids, n, k, dim);
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(device, pointsFlat, centroids, n, k, dim);
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++) centroids[off + d] = sums[off + d] / counts[c];
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(1023, Math.floor(1024 * (positions[i * 3] - minx) / xlen));
6587
- const iy = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen));
6588
- const iz = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen));
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
  }
@@ -6722,7 +6819,8 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6722
6819
  onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
6723
6820
  await yieldToUI();
6724
6821
  const logScales = new Float32Array(count * 3);
6725
- for (let i = 0; i < count * 3; i++) logScales[i] = Math.log(Math.max(1e-8, sSca[i]));
6822
+ for (let i = 0; i < count * 3; i++)
6823
+ logScales[i] = Math.log(Math.max(1e-8, sSca[i]));
6726
6824
  const scaleCodebook = buildCodebook(logScales, 256);
6727
6825
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6728
6826
  scalesData.fill(255);
@@ -6782,9 +6880,15 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6782
6880
  r1 = qx;
6783
6881
  r2 = qy;
6784
6882
  }
6785
- quatsData[off] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6786
- quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6787
- quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
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
+ );
6788
6892
  quatsData[off + 3] = largest + 252;
6789
6893
  }
6790
6894
  onProgress == null ? void 0 : onProgress("encode_color", 0.4);
@@ -6840,7 +6944,8 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6840
6944
  await yieldToUI();
6841
6945
  const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
6842
6946
  for (let c = 0; c < effectivePaletteSize; c++) {
6843
- for (let d = 0; d < shDim; d++) allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
6947
+ for (let d = 0; d < shDim; d++)
6948
+ allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
6844
6949
  }
6845
6950
  const shCodebook = buildCodebook(allCoeffValues, 256);
6846
6951
  const centW = 64 * numCoeffs;
@@ -6854,8 +6959,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6854
6959
  const off = (v * centW + u) * 4;
6855
6960
  const cOff = n * shDim;
6856
6961
  centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
6857
- centData[off + 1] = findNearest(shCodebook, centroids[cOff + c * 3 + 1]);
6858
- centData[off + 2] = findNearest(shCodebook, centroids[cOff + c * 3 + 2]);
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
+ );
6859
6970
  centData[off + 3] = 255;
6860
6971
  }
6861
6972
  }
@@ -18113,6 +18224,7 @@ class App {
18113
18224
  this.camera = new Camera();
18114
18225
  this.camera.setAspect(this.renderer.getAspectRatio());
18115
18226
  this.controls = new OrbitControls(this.camera, this.canvas);
18227
+ this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
18116
18228
  this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
18117
18229
  this.glbLoader = new GLBLoader(this.renderer.device);
18118
18230
  this.objLoader = new OBJLoader(this.renderer.device);
@@ -18733,6 +18845,67 @@ class App {
18733
18845
  getRenderer() {
18734
18846
  return this.renderer;
18735
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
+ }
18736
18909
  getCamera() {
18737
18910
  return this.camera;
18738
18911
  }