@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.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
  }
@@ -6624,12 +6721,14 @@ async function encodeWebP(width, height, pixelData) {
6624
6721
  }
6625
6722
  return new Uint8Array(await blob.arrayBuffer());
6626
6723
  }
6724
+ const yieldToUI = () => new Promise((r) => setTimeout(r, 0));
6627
6725
  async function serializeSOG(data, coordinateSystem = "blender", options = {}, onProgress) {
6628
6726
  const { maxSHBands = 3, iterations = 10, paletteSize = 1024 } = options;
6629
6727
  const { count, colors, opacities } = data;
6630
6728
  let { positions, scales, rotations } = data;
6631
6729
  let shCoeffs = data.shCoeffs ? new Float32Array(data.shCoeffs) : void 0;
6632
6730
  onProgress == null ? void 0 : onProgress("coord_transform", 0);
6731
+ await yieldToUI();
6633
6732
  if (coordinateSystem === "blender") {
6634
6733
  positions = new Float32Array(positions);
6635
6734
  scales = new Float32Array(scales);
@@ -6653,6 +6752,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6653
6752
  }
6654
6753
  }
6655
6754
  onProgress == null ? void 0 : onProgress("morton_sort", 0.05);
6755
+ await yieldToUI();
6656
6756
  const sortOrder = mortonSort(positions, count);
6657
6757
  const sPos = new Float32Array(count * 3);
6658
6758
  const sSca = new Float32Array(count * 3);
@@ -6686,6 +6786,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6686
6786
  const { width, height } = calcImageDimensions(count);
6687
6787
  const totalPixels = width * height;
6688
6788
  onProgress == null ? void 0 : onProgress("encode_position", 0.1);
6789
+ await yieldToUI();
6689
6790
  const logPos = new Float32Array(count * 3);
6690
6791
  for (let i = 0; i < count * 3; i++) logPos[i] = symLog(sPos[i]);
6691
6792
  const mins = [Infinity, Infinity, Infinity];
@@ -6714,8 +6815,10 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6714
6815
  meansUData[off + 3] = 255;
6715
6816
  }
6716
6817
  onProgress == null ? void 0 : onProgress("encode_scale", 0.2);
6818
+ await yieldToUI();
6717
6819
  const logScales = new Float32Array(count * 3);
6718
- 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]));
6719
6822
  const scaleCodebook = buildCodebook(logScales, 256);
6720
6823
  const scalesData = new Uint8ClampedArray(totalPixels * 4);
6721
6824
  scalesData.fill(255);
@@ -6727,6 +6830,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6727
6830
  scalesData[off + 3] = 255;
6728
6831
  }
6729
6832
  onProgress == null ? void 0 : onProgress("encode_quaternion", 0.3);
6833
+ await yieldToUI();
6730
6834
  const quatsData = new Uint8ClampedArray(totalPixels * 4);
6731
6835
  quatsData.fill(255);
6732
6836
  const SQRT2 = Math.SQRT2;
@@ -6774,12 +6878,19 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6774
6878
  r1 = qx;
6775
6879
  r2 = qy;
6776
6880
  }
6777
- quatsData[off] = Math.round(Math.max(0, Math.min(255, (r0 / SQRT2 + 0.5) * 255)));
6778
- quatsData[off + 1] = Math.round(Math.max(0, Math.min(255, (r1 / SQRT2 + 0.5) * 255)));
6779
- 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
+ );
6780
6890
  quatsData[off + 3] = largest + 252;
6781
6891
  }
6782
6892
  onProgress == null ? void 0 : onProgress("encode_color", 0.4);
6893
+ await yieldToUI();
6783
6894
  const dcValues = new Float32Array(count * 3);
6784
6895
  for (let i = 0; i < count; i++) {
6785
6896
  dcValues[i * 3] = (sCol[i * 3] - 0.5) / SH_C0;
@@ -6809,6 +6920,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6809
6920
  ];
6810
6921
  if (actualBands > 0 && sSH) {
6811
6922
  onProgress == null ? void 0 : onProgress("sh_compress", 0.5);
6923
+ await yieldToUI();
6812
6924
  const pointsFlat = new Float32Array(count * shDim);
6813
6925
  for (let i = 0; i < count; i++) {
6814
6926
  const b = i * 45;
@@ -6827,9 +6939,11 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6827
6939
  }
6828
6940
  );
6829
6941
  onProgress == null ? void 0 : onProgress("sh_quantize", 0.88);
6942
+ await yieldToUI();
6830
6943
  const allCoeffValues = new Float32Array(effectivePaletteSize * shDim);
6831
6944
  for (let c = 0; c < effectivePaletteSize; c++) {
6832
- 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];
6833
6947
  }
6834
6948
  const shCodebook = buildCodebook(allCoeffValues, 256);
6835
6949
  const centW = 64 * numCoeffs;
@@ -6843,8 +6957,14 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6843
6957
  const off = (v * centW + u) * 4;
6844
6958
  const cOff = n * shDim;
6845
6959
  centData[off] = findNearest(shCodebook, centroids[cOff + c * 3]);
6846
- centData[off + 1] = findNearest(shCodebook, centroids[cOff + c * 3 + 1]);
6847
- 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
+ );
6848
6968
  centData[off + 3] = 255;
6849
6969
  }
6850
6970
  }
@@ -6869,6 +6989,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6869
6989
  };
6870
6990
  }
6871
6991
  onProgress == null ? void 0 : onProgress("encode_webp", 0.92);
6992
+ await yieldToUI();
6872
6993
  const webps = await Promise.all(webpPromises);
6873
6994
  const [meansLWebP, meansUWebP, scalesWebP, quatsWebP, sh0WebP] = webps;
6874
6995
  const meta = {
@@ -6895,6 +7016,7 @@ async function serializeSOG(data, coordinateSystem = "blender", options = {}, on
6895
7016
  zipEntries["sh_labels.webp"] = webps[6];
6896
7017
  }
6897
7018
  onProgress == null ? void 0 : onProgress("pack_zip", 0.98);
7019
+ await yieldToUI();
6898
7020
  const zipData = zipSync(zipEntries, { level: 0 });
6899
7021
  onProgress == null ? void 0 : onProgress("done", 1);
6900
7022
  return zipData.buffer;
@@ -18100,6 +18222,7 @@ class App {
18100
18222
  this.camera = new Camera();
18101
18223
  this.camera.setAspect(this.renderer.getAspectRatio());
18102
18224
  this.controls = new OrbitControls(this.camera, this.canvas);
18225
+ this.controls.pickWorldPosition = (cx, cy) => this.pickSplatPosition(cx, cy);
18103
18226
  this.meshRenderer = new MeshRenderer(this.renderer, this.camera);
18104
18227
  this.glbLoader = new GLBLoader(this.renderer.device);
18105
18228
  this.objLoader = new OBJLoader(this.renderer.device);
@@ -18720,6 +18843,67 @@ class App {
18720
18843
  getRenderer() {
18721
18844
  return this.renderer;
18722
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
+ }
18723
18907
  getCamera() {
18724
18908
  return this.camera;
18725
18909
  }