@luxonis/depthai-viewer-common 3.9.8 → 4.0.0

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.
Files changed (154) hide show
  1. package/THIRD_PARTY_NOTICES.md +36 -304
  2. package/benchmark-cli.mjs +166 -11
  3. package/benchmark-lib.sh +40 -5
  4. package/benchmark-root.sh +79 -13
  5. package/benchmark.sh +112 -5
  6. package/dist/components/Streams.d.ts +6 -0
  7. package/dist/components/Streams.d.ts.map +1 -1
  8. package/dist/components/Streams.js +12 -4
  9. package/dist/components/Streams.js.map +1 -1
  10. package/dist/hooks/benchmark-automation.d.ts.map +1 -1
  11. package/dist/hooks/benchmark-automation.js +288 -63
  12. package/dist/hooks/benchmark-automation.js.map +1 -1
  13. package/dist/index.css +7 -0
  14. package/dist/utils/vite-dev-fix.d.ts.map +1 -1
  15. package/dist/utils/vite-dev-fix.js +0 -15
  16. package/dist/utils/vite-dev-fix.js.map +1 -1
  17. package/dist/visualizer/components/Panel.d.ts.map +1 -1
  18. package/dist/visualizer/components/Panel.js +6 -3
  19. package/dist/visualizer/components/Panel.js.map +1 -1
  20. package/dist/visualizer/components/shared-interval.d.ts +1 -1
  21. package/dist/visualizer/components/shared-interval.d.ts.map +1 -1
  22. package/dist/visualizer/components/shared-interval.js +6 -2
  23. package/dist/visualizer/components/shared-interval.js.map +1 -1
  24. package/dist/visualizer/connection/communicator.d.ts +1 -0
  25. package/dist/visualizer/connection/communicator.d.ts.map +1 -1
  26. package/dist/visualizer/connection/communicator.js.map +1 -1
  27. package/dist/visualizer/connection/connection.d.ts.map +1 -1
  28. package/dist/visualizer/connection/connection.js +8 -7
  29. package/dist/visualizer/connection/connection.js.map +1 -1
  30. package/dist/visualizer/connection/foxglove-connection.d.ts +4 -2
  31. package/dist/visualizer/connection/foxglove-connection.d.ts.map +1 -1
  32. package/dist/visualizer/connection/foxglove-connection.js +88 -40
  33. package/dist/visualizer/connection/foxglove-connection.js.map +1 -1
  34. package/dist/visualizer/context/MessagePipelineProvider.d.ts +1 -1
  35. package/dist/visualizer/context/MessagePipelineProvider.d.ts.map +1 -1
  36. package/dist/visualizer/context/MessagePipelineProvider.js +24 -6
  37. package/dist/visualizer/context/MessagePipelineProvider.js.map +1 -1
  38. package/dist/visualizer/context/VisualizerContext.d.ts.map +1 -1
  39. package/dist/visualizer/context/VisualizerContext.js +1 -32
  40. package/dist/visualizer/context/VisualizerContext.js.map +1 -1
  41. package/dist/visualizer/index.d.ts +1 -0
  42. package/dist/visualizer/index.d.ts.map +1 -1
  43. package/dist/visualizer/index.js +1 -0
  44. package/dist/visualizer/index.js.map +1 -1
  45. package/dist/visualizer/messaging/browser-decoder-fixtures.d.ts.map +1 -1
  46. package/dist/visualizer/messaging/browser-decoder-fixtures.js +187 -24
  47. package/dist/visualizer/messaging/browser-decoder-fixtures.js.map +1 -1
  48. package/dist/visualizer/messaging/decode-core.d.ts +9 -1
  49. package/dist/visualizer/messaging/decode-core.d.ts.map +1 -1
  50. package/dist/visualizer/messaging/decode-core.js +26 -16
  51. package/dist/visualizer/messaging/decode-core.js.map +1 -1
  52. package/dist/visualizer/messaging/decoder-fixtures.d.ts.map +1 -1
  53. package/dist/visualizer/messaging/decoder-fixtures.js +128 -1
  54. package/dist/visualizer/messaging/decoder-fixtures.js.map +1 -1
  55. package/dist/visualizer/messaging/decoder-harness.d.ts +2 -1
  56. package/dist/visualizer/messaging/decoder-harness.d.ts.map +1 -1
  57. package/dist/visualizer/messaging/decoder-harness.js.map +1 -1
  58. package/dist/visualizer/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.d.ts +6 -1
  59. package/dist/visualizer/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.d.ts.map +1 -1
  60. package/dist/visualizer/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js +5 -1
  61. package/dist/visualizer/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js.map +1 -1
  62. package/dist/visualizer/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts +10 -14
  63. package/dist/visualizer/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts.map +1 -1
  64. package/dist/visualizer/messaging/deserialization/pointcloud/poitcloudPoolManager.js +56 -288
  65. package/dist/visualizer/messaging/deserialization/pointcloud/poitcloudPoolManager.js.map +1 -1
  66. package/dist/visualizer/messaging/deserialization/pointcloud/utils.d.ts.map +1 -1
  67. package/dist/visualizer/messaging/deserialization/pointcloud/utils.js +2 -7
  68. package/dist/visualizer/messaging/deserialization/pointcloud/utils.js.map +1 -1
  69. package/dist/visualizer/messaging/deserialization/video/depth.d.ts +2 -1
  70. package/dist/visualizer/messaging/deserialization/video/depth.d.ts.map +1 -1
  71. package/dist/visualizer/messaging/deserialization/video/depth.js +9 -65
  72. package/dist/visualizer/messaging/deserialization/video/depth.js.map +1 -1
  73. package/dist/visualizer/messaging/deserialization/video/h264.d.ts +3 -3
  74. package/dist/visualizer/messaging/deserialization/video/h264.d.ts.map +1 -1
  75. package/dist/visualizer/messaging/deserialization/video/h264.js +80 -25
  76. package/dist/visualizer/messaging/deserialization/video/h264.js.map +1 -1
  77. package/dist/visualizer/messaging/deserialization/video/h265.d.ts +1 -1
  78. package/dist/visualizer/messaging/deserialization/video/h265.d.ts.map +1 -1
  79. package/dist/visualizer/messaging/deserialization/video/h265.js +62 -22
  80. package/dist/visualizer/messaging/deserialization/video/h265.js.map +1 -1
  81. package/dist/visualizer/messaging/deserialization/video/mjpeg.d.ts +1 -0
  82. package/dist/visualizer/messaging/deserialization/video/mjpeg.d.ts.map +1 -1
  83. package/dist/visualizer/messaging/deserialization/video/mjpeg.js +14 -2
  84. package/dist/visualizer/messaging/deserialization/video/mjpeg.js.map +1 -1
  85. package/dist/visualizer/messaging/deserialization/video/rgbd-to-pointcloud.d.ts +3 -1
  86. package/dist/visualizer/messaging/deserialization/video/rgbd-to-pointcloud.d.ts.map +1 -1
  87. package/dist/visualizer/messaging/deserialization/video/rgbd-to-pointcloud.js +152 -250
  88. package/dist/visualizer/messaging/deserialization/video/rgbd-to-pointcloud.js.map +1 -1
  89. package/dist/visualizer/messaging/deserialization.worker.d.ts +5 -3
  90. package/dist/visualizer/messaging/deserialization.worker.d.ts.map +1 -1
  91. package/dist/visualizer/messaging/deserialization.worker.js +16 -2
  92. package/dist/visualizer/messaging/deserialization.worker.js.map +1 -1
  93. package/dist/visualizer/messaging/message-event.d.ts +3 -0
  94. package/dist/visualizer/messaging/message-event.d.ts.map +1 -1
  95. package/dist/visualizer/messaging/message-event.js +18 -4
  96. package/dist/visualizer/messaging/message-event.js.map +1 -1
  97. package/dist/visualizer/messaging/message-handler.d.ts +3 -1
  98. package/dist/visualizer/messaging/message-handler.d.ts.map +1 -1
  99. package/dist/visualizer/messaging/message-handler.js +120 -49
  100. package/dist/visualizer/messaging/message-handler.js.map +1 -1
  101. package/dist/visualizer/output.css +7 -0
  102. package/dist/visualizer/panels/PointCloudPanel.d.ts.map +1 -1
  103. package/dist/visualizer/panels/PointCloudPanel.js +2 -0
  104. package/dist/visualizer/panels/PointCloudPanel.js.map +1 -1
  105. package/dist/visualizer/panels/image/ImageCanvasRenderer.d.ts.map +1 -1
  106. package/dist/visualizer/panels/image/ImageCanvasRenderer.js +246 -69
  107. package/dist/visualizer/panels/image/ImageCanvasRenderer.js.map +1 -1
  108. package/dist/visualizer/panels/image/annotation-overlay.d.ts.map +1 -1
  109. package/dist/visualizer/panels/image/annotation-overlay.js +16 -2
  110. package/dist/visualizer/panels/image/annotation-overlay.js.map +1 -1
  111. package/dist/visualizer/panels/image/raw-image-decoder.d.ts.map +1 -1
  112. package/dist/visualizer/panels/image/raw-image-decoder.js +1 -0
  113. package/dist/visualizer/panels/image/raw-image-decoder.js.map +1 -1
  114. package/dist/visualizer/panels/image/webgl-image-renderer.d.ts +11 -2
  115. package/dist/visualizer/panels/image/webgl-image-renderer.d.ts.map +1 -1
  116. package/dist/visualizer/panels/image/webgl-image-renderer.js +236 -41
  117. package/dist/visualizer/panels/image/webgl-image-renderer.js.map +1 -1
  118. package/dist/visualizer/panels/image/webgl-segmentation-mask-renderer.d.ts +16 -0
  119. package/dist/visualizer/panels/image/webgl-segmentation-mask-renderer.d.ts.map +1 -0
  120. package/dist/visualizer/panels/image/webgl-segmentation-mask-renderer.js +184 -0
  121. package/dist/visualizer/panels/image/webgl-segmentation-mask-renderer.js.map +1 -0
  122. package/dist/visualizer/panels/image/worker-image-decoder.d.ts.map +1 -1
  123. package/dist/visualizer/panels/image/worker-image-decoder.js +8 -3
  124. package/dist/visualizer/panels/image/worker-image-decoder.js.map +1 -1
  125. package/dist/visualizer/panels/pointcloud/PointCloudCanvasRenderer.d.ts.map +1 -1
  126. package/dist/visualizer/panels/pointcloud/PointCloudCanvasRenderer.js +256 -73
  127. package/dist/visualizer/panels/pointcloud/PointCloudCanvasRenderer.js.map +1 -1
  128. package/dist/visualizer/panels/pointcloud/point-cloud-materials.d.ts +5 -0
  129. package/dist/visualizer/panels/pointcloud/point-cloud-materials.d.ts.map +1 -0
  130. package/dist/visualizer/panels/pointcloud/point-cloud-materials.js +123 -0
  131. package/dist/visualizer/panels/pointcloud/point-cloud-materials.js.map +1 -0
  132. package/dist/visualizer/protobuf.generated/NNData.d.ts +92 -0
  133. package/dist/visualizer/protobuf.generated/NNData.d.ts.map +1 -0
  134. package/dist/visualizer/protobuf.generated/NNData.js +620 -0
  135. package/dist/visualizer/protobuf.generated/NNData.js.map +1 -0
  136. package/dist/visualizer/utils/benchmark-metrics.d.ts +27 -0
  137. package/dist/visualizer/utils/benchmark-metrics.d.ts.map +1 -0
  138. package/dist/visualizer/utils/benchmark-metrics.js +139 -0
  139. package/dist/visualizer/utils/benchmark-metrics.js.map +1 -0
  140. package/dist/visualizer/utils/comlink-wrap.d.ts.map +1 -1
  141. package/dist/visualizer/utils/comlink-wrap.js +5 -0
  142. package/dist/visualizer/utils/comlink-wrap.js.map +1 -1
  143. package/dist/visualizer/utils/poitcloud-sync.d.ts.map +1 -1
  144. package/dist/visualizer/utils/poitcloud-sync.js +5 -2
  145. package/dist/visualizer/utils/poitcloud-sync.js.map +1 -1
  146. package/package.json +1 -2
  147. package/dist/visualizer/messaging/deserialization/pointcloud/i420ToRgbaToPoitcloud.worker.d.ts +0 -2
  148. package/dist/visualizer/messaging/deserialization/pointcloud/i420ToRgbaToPoitcloud.worker.d.ts.map +0 -1
  149. package/dist/visualizer/messaging/deserialization/pointcloud/i420ToRgbaToPoitcloud.worker.js +0 -45
  150. package/dist/visualizer/messaging/deserialization/pointcloud/i420ToRgbaToPoitcloud.worker.js.map +0 -1
  151. package/dist/visualizer/messaging/deserialization/video/depth-coloring.worker.d.ts +0 -5
  152. package/dist/visualizer/messaging/deserialization/video/depth-coloring.worker.d.ts.map +0 -1
  153. package/dist/visualizer/messaging/deserialization/video/depth-coloring.worker.js +0 -124
  154. package/dist/visualizer/messaging/deserialization/video/depth-coloring.worker.js.map +0 -1
@@ -6,7 +6,8 @@ import { useLiveMessageSubscriptionController, useMessagePipeline, } from "../..
6
6
  import { useMessageSubscription } from "../../messaging/utils.js";
7
7
  import { readCanvasColor, useThemeMode } from "../../utils/use-theme-mode.js";
8
8
  import { DEFAULT_POINT_CLOUD_CAMERA_STATE } from "../pointcloud-camera.js";
9
- import { getPointCloudRenderScale, hasPointCloudColor, POINT_CLOUD_FRAGMENT_SHADER, POINT_CLOUD_VERTEX_SHADER, } from "./point-cloud-rendering.js";
9
+ import { createPackedPointCloudAttribute, createPackedPointCloudMaterial, createPointCloudMaterial, } from "./point-cloud-materials.js";
10
+ import { getPointCloudRenderScale, hasPointCloudColor } from "./point-cloud-rendering.js";
10
11
  const PANEL_STYLE = {
11
12
  width: "100%",
12
13
  height: "100%",
@@ -31,11 +32,11 @@ const DEFAULT_ORBIT_SPEED_RAD_PER_SECOND = 0.6;
31
32
  const ORBIT_SPEED_STEP_MULTIPLIER = 1.25;
32
33
  const MIN_ORBIT_SPEED_RAD_PER_SECOND = 0.05;
33
34
  const MAX_ORBIT_SPEED_RAD_PER_SECOND = 6;
34
- function createSubscriptionId() {
35
- return typeof crypto !== "undefined" && "randomUUID" in crypto
36
- ? crypto.randomUUID()
37
- : `pointcloud-${Math.random().toString(36).slice(2)}`;
38
- }
35
+ const FAST_PATH_BOUNDS_REFRESH_INTERVAL = 30;
36
+ const HOVER_PICK_INTERVAL_MS = 100;
37
+ const MAX_HOVER_PICK_POINT_COUNT = 100_000;
38
+ const MAX_CLICK_PICK_POINT_COUNT = 500_000;
39
+ const MAX_FAST_PATH_BOUNDS_SAMPLES = 16_384;
39
40
  function getTopicPointSettings(topic, initialConfig) {
40
41
  return {
41
42
  minValue: initialConfig.topics?.[topic]?.minValue ?? 0,
@@ -68,6 +69,9 @@ function isPointCloudMessage(message) {
68
69
  Array.isArray(candidate.fields) &&
69
70
  candidate.data instanceof Uint8Array);
70
71
  }
72
+ function extractRenderablePointCloudFrame(event) {
73
+ return extractPointCloudMessage(event);
74
+ }
71
75
  function extractPointCloudMessage(event) {
72
76
  if (!POINT_CLOUD_RENDER_SCHEMAS.has(event.schemaName)) {
73
77
  return undefined;
@@ -209,6 +213,7 @@ class StandalonePointCloudRenderer {
209
213
  #resizeObserver;
210
214
  #geometry;
211
215
  #material;
216
+ #packedMaterial;
212
217
  #points;
213
218
  #cameraReticle = createCameraReticle();
214
219
  #raycaster = new THREE.Raycaster();
@@ -222,6 +227,11 @@ class StandalonePointCloudRenderer {
222
227
  #whiteColorsArray;
223
228
  #positionsAttribute;
224
229
  #colorsAttribute;
230
+ #packedPointDataAttribute;
231
+ #positionInterleavedBuffer;
232
+ #usesPackedPointData = false;
233
+ #boundsPointCount = 0;
234
+ #framesSinceBoundsRefresh = FAST_PATH_BOUNDS_REFRESH_INTERVAL;
225
235
  #currentBackground = "";
226
236
  #pointSize;
227
237
  #pointSettings;
@@ -258,20 +268,8 @@ class StandalonePointCloudRenderer {
258
268
  this.#controls.addEventListener("change", this.#requestRender);
259
269
  this.#canvas.addEventListener("keydown", this.#handleKeyDown);
260
270
  this.#geometry = new THREE.BufferGeometry();
261
- this.#material = new THREE.ShaderMaterial({
262
- vertexShader: POINT_CLOUD_VERTEX_SHADER,
263
- fragmentShader: POINT_CLOUD_FRAGMENT_SHADER,
264
- uniforms: {
265
- uPointSize: { value: this.#pointSize },
266
- uUseDepthColor: { value: false },
267
- uMinDepth: { value: 0 },
268
- uMaxDepth: { value: 1 },
269
- },
270
- depthTest: true,
271
- depthWrite: true,
272
- transparent: false,
273
- toneMapped: false,
274
- });
271
+ this.#material = createPointCloudMaterial(this.#pointSize, this.#pointSettings.minValue, this.#pointSettings.maxValue);
272
+ this.#packedMaterial = createPackedPointCloudMaterial(this.#pointSize, this.#pointSettings.minValue, this.#pointSettings.maxValue);
275
273
  this.#points = new THREE.Points(this.#geometry, this.#material);
276
274
  this.#scene.add(this.#points);
277
275
  this.#scene.add(this.#cameraReticle);
@@ -298,6 +296,7 @@ class StandalonePointCloudRenderer {
298
296
  this.#controls.dispose();
299
297
  this.#geometry.dispose();
300
298
  this.#material.dispose();
299
+ this.#packedMaterial.dispose();
301
300
  this.#cameraReticle.geometry.dispose();
302
301
  if (Array.isArray(this.#cameraReticle.material)) {
303
302
  for (const material of this.#cameraReticle.material) {
@@ -322,6 +321,7 @@ class StandalonePointCloudRenderer {
322
321
  this.#pointCloud = pointCloud;
323
322
  const useDepthColor = !hasPointCloudColor(pointCloud.fields);
324
323
  this.#material.uniforms.uUseDepthColor.value = useDepthColor;
324
+ this.#packedMaterial.uniforms.uUseDepthColor.value = useDepthColor;
325
325
  this.#updateGeometry(pointCloud);
326
326
  this.#updateDepthColorRange();
327
327
  this.#updatePointsPose(pointCloud);
@@ -366,8 +366,10 @@ class StandalonePointCloudRenderer {
366
366
  }
367
367
  this.#requestRender();
368
368
  }
369
- pick(clientX, clientY) {
370
- if (this.#pointCloud == undefined || this.#geometry.drawRange.count === 0) {
369
+ pick(clientX, clientY, maxPointCount = Number.POSITIVE_INFINITY) {
370
+ if (this.#pointCloud == undefined ||
371
+ this.#geometry.drawRange.count === 0 ||
372
+ this.#pointCloud.pointCount > maxPointCount) {
371
373
  return undefined;
372
374
  }
373
375
  const rect = this.#canvas.getBoundingClientRect();
@@ -376,6 +378,9 @@ class StandalonePointCloudRenderer {
376
378
  }
377
379
  const pointer = new THREE.Vector2(((clientX - rect.left) / rect.width) * 2 - 1, -((clientY - rect.top) / rect.height) * 2 + 1);
378
380
  this.#raycaster.setFromCamera(pointer, this.#camera);
381
+ if (this.#usesPackedPointData) {
382
+ return this.#pickPackedPointCloud();
383
+ }
379
384
  const hit = this.#raycaster.intersectObject(this.#points, false)[0];
380
385
  if (!hit || hit.index == undefined) {
381
386
  return undefined;
@@ -576,12 +581,118 @@ class StandalonePointCloudRenderer {
576
581
  this.#cameraReticle.quaternion.copy(this.#points.quaternion);
577
582
  this.#points.updateMatrixWorld(true);
578
583
  }
579
- #syncBoundsFromGeometry() {
580
- this.#geometry.computeBoundingBox();
581
- this.#geometry.computeBoundingSphere();
582
- if (this.#geometry.boundingSphere) {
583
- this.#boundsSphere.copy(this.#geometry.boundingSphere);
584
+ #pickPackedPointCloud() {
585
+ const pointCloud = this.#pointCloud;
586
+ if (!pointCloud) {
587
+ return undefined;
588
+ }
589
+ const localRay = this.#raycaster.ray
590
+ .clone()
591
+ .applyMatrix4(new THREE.Matrix4().copy(this.#points.matrixWorld).invert());
592
+ const threshold = this.#raycaster.params.Points?.threshold ?? 1;
593
+ const averageScale = (this.#points.scale.x + this.#points.scale.y + this.#points.scale.z) / 3;
594
+ const localThresholdSq = (threshold / averageScale) ** 2;
595
+ const positions = new Float32Array(pointCloud.data.buffer, pointCloud.data.byteOffset, pointCloud.pointCount * 4);
596
+ const point = new THREE.Vector3();
597
+ const worldPoint = new THREE.Vector3();
598
+ let closestDistance = Number.POSITIVE_INFINITY;
599
+ let closestIndex;
600
+ for (let index = 0; index < pointCloud.pointCount; index += 1) {
601
+ const offset = index * 4;
602
+ point.set(positions[offset], positions[offset + 1], positions[offset + 2]);
603
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y) || !Number.isFinite(point.z)) {
604
+ continue;
605
+ }
606
+ if (localRay.distanceSqToPoint(point) > localThresholdSq) {
607
+ continue;
608
+ }
609
+ worldPoint.copy(point).applyMatrix4(this.#points.matrixWorld);
610
+ const distance = this.#raycaster.ray.origin.distanceTo(worldPoint);
611
+ if (distance < closestDistance) {
612
+ closestDistance = distance;
613
+ closestIndex = index;
614
+ }
584
615
  }
616
+ if (closestIndex == undefined) {
617
+ return undefined;
618
+ }
619
+ return {
620
+ index: closestIndex,
621
+ offset: getHitOffset(pointCloud, closestIndex),
622
+ };
623
+ }
624
+ #refreshFastPathBounds(pointCount, positions, pointStride) {
625
+ const shouldRefresh = this.#geometry.boundingSphere == undefined ||
626
+ this.#boundsPointCount !== pointCount ||
627
+ this.#framesSinceBoundsRefresh >= FAST_PATH_BOUNDS_REFRESH_INTERVAL;
628
+ if (!shouldRefresh) {
629
+ this.#framesSinceBoundsRefresh += 1;
630
+ return;
631
+ }
632
+ let minX = Number.POSITIVE_INFINITY;
633
+ let minY = Number.POSITIVE_INFINITY;
634
+ let minZ = Number.POSITIVE_INFINITY;
635
+ let maxX = Number.NEGATIVE_INFINITY;
636
+ let maxY = Number.NEGATIVE_INFINITY;
637
+ let maxZ = Number.NEGATIVE_INFINITY;
638
+ const sampleStride = Math.max(1, Math.ceil(pointCount / MAX_FAST_PATH_BOUNDS_SAMPLES));
639
+ // Bounds only drive camera fitting and coarse frustum culling. Sampling uniformly keeps
640
+ // their refresh cost bounded instead of periodically scanning millions of points on the
641
+ // main thread.
642
+ for (let index = 0; index < pointCount; index += sampleStride) {
643
+ const offset = index * pointStride;
644
+ const x = positions[offset];
645
+ const y = positions[offset + 1];
646
+ const z = positions[offset + 2];
647
+ if (x == undefined || y == undefined || z == undefined) {
648
+ continue;
649
+ }
650
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
651
+ continue;
652
+ }
653
+ if (x < minX)
654
+ minX = x;
655
+ if (y < minY)
656
+ minY = y;
657
+ if (z < minZ)
658
+ minZ = z;
659
+ if (x > maxX)
660
+ maxX = x;
661
+ if (y > maxY)
662
+ maxY = y;
663
+ if (z > maxZ)
664
+ maxZ = z;
665
+ }
666
+ if (minX === Number.POSITIVE_INFINITY) {
667
+ this.#geometry.boundingBox = new THREE.Box3();
668
+ this.#geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), 0);
669
+ }
670
+ else {
671
+ const boundingBox = new THREE.Box3(new THREE.Vector3(minX, minY, minZ), new THREE.Vector3(maxX, maxY, maxZ));
672
+ const padding = Math.max(1, boundingBox.getSize(new THREE.Vector3()).length() * 0.02);
673
+ boundingBox.expandByScalar(padding);
674
+ this.#geometry.boundingBox = boundingBox;
675
+ this.#geometry.boundingSphere = boundingBox.getBoundingSphere(new THREE.Sphere());
676
+ }
677
+ this.#boundsSphere.copy(this.#geometry.boundingSphere);
678
+ this.#boundsPointCount = pointCount;
679
+ this.#framesSinceBoundsRefresh = 0;
680
+ }
681
+ #disposeGeometryAttributes() {
682
+ // Three.js keys GPU buffers by attribute identity. Disposing before replacing an
683
+ // attribute ensures a resolution/layout change cannot strand the old GPU buffer.
684
+ this.#geometry.dispose();
685
+ for (const attributeName of Object.keys(this.#geometry.attributes)) {
686
+ this.#geometry.deleteAttribute(attributeName);
687
+ }
688
+ this.#geometry.boundingBox = null;
689
+ this.#geometry.boundingSphere = null;
690
+ this.#positionsAttribute = undefined;
691
+ this.#colorsAttribute = undefined;
692
+ this.#packedPointDataAttribute = undefined;
693
+ this.#positionInterleavedBuffer = undefined;
694
+ this.#boundsPointCount = 0;
695
+ this.#framesSinceBoundsRefresh = FAST_PATH_BOUNDS_REFRESH_INTERVAL;
585
696
  }
586
697
  #updateDepthColorRange() {
587
698
  const minDepth = Number.isFinite(this.#pointSettings.minValue)
@@ -594,31 +705,37 @@ class StandalonePointCloudRenderer {
594
705
  : typeof boundsMaxDepth === "number" && Number.isFinite(boundsMaxDepth)
595
706
  ? boundsMaxDepth
596
707
  : minDepth + 1;
708
+ const normalizedMaxDepth = maxDepth > minDepth ? maxDepth : minDepth + 1;
597
709
  this.#material.uniforms.uMinDepth.value = minDepth;
598
- this.#material.uniforms.uMaxDepth.value = maxDepth > minDepth ? maxDepth : minDepth + 1;
710
+ this.#material.uniforms.uMaxDepth.value = normalizedMaxDepth;
711
+ this.#packedMaterial.uniforms.uMinDepth.value = minDepth;
712
+ this.#packedMaterial.uniforms.uMaxDepth.value = normalizedMaxDepth;
599
713
  }
600
714
  #applyFastPathGeometry(pointCloud, layout) {
601
- if (this.#pointSettings.minValue > 0 || Number.isFinite(this.#pointSettings.maxValue)) {
602
- return false;
603
- }
604
715
  if (isStandardXYZRGBALayout(pointCloud, layout)) {
605
716
  const pointCount = pointCloud.pointCount;
606
717
  const positionsView = new Float32Array(pointCloud.data.buffer, pointCloud.data.byteOffset, pointCount * 4);
607
- const colorsView = new Uint8Array(pointCloud.data.buffer, pointCloud.data.byteOffset, pointCount * pointCloud.point_stride);
608
- const positionBuffer = new THREE.InterleavedBuffer(positionsView, 4);
609
- const colorBuffer = new THREE.InterleavedBuffer(colorsView, pointCloud.point_stride);
610
- this.#positionsAttribute = new THREE.InterleavedBufferAttribute(positionBuffer, 3, 0, false);
611
- this.#colorsAttribute = new THREE.InterleavedBufferAttribute(colorBuffer, 4, 12, true);
612
- this.#geometry.setAttribute("position", this.#positionsAttribute);
613
- this.#geometry.setAttribute("aColor", this.#colorsAttribute);
718
+ const packedPointData = new Uint32Array(pointCloud.data.buffer, pointCloud.data.byteOffset, pointCount * 4);
719
+ const canReuseAttributes = this.#packedPointDataAttribute != undefined &&
720
+ this.#packedPointDataAttribute.array.byteLength === packedPointData.byteLength;
721
+ if (canReuseAttributes) {
722
+ this.#packedPointDataAttribute.array = packedPointData;
723
+ this.#packedPointDataAttribute.needsUpdate = true;
724
+ }
725
+ else {
726
+ this.#disposeGeometryAttributes();
727
+ this.#packedPointDataAttribute = createPackedPointCloudAttribute(packedPointData);
728
+ this.#geometry.setAttribute("aPointData", this.#packedPointDataAttribute);
729
+ }
730
+ this.#usesPackedPointData = true;
731
+ this.#points.material = this.#packedMaterial;
614
732
  this.#geometry.setDrawRange(0, pointCount);
615
- this.#syncBoundsFromGeometry();
733
+ this.#refreshFastPathBounds(pointCount, positionsView, 4);
616
734
  return true;
617
735
  }
618
736
  if (isStandardXYZLayout(pointCloud, layout)) {
619
737
  const pointCount = pointCloud.pointCount;
620
738
  const positionsView = new Float32Array(pointCloud.data.buffer, pointCloud.data.byteOffset, pointCount * 3);
621
- const position = new THREE.BufferAttribute(positionsView, 3);
622
739
  const colorLength = pointCount * 4;
623
740
  const colors = this.#whiteColorsArray && this.#whiteColorsArray.length === colorLength
624
741
  ? this.#whiteColorsArray
@@ -627,13 +744,29 @@ class StandalonePointCloudRenderer {
627
744
  colors.fill(255);
628
745
  this.#whiteColorsArray = colors;
629
746
  }
630
- const color = new THREE.Uint8BufferAttribute(colors, 4, true);
631
- this.#positionsAttribute = position;
632
- this.#colorsAttribute = color;
633
- this.#geometry.setAttribute("position", position);
634
- this.#geometry.setAttribute("aColor", color);
747
+ const canReuseAttributes = this.#positionsAttribute instanceof THREE.InterleavedBufferAttribute &&
748
+ this.#colorsAttribute instanceof THREE.Uint8BufferAttribute &&
749
+ this.#positionInterleavedBuffer != undefined &&
750
+ this.#positionInterleavedBuffer.stride === 3 &&
751
+ this.#positionInterleavedBuffer.array.byteLength === positionsView.byteLength &&
752
+ this.#colorsAttribute.array === colors;
753
+ if (canReuseAttributes) {
754
+ this.#positionInterleavedBuffer.array = positionsView;
755
+ this.#positionInterleavedBuffer.needsUpdate = true;
756
+ }
757
+ else {
758
+ this.#disposeGeometryAttributes();
759
+ const positionBuffer = new THREE.InterleavedBuffer(positionsView, 3).setUsage(THREE.StreamDrawUsage);
760
+ this.#positionInterleavedBuffer = positionBuffer;
761
+ this.#positionsAttribute = new THREE.InterleavedBufferAttribute(positionBuffer, 3, 0, false);
762
+ this.#colorsAttribute = new THREE.Uint8BufferAttribute(colors, 4, true);
763
+ this.#geometry.setAttribute("position", this.#positionsAttribute);
764
+ this.#geometry.setAttribute("aColor", this.#colorsAttribute);
765
+ }
766
+ this.#usesPackedPointData = false;
767
+ this.#points.material = this.#material;
635
768
  this.#geometry.setDrawRange(0, pointCount);
636
- this.#syncBoundsFromGeometry();
769
+ this.#refreshFastPathBounds(pointCount, positionsView, 3);
637
770
  return true;
638
771
  }
639
772
  return false;
@@ -697,19 +830,25 @@ class StandalonePointCloudRenderer {
697
830
  const position = this.#positionsAttribute instanceof THREE.BufferAttribute &&
698
831
  this.#positionsAttribute.array === positions
699
832
  ? this.#positionsAttribute
700
- : new THREE.BufferAttribute(positions, 3);
833
+ : new THREE.BufferAttribute(positions, 3).setUsage(THREE.StreamDrawUsage);
701
834
  if (position === this.#positionsAttribute) {
702
835
  position.needsUpdate = true;
703
836
  }
704
837
  const color = this.#colorsAttribute instanceof THREE.Uint8BufferAttribute &&
705
838
  this.#colorsAttribute.array === colors
706
839
  ? this.#colorsAttribute
707
- : new THREE.Uint8BufferAttribute(colors, 4, true);
840
+ : new THREE.Uint8BufferAttribute(colors, 4, true).setUsage(THREE.StreamDrawUsage);
708
841
  if (color === this.#colorsAttribute) {
709
842
  color.needsUpdate = true;
710
843
  }
844
+ if (position !== this.#positionsAttribute || color !== this.#colorsAttribute) {
845
+ this.#disposeGeometryAttributes();
846
+ }
847
+ this.#positionInterleavedBuffer = undefined;
711
848
  this.#positionsAttribute = position;
712
849
  this.#colorsAttribute = color;
850
+ this.#usesPackedPointData = false;
851
+ this.#points.material = this.#material;
713
852
  this.#geometry.setAttribute("position", position);
714
853
  this.#geometry.setAttribute("aColor", color);
715
854
  this.#geometry.setDrawRange(0, visiblePointCount);
@@ -725,19 +864,29 @@ class StandalonePointCloudRenderer {
725
864
  this.#boundsSphere.copy(this.#geometry.boundingSphere);
726
865
  }
727
866
  }
867
+ function applyFrameToRenderer(renderer, frame, onRendered) {
868
+ renderer.applyPointCloud(frame);
869
+ onRendered?.();
870
+ return true;
871
+ }
728
872
  export function PointCloudCanvasRenderer({ topic, initialConfig, frameRenderedEvent, handleOnClick, handleCursorPositionUpdate, registerRendered, unregisterRendered, customOverlay, }) {
729
873
  const canvasRef = React.useRef(null);
730
874
  const rendererRef = React.useRef(undefined);
731
875
  const handledSeekTimeRef = React.useRef(undefined);
732
876
  const hoverFrameRef = React.useRef(undefined);
877
+ const pointCloudApplyFrameRef = React.useRef(undefined);
878
+ const pendingPointCloudRef = React.useRef(undefined);
733
879
  const latestPointerPositionRef = React.useRef(undefined);
880
+ const lastHoverPickAtRef = React.useRef(0);
734
881
  const pointerInteractionRef = React.useRef(false);
735
882
  const draggedDuringPointerRef = React.useRef(false);
736
- const subscriptionIdRef = React.useRef(createSubscriptionId());
737
883
  const liveMessageSubscriptions = useLiveMessageSubscriptionController();
738
- const setSubscriptions = useMessagePipeline((ctx) => ctx.setSubscriptions);
739
884
  const lastSeekTime = useMessagePipeline((ctx) => ctx.playerState.activeData?.lastSeekTime);
740
885
  const themeMode = useThemeMode();
886
+ const themeModeRef = React.useRef(themeMode);
887
+ const frameRenderedEventRef = React.useRef(frameRenderedEvent);
888
+ themeModeRef.current = themeMode;
889
+ frameRenderedEventRef.current = frameRenderedEvent;
741
890
  const cameraState = React.useMemo(() => getInitialCameraState(initialConfig.cameraState), [initialConfig.cameraState]);
742
891
  const pointSettings = React.useMemo(() => getTopicPointSettings(topic, initialConfig), [initialConfig, topic]);
743
892
  React.useEffect(() => {
@@ -745,29 +894,39 @@ export function PointCloudCanvasRenderer({ topic, initialConfig, frameRenderedEv
745
894
  if (!canvas) {
746
895
  return;
747
896
  }
748
- const renderer = new StandalonePointCloudRenderer({
749
- canvas,
750
- pointSettings,
751
- cameraState,
752
- });
897
+ let renderer;
898
+ try {
899
+ renderer = new StandalonePointCloudRenderer({ canvas, pointSettings, cameraState });
900
+ }
901
+ catch (error) {
902
+ console.error("Point-cloud renderer initialization failed.", error);
903
+ return;
904
+ }
753
905
  rendererRef.current = renderer;
906
+ renderer.setThemeMode(themeModeRef.current);
754
907
  registerRendered?.(renderer);
908
+ const pendingFrame = pendingPointCloudRef.current;
909
+ if (pendingFrame) {
910
+ applyFrameToRenderer(renderer, pendingFrame, () => frameRenderedEventRef.current?.(0));
911
+ pendingPointCloudRef.current = undefined;
912
+ }
755
913
  return () => {
756
914
  if (hoverFrameRef.current != undefined) {
757
915
  cancelAnimationFrame(hoverFrameRef.current);
758
916
  hoverFrameRef.current = undefined;
759
917
  }
918
+ if (pointCloudApplyFrameRef.current != undefined) {
919
+ cancelAnimationFrame(pointCloudApplyFrameRef.current);
920
+ pointCloudApplyFrameRef.current = undefined;
921
+ }
922
+ pendingPointCloudRef.current = undefined;
760
923
  renderer.dispose();
761
- rendererRef.current = undefined;
924
+ if (rendererRef.current === renderer) {
925
+ rendererRef.current = undefined;
926
+ }
762
927
  unregisterRendered?.();
763
928
  };
764
929
  }, [cameraState, pointSettings, registerRendered, unregisterRendered]);
765
- React.useEffect(() => {
766
- setSubscriptions(subscriptionIdRef.current, [{ topic, preloadType: "partial" }]);
767
- return () => {
768
- setSubscriptions(subscriptionIdRef.current, []);
769
- };
770
- }, [setSubscriptions, topic]);
771
930
  React.useEffect(() => {
772
931
  const renderer = rendererRef.current;
773
932
  if (!renderer) {
@@ -784,25 +943,44 @@ export function PointCloudCanvasRenderer({ topic, initialConfig, frameRenderedEv
784
943
  if (!renderer) {
785
944
  return;
786
945
  }
787
- renderer.handleSeekRestore(liveMessageSubscriptions.getCachedMessages([topic]));
946
+ if (pointCloudApplyFrameRef.current != undefined) {
947
+ cancelAnimationFrame(pointCloudApplyFrameRef.current);
948
+ pointCloudApplyFrameRef.current = undefined;
949
+ }
950
+ pendingPointCloudRef.current = undefined;
951
+ const cachedMessages = liveMessageSubscriptions.getCachedMessages([topic]);
952
+ for (let index = cachedMessages.length - 1; index >= 0; index -= 1) {
953
+ const frame = extractRenderablePointCloudFrame(cachedMessages[index]);
954
+ if (frame && applyFrameToRenderer(renderer, frame)) {
955
+ return;
956
+ }
957
+ }
958
+ renderer.clear();
788
959
  }, [lastSeekTime, liveMessageSubscriptions, topic]);
789
960
  useMessageSubscription({
790
961
  topics: [topic],
791
962
  onMessages: (messages, incomingLastSeekTime) => {
792
- const renderer = rendererRef.current;
793
- if (!renderer) {
794
- return;
795
- }
796
963
  if (incomingLastSeekTime != undefined) {
797
964
  handledSeekTimeRef.current = incomingLastSeekTime;
798
965
  }
799
966
  for (let index = messages.length - 1; index >= 0; index -= 1) {
800
- const pointCloud = extractPointCloudMessage(messages[index]);
801
- if (!pointCloud) {
967
+ const frame = extractRenderablePointCloudFrame(messages[index]);
968
+ if (!frame) {
802
969
  continue;
803
970
  }
804
- renderer.applyPointCloud(pointCloud);
805
- frameRenderedEvent?.(0);
971
+ pendingPointCloudRef.current = frame;
972
+ if (pointCloudApplyFrameRef.current == undefined && rendererRef.current) {
973
+ pointCloudApplyFrameRef.current = requestAnimationFrame(() => {
974
+ pointCloudApplyFrameRef.current = undefined;
975
+ const pendingPointCloud = pendingPointCloudRef.current;
976
+ pendingPointCloudRef.current = undefined;
977
+ const activeRenderer = rendererRef.current;
978
+ if (!pendingPointCloud || !activeRenderer) {
979
+ return;
980
+ }
981
+ applyFrameToRenderer(activeRenderer, pendingPointCloud, () => frameRenderedEventRef.current?.(0));
982
+ });
983
+ }
806
984
  return;
807
985
  }
808
986
  },
@@ -817,7 +995,12 @@ export function PointCloudCanvasRenderer({ topic, initialConfig, frameRenderedEv
817
995
  if (!renderer || !position || !handleCursorPositionUpdate) {
818
996
  return;
819
997
  }
820
- const hit = renderer.pick(position.x, position.y);
998
+ const now = performance.now();
999
+ if (now - lastHoverPickAtRef.current < HOVER_PICK_INTERVAL_MS) {
1000
+ return;
1001
+ }
1002
+ lastHoverPickAtRef.current = now;
1003
+ const hit = renderer.pick(position.x, position.y, MAX_HOVER_PICK_POINT_COUNT);
821
1004
  handleCursorPositionUpdate(position, hit?.offset);
822
1005
  }, [handleCursorPositionUpdate]);
823
1006
  const handleCanvasPointerMove = React.useCallback((event) => {
@@ -852,7 +1035,7 @@ export function PointCloudCanvasRenderer({ topic, initialConfig, frameRenderedEv
852
1035
  handleOnClick(event, undefined);
853
1036
  return;
854
1037
  }
855
- const hit = renderer.pick(event.clientX, event.clientY);
1038
+ const hit = renderer.pick(event.clientX, event.clientY, MAX_CLICK_PICK_POINT_COUNT);
856
1039
  handleOnClick(event, hit?.offset);
857
1040
  }, [handleOnClick]);
858
1041
  const handleCanvasPointerDown = React.useCallback((event) => {