@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 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
  }
@@ -6626,12 +6723,14 @@ async function encodeWebP(width, height, pixelData) {
6626
6723
  }
6627
6724
  return new Uint8Array(await blob.arrayBuffer());
6628
6725
  }
6726
+ const yieldToUI = () => new Promise((r) => setTimeout(r, 0));
6629
6727
  async function serializeSOG(data, coordinateSystem = "blender", options = {}, onProgress) {
6630
6728
  const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6631
6729
  const { count, colors, opacities } = data;
6632
6730
  let { positions, scales, rotations } = data;
6633
6731
  let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6634
6732
  onProgress == null ? void 0 : onProgress("coord_transform", 0);
6733
+ await yieldToUI();
6635
6734
  if (coordinateSystem === "blender") {
6636
6735
  positions = new Float32Array(positions);
6637
6736
  scales = new Float32Array(scales);
@@ -6655,6 +6754,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6655
6754
  }
6656
6755
  }
6657
6756
  onProgress == null ? void 0 : onProgress("morton_sort", 0.05);
6757
+ await yieldToUI();
6658
6758
  const sortOrder = mortonSort(positions, count);
6659
6759
  const sPos = new Float32Array(count * 3);
6660
6760
  const sSca = new Float32Array(count * 3);
@@ -6688,6 +6788,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6688
6788
  const { width, height } = calcImageDimensions(count);
6689
6789
  const totalPixels = width * height;
6690
6790
  onProgress == null ? void 0 : onProgress("encode_position", 0.1);
6791
+ await yieldToUI();
6691
6792
  const logPos = new Float32Array(count * 3);
6692
6793
  for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6693
6794
  const mins = [Infinity, Infinity, Infinity];
@@ -6716,8 +6817,10 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6716
6817
  meansUData[off + 3] = 255;
6717
6818
  }
6718
6819
  onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
6820
+ await yieldToUI();
6719
6821
  const logScales = new Float32Array(count * 3);
6720
- 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]));
6721
6824
  const scaleCodebook = buildCodebook(logScales, 256);
6722
6825
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6723
6826
  scalesData.fill(255);
@@ -6729,6 +6832,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6729
6832
  scalesData[off + 3] = 255;
6730
6833
  }
6731
6834
  onProgress == null ? void 0 : onProgress("encode_quaternion", 0.3);
6835
+ await yieldToUI();
6732
6836
  const quatsData = new Uint8ClampedArray(totalPixels * 4);
6733
6837
  quatsData.fill(255);
6734
6838
  const SQRT2 = Math.SQRT2;
@@ -6776,12 +6880,19 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6776
6880
  r1 = qx;
6777
6881
  r2 = qy;
6778
6882
  }
6779
- quatsData[off] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6780
- quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6781
- 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
+ );
6782
6892
  quatsData[off + 3] = largest + 252;
6783
6893
  }
6784
6894
  onProgress == null ? void 0 : onProgress("encode_color", 0.4);
6895
+ await yieldToUI();
6785
6896
  const dcValues = new Float32Array(count * 3);
6786
6897
  for (let i = 0; i < count; i++) {
6787
6898
  dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
@@ -6811,6 +6922,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6811
6922
  ];
6812
6923
  if (actualBands > 0 && sSH) {
6813
6924
  onProgress == null ? void 0 : onProgress("sh_compress", 0.5);
6925
+ await yieldToUI();
6814
6926
  const pointsFlat = new Float32Array(count * shDim);
6815
6927
  for (let i = 0; i < count; i++) {
6816
6928
  const b = i * 45;
@@ -6829,9 +6941,11 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6829
6941
  }
6830
6942
  );
6831
6943
  onProgress == null ? void 0 : onProgress("sh_quantize", 0.88);
6944
+ await yieldToUI();
6832
6945
  const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
6833
6946
  for (let c = 0; c < effectivePaletteSize; c++) {
6834
- 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];
6835
6949
  }
6836
6950
  const shCodebook = buildCodebook(allCoeffValues, 256);
6837
6951
  const centW = 64 * numCoeffs;
@@ -6845,8 +6959,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6845
6959
  const off = (v * centW + u) * 4;
6846
6960
  const cOff = n * shDim;
6847
6961
  centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
6848
- centData[off + 1] = findNearest(shCodebook, centroids[cOff + c * 3 + 1]);
6849
- 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
+ );
6850
6970
  centData[off + 3] = 255;
6851
6971
  }
6852
6972
  }
@@ -6871,6 +6991,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6871
6991
  };
6872
6992
  }
6873
6993
  onProgress == null ? void 0 : onProgress("encode_webp", 0.92);
6994
+ await yieldToUI();
6874
6995
  const webps = await Promise.all(webpPromises);
6875
6996
  const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
6876
6997
  const meta = {
@@ -6897,6 +7018,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6897
7018
  zipEntries["sh_labels.webp"] = webps[6];
6898
7019
  }
6899
7020
  onProgress == null ? void 0 : onProgress("pack_zip", 0.98);
7021
+ await yieldToUI();
6900
7022
  const zipData = zipSync(zipEntries, { level: 0 });
6901
7023
  onProgress == null ? void 0 : onProgress("done", 1);
6902
7024
  return zipData.buffer;
@@ -18102,6 +18224,7 @@ class App {
18102
18224
  this.camera = new Camera();
18103
18225
  this.camera.setAspect(this.renderer.getAspectRatio());
18104
18226
  this.controls = new OrbitControls(this.camera, this.canvas);
18227
+ this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
18105
18228
  this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
18106
18229
  this.glbLoader = new GLBLoader(this.renderer.device);
18107
18230
  this.objLoader = new OBJLoader(this.renderer.device);
@@ -18722,6 +18845,67 @@ class App {
18722
18845
  getRenderer() {
18723
18846
  return this.renderer;
18724
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
+ }
18725
18909
  getCamera() {
18726
18910
  return this.camera;
18727
18911
  }