@ifc-lite/renderer 6.4.0 → 6.5.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 (223) hide show
  1. package/dist/camera-animation.d.ts +7 -1
  2. package/dist/camera-animation.d.ts.map +1 -1
  3. package/dist/camera-animation.js +19 -3
  4. package/dist/camera-animation.js.map +1 -1
  5. package/dist/camera-fit-geometry.d.ts +47 -0
  6. package/dist/camera-fit-geometry.d.ts.map +1 -0
  7. package/dist/camera-fit-geometry.js +44 -0
  8. package/dist/camera-fit-geometry.js.map +1 -0
  9. package/dist/camera-framing.d.ts +27 -21
  10. package/dist/camera-framing.d.ts.map +1 -1
  11. package/dist/camera-framing.js +47 -44
  12. package/dist/camera-framing.js.map +1 -1
  13. package/dist/camera-preset-view.d.ts +18 -0
  14. package/dist/camera-preset-view.d.ts.map +1 -1
  15. package/dist/camera-preset-view.js +42 -0
  16. package/dist/camera-preset-view.js.map +1 -1
  17. package/dist/camera.d.ts +14 -1
  18. package/dist/camera.d.ts.map +1 -1
  19. package/dist/camera.js +14 -2
  20. package/dist/camera.js.map +1 -1
  21. package/dist/clip-types.d.ts +35 -0
  22. package/dist/clip-types.d.ts.map +1 -0
  23. package/dist/clip-types.js +5 -0
  24. package/dist/clip-types.js.map +1 -0
  25. package/dist/device-recovery.js +11 -1
  26. package/dist/device-recovery.js.map +1 -1
  27. package/dist/device.d.ts +4 -4
  28. package/dist/device.js +9 -9
  29. package/dist/device.js.map +1 -1
  30. package/dist/extra-clip-planes.d.ts +57 -0
  31. package/dist/extra-clip-planes.d.ts.map +1 -0
  32. package/dist/extra-clip-planes.js +161 -0
  33. package/dist/extra-clip-planes.js.map +1 -0
  34. package/dist/gpu-counters.d.ts +4 -2
  35. package/dist/gpu-counters.d.ts.map +1 -1
  36. package/dist/gpu-counters.js +9 -6
  37. package/dist/gpu-counters.js.map +1 -1
  38. package/dist/index.d.ts +18 -5
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +68 -90
  41. package/dist/index.js.map +1 -1
  42. package/dist/mesh-rte-uniforms.d.ts +7 -3
  43. package/dist/mesh-rte-uniforms.d.ts.map +1 -1
  44. package/dist/mesh-rte-uniforms.js +16 -2
  45. package/dist/mesh-rte-uniforms.js.map +1 -1
  46. package/dist/opaque-visibility-commands.d.ts +3 -0
  47. package/dist/opaque-visibility-commands.d.ts.map +1 -0
  48. package/dist/opaque-visibility-commands.js +11 -0
  49. package/dist/opaque-visibility-commands.js.map +1 -0
  50. package/dist/opaque-visibility-pass.d.ts +37 -0
  51. package/dist/opaque-visibility-pass.d.ts.map +1 -0
  52. package/dist/opaque-visibility-pass.js +230 -0
  53. package/dist/opaque-visibility-pass.js.map +1 -0
  54. package/dist/pick-types.d.ts +5 -1
  55. package/dist/pick-types.d.ts.map +1 -1
  56. package/dist/picker-rte-uniforms.d.ts +1 -1
  57. package/dist/picker-rte-uniforms.d.ts.map +1 -1
  58. package/dist/picker-rte-uniforms.js +4 -2
  59. package/dist/picker-rte-uniforms.js.map +1 -1
  60. package/dist/picker-shaders.d.ts +5 -0
  61. package/dist/picker-shaders.d.ts.map +1 -0
  62. package/dist/picker-shaders.js +157 -0
  63. package/dist/picker-shaders.js.map +1 -0
  64. package/dist/picker.d.ts +3 -0
  65. package/dist/picker.d.ts.map +1 -1
  66. package/dist/picker.js +43 -133
  67. package/dist/picker.js.map +1 -1
  68. package/dist/pipeline.d.ts +5 -9
  69. package/dist/pipeline.d.ts.map +1 -1
  70. package/dist/pipeline.js +34 -29
  71. package/dist/pipeline.js.map +1 -1
  72. package/dist/point-picker.d.ts +2 -1
  73. package/dist/point-picker.d.ts.map +1 -1
  74. package/dist/point-picker.js +12 -4
  75. package/dist/point-picker.js.map +1 -1
  76. package/dist/pointcloud/point-cloud-renderer.d.ts +4 -4
  77. package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -1
  78. package/dist/pointcloud/point-cloud-renderer.js +1 -1
  79. package/dist/pointcloud/point-cloud-renderer.js.map +1 -1
  80. package/dist/pointcloud/point-cloud-uniforms.d.ts +3 -1
  81. package/dist/pointcloud/point-cloud-uniforms.d.ts.map +1 -1
  82. package/dist/pointcloud/point-cloud-uniforms.js +3 -1
  83. package/dist/pointcloud/point-cloud-uniforms.js.map +1 -1
  84. package/dist/pointcloud/point-pipeline.d.ts +3 -1
  85. package/dist/pointcloud/point-pipeline.d.ts.map +1 -1
  86. package/dist/pointcloud/point-pipeline.js +4 -1
  87. package/dist/pointcloud/point-pipeline.js.map +1 -1
  88. package/dist/pointcloud/point-shader.wgsl.d.ts +1 -1
  89. package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -1
  90. package/dist/pointcloud/point-shader.wgsl.js +8 -0
  91. package/dist/pointcloud/point-shader.wgsl.js.map +1 -1
  92. package/dist/post-pass-chain.d.ts +1 -0
  93. package/dist/post-pass-chain.d.ts.map +1 -1
  94. package/dist/post-pass-chain.js +1 -0
  95. package/dist/post-pass-chain.js.map +1 -1
  96. package/dist/quantized-lifecycle.d.ts +6 -0
  97. package/dist/quantized-lifecycle.d.ts.map +1 -0
  98. package/dist/quantized-lifecycle.js +19 -0
  99. package/dist/quantized-lifecycle.js.map +1 -0
  100. package/dist/raycast-engine.d.ts.map +1 -1
  101. package/dist/raycast-engine.js +3 -2
  102. package/dist/raycast-engine.js.map +1 -1
  103. package/dist/render-section-draw.d.ts +5 -0
  104. package/dist/render-section-draw.d.ts.map +1 -1
  105. package/dist/render-section-draw.js +59 -25
  106. package/dist/render-section-draw.js.map +1 -1
  107. package/dist/render-section-plane.d.ts +5 -33
  108. package/dist/render-section-plane.d.ts.map +1 -1
  109. package/dist/render-section-plane.js +8 -175
  110. package/dist/render-section-plane.js.map +1 -1
  111. package/dist/renderer-frame-timing.d.ts +1 -0
  112. package/dist/renderer-frame-timing.d.ts.map +1 -1
  113. package/dist/renderer-frame-timing.js +8 -1
  114. package/dist/renderer-frame-timing.js.map +1 -1
  115. package/dist/renderer-overlays.d.ts +8 -2
  116. package/dist/renderer-overlays.d.ts.map +1 -1
  117. package/dist/renderer-overlays.js +22 -45
  118. package/dist/renderer-overlays.js.map +1 -1
  119. package/dist/scene-raycaster.d.ts +1 -1
  120. package/dist/scene-raycaster.d.ts.map +1 -1
  121. package/dist/scene-raycaster.js +18 -44
  122. package/dist/scene-raycaster.js.map +1 -1
  123. package/dist/scene-rect-select.d.ts.map +1 -1
  124. package/dist/scene-rect-select.js +2 -1
  125. package/dist/scene-rect-select.js.map +1 -1
  126. package/dist/section-2d-lift.d.ts +25 -0
  127. package/dist/section-2d-lift.d.ts.map +1 -1
  128. package/dist/section-2d-lift.js +35 -0
  129. package/dist/section-2d-lift.js.map +1 -1
  130. package/dist/section-2d-overlay.d.ts +41 -12
  131. package/dist/section-2d-overlay.d.ts.map +1 -1
  132. package/dist/section-2d-overlay.js +121 -179
  133. package/dist/section-2d-overlay.js.map +1 -1
  134. package/dist/section-2d-types.d.ts +2 -0
  135. package/dist/section-2d-types.d.ts.map +1 -1
  136. package/dist/section-cap-geometry.d.ts +33 -0
  137. package/dist/section-cap-geometry.d.ts.map +1 -0
  138. package/dist/section-cap-geometry.js +137 -0
  139. package/dist/section-cap-geometry.js.map +1 -0
  140. package/dist/section-cap-set.d.ts +60 -0
  141. package/dist/section-cap-set.d.ts.map +1 -0
  142. package/dist/section-cap-set.js +212 -0
  143. package/dist/section-cap-set.js.map +1 -0
  144. package/dist/section-cap-style.d.ts +3 -0
  145. package/dist/section-cap-style.d.ts.map +1 -1
  146. package/dist/section-cap-style.js.map +1 -1
  147. package/dist/section-cap-upload.d.ts +16 -0
  148. package/dist/section-cap-upload.d.ts.map +1 -0
  149. package/dist/section-cap-upload.js +52 -0
  150. package/dist/section-cap-upload.js.map +1 -0
  151. package/dist/section-exclusions.d.ts +26 -0
  152. package/dist/section-exclusions.d.ts.map +1 -0
  153. package/dist/section-exclusions.js +95 -0
  154. package/dist/section-exclusions.js.map +1 -0
  155. package/dist/section-plane-frames.d.ts +48 -0
  156. package/dist/section-plane-frames.d.ts.map +1 -0
  157. package/dist/section-plane-frames.js +49 -0
  158. package/dist/section-plane-frames.js.map +1 -0
  159. package/dist/section-plane-resolve.d.ts +63 -0
  160. package/dist/section-plane-resolve.d.ts.map +1 -0
  161. package/dist/section-plane-resolve.js +200 -0
  162. package/dist/section-plane-resolve.js.map +1 -0
  163. package/dist/selection-mask-pass.d.ts +2 -0
  164. package/dist/selection-mask-pass.d.ts.map +1 -1
  165. package/dist/selection-mask-pass.js +16 -4
  166. package/dist/selection-mask-pass.js.map +1 -1
  167. package/dist/selection-mask-pipelines.d.ts +5 -1
  168. package/dist/selection-mask-pipelines.d.ts.map +1 -1
  169. package/dist/selection-mask-pipelines.js +27 -3
  170. package/dist/selection-mask-pipelines.js.map +1 -1
  171. package/dist/selection-outline-frame.d.ts +3 -1
  172. package/dist/selection-outline-frame.d.ts.map +1 -1
  173. package/dist/selection-outline-frame.js +2 -1
  174. package/dist/selection-outline-frame.js.map +1 -1
  175. package/dist/shaders/depth-nudge.wgsl.d.ts +2 -2
  176. package/dist/shaders/depth-nudge.wgsl.d.ts.map +1 -1
  177. package/dist/shaders/depth-nudge.wgsl.js +1 -18
  178. package/dist/shaders/depth-nudge.wgsl.js.map +1 -1
  179. package/dist/shaders/main.wgsl.d.ts +1 -1
  180. package/dist/shaders/main.wgsl.d.ts.map +1 -1
  181. package/dist/shaders/main.wgsl.js +9 -1
  182. package/dist/shaders/main.wgsl.js.map +1 -1
  183. package/dist/shaders/mesh-uniforms.wgsl.d.ts +1 -1
  184. package/dist/shaders/mesh-uniforms.wgsl.d.ts.map +1 -1
  185. package/dist/shaders/mesh-uniforms.wgsl.js +11 -3
  186. package/dist/shaders/mesh-uniforms.wgsl.js.map +1 -1
  187. package/dist/shaders/opaque-visibility.wgsl.d.ts +11 -0
  188. package/dist/shaders/opaque-visibility.wgsl.d.ts.map +1 -0
  189. package/dist/shaders/opaque-visibility.wgsl.js +55 -0
  190. package/dist/shaders/opaque-visibility.wgsl.js.map +1 -0
  191. package/dist/shaders/section-2d-overlay.wgsl.d.ts +11 -5
  192. package/dist/shaders/section-2d-overlay.wgsl.d.ts.map +1 -1
  193. package/dist/shaders/section-2d-overlay.wgsl.js +31 -5
  194. package/dist/shaders/section-2d-overlay.wgsl.js.map +1 -1
  195. package/dist/shaders/selection-mask.wgsl.d.ts +1 -1
  196. package/dist/shaders/selection-mask.wgsl.d.ts.map +1 -1
  197. package/dist/shaders/selection-mask.wgsl.js +10 -4
  198. package/dist/shaders/selection-mask.wgsl.js.map +1 -1
  199. package/dist/shaders/shadow.wgsl.d.ts +1 -1
  200. package/dist/shaders/shadow.wgsl.d.ts.map +1 -1
  201. package/dist/shaders/shadow.wgsl.js +22 -7
  202. package/dist/shaders/shadow.wgsl.js.map +1 -1
  203. package/dist/shaders/symbolic-overlay.wgsl.d.ts +1 -1
  204. package/dist/shaders/symbolic-overlay.wgsl.d.ts.map +1 -1
  205. package/dist/shadow-occluders.d.ts.map +1 -1
  206. package/dist/shadow-occluders.js +4 -0
  207. package/dist/shadow-occluders.js.map +1 -1
  208. package/dist/shadow-pass.d.ts +2 -5
  209. package/dist/shadow-pass.d.ts.map +1 -1
  210. package/dist/shadow-pass.js +17 -17
  211. package/dist/shadow-pass.js.map +1 -1
  212. package/dist/shadow-types.d.ts +7 -0
  213. package/dist/shadow-types.d.ts.map +1 -1
  214. package/dist/shadow-types.js.map +1 -1
  215. package/dist/source-curve-clipping.d.ts.map +1 -1
  216. package/dist/source-curve-clipping.js +7 -1
  217. package/dist/source-curve-clipping.js.map +1 -1
  218. package/dist/source-curve-snap.d.ts.map +1 -1
  219. package/dist/source-curve-snap.js +2 -1
  220. package/dist/source-curve-snap.js.map +1 -1
  221. package/dist/types.d.ts +12 -16
  222. package/dist/types.d.ts.map +1 -1
  223. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /* This Source Code Form is subject to the terms of the Mozilla Public
2
2
  * License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { sectionExclusions } from './section-exclusions.js';
4
5
  /**
5
6
  * @ifc-lite/renderer - WebGPU renderer
6
7
  */
@@ -10,6 +11,7 @@ export { Camera } from './camera.js';
10
11
  export { appearanceSourceTriangle, expandAppearanceCorners, equivalentAppearanceGeometry } from './appearance-uvs.js';
11
12
  export { sameCompanionParts } from './appearance-companions.js';
12
13
  export { invertAppearancePartition, validateAppearancePartition } from './appearance-preview.js';
14
+ import { deferOpaqueVisibilityFrame } from './opaque-visibility-pass.js';
13
15
  import { createReferenceImageManager } from './reference-image-host.js';
14
16
  import { MAX_DRAWING_BUFFER_PIXEL_RATIO, measureDrawingBuffer, resizeRendererViewport } from './renderer-viewport.js';
15
17
  export { pickFitPolicy } from './camera-fit-policy.js';
@@ -35,6 +37,7 @@ export { SymbolicTextAtlas } from './symbolic-text-atlas.js';
35
37
  // holds the styling primitives shared with the store and UI.
36
38
  export { DEFAULT_CAP_STYLE, HATCH_PATTERN_IDS } from './section-cap-style.js';
37
39
  export { planeBasis, nearestCardinalAxis } from './section-plane-basis.js';
40
+ export { capHighlightKey } from './section-2d-overlay.js';
38
41
  export { LINE_OVERLAY_CHANNELS } from './section-2d-overlay.js';
39
42
  export { Raycaster } from './raycaster.js';
40
43
  export { SnapDetector, SnapType } from './snap-detector.js';
@@ -42,6 +45,7 @@ export { BVH } from './bvh.js';
42
45
  export { hostsOtherEntities } from './mesh-entity-hosting.js';
43
46
  export { FederationRegistry, federationRegistry } from './federation-registry.js';
44
47
  export * from './types.js';
48
+ export { MAX_EXTRA_CLIP_PLANES } from './extra-clip-planes.js';
45
49
  export { resolveEnvironment, deriveSkyGradient, packEnvironmentUniforms, ENVIRONMENT_UNIFORM_SIZE, } from './environment.js';
46
50
  export { DEFAULT_OVERLAY_THEME } from './overlay-theme.js';
47
51
  import { DEFAULT_OVERLAY_THEME } from './overlay-theme.js';
@@ -82,7 +86,8 @@ import { viewBasis } from './math.js';
82
86
  import { isRteAabbVisible, rteFrustum, sourceFrustumFromRte } from './rte-frustum.js';
83
87
  import { VisualEnhancementResolver, livePostEffects } from './visual-enhancement.js';
84
88
  import { packClipBox } from './clip-box.js';
85
- import { MESH_FLAGS_BYTE_OFFSET, MESH_FLAG_RTE_DRAWABLE, MESH_UNIFORM_FLOATS, MESH_UNIFORM_OFFSET, packRteCameraOrigin, packRteFragmentSpace, } from './mesh-rte-uniforms.js';
89
+ import { resolveSectionPlaneFrames } from './section-plane-frames.js';
90
+ import { MESH_FLAGS_BYTE_OFFSET, MESH_FLAG_RTE_DRAWABLE, MESH_UNIFORM_FLOATS, MESH_UNIFORM_OFFSET, packRteCameraOrigin, packRteFragmentSpace, packSectionPlaneLanes, } from './mesh-rte-uniforms.js';
86
91
  import { packMeshMaterial } from './mesh-material.js';
87
92
  import { RendererOverlays } from './renderer-overlays.js';
88
93
  import { resolveSectionPlaneFrame } from './render-section-plane.js';
@@ -118,6 +123,7 @@ import { appendPointCloudChunk as appendPointCloudChunkImpl, beginPointCloudStre
118
123
  import { DeviationComputer } from './deviation/deviation-computer.js';
119
124
  export { computeDeviationStatistics, computeDeviationStatisticsAsync, countWithinToleranceAsync, deviationHistogramAsync, summarizeDeviationAssetsAsync } from './deviation/deviation-statistics.js';
120
125
  import { runGuardedGpuUpload, isDeviceLossThrow } from './gpu-upload-guard.js';
126
+ import { enableRendererQuantizedBatches } from './quantized-lifecycle.js';
121
127
  import { recoverRendererDevice, rendererDeviceLostError } from './device-recovery.js';
122
128
  /**
123
129
  * The reason `whenReady()` rejects when the renderer is destroyed.
@@ -406,13 +412,17 @@ export class Renderer {
406
412
  // One 336-byte buffer serves all frame batches/meshes (84 floats including RTE lanes).
407
413
  uniformScratch = new Float32Array(MESH_UNIFORM_FLOATS);
408
414
  uniformScratchU32 = new Uint32Array(this.uniformScratch.buffer, MESH_FLAGS_BYTE_OFFSET, 4);
409
- // overrideParams lanes (#6076): zero except inside a painting renderBatch call.
415
+ // overrideParams: full entity anchor for clipping, plus colour-override mode (#6076).
410
416
  uniformOverrideU32 = new Uint32Array(this.uniformScratch.buffer, MESH_UNIFORM_OFFSET.overrideParams * 4, 4);
411
417
  // What the last render() actually clipped, so the GPU picker can mirror it and
412
418
  // section/crop-clipped geometry stays unpickable, not just invisible. Updated
413
419
  // every render; read by pick()/pickRect(). null = nothing clipped that frame.
414
420
  _activePickSection = null;
415
421
  _activePickClipBox = null;
422
+ _sectionExcludedIds = new Set();
423
+ /** Snapshot of the extra planes the last frame clipped against (#7545), mirrored by the pickers. */
424
+ _activePickExtraPlanes = null;
425
+ _sectionFrameInput = null; // what the last frame resolved the section planes from
416
426
  constructor(canvas) {
417
427
  this.canvas = canvas;
418
428
  this.device = new WebGPUDevice();
@@ -452,6 +462,7 @@ export class Renderer {
452
462
  // init there is nothing to invalidate and this is a no-op.
453
463
  const generation = ++this.initGeneration;
454
464
  this.ready = false;
465
+ this.scene.setQuantizedBatches(false); // #7655: admission belongs to the retired GPU lifetime.
455
466
  // Re-arm `whenReady()`: this instance is being brought back up, so a
456
467
  // wait requested from here on is "not ready yet" again, not "destroyed".
457
468
  this.destroyed = false;
@@ -634,8 +645,8 @@ export class Renderer {
634
645
  w.reject(error);
635
646
  }
636
647
  /**
637
- * Subscribe to non-intentional GPU device loss (driver reset / VRAM
638
- * exhaustion — NOT an intentional `destroy()`). Fired at most once per
648
+ * Subscribe to GPU device loss outside owned teardown (including browser
649
+ * destruction, driver reset or VRAM exhaustion). Fired at most once per
639
650
  * device. After it fires, `render()` is a no-op and the renderer reports
640
651
  * itself un-ready (`isReady()` false, `whenReady()` rejecting with
641
652
  * `RendererDeviceLostError`) until it is re-initialised, so the typical
@@ -1318,13 +1329,7 @@ export class Renderer {
1318
1329
  * rejecting pipeline creation) batches stay on the f32 path.
1319
1330
  */
1320
1331
  async enableQuantizedBatches() {
1321
- this.recovery.quantizedBatchesRequested = true;
1322
- if (!this.pipeline)
1323
- return false;
1324
- const ok = await this.pipeline.ensureQuantizedPipelines();
1325
- if (ok)
1326
- this.scene.setQuantizedBatches(true);
1327
- return ok;
1332
+ return enableRendererQuantizedBatches(this);
1328
1333
  }
1329
1334
  /** Draw a frame through the shared submission path, preserving the void API. */
1330
1335
  render(options = {}) { this.renderWithResult(options); }
@@ -1570,10 +1575,6 @@ export class Renderer {
1570
1575
  // would never show it. See issue #677. Pure routing decision lives in
1571
1576
  // overlay-routing.ts and is unit-tested there.
1572
1577
  const colorOverrides = this.scene.getColorOverrides();
1573
- // PERFORMANCE FIX: Use batch-level visibility filtering instead of creating individual meshes
1574
- // Only create individual meshes for selected elements (for highlighting)
1575
- // Batches are filtered at render time - fully visible batches render normally,
1576
- // partially visible batches are skipped (their visible elements will be in other batches or individual meshes)
1577
1578
  // Ensure all existing meshes have GPU resources
1578
1579
  this.ensureMeshResources();
1579
1580
  // Frustum culling (if enabled and spatial index available)
@@ -1667,6 +1668,9 @@ export class Renderer {
1667
1668
  spendLogLatch: () => { this._loggedSectionBounds = true; },
1668
1669
  });
1669
1670
  const sectionPlaneData = sectionFrame.sectionPlaneData;
1671
+ const extraClipPlanes = sectionFrame.extraPlanes;
1672
+ const { bounds: frameBounds } = sectionFrame;
1673
+ this._sectionFrameInput = frameBounds && { primary: options.sectionPlane ?? null, extras: options.extraSectionPlanes ?? [], bounds: frameBounds, buildingRotation: options.buildingRotation };
1670
1674
  if (sectionFrame.bounds) {
1671
1675
  // Store bounds for section plane visual and camera near/far.
1672
1676
  // Two wrappers over the same min/max, exactly as before the
@@ -1689,6 +1693,9 @@ export class Renderer {
1689
1693
  flipped: !!options.sectionPlane?.flipped,
1690
1694
  }
1691
1695
  : null;
1696
+ this._sectionExcludedIds = new Set(options.sectionExcludedIds);
1697
+ sectionExclusions(device).update(this._sectionExcludedIds);
1698
+ this._activePickExtraPlanes = extraClipPlanes.length > 0 ? extraClipPlanes.map((p) => ({ normal: [...p.normal], distance: p.distance, flipped: p.flipped })) : null;
1692
1699
  this._activePickClipBox = options.clipBox?.enabled
1693
1700
  ? {
1694
1701
  enabled: true,
@@ -1723,18 +1730,7 @@ export class Renderer {
1723
1730
  meshBuf[35] = isSelected ? mesh.color[3] : alphaForMesh(mesh.expressId, mesh.color[3]);
1724
1731
  packMeshMaterial(meshBuf, mesh.color[3], mesh.finish ?? mesh.material);
1725
1732
  // Section plane data (offset 40-43)
1726
- if (sectionPlaneData) {
1727
- meshBuf[40] = sectionPlaneData.normal[0];
1728
- meshBuf[41] = sectionPlaneData.normal[1];
1729
- meshBuf[42] = sectionPlaneData.normal[2];
1730
- meshBuf[43] = sectionPlaneData.distance;
1731
- }
1732
- else {
1733
- meshBuf[40] = 0;
1734
- meshBuf[41] = 0;
1735
- meshBuf[42] = 0;
1736
- meshBuf[43] = 0;
1737
- }
1733
+ packSectionPlaneLanes(sectionPlaneData, meshBuf);
1738
1734
  // Clip box (offset 48-55: min.xyz + pad, max.xyz + pad) → enable bit
1739
1735
  const clipBit = packClipBox(options.clipBox, meshBuf, 48);
1740
1736
  // Flags (offset 44-47 as u32)
@@ -1748,7 +1744,7 @@ export class Renderer {
1748
1744
  meshFlags[3] = 0;
1749
1745
  // Individual meshes retain their origin and share the RTE
1750
1746
  // fragment contract with colour batches.
1751
- packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, meshBuf);
1747
+ packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, meshBuf, extraClipPlanes);
1752
1748
  const meshOrigin = mesh.rteOrigin
1753
1749
  ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
1754
1750
  if (!relativeToEyeFrame.tryPackDrawableOrigin(meshOrigin, meshBuf, MESH_UNIFORM_OFFSET.drawableDelta)) {
@@ -1756,6 +1752,7 @@ export class Renderer {
1756
1752
  continue;
1757
1753
  }
1758
1754
  meshFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
1755
+ new Uint32Array(meshBuf.buffer)[MESH_UNIFORM_OFFSET.overrideParams] = mesh.expressId;
1759
1756
  device.queue.writeBuffer(mesh.uniformBuffer, 0, meshBuf);
1760
1757
  }
1761
1758
  }
@@ -1849,6 +1846,7 @@ export class Renderer {
1849
1846
  // floor it no longer covers. `sectionPlaneData` already
1850
1847
  // folds in the terrain clip, so that is covered as well.
1851
1848
  this.shadowPass.render(encoder, fit.lightViewProj, occluders, {
1849
+ sectionExcludedIds: this._sectionExcludedIds,
1852
1850
  section: sectionPlaneData?.enabled
1853
1851
  ? {
1854
1852
  normal: sectionPlaneData.normal,
@@ -1859,6 +1857,7 @@ export class Renderer {
1859
1857
  box: options.clipBox?.enabled
1860
1858
  ? { min: options.clipBox.min, max: options.clipBox.max }
1861
1859
  : null,
1860
+ extraPlanes: extraClipPlanes,
1862
1861
  }, { cameraWorld: shadowEye });
1863
1862
  // Shadow uniform: light matrix + sampling params. The kernel
1864
1863
  // width follows the sun's angular size (physical, ~0.53°
@@ -1926,7 +1925,7 @@ export class Renderer {
1926
1925
  clearValue: { r: 0, g: 0, b: 0, a: 0 },
1927
1926
  storeOp: (needsObjectIdPass ? 'store' : 'discard'),
1928
1927
  });
1929
- const pass = encoder.beginRenderPass({
1928
+ const pass = this.pipeline.visibility.begin(encoder, {
1930
1929
  timestampWrites: renderPassTimestampWrites(encoder, 'main'),
1931
1930
  colorAttachments,
1932
1931
  depthStencilAttachment: {
@@ -1940,7 +1939,7 @@ export class Renderer {
1940
1939
  stencilLoadOp: 'clear',
1941
1940
  stencilStoreOp: 'store',
1942
1941
  },
1943
- });
1942
+ }, this.camera.getProjectionMode() === 'orthographic', this.canvas.width, this.canvas.height);
1944
1943
  // Global lighting environment: write the uniform once per frame.
1945
1944
  // The group(1) bind is deferred until AFTER the sky pass below —
1946
1945
  // the sky pipeline has an incompatible layout (its own group(0),
@@ -1996,6 +1995,7 @@ export class Renderer {
1996
1995
  // valid for every main-family draw that follows.
1997
1996
  this.pipeline.setEntityColorTableBuffer(this.scene.getEntityColorTable().getBuffer());
1998
1997
  pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
1998
+ pass.setBindGroup(3, sectionExclusions(device).bindGroup);
1999
1999
  // Selected meshes drawn this frame, for the selection-mask pass
2000
2000
  // (#5390) after `pass.end()` below. Populated inside the batched
2001
2001
  // branch, where the highlight draw itself happens; empty in the
@@ -2184,9 +2184,6 @@ export class Renderer {
2184
2184
  if (partialEpochChanged) {
2185
2185
  this.scene.retireUnusedAlphaSlots(partialRequests.requestedKeys());
2186
2186
  }
2187
- // Build a uniform template ONCE per frame — shared across all batches.
2188
- // Only the 4-float color (offset 32) differs per batch; everything else
2189
- // (viewProj, identity model, material, section plane, flags) is identical.
2190
2187
  const tpl = this.uniformScratch;
2191
2188
  const tplFlags = this.uniformScratchU32;
2192
2189
  tpl.set(viewProj, 0);
@@ -2212,18 +2209,7 @@ export class Renderer {
2212
2209
  // Colour + material row (tpl[32..39]) are patched per draw; the
2213
2210
  // instanced passes keep this opaque default.
2214
2211
  packMeshMaterial(tpl);
2215
- if (sectionPlaneData) {
2216
- tpl[40] = sectionPlaneData.normal[0];
2217
- tpl[41] = sectionPlaneData.normal[1];
2218
- tpl[42] = sectionPlaneData.normal[2];
2219
- tpl[43] = sectionPlaneData.distance;
2220
- }
2221
- else {
2222
- tpl[40] = 0;
2223
- tpl[41] = 0;
2224
- tpl[42] = 0;
2225
- tpl[43] = 0;
2226
- }
2212
+ packSectionPlaneLanes(sectionPlaneData, tpl);
2227
2213
  // Clip box (offset 48-55: min.xyz + pad, max.xyz + pad) → enable bit
2228
2214
  const tplClipBit = packClipBox(options.clipBox, tpl, 48);
2229
2215
  // flags layout (main shader):
@@ -2244,7 +2230,7 @@ export class Renderer {
2244
2230
  // camera-relative frame. Their fragment clip inputs must use
2245
2231
  // that same frame; mixing a local vertex with a 5,000 km f32
2246
2232
  // world plane loses centimetre cuts before the comparison.
2247
- packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
2233
+ packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl, extraClipPlanes);
2248
2234
  // Helper function to render a batch — patches color into the shared template.
2249
2235
  // `paint` = a depth-writing opaque draw: colour overrides shade it from the
2250
2236
  // entity colour table (#6076); transparent draws keep their own colour.
@@ -2258,12 +2244,7 @@ export class Renderer {
2258
2244
  tpl[34] = batch.color[2];
2259
2245
  tpl[35] = alphaForBatch(batch, batch.color[3]);
2260
2246
  packMeshMaterial(tpl, batch.color[3], batch.finish);
2261
- // Per-batch local frame: the batch's vertices are stored
2262
- // RELATIVE to batch.origin (f32-small), so set the model
2263
- // matrix translation column to origin (world = origin + pos).
2264
- // The 12 rotation/scale floats stay identity from the template;
2265
- // the translation lives at column-major indices 12/13/14 →
2266
- // tpl[28/29/30]. [0,0,0] for legacy absolute batches.
2247
+ // Rebase batch-local vertices; translation lives in tpl[28..30].
2267
2248
  const o = batch.origin;
2268
2249
  tpl[28] = o ? o[0] : 0;
2269
2250
  tpl[29] = o ? o[1] : 0;
@@ -2282,7 +2263,8 @@ export class Renderer {
2282
2263
  tpl[58] = qz ? qz.min[2] : 0;
2283
2264
  tpl[59] = qz ? qz.step : 0;
2284
2265
  const painted = paint && batchHasColorOverride(batch, colorOverrides, colorOverrideGen);
2285
- packOverrideParams(this.uniformOverrideU32, painted ? batchEntityIdAnchor(batch) : null, painted, options.emphasizeOverrides === true);
2266
+ packOverrideParams(this.uniformOverrideU32, batchEntityIdAnchor(batch), painted, options.emphasizeOverrides === true);
2267
+ this.uniformOverrideU32[0] = batchEntityIdAnchor(batch) ?? 0xFFFFFFFF;
2286
2268
  device.queue.writeBuffer(batch.uniformBuffer, 0, tpl);
2287
2269
  this.uniformOverrideU32.fill(0);
2288
2270
  // Single draw call for entire batch! LOD1-selected batches
@@ -2386,6 +2368,7 @@ export class Renderer {
2386
2368
  pass.setPipeline(this.pipeline.getInstancedPipeline());
2387
2369
  pass.setBindGroup(0, this.pipeline.getBindGroup());
2388
2370
  pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
2371
+ pass.setBindGroup(3, sectionExclusions(device).bindGroup);
2389
2372
  for (const [i, it] of visibleInstanced.entries()) {
2390
2373
  pass.setVertexBuffer(0, it.vertexBuffer);
2391
2374
  pass.setVertexBuffer(1, it.instanceBuffer);
@@ -2461,6 +2444,7 @@ export class Renderer {
2461
2444
  // CHOICE in the shader follows the same rule as everywhere
2462
2445
  // else, for the same reason `tm.material` does.
2463
2446
  packMeshMaterial(tpl, tm.color[3], tm.finish ?? tm.material);
2447
+ this.uniformOverrideU32[0] = tm.expressId;
2464
2448
  device.queue.writeBuffer(tm.uniformBuffer, 0, tpl);
2465
2449
  pass.setBindGroup(0, tm.bindGroup);
2466
2450
  pass.setVertexBuffer(0, tm.vertexBuffer);
@@ -2586,6 +2570,7 @@ export class Renderer {
2586
2570
  pass.setPipeline(instancedTransparentPipeline);
2587
2571
  pass.setBindGroup(0, this.pipeline.getBindGroup());
2588
2572
  pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
2573
+ pass.setBindGroup(3, sectionExclusions(device).bindGroup);
2589
2574
  for (const [i, it] of visibleInstanced.entries()) {
2590
2575
  pass.setVertexBuffer(0, it.vertexBuffer);
2591
2576
  pass.setVertexBuffer(1, it.instanceBuffer);
@@ -2617,18 +2602,7 @@ export class Renderer {
2617
2602
  tpl[34] = mesh.color[2];
2618
2603
  tpl[35] = alphaForMesh(mesh.expressId, mesh.color[3]);
2619
2604
  packMeshMaterial(tpl, mesh.color[3], mesh.finish ?? mesh.material);
2620
- if (sectionPlaneData) {
2621
- tpl[40] = sectionPlaneData.normal[0];
2622
- tpl[41] = sectionPlaneData.normal[1];
2623
- tpl[42] = sectionPlaneData.normal[2];
2624
- tpl[43] = sectionPlaneData.distance;
2625
- }
2626
- else {
2627
- tpl[40] = 0;
2628
- tpl[41] = 0;
2629
- tpl[42] = 0;
2630
- tpl[43] = 0;
2631
- }
2605
+ packSectionPlaneLanes(sectionPlaneData, tpl);
2632
2606
  tplFlags[0] = 0;
2633
2607
  tplFlags[1] =
2634
2608
  (sectionPlaneData?.enabled ? 1 : 0) |
@@ -2636,11 +2610,12 @@ export class Renderer {
2636
2610
  tplClipBit;
2637
2611
  tplFlags[2] = 0;
2638
2612
  tplFlags[3] = 0;
2639
- packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
2613
+ packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl, extraClipPlanes);
2640
2614
  const transparentOrigin = mesh.rteOrigin ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
2641
2615
  if (!relativeToEyeFrame.tryPackDrawableOrigin(transparentOrigin, tpl, MESH_UNIFORM_OFFSET.drawableDelta))
2642
2616
  continue;
2643
2617
  tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
2618
+ this.uniformOverrideU32[0] = mesh.expressId;
2644
2619
  device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
2645
2620
  pass.setBindGroup(0, mesh.bindGroup);
2646
2621
  pass.setVertexBuffer(0, mesh.vertexBuffer);
@@ -2679,18 +2654,7 @@ export class Renderer {
2679
2654
  tpl[34] = mesh.color[2];
2680
2655
  tpl[35] = mesh.color[3];
2681
2656
  packMeshMaterial(tpl, mesh.color[3], mesh.finish ?? mesh.material);
2682
- if (sectionPlaneData) {
2683
- tpl[40] = sectionPlaneData.normal[0];
2684
- tpl[41] = sectionPlaneData.normal[1];
2685
- tpl[42] = sectionPlaneData.normal[2];
2686
- tpl[43] = sectionPlaneData.distance;
2687
- }
2688
- else {
2689
- tpl[40] = 0;
2690
- tpl[41] = 0;
2691
- tpl[42] = 0;
2692
- tpl[43] = 0;
2693
- }
2657
+ packSectionPlaneLanes(sectionPlaneData, tpl);
2694
2658
  tplFlags[0] = 1; // isSelected
2695
2659
  tplFlags[1] =
2696
2660
  (sectionPlaneData?.enabled ? 1 : 0) |
@@ -2698,11 +2662,12 @@ export class Renderer {
2698
2662
  tplClipBit;
2699
2663
  tplFlags[2] = 0;
2700
2664
  tplFlags[3] = 0;
2701
- packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
2665
+ packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl, extraClipPlanes);
2702
2666
  const selectedOrigin = mesh.rteOrigin ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
2703
2667
  if (!relativeToEyeFrame.tryPackDrawableOrigin(selectedOrigin, tpl, MESH_UNIFORM_OFFSET.drawableDelta))
2704
2668
  continue;
2705
2669
  tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
2670
+ this.uniformOverrideU32[0] = mesh.expressId;
2706
2671
  device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
2707
2672
  pass.setPipeline(this.pipeline.getSelectionPipeline());
2708
2673
  pass.setBindGroup(0, mesh.bindGroup);
@@ -2760,6 +2725,7 @@ export class Renderer {
2760
2725
  ? { ...sectionPlaneData, flipped: options.sectionPlane?.flipped === true }
2761
2726
  : null,
2762
2727
  clipBox: options.clipBox,
2728
+ extraPlanes: extraClipPlanes,
2763
2729
  viewport: { width: this.canvas.width / this.pixelRatio, height: this.canvas.height / this.pixelRatio },
2764
2730
  });
2765
2731
  }
@@ -2777,6 +2743,8 @@ export class Renderer {
2777
2743
  relativeToEyeFrame,
2778
2744
  rteViewProj: relativeToEyeFrame.getViewProjection().m,
2779
2745
  rteCamera: relativeToEyeFrame.getCameraWorld(),
2746
+ extraPlanes: extraClipPlanes,
2747
+ primaryPlane: sectionPlaneData && options.sectionPlane?.enabled ? { ...sectionPlaneData, flipped: options.sectionPlane.flipped === true } : null,
2780
2748
  });
2781
2749
  pass.end();
2782
2750
  // Selection/hover outline input (#5390): built from the meshes the
@@ -2791,7 +2759,7 @@ export class Renderer {
2791
2759
  : (this.hoverMeshes.release(), []);
2792
2760
  const selectionOutline = buildSelectionOutlineFrame({
2793
2761
  uniformBufferSize: this.pipeline.getUniformBufferSize(), viewProj, relativeToEyeFrame,
2794
- section: sectionPlaneData, sectionFlipped: options.sectionPlane?.flipped, clipBox: options.clipBox,
2762
+ section: sectionPlaneData, sectionFlipped: options.sectionPlane?.flipped, clipBox: options.clipBox, extraPlanes: extraClipPlanes,
2795
2763
  selectedMeshes: selectedMeshesForMask, hoverPieces, hoveredId: hoverId, selectedModelIndex: hoverModel,
2796
2764
  instancedTemplates: this.scene.getInstancedTemplates(),
2797
2765
  instancedHovered: hoverId != null ? this.scene.getInstancedTemplatesOf(hoverId, hoverModel) : [],
@@ -2813,7 +2781,7 @@ export class Renderer {
2813
2781
  pixelRatio: this.pixelRatio,
2814
2782
  // EDL only when point clouds are loaded and the user enabled it.
2815
2783
  edl: this.edlOptions.enabled && this.pointCloudRenderer?.hasAssets() ? this.edlOptions : null,
2816
- selectionOutline,
2784
+ selectionOutline, geometricVisibility: this.pipeline.visibility.getBinding(),
2817
2785
  });
2818
2786
  colorReadback = colorCapture && encodeRendererColorFrameCapture(device, encoder, colorCapture);
2819
2787
  submitRendererGpuTiming(this, device, encoder);
@@ -2881,8 +2849,10 @@ export class Renderer {
2881
2849
  // `getOrCreatePartialBatch`'s CPU-side geometry merge running out of
2882
2850
  // host memory, and that throws a `RangeError` — which is exactly why
2883
2851
  // the discriminator keys on the TYPE and not on "a frame threw".
2884
- this.containFrameThrow(error, 'encode');
2885
2852
  cancelRendererColorFrame(this);
2853
+ if (deferOpaqueVisibilityFrame(error, () => this.requestRender()))
2854
+ return false;
2855
+ this.containFrameThrow(error, 'encode');
2886
2856
  return false;
2887
2857
  }
2888
2858
  }
@@ -2927,7 +2897,7 @@ export class Renderer {
2927
2897
  * same fragments and clipped-away geometry can't be selected.
2928
2898
  */
2929
2899
  activePickClip() {
2930
- return { sectionPlane: this._activePickSection, clipBox: this._activePickClipBox };
2900
+ return { sectionExcludedIds: this._sectionExcludedIds, sectionPlane: this._activePickSection, extraPlanes: this._activePickExtraPlanes, clipBox: this._activePickClipBox };
2931
2901
  }
2932
2902
  /**
2933
2903
  * GPU-based rectangle pick. Drag-select returns the set of
@@ -2947,7 +2917,7 @@ export class Renderer {
2947
2917
  }
2948
2918
  /** Whether the last rendered frame clipped surfaces (section, terrain or box). */
2949
2919
  hasActiveClipping() {
2950
- return this._activePickSection !== null || this._activePickClipBox !== null;
2920
+ return this._activePickSection !== null || this._activePickExtraPlanes !== null || this._activePickClipBox !== null;
2951
2921
  }
2952
2922
  /** Exact visible-surface raycast in CSS canvas coordinates. */
2953
2923
  raycastScene(x, y, options) {
@@ -3060,14 +3030,21 @@ export class Renderer {
3060
3030
  */
3061
3031
  uploadSection2DOverlay(polygons, lines, axis, position, // 0-100 percentage
3062
3032
  sectionRange, // Same storey-based range as section plane
3063
- flipped = false, customPlane) {
3064
- this.overlays.uploadSection2DOverlay(polygons, lines, axis, position, sectionRange, flipped, customPlane);
3033
+ flipped = false, customPlane, planeId) {
3034
+ this.overlays.uploadSection2DOverlay(polygons, lines, axis, position, sectionRange, flipped, customPlane, planeId);
3065
3035
  }
3066
3036
  /**
3067
- * Clear the 2D section overlay
3037
+ * Clear the 2D section overlay: the primary plane's cap, or with `planeId`
3038
+ * the cap of that cutting plane beyond the first (#7545).
3068
3039
  */
3069
- clearSection2DOverlay() {
3070
- this.overlays.clearSection2DOverlay();
3040
+ clearSection2DOverlay(planeId) {
3041
+ this.overlays.clearSection2DOverlay(planeId);
3042
+ }
3043
+ /** Every section plane's world frame (#7545), for the UI's tags and handle: the last frame's bounds and rotation, and `planes` (the caller's newest) when given. */
3044
+ getSectionPlaneFrames(planes) { const i = this._sectionFrameInput; return i ? resolveSectionPlaneFrames(planes ? { ...i, ...planes } : i) : []; }
3045
+ /** Clear the caps of every cutting plane beyond the first whose id is not in `keep` (#7545). */
3046
+ clearSection2DExtraOverlays(keep = []) {
3047
+ this.overlays.clearSection2DExtraOverlays(keep);
3071
3048
  }
3072
3049
  /**
3073
3050
  * Set the one colour vocabulary every overlay draws in (#5484): the selection
@@ -3301,6 +3278,7 @@ export class Renderer {
3301
3278
  // Render pipelines (textures + uniform buffers)
3302
3279
  this.pipeline?.destroy();
3303
3280
  this.pipeline = null;
3281
+ this.scene.setQuantizedBatches(false);
3304
3282
  // Picker GPU resources. The manager holds its own reference, so clear
3305
3283
  // that too — otherwise its `if (!this.picker) return null` guard keeps
3306
3284
  // pointing at a destroyed picker and a stray click still drives dead
@@ -3335,7 +3313,7 @@ export class Renderer {
3335
3313
  // above was created from it and has already been destroyed, so nothing
3336
3314
  // will touch the device after this. Without it, an app that spins up a
3337
3315
  // renderer per model keeps N live devices (and their VRAM) alive. The
3338
- // lost-handler special-cases reason 'destroyed' so this is not reported
3316
+ // lost-handler excludes this retired lifetime so this is not reported
3339
3317
  // as a fault. render() early-returns while the device is uninitialised.
3340
3318
  this.device.destroy();
3341
3319
  }