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