@d5techs/3dgs-lib 1.4.25 → 1.4.27

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");
@@ -762,6 +764,9 @@ const _OrbitControls = class _OrbitControls {
762
764
  if (this.enableDamping) {
763
765
  this.velocityTheta += -deltaX * this.rotateSpeed;
764
766
  this.velocityPhi += -deltaY * this.rotateSpeed;
767
+ const maxRotVel = 0.5;
768
+ this.velocityTheta = Math.max(-maxRotVel, Math.min(maxRotVel, this.velocityTheta));
769
+ this.velocityPhi = Math.max(-maxRotVel, Math.min(maxRotVel, this.velocityPhi));
765
770
  } else {
766
771
  this.theta -= deltaX * this.rotateSpeed;
767
772
  this.phi -= deltaY * this.rotateSpeed;
@@ -780,9 +785,15 @@ const _OrbitControls = class _OrbitControls {
780
785
  onWheel(e) {
781
786
  e.preventDefault();
782
787
  if (!this.enabled) return;
783
- const zoomDelta = e.deltaY * this.zoomSpeed;
788
+ let delta = e.deltaY;
789
+ if (e.deltaMode === 1) delta *= 40;
790
+ else if (e.deltaMode === 2) delta *= 800;
791
+ const normalizedDelta = Math.sign(delta) * Math.min(Math.abs(delta), 150);
792
+ const zoomDelta = normalizedDelta * this.zoomSpeed;
784
793
  if (this.enableDamping) {
785
794
  this.velocityDistance += zoomDelta;
795
+ const maxVelocity = 2;
796
+ this.velocityDistance = Math.max(-maxVelocity, Math.min(maxVelocity, this.velocityDistance));
786
797
  } else {
787
798
  this.distance *= Math.exp(zoomDelta);
788
799
  this.distance = Math.max(
@@ -839,6 +850,13 @@ const _OrbitControls = class _OrbitControls {
839
850
  }
840
851
  onDblClick(e) {
841
852
  if (!this.enabled) return;
853
+ if (this.pickWorldPosition) {
854
+ const hit = this.pickWorldPosition(e.clientX, e.clientY);
855
+ if (hit) {
856
+ this.animateToTarget(hit);
857
+ }
858
+ return;
859
+ }
842
860
  const rect = this.canvas.getBoundingClientRect();
843
861
  const ndcX = (e.clientX - rect.left) / rect.width * 2 - 1;
844
862
  const ndcY = -((e.clientY - rect.top) / rect.height * 2 - 1);
@@ -6214,7 +6232,10 @@ function buildCodebook(values, k = 256) {
6214
6232
  for (let i = 0; i < N; i++) {
6215
6233
  const uniformPos = (sorted[i] - vMin) / vRange;
6216
6234
  const quantilePos = i / N;
6217
- const bin = Math.min(H - 1, Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos)));
6235
+ const bin = Math.min(
6236
+ H - 1,
6237
+ Math.floor(H * (beta * quantilePos + (1 - beta) * uniformPos))
6238
+ );
6218
6239
  counts[bin]++;
6219
6240
  sums[bin] += sorted[i];
6220
6241
  }
@@ -6291,7 +6312,8 @@ function buildCodebook(values, k = 256) {
6291
6312
  centroidValues.sort();
6292
6313
  const result = new Array(k);
6293
6314
  for (let i = 0; i < effectiveK; i++) result[i] = centroidValues[i];
6294
- for (let i = effectiveK; i < k; i++) result[i] = centroidValues[effectiveK - 1];
6315
+ for (let i = effectiveK; i < k; i++)
6316
+ result[i] = centroidValues[effectiveK - 1];
6295
6317
  return result;
6296
6318
  }
6297
6319
  function findNearest(sortedCB, value) {
@@ -6419,27 +6441,66 @@ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, num
6419
6441
  const WORKGROUP_SIZE2 = 64;
6420
6442
  const BATCH_SIZE = 1024 * WORKGROUP_SIZE2;
6421
6443
  const numBatches = Math.ceil(numPoints / BATCH_SIZE);
6422
- const shaderModule = device.createShaderModule({ code: getGpuClusterShader(dim) });
6444
+ const shaderModule = device.createShaderModule({
6445
+ code: getGpuClusterShader(dim)
6446
+ });
6423
6447
  const bindGroupLayout = device.createBindGroupLayout({
6424
6448
  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" } }
6449
+ {
6450
+ binding: 0,
6451
+ visibility: GPUShaderStage.COMPUTE,
6452
+ buffer: { type: "uniform" }
6453
+ },
6454
+ {
6455
+ binding: 1,
6456
+ visibility: GPUShaderStage.COMPUTE,
6457
+ buffer: { type: "read-only-storage" }
6458
+ },
6459
+ {
6460
+ binding: 2,
6461
+ visibility: GPUShaderStage.COMPUTE,
6462
+ buffer: { type: "read-only-storage" }
6463
+ },
6464
+ {
6465
+ binding: 3,
6466
+ visibility: GPUShaderStage.COMPUTE,
6467
+ buffer: { type: "storage" }
6468
+ }
6429
6469
  ]
6430
6470
  });
6431
6471
  const pipeline = device.createComputePipeline({
6432
- layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
6472
+ layout: device.createPipelineLayout({
6473
+ bindGroupLayouts: [bindGroupLayout]
6474
+ }),
6433
6475
  compute: { module: shaderModule, entryPoint: "main" }
6434
6476
  });
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);
6477
+ const uniformBuf = device.createBuffer({
6478
+ size: 8,
6479
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
6480
+ });
6481
+ const centroidsBuf = device.createBuffer({
6482
+ size: centroidsFlat.byteLength,
6483
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
6484
+ });
6485
+ device.queue.writeBuffer(
6486
+ centroidsBuf,
6487
+ 0,
6488
+ centroidsFlat.buffer
6489
+ );
6438
6490
  const pointsBufSize = BATCH_SIZE * dim * 4;
6439
- const pointsBuf = device.createBuffer({ size: pointsBufSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
6491
+ const pointsBuf = device.createBuffer({
6492
+ size: pointsBufSize,
6493
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
6494
+ });
6440
6495
  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 });
6496
+ const resultsBuf = device.createBuffer({
6497
+ size: resultsBufSize,
6498
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
6499
+ });
6500
+ const readbackBuf = device.createBuffer({
6501
+ size: resultsBufSize,
6502
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
6503
+ });
6443
6504
  const labels = new Uint32Array(numPoints);
6444
6505
  const bindGroup = device.createBindGroup({
6445
6506
  layout: bindGroupLayout,
@@ -6454,18 +6515,36 @@ async function gpuAssignLabels(device, pointsFlat, centroidsFlat, numPoints, num
6454
6515
  const offset = batch * BATCH_SIZE;
6455
6516
  const currentBatchSize = Math.min(numPoints - offset, BATCH_SIZE);
6456
6517
  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);
6518
+ device.queue.writeBuffer(
6519
+ uniformBuf,
6520
+ 0,
6521
+ new Uint32Array([currentBatchSize, numCentroids])
6522
+ );
6523
+ device.queue.writeBuffer(
6524
+ pointsBuf,
6525
+ 0,
6526
+ pointsFlat.buffer,
6527
+ offset * dim * 4,
6528
+ currentBatchSize * dim * 4
6529
+ );
6459
6530
  const encoder = device.createCommandEncoder();
6460
6531
  const pass = encoder.beginComputePass();
6461
6532
  pass.setPipeline(pipeline);
6462
6533
  pass.setBindGroup(0, bindGroup);
6463
6534
  pass.dispatchWorkgroups(groups);
6464
6535
  pass.end();
6465
- encoder.copyBufferToBuffer(resultsBuf, 0, readbackBuf, 0, currentBatchSize * 4);
6536
+ encoder.copyBufferToBuffer(
6537
+ resultsBuf,
6538
+ 0,
6539
+ readbackBuf,
6540
+ 0,
6541
+ currentBatchSize * 4
6542
+ );
6466
6543
  device.queue.submit([encoder.finish()]);
6467
6544
  await readbackBuf.mapAsync(GPUMapMode.READ);
6468
- const mapped = new Uint32Array(readbackBuf.getMappedRange(0, currentBatchSize * 4));
6545
+ const mapped = new Uint32Array(
6546
+ readbackBuf.getMappedRange(0, currentBatchSize * 4)
6547
+ );
6469
6548
  labels.set(mapped, offset);
6470
6549
  readbackBuf.unmap();
6471
6550
  }
@@ -6489,13 +6568,23 @@ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
6489
6568
  const centroids = new Float32Array(k * dim);
6490
6569
  const indices = pickRandomIndices(n, k);
6491
6570
  for (let i = 0; i < k; i++) {
6492
- centroids.set(pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim), i * dim);
6571
+ centroids.set(
6572
+ pointsFlat.subarray(indices[i] * dim, indices[i] * dim + dim),
6573
+ i * dim
6574
+ );
6493
6575
  }
6494
6576
  const device = await getGpuDevice();
6495
6577
  for (let iter = 0; iter < iterations; iter++) {
6496
6578
  onProgress == null ? void 0 : onProgress(iter, iterations);
6497
6579
  if (device) {
6498
- const newLabels = await gpuAssignLabels(device, pointsFlat, centroids, n, k, dim);
6580
+ const newLabels = await gpuAssignLabels(
6581
+ device,
6582
+ pointsFlat,
6583
+ centroids,
6584
+ n,
6585
+ k,
6586
+ dim
6587
+ );
6499
6588
  labels.set(newLabels);
6500
6589
  } else {
6501
6590
  assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
@@ -6504,7 +6593,14 @@ async function kmeansGpu(pointsFlat, n, dim, k, iterations, onProgress) {
6504
6593
  }
6505
6594
  onProgress == null ? void 0 : onProgress(iterations, iterations);
6506
6595
  if (device) {
6507
- const finalLabels = await gpuAssignLabels(device, pointsFlat, centroids, n, k, dim);
6596
+ const finalLabels = await gpuAssignLabels(
6597
+ device,
6598
+ pointsFlat,
6599
+ centroids,
6600
+ n,
6601
+ k,
6602
+ dim
6603
+ );
6508
6604
  labels.set(finalLabels);
6509
6605
  } else {
6510
6606
  assignLabelsCpu(pointsFlat, centroids, labels, n, k, dim);
@@ -6550,7 +6646,8 @@ function updateCentroids(points, centroids, labels, n, k, dim) {
6550
6646
  for (let c = 0; c < k; c++) {
6551
6647
  if (counts[c] > 0) {
6552
6648
  const off = c * dim;
6553
- for (let d = 0; d < dim; d++) centroids[off + d] = sums[off + d] / counts[c];
6649
+ for (let d = 0; d < dim; d++)
6650
+ centroids[off + d] = sums[off + d] / counts[c];
6554
6651
  } else {
6555
6652
  const idx = Math.floor(Math.random() * n);
6556
6653
  centroids.set(points.subarray(idx * dim, idx * dim + dim), c * dim);
@@ -6583,9 +6680,18 @@ function mortonSort(positions, count) {
6583
6680
  const morton = new Uint32Array(count);
6584
6681
  const indices = new Uint32Array(count);
6585
6682
  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));
6683
+ const ix = Math.min(
6684
+ 1023,
6685
+ Math.floor(1024 * (positions[i * 3] - minx) / xlen)
6686
+ );
6687
+ const iy = Math.min(
6688
+ 1023,
6689
+ Math.floor(1024 * (positions[i * 3 + 1] - miny) / ylen)
6690
+ );
6691
+ const iz = Math.min(
6692
+ 1023,
6693
+ Math.floor(1024 * (positions[i * 3 + 2] - minz) / zlen)
6694
+ );
6589
6695
  morton[i] = (part1By2(iz) << 2) + (part1By2(iy) << 1) + part1By2(ix);
6590
6696
  indices[i] = i;
6591
6697
  }
@@ -6722,7 +6828,8 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6722
6828
  onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
6723
6829
  await yieldToUI();
6724
6830
  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]));
6831
+ for (let i = 0; i < count * 3; i++)
6832
+ logScales[i] = Math.log(Math.max(1e-8, sSca[i]));
6726
6833
  const scaleCodebook = buildCodebook(logScales, 256);
6727
6834
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6728
6835
  scalesData.fill(255);
@@ -6782,9 +6889,15 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6782
6889
  r1 = qx;
6783
6890
  r2 = qy;
6784
6891
  }
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)));
6892
+ quatsData[off] = Math.round(
6893
+ Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255))
6894
+ );
6895
+ quatsData[off + 1] = Math.round(
6896
+ Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255))
6897
+ );
6898
+ quatsData[off + 2] = Math.round(
6899
+ Math.max(0, Math.min(255, (r2 / SQRT2 + 0.5) * 255))
6900
+ );
6788
6901
  quatsData[off + 3] = largest + 252;
6789
6902
  }
6790
6903
  onProgress == null ? void 0 : onProgress("encode_color", 0.4);
@@ -6840,7 +6953,8 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6840
6953
  await yieldToUI();
6841
6954
  const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
6842
6955
  for (let c = 0; c < effectivePaletteSize; c++) {
6843
- for (let d = 0; d < shDim; d++) allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
6956
+ for (let d = 0; d < shDim; d++)
6957
+ allCoeffValues[c * shDim + d] = centroids[c * shDim + d];
6844
6958
  }
6845
6959
  const shCodebook = buildCodebook(allCoeffValues, 256);
6846
6960
  const centW = 64 * numCoeffs;
@@ -6854,8 +6968,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6854
6968
  const off = (v * centW + u) * 4;
6855
6969
  const cOff = n * shDim;
6856
6970
  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]);
6971
+ centData[off + 1] = findNearest(
6972
+ shCodebook,
6973
+ centroids[cOff + c * 3 + 1]
6974
+ );
6975
+ centData[off + 2] = findNearest(
6976
+ shCodebook,
6977
+ centroids[cOff + c * 3 + 2]
6978
+ );
6859
6979
  centData[off + 3] = 255;
6860
6980
  }
6861
6981
  }
@@ -18113,6 +18233,7 @@ class App {
18113
18233
  this.camera = new Camera();
18114
18234
  this.camera.setAspect(this.renderer.getAspectRatio());
18115
18235
  this.controls = new OrbitControls(this.camera, this.canvas);
18236
+ this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
18116
18237
  this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
18117
18238
  this.glbLoader = new GLBLoader(this.renderer.device);
18118
18239
  this.objLoader = new OBJLoader(this.renderer.device);
@@ -18733,6 +18854,67 @@ class App {
18733
18854
  getRenderer() {
18734
18855
  return this.renderer;
18735
18856
  }
18857
+ /**
18858
+ * CPU splat 拾取:在屏幕坐标 (clientX, clientY) 处找最近的 splat,返回世界坐标或 null
18859
+ */
18860
+ pickSplatPosition(clientX, clientY) {
18861
+ const gsRenderer = this.sceneManager.getGSRenderer();
18862
+ if (!gsRenderer) return null;
18863
+ const positions = gsRenderer.getCPUPositions();
18864
+ if (!positions) return null;
18865
+ const rect = this.canvas.getBoundingClientRect();
18866
+ const ndcX = (clientX - rect.left) / rect.width * 2 - 1;
18867
+ const ndcY = -((clientY - rect.top) / rect.height * 2 - 1);
18868
+ const modelMatrix = gsRenderer.getModelMatrix();
18869
+ const vp = this.camera.viewProjectionMatrix;
18870
+ const count = positions.length / 3;
18871
+ const step = count > 3e5 ? Math.ceil(count / 3e5) : 1;
18872
+ const halfW = rect.width / 2;
18873
+ const halfH = rect.height / 2;
18874
+ const pickRadiusPx = 20;
18875
+ const pickRadSq = pickRadiusPx * pickRadiusPx;
18876
+ let bestCamDistSq = Infinity;
18877
+ let bestIdx = -1;
18878
+ let bestScreenDistSq = Infinity;
18879
+ const m0 = modelMatrix[0], m1 = modelMatrix[1], m2 = modelMatrix[2];
18880
+ const m4 = modelMatrix[4], m5 = modelMatrix[5], m6 = modelMatrix[6];
18881
+ const m8 = modelMatrix[8], m9 = modelMatrix[9], m10 = modelMatrix[10];
18882
+ const m12 = modelMatrix[12], m13 = modelMatrix[13], m14 = modelMatrix[14];
18883
+ const v0 = vp[0], v1 = vp[1], v3 = vp[3];
18884
+ const v4 = vp[4], v5 = vp[5], v7 = vp[7];
18885
+ const v8 = vp[8], v9 = vp[9], v11 = vp[11];
18886
+ const v12 = vp[12], v13 = vp[13], v15 = vp[15];
18887
+ const rox = this.camera.position[0], roy = this.camera.position[1], roz = this.camera.position[2];
18888
+ for (let i = 0; i < count; i += step) {
18889
+ const i3 = i * 3;
18890
+ const lx = positions[i3], ly = positions[i3 + 1], lz = positions[i3 + 2];
18891
+ const tx = m0 * lx + m4 * ly + m8 * lz + m12;
18892
+ const ty = m1 * lx + m5 * ly + m9 * lz + m13;
18893
+ const tz = m2 * lx + m6 * ly + m10 * lz + m14;
18894
+ const cw = v3 * tx + v7 * ty + v11 * tz + v15;
18895
+ if (cw <= 0) continue;
18896
+ const invCw = 1 / cw;
18897
+ const pixX = ((v0 * tx + v4 * ty + v8 * tz + v12) * invCw - ndcX) * halfW;
18898
+ const pixY = ((v1 * tx + v5 * ty + v9 * tz + v13) * invCw - ndcY) * halfH;
18899
+ const screenDistSq = pixX * pixX + pixY * pixY;
18900
+ if (screenDistSq < pickRadSq) {
18901
+ const dx = tx - rox, dy = ty - roy, dz = tz - roz;
18902
+ const camDistSq = dx * dx + dy * dy + dz * dz;
18903
+ if (camDistSq < bestCamDistSq * 0.98 || camDistSq < bestCamDistSq * 1.02 && screenDistSq < bestScreenDistSq) {
18904
+ bestCamDistSq = camDistSq;
18905
+ bestScreenDistSq = screenDistSq;
18906
+ bestIdx = i;
18907
+ }
18908
+ }
18909
+ }
18910
+ if (bestIdx < 0) return null;
18911
+ const hx = positions[bestIdx * 3], hy = positions[bestIdx * 3 + 1], hz = positions[bestIdx * 3 + 2];
18912
+ return [
18913
+ m0 * hx + m4 * hy + m8 * hz + m12,
18914
+ m1 * hx + m5 * hy + m9 * hz + m13,
18915
+ m2 * hx + m6 * hy + m10 * hz + m14
18916
+ ];
18917
+ }
18736
18918
  getCamera() {
18737
18919
  return this.camera;
18738
18920
  }