@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.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(H - 1, Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos)));
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++) result[i] = centroidValues[effectiveK - 1];
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({ code: getGpuClusterShader(dim) });
6433
+ const shaderModule = device.createShaderModule({
6434
+ code: getGpuClusterShader(dim)
6435
+ });
6421
6436
  const bindGroupLayout = device.createBindGroupLayout({
6422
6437
  entries: [
6423
- { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
6424
- { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
6425
- { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
6426
- { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } }
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({ bindGroupLayouts: [bindGroupLayout] }),
6461
+ layout: device.createPipelineLayout({
6462
+ bindGroupLayouts: [bindGroupLayout]
6463
+ }),
6431
6464
  compute: { module: shaderModule, entryPoint: "main" }
6432
6465
  });
6433
- const uniformBuf = device.createBuffer({ size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
6434
- const centroidsBuf = device.createBuffer({ size: centroidsFlat.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
6435
- device.queue.writeBuffer(centroidsBuf, 0, centroidsFlat.buffer);
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({ size: pointsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
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({ size: resultsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
6440
- const readbackBuf = device.createBuffer({ size: resultsBufSize, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
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(uniformBuf, 0, new Uint32Array([currentBatchSize, numCentroids]));
6456
- device.queue.writeBuffer(pointsBuf, 0, pointsFlat.buffer, offset * dim * 4, currentBatchSize * dim * 4);
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(resultsBuf, 0, readbackBuf, 0, currentBatchSize * 4);
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(readbackBuf.getMappedRange(0, currentBatchSize * 4));
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(pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim), i * dim);
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(device, pointsFlat, centroids, n, k, dim);
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(device, pointsFlat, centroids, n, k, dim);
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++) centroids[off + d] = sums[off + d] / counts[c];
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(1023, Math.floor(1024 * (positions[i * 3] - minx) / xlen));
6585
- const iy = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen));
6586
- const iz = Math.min(1023, Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen));
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++) logScales[i] = Math.log(Math.max(1e-8, sSca[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(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6784
- quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6785
- quatsData[off + 2] = Math.round(Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255)));
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++) allCoeffValues[c * shDim + d] = centroids[c * 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(shCodebook, centroids[cOff + c * 3 + 1]);
6856
- centData[off + 2] = findNearest(shCodebook, centroids[cOff + c * 3 + 2]);
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
  }