@ifc-lite/renderer 3.2.0 → 4.2.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.
- package/dist/aabb-edges.d.ts +10 -0
- package/dist/aabb-edges.d.ts.map +1 -1
- package/dist/aabb-edges.js +13 -0
- package/dist/aabb-edges.js.map +1 -1
- package/dist/ao-params.d.ts +85 -0
- package/dist/ao-params.d.ts.map +1 -0
- package/dist/ao-params.js +109 -0
- package/dist/ao-params.js.map +1 -0
- package/dist/ao-pass.d.ts +47 -0
- package/dist/ao-pass.d.ts.map +1 -0
- package/dist/ao-pass.js +157 -0
- package/dist/ao-pass.js.map +1 -0
- package/dist/camera-matrices.d.ts +3 -0
- package/dist/camera-matrices.d.ts.map +1 -1
- package/dist/camera-matrices.js +12 -1
- package/dist/camera-matrices.js.map +1 -1
- package/dist/camera-orientation.d.ts +62 -0
- package/dist/camera-orientation.d.ts.map +1 -0
- package/dist/camera-orientation.js +155 -0
- package/dist/camera-orientation.js.map +1 -0
- package/dist/camera-projection.d.ts +3 -1
- package/dist/camera-projection.d.ts.map +1 -1
- package/dist/camera-projection.js +26 -20
- package/dist/camera-projection.js.map +1 -1
- package/dist/camera-surface-zoom.d.ts +32 -0
- package/dist/camera-surface-zoom.d.ts.map +1 -0
- package/dist/camera-surface-zoom.js +37 -0
- package/dist/camera-surface-zoom.js.map +1 -0
- package/dist/camera.d.ts +32 -61
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +71 -167
- package/dist/camera.js.map +1 -1
- package/dist/clash-solid-pipeline.d.ts +9 -3
- package/dist/clash-solid-pipeline.d.ts.map +1 -1
- package/dist/clash-solid-pipeline.js +31 -8
- package/dist/clash-solid-pipeline.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -3
- package/dist/constants.js.map +1 -1
- package/dist/depth-reconstruct.d.ts +43 -0
- package/dist/depth-reconstruct.d.ts.map +1 -0
- package/dist/depth-reconstruct.js +35 -0
- package/dist/depth-reconstruct.js.map +1 -0
- package/dist/device.js +1 -1
- package/dist/device.js.map +1 -1
- package/dist/environment.d.ts +26 -15
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +24 -13
- package/dist/environment.js.map +1 -1
- package/dist/federation-registry.d.ts +16 -0
- package/dist/federation-registry.d.ts.map +1 -1
- package/dist/federation-registry.js +80 -9
- package/dist/federation-registry.js.map +1 -1
- package/dist/gpu-static-upload.d.ts +39 -0
- package/dist/gpu-static-upload.d.ts.map +1 -0
- package/dist/gpu-static-upload.js +70 -0
- package/dist/gpu-static-upload.js.map +1 -0
- package/dist/gpu-upload-guard.d.ts +12 -23
- package/dist/gpu-upload-guard.d.ts.map +1 -1
- package/dist/gpu-upload-guard.js +12 -26
- package/dist/gpu-upload-guard.js.map +1 -1
- package/dist/index.d.ts +29 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -252
- package/dist/index.js.map +1 -1
- package/dist/instanced-render.d.ts +22 -2
- package/dist/instanced-render.d.ts.map +1 -1
- package/dist/instanced-render.js +47 -10
- package/dist/instanced-render.js.map +1 -1
- package/dist/instanced-rte.d.ts +17 -0
- package/dist/instanced-rte.d.ts.map +1 -0
- package/dist/instanced-rte.js +32 -0
- package/dist/instanced-rte.js.map +1 -0
- package/dist/mesh-rte-uniforms.d.ts +46 -0
- package/dist/mesh-rte-uniforms.d.ts.map +1 -0
- package/dist/mesh-rte-uniforms.js +59 -0
- package/dist/mesh-rte-uniforms.js.map +1 -0
- package/dist/model-bounds-tracker.d.ts +7 -0
- package/dist/model-bounds-tracker.d.ts.map +1 -1
- package/dist/model-bounds-tracker.js +16 -5
- package/dist/model-bounds-tracker.js.map +1 -1
- package/dist/model-translation.d.ts +1 -1
- package/dist/model-translation.d.ts.map +1 -1
- package/dist/model-translation.js +50 -2
- package/dist/model-translation.js.map +1 -1
- package/dist/pick-rte-snapshot.d.ts +7 -0
- package/dist/pick-rte-snapshot.d.ts.map +1 -0
- package/dist/pick-rte-snapshot.js +15 -0
- package/dist/pick-rte-snapshot.js.map +1 -0
- package/dist/pick-types.d.ts +17 -0
- package/dist/pick-types.d.ts.map +1 -0
- package/dist/pick-types.js +5 -0
- package/dist/pick-types.js.map +1 -0
- package/dist/pick-uniforms.d.ts +6 -0
- package/dist/pick-uniforms.d.ts.map +1 -1
- package/dist/pick-uniforms.js +22 -18
- package/dist/pick-uniforms.js.map +1 -1
- package/dist/pick-world-position.d.ts +18 -0
- package/dist/pick-world-position.d.ts.map +1 -0
- package/dist/pick-world-position.js +21 -0
- package/dist/pick-world-position.js.map +1 -0
- package/dist/picker-readbacks.d.ts +6 -0
- package/dist/picker-readbacks.d.ts.map +1 -0
- package/dist/picker-readbacks.js +20 -0
- package/dist/picker-readbacks.js.map +1 -0
- package/dist/picker-rte-uniforms.d.ts +6 -0
- package/dist/picker-rte-uniforms.d.ts.map +1 -0
- package/dist/picker-rte-uniforms.js +41 -0
- package/dist/picker-rte-uniforms.js.map +1 -0
- package/dist/picker.d.ts +6 -7
- package/dist/picker.d.ts.map +1 -1
- package/dist/picker.js +88 -115
- package/dist/picker.js.map +1 -1
- package/dist/picking-manager.d.ts +8 -0
- package/dist/picking-manager.d.ts.map +1 -1
- package/dist/picking-manager.js +46 -19
- package/dist/picking-manager.js.map +1 -1
- package/dist/pipeline.d.ts +1 -7
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +17 -20
- package/dist/pipeline.js.map +1 -1
- package/dist/point-picker.d.ts +5 -1
- package/dist/point-picker.d.ts.map +1 -1
- package/dist/point-picker.js +49 -9
- package/dist/point-picker.js.map +1 -1
- package/dist/pointcloud/point-cloud-handle-ids.d.ts +22 -0
- package/dist/pointcloud/point-cloud-handle-ids.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-handle-ids.js +31 -0
- package/dist/pointcloud/point-cloud-handle-ids.js.map +1 -0
- package/dist/pointcloud/point-cloud-node.d.ts +14 -6
- package/dist/pointcloud/point-cloud-node.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-node.js.map +1 -1
- package/dist/pointcloud/point-cloud-pick-sources.d.ts +47 -0
- package/dist/pointcloud/point-cloud-pick-sources.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-pick-sources.js +32 -0
- package/dist/pointcloud/point-cloud-pick-sources.js.map +1 -0
- package/dist/pointcloud/point-cloud-placement.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-placement.js +20 -5
- package/dist/pointcloud/point-cloud-placement.js.map +1 -1
- package/dist/pointcloud/point-cloud-ray-transform.d.ts +78 -4
- package/dist/pointcloud/point-cloud-ray-transform.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-ray-transform.js +31 -84
- package/dist/pointcloud/point-cloud-ray-transform.js.map +1 -1
- package/dist/pointcloud/point-cloud-renderer.d.ts +21 -19
- package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-renderer.js +46 -52
- package/dist/pointcloud/point-cloud-renderer.js.map +1 -1
- package/dist/pointcloud/point-cloud-stream-lifecycle.d.ts +44 -0
- package/dist/pointcloud/point-cloud-stream-lifecycle.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-stream-lifecycle.js +44 -0
- package/dist/pointcloud/point-cloud-stream-lifecycle.js.map +1 -0
- package/dist/pointcloud/point-cloud-uniforms.d.ts +5 -0
- package/dist/pointcloud/point-cloud-uniforms.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-uniforms.js +55 -44
- package/dist/pointcloud/point-cloud-uniforms.js.map +1 -1
- package/dist/pointcloud/point-cloud-visibility.d.ts +10 -0
- package/dist/pointcloud/point-cloud-visibility.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-visibility.js +18 -0
- package/dist/pointcloud/point-cloud-visibility.js.map +1 -0
- package/dist/pointcloud/point-pipeline.d.ts +4 -9
- package/dist/pointcloud/point-pipeline.d.ts.map +1 -1
- package/dist/pointcloud/point-pipeline.js +4 -9
- package/dist/pointcloud/point-pipeline.js.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.d.ts +1 -21
- package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.js +25 -5
- package/dist/pointcloud/point-shader.wgsl.js.map +1 -1
- package/dist/post-pass-chain.d.ts +38 -0
- package/dist/post-pass-chain.d.ts.map +1 -0
- package/dist/post-pass-chain.js +99 -0
- package/dist/post-pass-chain.js.map +1 -0
- package/dist/post-processor.d.ts +12 -26
- package/dist/post-processor.d.ts.map +1 -1
- package/dist/post-processor.js +14 -72
- package/dist/post-processor.js.map +1 -1
- package/dist/raycast-engine.d.ts +7 -5
- package/dist/raycast-engine.d.ts.map +1 -1
- package/dist/raycast-engine.js +17 -20
- package/dist/raycast-engine.js.map +1 -1
- package/dist/raycast-point-cloud-query.d.ts +1 -1
- package/dist/raycast-point-cloud-query.d.ts.map +1 -1
- package/dist/reference-image-pipeline.d.ts +1 -1
- package/dist/reference-image-pipeline.d.ts.map +1 -1
- package/dist/reference-image-pipeline.js +25 -7
- package/dist/reference-image-pipeline.js.map +1 -1
- package/dist/reference-images.d.ts +1 -1
- package/dist/reference-images.d.ts.map +1 -1
- package/dist/reference-images.js +13 -5
- package/dist/reference-images.js.map +1 -1
- package/dist/relative-to-eye.d.ts +146 -0
- package/dist/relative-to-eye.d.ts.map +1 -0
- package/dist/relative-to-eye.js +326 -0
- package/dist/relative-to-eye.js.map +1 -0
- package/dist/render-section-draw.d.ts +11 -0
- package/dist/render-section-draw.d.ts.map +1 -1
- package/dist/render-section-draw.js +6 -3
- package/dist/render-section-draw.js.map +1 -1
- package/dist/renderer-color-readback.d.ts +50 -0
- package/dist/renderer-color-readback.d.ts.map +1 -0
- package/dist/renderer-color-readback.js +145 -0
- package/dist/renderer-color-readback.js.map +1 -0
- package/dist/renderer-overlays.d.ts +12 -3
- package/dist/renderer-overlays.d.ts.map +1 -1
- package/dist/renderer-overlays.js +33 -20
- package/dist/renderer-overlays.js.map +1 -1
- package/dist/renderer-screenshot.d.ts +4 -0
- package/dist/renderer-screenshot.d.ts.map +1 -0
- package/dist/renderer-screenshot.js +17 -0
- package/dist/renderer-screenshot.js.map +1 -0
- package/dist/renderer-symbolic-overlays.d.ts +4 -2
- package/dist/renderer-symbolic-overlays.d.ts.map +1 -1
- package/dist/renderer-symbolic-overlays.js +33 -13
- package/dist/renderer-symbolic-overlays.js.map +1 -1
- package/dist/renderer-viewport.d.ts +45 -0
- package/dist/renderer-viewport.d.ts.map +1 -1
- package/dist/renderer-viewport.js +44 -0
- package/dist/renderer-viewport.js.map +1 -1
- package/dist/rte-clip-space.d.ts +12 -0
- package/dist/rte-clip-space.d.ts.map +1 -0
- package/dist/rte-clip-space.js +22 -0
- package/dist/rte-clip-space.js.map +1 -0
- package/dist/rte-frustum.d.ts +24 -0
- package/dist/rte-frustum.d.ts.map +1 -0
- package/dist/rte-frustum.js +45 -0
- package/dist/rte-frustum.js.map +1 -0
- package/dist/scene-batch-upload.d.ts.map +1 -1
- package/dist/scene-batch-upload.js +14 -38
- package/dist/scene-batch-upload.js.map +1 -1
- package/dist/scene-device-recovery.d.ts.map +1 -1
- package/dist/scene-device-recovery.js +5 -9
- package/dist/scene-device-recovery.js.map +1 -1
- package/dist/scene-finalize-regroup.d.ts +54 -0
- package/dist/scene-finalize-regroup.d.ts.map +1 -0
- package/dist/scene-finalize-regroup.js +136 -0
- package/dist/scene-finalize-regroup.js.map +1 -0
- package/dist/scene-instance-bounds.d.ts +12 -0
- package/dist/scene-instance-bounds.d.ts.map +1 -0
- package/dist/scene-instance-bounds.js +48 -0
- package/dist/scene-instance-bounds.js.map +1 -0
- package/dist/scene-instance-materialization.d.ts +4 -1
- package/dist/scene-instance-materialization.d.ts.map +1 -1
- package/dist/scene-instance-materialization.js +21 -6
- package/dist/scene-instance-materialization.js.map +1 -1
- package/dist/scene-instance-translation.d.ts +17 -0
- package/dist/scene-instance-translation.d.ts.map +1 -0
- package/dist/scene-instance-translation.js +50 -0
- package/dist/scene-instance-translation.js.map +1 -0
- package/dist/scene-instance-types.d.ts +6 -0
- package/dist/scene-instance-types.d.ts.map +1 -1
- package/dist/scene-model-translation.d.ts +2 -0
- package/dist/scene-model-translation.d.ts.map +1 -1
- package/dist/scene-model-translation.js +1 -1
- package/dist/scene-model-translation.js.map +1 -1
- package/dist/scene-rect-select.d.ts +11 -1
- package/dist/scene-rect-select.d.ts.map +1 -1
- package/dist/scene-rect-select.js +7 -4
- package/dist/scene-rect-select.js.map +1 -1
- package/dist/scene.d.ts +17 -19
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +109 -238
- package/dist/scene.js.map +1 -1
- package/dist/section-2d-lift.d.ts +10 -2
- package/dist/section-2d-lift.d.ts.map +1 -1
- package/dist/section-2d-lift.js +14 -6
- package/dist/section-2d-lift.js.map +1 -1
- package/dist/section-2d-line-buffer.d.ts +12 -4
- package/dist/section-2d-line-buffer.d.ts.map +1 -1
- package/dist/section-2d-line-buffer.js +59 -33
- package/dist/section-2d-line-buffer.js.map +1 -1
- package/dist/section-2d-overlay.d.ts +18 -72
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +72 -103
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-2d-types.d.ts +38 -0
- package/dist/section-2d-types.d.ts.map +1 -0
- package/dist/section-2d-types.js +6 -0
- package/dist/section-2d-types.js.map +1 -0
- package/dist/section-cap-pipelines.d.ts +6 -0
- package/dist/section-cap-pipelines.d.ts.map +1 -0
- package/dist/section-cap-pipelines.js +87 -0
- package/dist/section-cap-pipelines.js.map +1 -0
- package/dist/section-plane-geometry.d.ts +24 -0
- package/dist/section-plane-geometry.d.ts.map +1 -0
- package/dist/section-plane-geometry.js +93 -0
- package/dist/section-plane-geometry.js.map +1 -0
- package/dist/section-plane.d.ts +23 -24
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js +58 -174
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/ao.wgsl.d.ts +2 -0
- package/dist/shaders/ao.wgsl.d.ts.map +1 -0
- package/dist/shaders/ao.wgsl.js +234 -0
- package/dist/shaders/ao.wgsl.js.map +1 -0
- package/dist/shaders/color-transfer.wgsl.d.ts +26 -0
- package/dist/shaders/color-transfer.wgsl.d.ts.map +1 -0
- package/dist/shaders/color-transfer.wgsl.js +53 -0
- package/dist/shaders/color-transfer.wgsl.js.map +1 -0
- package/dist/shaders/depth-reconstruct.wgsl.d.ts +16 -0
- package/dist/shaders/depth-reconstruct.wgsl.d.ts.map +1 -0
- package/dist/shaders/depth-reconstruct.wgsl.js +69 -0
- package/dist/shaders/depth-reconstruct.wgsl.js.map +1 -0
- package/dist/shaders/main-rte-shadow.wgsl.d.ts +4 -0
- package/dist/shaders/main-rte-shadow.wgsl.d.ts.map +1 -0
- package/dist/shaders/main-rte-shadow.wgsl.js +54 -0
- package/dist/shaders/main-rte-shadow.wgsl.js.map +1 -0
- package/dist/shaders/main.wgsl.d.ts +1 -6
- package/dist/shaders/main.wgsl.d.ts.map +1 -1
- package/dist/shaders/main.wgsl.js +110 -159
- package/dist/shaders/main.wgsl.js.map +1 -1
- package/dist/shaders/relative-to-eye.wgsl.d.ts +12 -0
- package/dist/shaders/relative-to-eye.wgsl.d.ts.map +1 -0
- package/dist/shaders/relative-to-eye.wgsl.js +40 -0
- package/dist/shaders/relative-to-eye.wgsl.js.map +1 -0
- package/dist/shaders/section-2d-overlay.wgsl.d.ts +28 -12
- package/dist/shaders/section-2d-overlay.wgsl.d.ts.map +1 -1
- package/dist/shaders/section-2d-overlay.wgsl.js +40 -13
- package/dist/shaders/section-2d-overlay.wgsl.js.map +1 -1
- package/dist/shaders/shadow.wgsl.d.ts +1 -1
- package/dist/shaders/shadow.wgsl.d.ts.map +1 -1
- package/dist/shaders/shadow.wgsl.js +25 -7
- package/dist/shaders/shadow.wgsl.js.map +1 -1
- package/dist/shaders/sky.wgsl.d.ts +1 -11
- package/dist/shaders/sky.wgsl.d.ts.map +1 -1
- package/dist/shaders/sky.wgsl.js +17 -19
- package/dist/shaders/sky.wgsl.js.map +1 -1
- package/dist/shaders/symbolic-overlay.wgsl.d.ts +2 -9
- package/dist/shaders/symbolic-overlay.wgsl.d.ts.map +1 -1
- package/dist/shaders/symbolic-overlay.wgsl.js +88 -17
- package/dist/shaders/symbolic-overlay.wgsl.js.map +1 -1
- package/dist/shaders/textured.wgsl.d.ts.map +1 -1
- package/dist/shaders/textured.wgsl.js +11 -9
- package/dist/shaders/textured.wgsl.js.map +1 -1
- package/dist/shadow-light-matrix.d.ts +7 -0
- package/dist/shadow-light-matrix.d.ts.map +1 -1
- package/dist/shadow-light-matrix.js +11 -0
- package/dist/shadow-light-matrix.js.map +1 -1
- package/dist/shadow-occluder-batches.d.ts +10 -0
- package/dist/shadow-occluder-batches.d.ts.map +1 -0
- package/dist/shadow-occluder-batches.js +39 -0
- package/dist/shadow-occluder-batches.js.map +1 -0
- package/dist/shadow-occluders.d.ts.map +1 -1
- package/dist/shadow-occluders.js +4 -0
- package/dist/shadow-occluders.js.map +1 -1
- package/dist/shadow-pass.d.ts +5 -51
- package/dist/shadow-pass.d.ts.map +1 -1
- package/dist/shadow-pass.js +49 -45
- package/dist/shadow-pass.js.map +1 -1
- package/dist/shadow-types.d.ts +39 -0
- package/dist/shadow-types.d.ts.map +1 -0
- package/dist/shadow-types.js +15 -0
- package/dist/shadow-types.js.map +1 -0
- package/dist/symbolic-overlay-geometry.d.ts +7 -0
- package/dist/symbolic-overlay-geometry.d.ts.map +1 -0
- package/dist/symbolic-overlay-geometry.js +43 -0
- package/dist/symbolic-overlay-geometry.js.map +1 -0
- package/dist/symbolic-overlay-pipelines.d.ts +24 -36
- package/dist/symbolic-overlay-pipelines.d.ts.map +1 -1
- package/dist/symbolic-overlay-pipelines.js +175 -182
- package/dist/symbolic-overlay-pipelines.js.map +1 -1
- package/dist/symbolic-text-atlas.d.ts +21 -0
- package/dist/symbolic-text-atlas.d.ts.map +1 -1
- package/dist/symbolic-text-atlas.js +29 -1
- package/dist/symbolic-text-atlas.js.map +1 -1
- package/dist/types.d.ts +20 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/visual-enhancement.d.ts +23 -1
- package/dist/visual-enhancement.d.ts.map +1 -1
- package/dist/visual-enhancement.js +38 -12
- package/dist/visual-enhancement.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { appearanceSourceTriangle, expandAppearanceCorners, equivalentAppearance
|
|
|
11
11
|
export { sameCompanionParts } from './appearance-companions.js';
|
|
12
12
|
export { invertAppearancePartition, validateAppearancePartition } from './appearance-preview.js';
|
|
13
13
|
import { createReferenceImageManager } from './reference-image-host.js';
|
|
14
|
-
import { resizeRendererViewport } from './renderer-viewport.js';
|
|
14
|
+
import { measureDrawingBuffer, resizeRendererViewport } from './renderer-viewport.js';
|
|
15
15
|
export { pickFitPolicy } from './camera-fit-policy.js';
|
|
16
16
|
// `Scene` is NOT exported: an exported class republishes every method `SceneContents` above just froze.
|
|
17
17
|
export { Picker } from './picker.js';
|
|
@@ -79,32 +79,37 @@ import { Scene } from './scene.js';
|
|
|
79
79
|
import { Picker } from './picker.js';
|
|
80
80
|
import { reportableItemId } from './pick-resolve.js';
|
|
81
81
|
import { MathUtils, viewBasis } from './math.js';
|
|
82
|
-
import {
|
|
83
|
-
import { VisualEnhancementResolver } from './visual-enhancement.js';
|
|
82
|
+
import { isRteAabbVisible, rteFrustum, sourceFrustumFromRte } from './rte-frustum.js';
|
|
83
|
+
import { VisualEnhancementResolver, livePostEffects } from './visual-enhancement.js';
|
|
84
84
|
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';
|
|
85
86
|
import { RendererOverlays } from './renderer-overlays.js';
|
|
86
87
|
import { resolveSectionPlaneFrame } from './render-section-plane.js';
|
|
87
88
|
import { PickingManager } from './picking-manager.js';
|
|
88
89
|
import { RaycastEngine } from './raycast-engine.js';
|
|
89
90
|
import { RenderDegradationMonitor } from './render-degradation.js';
|
|
90
|
-
import {
|
|
91
|
+
import { PostPassChain } from './post-pass-chain.js';
|
|
91
92
|
import { InteractionEffectsGovernor } from './interaction-effects-governor.js';
|
|
92
93
|
import { VisibilityEpochTracker } from './visibility-epoch.js';
|
|
93
94
|
import { isEntityVisible } from './entity-visibility.js';
|
|
94
95
|
import { ModelBoundsTracker } from './model-bounds-tracker.js';
|
|
95
96
|
import { resolveContributionThresholdPx, projectedAabbRadiusPx, projectedInstancedRadiusPx } from './contribution-cull.js';
|
|
96
|
-
import { EdlPass } from './edl-pass.js';
|
|
97
97
|
import { SkyPass } from './sky-pass.js';
|
|
98
98
|
import { skyShaderSource } from './shaders/sky.wgsl.js';
|
|
99
99
|
import { resolveEnvironment } from './environment.js';
|
|
100
100
|
import { ShadowPass, resolveShadowMapResolution } from './shadow-pass.js';
|
|
101
|
-
import { fitSunLightMatrix, cameraFrustumFocusCorners } from './shadow-light-matrix.js';
|
|
102
|
-
import { collectShadowOccluders
|
|
101
|
+
import { fitSunLightMatrix, cameraFrustumFocusCorners, resolveShadowNormalBiasMetres } from './shadow-light-matrix.js';
|
|
102
|
+
import { collectShadowOccluders } from './shadow-occluders.js';
|
|
103
|
+
import { uploadInstancedRteDeltas } from './instanced-rte.js';
|
|
104
|
+
import { shadowOccluderBatches } from './shadow-occluder-batches.js';
|
|
105
|
+
import { captureRendererScreenshot } from './renderer-screenshot.js';
|
|
106
|
+
import { beginRendererColorFrameCapture, cancelRendererColorFrame, discardRendererColorFrameReadback, encodeRendererColorFrameCapture, requestRendererColorFrame, retryRendererColorFrame, settleRendererColorFrameCapture } from './renderer-color-readback.js';
|
|
103
107
|
import { shouldRouteMeshTransparent, shouldRouteBatchTransparent, splitVisibleIdsByPromotion, DEFAULT_GHOST_ALPHA } from './overlay-routing.js';
|
|
104
108
|
import { XRayAlpha, XRayEpochTracker } from './xray-alpha.js';
|
|
105
109
|
import { PartialBatchRequests } from './partial-batch-requests.js';
|
|
106
110
|
import { colorSaltByte, packEntityLane } from './scene-geometry.js';
|
|
107
111
|
import { PointCloudRenderer } from './pointcloud/point-cloud-renderer.js';
|
|
112
|
+
import { appendPointCloudChunk as appendPointCloudChunkImpl, beginPointCloudStream as beginPointCloudStreamImpl, endPointCloudStream as endPointCloudStreamImpl, removePointCloudAsset as removePointCloudAssetImpl, } from './pointcloud/point-cloud-stream-lifecycle.js';
|
|
108
113
|
import { DeviationComputer } from './deviation/deviation-computer.js';
|
|
109
114
|
import { runGuardedGpuUpload, isDeviceLossThrow } from './gpu-upload-guard.js';
|
|
110
115
|
import { recoverRendererDevice, rendererDeviceLostError } from './device-recovery.js';
|
|
@@ -142,6 +147,7 @@ export class Renderer {
|
|
|
142
147
|
overlays = new RendererOverlays({
|
|
143
148
|
getModelBounds: () => this.getModelBounds(),
|
|
144
149
|
expandModelBoundsWithFlatVertices: (positions, stride) => this.modelBoundsTracker.expandWithFlatVertices(positions, stride),
|
|
150
|
+
expandModelBoundsWithAnchoredLineVertices: (positions, origin, stride) => this.modelBoundsTracker.expandWithAnchoredVertices(positions, origin, stride),
|
|
145
151
|
syncCameraSceneBounds: () => {
|
|
146
152
|
if (this.modelBounds)
|
|
147
153
|
this.camera.setSceneBounds(this.modelBounds);
|
|
@@ -150,9 +156,12 @@ export class Renderer {
|
|
|
150
156
|
});
|
|
151
157
|
referenceImages = createReferenceImageManager(this);
|
|
152
158
|
getReferenceImages() { return this.referenceImages; }
|
|
153
|
-
|
|
159
|
+
// Ambient occlusion, separation lines and eye-dome lighting (post-pass-chain.ts),
|
|
160
|
+
// created on the first rendered frame.
|
|
161
|
+
postPasses = null;
|
|
162
|
+
/** Device px per CSS px in the drawing buffer; CSS-px sizes scale by it (#5383). */
|
|
163
|
+
pixelRatio = 1;
|
|
154
164
|
interactionEffects = new InteractionEffectsGovernor();
|
|
155
|
-
edlPass = null;
|
|
156
165
|
// Procedural sky background — created lazily on the first frame that
|
|
157
166
|
// enables it (most sessions never do).
|
|
158
167
|
skyPass = null;
|
|
@@ -173,6 +182,7 @@ export class Renderer {
|
|
|
173
182
|
pointCloudRenderer = null;
|
|
174
183
|
pointCloudStreamEpoch = 0;
|
|
175
184
|
pointCloudStreamEpochs = new Map(); // handle id → epoch; cleanup rebuilds `{ id }`, cleared per epoch
|
|
185
|
+
pointCloudNextHandleId = 1; // watermark carried across teardown() so a replacement never reissues an id
|
|
176
186
|
/** Set true at the end of the LATEST `init()`; gates `whenReady()` and
|
|
177
187
|
* `isReady()`. Revoked synchronously by `init()` and by `destroy()`, and
|
|
178
188
|
* overridden (not cleared) by a device loss — see `deviceLost`, which the
|
|
@@ -381,13 +391,9 @@ export class Renderer {
|
|
|
381
391
|
// One-shot log guard — prints Y-up clip bounds on first section-enable so
|
|
382
392
|
// users can confirm the slider is operating on the intended range.
|
|
383
393
|
_loggedSectionBounds = false;
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// 60 floats = the WGSL Uniforms struct incl. quantParams (see
|
|
388
|
-
// pipeline.getUniformBufferSize).
|
|
389
|
-
uniformScratch = new Float32Array(60);
|
|
390
|
-
uniformScratchU32 = new Uint32Array(this.uniformScratch.buffer, 176, 4);
|
|
394
|
+
// One 336-byte buffer serves all frame batches/meshes (84 floats including RTE lanes).
|
|
395
|
+
uniformScratch = new Float32Array(MESH_UNIFORM_FLOATS);
|
|
396
|
+
uniformScratchU32 = new Uint32Array(this.uniformScratch.buffer, MESH_FLAGS_BYTE_OFFSET, 4);
|
|
391
397
|
// What the last render() actually clipped, so the GPU picker can mirror it and
|
|
392
398
|
// section/crop-clipped geometry stays unpickable, not just invisible. Updated
|
|
393
399
|
// every render; read by pick()/pickRect(). null = nothing clipped that frame.
|
|
@@ -409,8 +415,8 @@ export class Renderer {
|
|
|
409
415
|
* are released first. The comment below advertises a `destroy()` + `init()`
|
|
410
416
|
* re-init flow, and the obvious device-loss auto-recovery is to call
|
|
411
417
|
* `init()` on the live instance — which, without this, silently orphaned
|
|
412
|
-
* two render pipelines, the picker, the post
|
|
413
|
-
* deviation pipelines
|
|
418
|
+
* two render pipelines, the picker, the post passes, the point-cloud and
|
|
419
|
+
* deviation pipelines and the overlay layer's glyph atlas, per
|
|
414
420
|
* recovery (#2448). Making the method self-safe is cheaper than trusting
|
|
415
421
|
* every future caller to remember.
|
|
416
422
|
*
|
|
@@ -473,8 +479,8 @@ export class Renderer {
|
|
|
473
479
|
await initializingDevice.init(this.canvas);
|
|
474
480
|
// A `destroy()` (or a newer `init()`) landed while we were parked on the
|
|
475
481
|
// device. Everything below allocates a full GPU stack — two pipelines,
|
|
476
|
-
// the picker, the
|
|
477
|
-
//
|
|
482
|
+
// the picker, the point-cloud and deviation pipelines, the overlay
|
|
483
|
+
// glyph atlas — and this aborted
|
|
478
484
|
// path runs no second teardown, so all of it would be orphaned outright
|
|
479
485
|
// (#2465). `markReady()`'s generation check is not enough on its own: it
|
|
480
486
|
// withholds the readiness PUBLICATION, not the allocation. Release the
|
|
@@ -484,15 +490,15 @@ export class Renderer {
|
|
|
484
490
|
this.device.destroy();
|
|
485
491
|
return;
|
|
486
492
|
}
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
|
|
493
|
+
// Size the buffer to the element's device pixels, the same sizing every
|
|
494
|
+
// frame applies (#5383); an unlaid-out canvas keeps its attributes. Both
|
|
495
|
+
// are clamped to the GPU's max 2D texture dimension so the initial
|
|
496
|
+
// pipeline allocations can't overflow on tall/wide layouts.
|
|
491
497
|
const maxDim = this.device.getMaxTextureDimension();
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
const width = Math.min(
|
|
495
|
-
const height = Math.min(
|
|
498
|
+
const measured = measureDrawingBuffer(this.canvas, maxDim);
|
|
499
|
+
this.pixelRatio = measured?.pixelRatio ?? 1;
|
|
500
|
+
const width = Math.max(1, Math.min(measured?.width ?? this.canvas.width, maxDim));
|
|
501
|
+
const height = Math.max(1, Math.min(measured?.height ?? this.canvas.height, maxDim));
|
|
496
502
|
// Set pixel dimensions if not already set, or if we clamped them down
|
|
497
503
|
if (!this.canvas.width || !this.canvas.height || this.canvas.width !== width || this.canvas.height !== height) {
|
|
498
504
|
this.canvas.width = width;
|
|
@@ -502,25 +508,11 @@ export class Renderer {
|
|
|
502
508
|
this.picker = new Picker(this.device, width, height);
|
|
503
509
|
this.overlays.init(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
|
|
504
510
|
this.referenceImages.init(this.device.getDevice(), this.device.getFormat(), this.pipeline.getSampleCount());
|
|
505
|
-
|
|
506
|
-
// depth TEXTURE_BINDING), rendering still works without post-processing.
|
|
507
|
-
try {
|
|
508
|
-
this.postProcessor = new PostProcessor(this.device, {
|
|
509
|
-
enableContactShading: true,
|
|
510
|
-
contactRadius: 1.0,
|
|
511
|
-
contactIntensity: 0.3,
|
|
512
|
-
}, this.pipeline.getSampleCount());
|
|
513
|
-
}
|
|
514
|
-
catch (e) {
|
|
515
|
-
console.warn('[Renderer] PostProcessor init failed (post-processing disabled):', e);
|
|
516
|
-
this.postProcessor = null;
|
|
517
|
-
}
|
|
518
|
-
this.pointCloudRenderer = new PointCloudRenderer(this.device.getDevice(), this.device.getFormat(), 'depth24plus-stencil8', this.pipeline.getSampleCount());
|
|
511
|
+
this.pointCloudRenderer = new PointCloudRenderer(this.device.getDevice(), this.device.getFormat(), 'depth24plus-stencil8', this.pipeline.getSampleCount(), this.pointCloudNextHandleId);
|
|
519
512
|
// Compute pipeline for the BIM↔scan deviation heatmap. Lazily
|
|
520
513
|
// owns the per-triangle BVH GPU buffers; idle until the first
|
|
521
514
|
// `computeDeviations` call.
|
|
522
515
|
this.deviationComputer.init(this.device.getDevice());
|
|
523
|
-
this.edlPass = new EdlPass(this.device, this.pipeline.getSampleCount());
|
|
524
516
|
this.camera.setAspect(width / height);
|
|
525
517
|
// Update picking manager with initialized picker
|
|
526
518
|
this.pickingManager.setPicker(this.picker);
|
|
@@ -707,6 +699,7 @@ export class Renderer {
|
|
|
707
699
|
}
|
|
708
700
|
this.deviceLossSequence++;
|
|
709
701
|
this.deviceLost = true;
|
|
702
|
+
cancelRendererColorFrame(this);
|
|
710
703
|
this.recovery.lostReferenceImages = this.referenceImages.hasImages();
|
|
711
704
|
this.referenceImages.destroy();
|
|
712
705
|
this.deviceLostGeneration = this.initGeneration;
|
|
@@ -766,11 +759,13 @@ export class Renderer {
|
|
|
766
759
|
// regions really do have such a source on a HEALTHY device: the outer
|
|
767
760
|
// one runs `scene.restoreAllEvicted()` for capture frames, the encode
|
|
768
761
|
// one builds visibility sub-batches through
|
|
769
|
-
// `scene.getOrCreatePartialBatch()`, and both
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
773
|
-
//
|
|
762
|
+
// `scene.getOrCreatePartialBatch()`, and both merge geometry into
|
|
763
|
+
// fresh typed arrays, whose allocation throws a plain `RangeError`
|
|
764
|
+
// ("Array buffer allocation failed") under host memory pressure.
|
|
765
|
+
// (Before #5429 the GPU upload itself added a second source, the
|
|
766
|
+
// mapped-at-creation `createBuffer` RangeError; uploads now go through
|
|
767
|
+
// `createStaticGpuBuffer`, which cannot raise it.) Latching there
|
|
768
|
+
// would kill the viewport for a failure whose blast radius should be one frame, and
|
|
774
769
|
// would raise a false "graphics device was lost" toast plus false
|
|
775
770
|
// `device_lost` telemetry on top.
|
|
776
771
|
//
|
|
@@ -879,38 +874,16 @@ export class Renderer {
|
|
|
879
874
|
* chunks will arrive (currently a no-op but kept for symmetry).
|
|
880
875
|
*/
|
|
881
876
|
beginPointCloudStream(meta) {
|
|
882
|
-
|
|
883
|
-
throw rendererDeviceLostError();
|
|
884
|
-
if (!this.pointCloudRenderer) {
|
|
885
|
-
throw new Error('Renderer not initialized. Call init() first.');
|
|
886
|
-
}
|
|
887
|
-
const handle = this.pointCloudRenderer.beginAsset(meta);
|
|
888
|
-
this.pointCloudStreamEpochs.set(handle.id, this.pointCloudStreamEpoch);
|
|
889
|
-
return handle;
|
|
890
|
-
}
|
|
891
|
-
currentPointCloudStreamRenderer(handle) {
|
|
892
|
-
if (this.deviceLost || !this.pointCloudRenderer
|
|
893
|
-
|| this.pointCloudStreamEpochs.get(handle.id) !== this.pointCloudStreamEpoch)
|
|
894
|
-
throw rendererDeviceLostError();
|
|
895
|
-
return this.pointCloudRenderer;
|
|
877
|
+
return beginPointCloudStreamImpl(this, meta);
|
|
896
878
|
}
|
|
897
879
|
appendPointCloudChunk(handle, chunk) {
|
|
898
|
-
this
|
|
899
|
-
this.modelBoundsTracker.expandForPointClouds();
|
|
900
|
-
this.camera.setSceneBounds(this.modelBounds);
|
|
901
|
-
this.requestRender();
|
|
880
|
+
appendPointCloudChunkImpl(this, handle, chunk);
|
|
902
881
|
}
|
|
903
882
|
endPointCloudStream(handle) {
|
|
904
|
-
this
|
|
905
|
-
this.requestRender();
|
|
883
|
+
endPointCloudStreamImpl(this, handle);
|
|
906
884
|
}
|
|
907
885
|
removePointCloudAsset(handle) {
|
|
908
|
-
|
|
909
|
-
return;
|
|
910
|
-
this.pointCloudRenderer?.removeAsset(handle);
|
|
911
|
-
// Bounds may have shrunk — recompute from scratch so fit-to-view
|
|
912
|
-
// and section-plane sliders see fresh extents.
|
|
913
|
-
this.refreshPlacementBounds();
|
|
886
|
+
removePointCloudAssetImpl(this, handle);
|
|
914
887
|
}
|
|
915
888
|
/**
|
|
916
889
|
* Reassign a streamed point-cloud's expressId after upload. Use
|
|
@@ -958,6 +931,13 @@ export class Renderer {
|
|
|
958
931
|
this.clearCaches();
|
|
959
932
|
this.refreshPlacementBounds();
|
|
960
933
|
}
|
|
934
|
+
/** Toggle one resident streamed scan without releasing its GPU buffers. */
|
|
935
|
+
setPointCloudVisibility(handle, visible) {
|
|
936
|
+
if (!this.pointCloudRenderer?.setAssetVisible(handle, visible))
|
|
937
|
+
return;
|
|
938
|
+
this.clearCaches();
|
|
939
|
+
this.refreshPlacementBounds();
|
|
940
|
+
}
|
|
961
941
|
refreshPlacementBounds() {
|
|
962
942
|
this.modelBoundsTracker.recompute();
|
|
963
943
|
this.camera.setSceneBounds(this.modelBounds);
|
|
@@ -1157,65 +1137,7 @@ export class Renderer {
|
|
|
1157
1137
|
* collector drops them.
|
|
1158
1138
|
*/
|
|
1159
1139
|
shadowOccluderBatches(options, device, hasVisibilityFiltering) {
|
|
1160
|
-
|
|
1161
|
-
if (!hasVisibilityFiltering) {
|
|
1162
|
-
for (const batch of all)
|
|
1163
|
-
this.noteShadowOccluderResidency(batch);
|
|
1164
|
-
return all;
|
|
1165
|
-
}
|
|
1166
|
-
const pipeline = this.pipeline;
|
|
1167
|
-
const out = [];
|
|
1168
|
-
for (const batch of all) {
|
|
1169
|
-
const vis = classifyBatchVisibility(batch.expressIds, options.hiddenIds, options.isolatedIds);
|
|
1170
|
-
if (vis.kind === 'none')
|
|
1171
|
-
continue; // fully hidden → does not cast
|
|
1172
|
-
if (vis.kind === 'all') {
|
|
1173
|
-
this.noteShadowOccluderResidency(batch);
|
|
1174
|
-
out.push(batch); // fully visible → its own buffers
|
|
1175
|
-
continue;
|
|
1176
|
-
}
|
|
1177
|
-
// Partially hidden. Transparent parents don't cast (collector's alpha
|
|
1178
|
-
// filter) — skip rather than build a wasted, divergent-key clone.
|
|
1179
|
-
if (batch.color[3] < DEFAULT_MIN_CAST_ALPHA)
|
|
1180
|
-
continue;
|
|
1181
|
-
// The partial sub-batch is built from the PARENT's CPU meshData, so a
|
|
1182
|
-
// cold parent yields nothing until it is restored. Queue that restore
|
|
1183
|
-
// here too: the colour pass only queues it for parents inside its own
|
|
1184
|
-
// frustum, but an up-sun occluder behind the camera still has to cast.
|
|
1185
|
-
this.noteShadowOccluderResidency(batch);
|
|
1186
|
-
// Opaque partial: reuse the colour pass's cached sub-batch. The key is
|
|
1187
|
-
// visibility-content-independent; `_partialBatchEpoch` invalidates it on
|
|
1188
|
-
// any hide/isolate or override change, so the clone is always current.
|
|
1189
|
-
// Without a pipeline the renderer isn't drawing, so skip (the shadow
|
|
1190
|
-
// pass won't run either); casting the whole parent would be wrong.
|
|
1191
|
-
if (!pipeline)
|
|
1192
|
-
continue;
|
|
1193
|
-
const sub = this.scene.getOrCreatePartialBatch(`${batch.colorKey}:${batch.id}`, batch.colorKey, vis.visibleIds, device, pipeline, this._partialBatchEpoch);
|
|
1194
|
-
// A cold parent yields an empty partial (its residency restore is queued
|
|
1195
|
-
// above); skip this frame — the collector drops zero-index draws anyway,
|
|
1196
|
-
// and the subset casts once resident.
|
|
1197
|
-
if (sub && sub.indexCount > 0)
|
|
1198
|
-
out.push(sub);
|
|
1199
|
-
}
|
|
1200
|
-
return out;
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Keep a shadow occluder batch resident so it does not thin out silently on
|
|
1204
|
-
* large models under the GPU residency budget (#2670 review). The depth pass
|
|
1205
|
-
* reads these batches' buffers, but that read did not count as usage, so an
|
|
1206
|
-
* up-sun occluder outside the colour frustum aged into an eviction candidate.
|
|
1207
|
-
* Transparent batches never cast (the collector's alpha filter), so their
|
|
1208
|
-
* residency is irrelevant here.
|
|
1209
|
-
*/
|
|
1210
|
-
noteShadowOccluderResidency(batch) {
|
|
1211
|
-
if (batch.color[3] < DEFAULT_MIN_CAST_ALPHA)
|
|
1212
|
-
return;
|
|
1213
|
-
if (batch.gpuResident === false) {
|
|
1214
|
-
this.scene.requestBatchResidency(batch);
|
|
1215
|
-
}
|
|
1216
|
-
else {
|
|
1217
|
-
this.scene.recordBatchDrawn(batch);
|
|
1218
|
-
}
|
|
1140
|
+
return shadowOccluderBatches(this.scene, options, device, hasVisibilityFiltering, this.pipeline, this._partialBatchEpoch);
|
|
1219
1141
|
}
|
|
1220
1142
|
/** Guarded entry point (#4885) for the unguarded body below. */
|
|
1221
1143
|
createMeshFromData(meshData) {
|
|
@@ -1234,18 +1156,17 @@ export class Renderer {
|
|
|
1234
1156
|
const interleaved = new Float32Array(interleavedRaw);
|
|
1235
1157
|
const interleavedU32 = new Uint32Array(interleavedRaw);
|
|
1236
1158
|
// Build this individual mesh (selection highlight + GPU object-id picker)
|
|
1237
|
-
// in
|
|
1159
|
+
// in the same small local frame as its source batch.
|
|
1238
1160
|
// CRITICAL: replicate the BATCH's exact two-step f32 path so the highlight
|
|
1239
1161
|
// is bit-coincident with its source surface (no z-fight, no depth bias):
|
|
1240
1162
|
// batch stores s = f32(local + (origin - sharedOrigin)) [merge]
|
|
1241
|
-
// batch shader
|
|
1242
|
-
// We
|
|
1243
|
-
//
|
|
1163
|
+
// batch shader RTE = sharedOrigin - eye + s [draw]
|
|
1164
|
+
// We retain `s` and the canonical origin separately. When there is no
|
|
1165
|
+
// shared origin, retain the piece origin instead of folding it in.
|
|
1244
1166
|
const o = meshData.origin;
|
|
1245
1167
|
const so = this.scene.getSharedFrameOrigin(meshData.modelIndex, meshData);
|
|
1246
1168
|
const ox = o ? o[0] : 0, oy = o ? o[1] : 0, oz = o ? o[2] : 0;
|
|
1247
1169
|
const fr = Math.fround;
|
|
1248
|
-
const sox = so ? fr(so[0]) : null, soy = so ? fr(so[1]) : 0, soz = so ? fr(so[2]) : 0;
|
|
1249
1170
|
const dx = so ? (ox - so[0]) : ox, dy = so ? (oy - so[1]) : oy, dz = so ? (oz - so[2]) : oz;
|
|
1250
1171
|
// Quantized batches (issue #1682 phase 6) render lattice-snapped
|
|
1251
1172
|
// positions: the shader's quantMin + q*step is exactly the lattice
|
|
@@ -1262,9 +1183,9 @@ export class Renderer {
|
|
|
1262
1183
|
for (let i = 0; i < vertexCount; i++) {
|
|
1263
1184
|
const base = i * 7;
|
|
1264
1185
|
const posBase = i * 3;
|
|
1265
|
-
interleaved[base] = so ?
|
|
1266
|
-
interleaved[base + 1] = so ?
|
|
1267
|
-
interleaved[base + 2] = so ?
|
|
1186
|
+
interleaved[base] = so ? snap(fr(p[posBase] + dx)) : snap(p[posBase]);
|
|
1187
|
+
interleaved[base + 1] = so ? snap(fr(p[posBase + 1] + dy)) : snap(p[posBase + 1]);
|
|
1188
|
+
interleaved[base + 2] = so ? snap(fr(p[posBase + 2] + dz)) : snap(p[posBase + 2]);
|
|
1268
1189
|
const hasNormals = meshData.normals.length > 0;
|
|
1269
1190
|
interleaved[base + 3] = hasNormals ? meshData.normals[posBase] : 0;
|
|
1270
1191
|
interleaved[base + 4] = hasNormals ? meshData.normals[posBase + 1] : 0;
|
|
@@ -1294,7 +1215,9 @@ export class Renderer {
|
|
|
1294
1215
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
1295
1216
|
});
|
|
1296
1217
|
device.queue.writeBuffer(indexBuffer, 0, meshData.indices);
|
|
1297
|
-
//
|
|
1218
|
+
// Keep the hydrated mesh in its decoded local frame. Folding this
|
|
1219
|
+
// origin back into f32 vertices used to make selection/highlight the
|
|
1220
|
+
// only mesh path that lost centimetres at national-grid coordinates.
|
|
1298
1221
|
// Flagged `hydrated` so it can be freed when its entity leaves the
|
|
1299
1222
|
// selection — these duplicate geometry already drawn by a batch and would
|
|
1300
1223
|
// otherwise accumulate + double-draw (see Scene.disposeHydratedMeshesExcept).
|
|
@@ -1309,7 +1232,14 @@ export class Renderer {
|
|
|
1309
1232
|
vertexBuffer,
|
|
1310
1233
|
indexBuffer,
|
|
1311
1234
|
indexCount: meshData.indices.length,
|
|
1312
|
-
transform:
|
|
1235
|
+
transform: (() => {
|
|
1236
|
+
const transform = MathUtils.identity();
|
|
1237
|
+
transform.m[12] = so ? so[0] : ox;
|
|
1238
|
+
transform.m[13] = so ? so[1] : oy;
|
|
1239
|
+
transform.m[14] = so ? so[2] : oz;
|
|
1240
|
+
return transform;
|
|
1241
|
+
})(),
|
|
1242
|
+
rteOrigin: so ? [so[0], so[1], so[2]] : [ox, oy, oz],
|
|
1313
1243
|
color: meshData.color,
|
|
1314
1244
|
hydrated: true,
|
|
1315
1245
|
});
|
|
@@ -1469,6 +1399,7 @@ export class Renderer {
|
|
|
1469
1399
|
// emit a stream of validation errors. Stay quiet until re-init.
|
|
1470
1400
|
if (this.deviceLost) {
|
|
1471
1401
|
this._renderSkipCount++;
|
|
1402
|
+
cancelRendererColorFrame(this);
|
|
1472
1403
|
return;
|
|
1473
1404
|
}
|
|
1474
1405
|
try {
|
|
@@ -1480,6 +1411,7 @@ export class Renderer {
|
|
|
1480
1411
|
// as "did not fail" — see `frameContainedThrow`.
|
|
1481
1412
|
if (!this.frameContainedThrow)
|
|
1482
1413
|
this.consecutiveDegradedFrames = 0;
|
|
1414
|
+
retryRendererColorFrame(this, () => this.requestRender());
|
|
1483
1415
|
}
|
|
1484
1416
|
catch (error) {
|
|
1485
1417
|
// Safari (26.5) reports device loss SYNCHRONOUSLY: a call against a
|
|
@@ -1495,6 +1427,7 @@ export class Renderer {
|
|
|
1495
1427
|
// host down with us".
|
|
1496
1428
|
this._renderSkipCount++;
|
|
1497
1429
|
this.containFrameThrow(error, 'frame');
|
|
1430
|
+
cancelRendererColorFrame(this);
|
|
1498
1431
|
}
|
|
1499
1432
|
}
|
|
1500
1433
|
/**
|
|
@@ -1506,23 +1439,16 @@ export class Renderer {
|
|
|
1506
1439
|
this._renderSkipCount++;
|
|
1507
1440
|
return;
|
|
1508
1441
|
}
|
|
1509
|
-
//
|
|
1510
|
-
//
|
|
1511
|
-
|
|
1512
|
-
//
|
|
1513
|
-
|
|
1514
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
1515
|
-
const maxDim = this.device.getMaxTextureDimension();
|
|
1516
|
-
const rawWidth = Math.max(1, Math.floor(rect.width));
|
|
1517
|
-
const widthAligned = Math.max(64, Math.floor(rawWidth / 64) * 64);
|
|
1518
|
-
const width = Math.min(widthAligned, Math.floor(maxDim / 64) * 64);
|
|
1519
|
-
const rawHeight = Math.max(1, Math.floor(rect.height));
|
|
1520
|
-
const height = Math.min(rawHeight, maxDim);
|
|
1521
|
-
// Skip rendering if canvas is too small
|
|
1522
|
-
if (width < 64 || height < 10) {
|
|
1442
|
+
// Drawing buffer = the element's device-pixel size (capped ratio, clamped
|
|
1443
|
+
// to the GPU's max texture dimension); see computeDrawingBufferSize (#5383).
|
|
1444
|
+
const measured = measureDrawingBuffer(this.canvas, this.device.getMaxTextureDimension());
|
|
1445
|
+
// Skip rendering while the canvas is collapsed or too small.
|
|
1446
|
+
if (!measured || measured.height < 10) {
|
|
1523
1447
|
this._renderSkipCount++;
|
|
1524
1448
|
return;
|
|
1525
1449
|
}
|
|
1450
|
+
const { width, height } = measured;
|
|
1451
|
+
this.pixelRatio = measured.pixelRatio;
|
|
1526
1452
|
// Update canvas pixel dimensions if needed
|
|
1527
1453
|
const dimensionsChanged = this.canvas.width !== width || this.canvas.height !== height;
|
|
1528
1454
|
if (dimensionsChanged) {
|
|
@@ -1546,6 +1472,13 @@ export class Renderer {
|
|
|
1546
1472
|
}
|
|
1547
1473
|
const device = this.device.getDevice();
|
|
1548
1474
|
const viewProj = this.camera.getViewProjMatrix().m;
|
|
1475
|
+
const relativeToEyeFrame = this.camera.getRelativeToEyeFrame();
|
|
1476
|
+
// Culling must share the translation-free RTE frame used by every GPU
|
|
1477
|
+
// pass. `sourceFrustum` is the f64 plane equivalent for the immutable
|
|
1478
|
+
// source-space BVH; render-time boxes stay eye-relative below.
|
|
1479
|
+
const rteCullFrustum = rteFrustum(relativeToEyeFrame);
|
|
1480
|
+
const rteCullCamera = relativeToEyeFrame.getCameraWorld();
|
|
1481
|
+
const sourceCullFrustum = sourceFrustumFromRte(rteCullFrustum, rteCullCamera);
|
|
1549
1482
|
// Frame stats (issue #1682): geometry draw calls + per-frame cull
|
|
1550
1483
|
// outcomes, snapshotted into _lastFrameStats before queue.submit.
|
|
1551
1484
|
let frameDrawCalls = 0;
|
|
@@ -1585,11 +1518,8 @@ export class Renderer {
|
|
|
1585
1518
|
const edgeIntensity = Math.min(3.0, Math.max(0.0, visualEnhancement.edgeContrast.intensity));
|
|
1586
1519
|
const edgeEnabledU32 = edgeEnabled ? 1 : 0;
|
|
1587
1520
|
const edgeIntensityMilliU32 = Math.round(edgeIntensity * 1000);
|
|
1588
|
-
|
|
1589
|
-
const
|
|
1590
|
-
&& visualEnhancement.separationLines.enabled
|
|
1591
|
-
&& visualEnhancement.separationLines.quality !== 'off';
|
|
1592
|
-
const needsObjectIdPass = contactEnabled || separationEnabled;
|
|
1521
|
+
// Only the separation lines read the object-id attachment after the pass.
|
|
1522
|
+
const needsObjectIdPass = livePostEffects(visualEnhancement, effectsLive).separationLines;
|
|
1593
1523
|
// Check if visibility filtering is active
|
|
1594
1524
|
const hasHiddenFilter = options.hiddenIds && options.hiddenIds.size > 0;
|
|
1595
1525
|
const hasIsolatedFilter = options.isolatedIds !== null && options.isolatedIds !== undefined;
|
|
@@ -1697,8 +1627,7 @@ export class Renderer {
|
|
|
1697
1627
|
// Frustum culling (if enabled and spatial index available)
|
|
1698
1628
|
if (options.enableFrustumCulling && options.spatialIndex) {
|
|
1699
1629
|
try {
|
|
1700
|
-
const
|
|
1701
|
-
const visibleIds = new Set(options.spatialIndex.queryFrustum(frustum));
|
|
1630
|
+
const visibleIds = new Set(options.spatialIndex.queryFrustum(sourceCullFrustum));
|
|
1702
1631
|
meshes = meshes.filter(mesh => visibleIds.has(mesh.expressId));
|
|
1703
1632
|
}
|
|
1704
1633
|
catch (error) {
|
|
@@ -1739,12 +1668,15 @@ export class Renderer {
|
|
|
1739
1668
|
}
|
|
1740
1669
|
return; // Skip this frame, context will be reconfigured next frame
|
|
1741
1670
|
}
|
|
1671
|
+
let colorCapture = null;
|
|
1672
|
+
let colorReadback = null;
|
|
1742
1673
|
try {
|
|
1743
1674
|
const clearColor = options.clearColor
|
|
1744
1675
|
? (Array.isArray(options.clearColor)
|
|
1745
1676
|
? { r: options.clearColor[0], g: options.clearColor[1], b: options.clearColor[2], a: options.clearColor[3] }
|
|
1746
1677
|
: options.clearColor)
|
|
1747
1678
|
: { r: 0.1, g: 0.1, b: 0.1, a: 1 };
|
|
1679
|
+
colorCapture = beginRendererColorFrameCapture(this, currentTexture, this.canvas.width, this.canvas.height, this.device.getFormat());
|
|
1748
1680
|
const textureView = currentTexture.createView();
|
|
1749
1681
|
const objectIdView = this.pipeline.getObjectIdTextureView();
|
|
1750
1682
|
// Separate meshes into opaque and transparent
|
|
@@ -1818,6 +1750,8 @@ export class Renderer {
|
|
|
1818
1750
|
for (const mesh of allMeshes) {
|
|
1819
1751
|
if (mesh.uniformBuffer) {
|
|
1820
1752
|
meshBuf.set(viewProj, 0);
|
|
1753
|
+
relativeToEyeFrame.packUniforms(meshBuf, MESH_UNIFORM_OFFSET.rteViewProj);
|
|
1754
|
+
packRteCameraOrigin(relativeToEyeFrame, meshBuf);
|
|
1821
1755
|
meshBuf.set(mesh.transform.m, 16);
|
|
1822
1756
|
// Check if mesh is selected (single or multi-selection)
|
|
1823
1757
|
// For multi-model support: also check modelIndex if provided
|
|
@@ -1858,6 +1792,13 @@ export class Renderer {
|
|
|
1858
1792
|
clipBit;
|
|
1859
1793
|
meshFlags[2] = edgeEnabledU32;
|
|
1860
1794
|
meshFlags[3] = edgeIntensityMilliU32;
|
|
1795
|
+
// Individual meshes retain their origin and share the RTE
|
|
1796
|
+
// fragment contract with colour batches.
|
|
1797
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, meshBuf);
|
|
1798
|
+
const meshOrigin = mesh.rteOrigin
|
|
1799
|
+
?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
|
|
1800
|
+
relativeToEyeFrame.packDrawableOrigin(meshOrigin, meshBuf, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
1801
|
+
meshFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
1861
1802
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, meshBuf);
|
|
1862
1803
|
}
|
|
1863
1804
|
}
|
|
@@ -1887,8 +1828,16 @@ export class Renderer {
|
|
|
1887
1828
|
else {
|
|
1888
1829
|
this.shadowPass.setResolution(resolution);
|
|
1889
1830
|
}
|
|
1890
|
-
|
|
1891
|
-
const
|
|
1831
|
+
// Fit shadows in the same eye-relative frame as vertices.
|
|
1832
|
+
const shadowEye = relativeToEyeFrame.getCameraWorld();
|
|
1833
|
+
const sourceBoundsMin = [bounds.min.x, bounds.min.y, bounds.min.z];
|
|
1834
|
+
const sourceBoundsMax = [bounds.max.x, bounds.max.y, bounds.max.z];
|
|
1835
|
+
const boundsMin = [
|
|
1836
|
+
bounds.min.x - shadowEye[0], bounds.min.y - shadowEye[1], bounds.min.z - shadowEye[2],
|
|
1837
|
+
];
|
|
1838
|
+
const boundsMax = [
|
|
1839
|
+
bounds.max.x - shadowEye[0], bounds.max.y - shadowEye[1], bounds.max.z - shadowEye[2],
|
|
1840
|
+
];
|
|
1892
1841
|
// Lateral shadow fit. AT REST: fit to the camera frustum
|
|
1893
1842
|
// clipped to the model (maintainer #1) so a small building on
|
|
1894
1843
|
// a large site keeps sharp shadows instead of spending the
|
|
@@ -1911,9 +1860,13 @@ export class Renderer {
|
|
|
1911
1860
|
aspect: this.canvas.height > 0 ? this.canvas.width / this.canvas.height : 1,
|
|
1912
1861
|
ortho: this.camera.getProjectionMode() === 'orthographic',
|
|
1913
1862
|
orthoHalfHeight: this.camera.getOrthoSize(),
|
|
1914
|
-
boundsMin,
|
|
1915
|
-
boundsMax,
|
|
1916
|
-
})
|
|
1863
|
+
boundsMin: sourceBoundsMin,
|
|
1864
|
+
boundsMax: sourceBoundsMax,
|
|
1865
|
+
})?.map((corner) => ({
|
|
1866
|
+
x: corner.x - shadowEye[0],
|
|
1867
|
+
y: corner.y - shadowEye[1],
|
|
1868
|
+
z: corner.z - shadowEye[2],
|
|
1869
|
+
}));
|
|
1917
1870
|
}
|
|
1918
1871
|
const sun = resolveEnvironment(options.environment).sunDirection;
|
|
1919
1872
|
const fit = fitSunLightMatrix({ sunDirection: sun, boundsMin, boundsMax, focusCorners });
|
|
@@ -1948,7 +1901,7 @@ export class Renderer {
|
|
|
1948
1901
|
box: options.clipBox?.enabled
|
|
1949
1902
|
? { min: options.clipBox.min, max: options.clipBox.max }
|
|
1950
1903
|
: null,
|
|
1951
|
-
});
|
|
1904
|
+
}, { cameraWorld: shadowEye });
|
|
1952
1905
|
// Shadow uniform: light matrix + sampling params. The kernel
|
|
1953
1906
|
// width follows the sun's angular size (physical, ~0.53°
|
|
1954
1907
|
// like Blender's Sun lamp). Bias scales with the kernel: a
|
|
@@ -1958,7 +1911,7 @@ export class Renderer {
|
|
|
1958
1911
|
const texelWorld = (2 * fit.orthoHalfWidth) / resolution;
|
|
1959
1912
|
const sunAngleDeg = shadowOpts.sunAngleDeg ?? 0.53;
|
|
1960
1913
|
const pcfRadius = Math.min(Math.max(sunAngleDeg * 3.0, 0.75), 8.0);
|
|
1961
|
-
const normalBias = texelWorld
|
|
1914
|
+
const normalBias = resolveShadowNormalBiasMetres(texelWorld, pcfRadius);
|
|
1962
1915
|
const s = this.shadowScratch;
|
|
1963
1916
|
s.set(fit.lightViewProj.m, 0);
|
|
1964
1917
|
s[16] = 1 / resolution; // texelSize
|
|
@@ -2094,7 +2047,7 @@ export class Renderer {
|
|
|
2094
2047
|
if (allBatchedMeshes.length > 0 || this.scene.getTexturedMeshes().length > 0 || this.scene.getInstancedTemplates().length > 0) {
|
|
2095
2048
|
// Frustum culling for batched meshes - skip entire batches outside the camera view
|
|
2096
2049
|
// This is the primary performance optimization for large models (200K+ meshes)
|
|
2097
|
-
const frustum =
|
|
2050
|
+
const frustum = rteCullFrustum;
|
|
2098
2051
|
// Contribution culling (issue #1682): skip batches whose world
|
|
2099
2052
|
// AABB projects below a pixel threshold. Disabled unless the
|
|
2100
2053
|
// caller opts in via options.contributionCull; the threshold is
|
|
@@ -2121,7 +2074,7 @@ export class Renderer {
|
|
|
2121
2074
|
mode: this.camera.getProjectionMode(),
|
|
2122
2075
|
fovYRadians: this.camera.getFOV(),
|
|
2123
2076
|
orthoHalfHeight: this.camera.getOrthoSize(),
|
|
2124
|
-
viewportHeightPx: this.canvas.height,
|
|
2077
|
+
viewportHeightPx: this.canvas.height / this.pixelRatio, // CSS px, like the thresholds
|
|
2125
2078
|
};
|
|
2126
2079
|
}
|
|
2127
2080
|
// Per-batch visibility (only meaningful while filtering is active).
|
|
@@ -2176,7 +2129,7 @@ export class Renderer {
|
|
|
2176
2129
|
// Frustum culling: skip batches entirely outside the camera view
|
|
2177
2130
|
if (batch.bounds) {
|
|
2178
2131
|
const batchAABB = { min: batch.bounds.min, max: batch.bounds.max };
|
|
2179
|
-
if (!
|
|
2132
|
+
if (!isRteAabbVisible(frustum, batchAABB, rteCullCamera)) {
|
|
2180
2133
|
frameBatchesFrustumCulled++;
|
|
2181
2134
|
continue; // Entire batch is off-screen
|
|
2182
2135
|
}
|
|
@@ -2272,6 +2225,8 @@ export class Renderer {
|
|
|
2272
2225
|
const tpl = this.uniformScratch;
|
|
2273
2226
|
const tplFlags = this.uniformScratchU32;
|
|
2274
2227
|
tpl.set(viewProj, 0);
|
|
2228
|
+
relativeToEyeFrame.packUniforms(tpl, MESH_UNIFORM_OFFSET.rteViewProj);
|
|
2229
|
+
packRteCameraOrigin(relativeToEyeFrame, tpl);
|
|
2275
2230
|
// Identity model matrix (positions already in world space)
|
|
2276
2231
|
tpl[16] = 1;
|
|
2277
2232
|
tpl[17] = 0;
|
|
@@ -2322,6 +2277,11 @@ export class Renderer {
|
|
|
2322
2277
|
tplClipBit;
|
|
2323
2278
|
tplFlags[2] = edgeEnabledU32;
|
|
2324
2279
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
2280
|
+
// Flat/quantized/textured batches enter WGSL in the single
|
|
2281
|
+
// camera-relative frame. Their fragment clip inputs must use
|
|
2282
|
+
// that same frame; mixing a local vertex with a 5,000 km f32
|
|
2283
|
+
// world plane loses centimetre cuts before the comparison.
|
|
2284
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
|
|
2325
2285
|
// Helper function to render a batch — patches color into the shared template
|
|
2326
2286
|
const renderBatch = (batch) => {
|
|
2327
2287
|
if (!batch.bindGroup || !batch.uniformBuffer)
|
|
@@ -2341,6 +2301,11 @@ export class Renderer {
|
|
|
2341
2301
|
tpl[28] = o ? o[0] : 0;
|
|
2342
2302
|
tpl[29] = o ? o[1] : 0;
|
|
2343
2303
|
tpl[30] = o ? o[2] : 0;
|
|
2304
|
+
// The regular model matrix remains for absolute-space
|
|
2305
|
+
// fragment work (section/shadow); vertex projection uses
|
|
2306
|
+
// this f64-subtracted high/low origin instead.
|
|
2307
|
+
relativeToEyeFrame.packDrawableOrigin(o ?? [0, 0, 0], tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2308
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2344
2309
|
// Quantized dequantization params (issue #1682 phase 6);
|
|
2345
2310
|
// zeroed for f32 batches (their pipelines ignore them).
|
|
2346
2311
|
const qz = batch.quantized;
|
|
@@ -2420,7 +2385,7 @@ export class Renderer {
|
|
|
2420
2385
|
const kept = [];
|
|
2421
2386
|
for (const it of instancedTemplates) {
|
|
2422
2387
|
if (it.bounds) {
|
|
2423
|
-
if (!
|
|
2388
|
+
if (!isRteAabbVisible(frustum, it.bounds, rteCullCamera)) {
|
|
2424
2389
|
frameInstancedFrustumCulled++;
|
|
2425
2390
|
continue;
|
|
2426
2391
|
}
|
|
@@ -2438,11 +2403,12 @@ export class Renderer {
|
|
|
2438
2403
|
visibleInstanced = kept;
|
|
2439
2404
|
}
|
|
2440
2405
|
if (visibleInstanced.length > 0) {
|
|
2406
|
+
uploadInstancedRteDeltas(device, visibleInstanced, relativeToEyeFrame.getCameraWorld());
|
|
2441
2407
|
// Opaque instanced pass. flags.x bit 2 marks "instanced pass" so the
|
|
2442
2408
|
// shader routes per-instance opacity: opaque (or selected) occurrences
|
|
2443
2409
|
// draw here; translucent ones (lens/x-ray/compare overrides) are
|
|
2444
2410
|
// discarded and drawn in the transparent sub-pass below.
|
|
2445
|
-
this.pipeline.writeRawUniforms(tpl, 0x4);
|
|
2411
|
+
this.pipeline.writeRawUniforms(tpl, MESH_FLAG_RTE_DRAWABLE | 0x4);
|
|
2446
2412
|
pass.setPipeline(this.pipeline.getInstancedPipeline());
|
|
2447
2413
|
pass.setBindGroup(0, this.pipeline.getBindGroup());
|
|
2448
2414
|
pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
|
|
@@ -2502,6 +2468,8 @@ export class Renderer {
|
|
|
2502
2468
|
tpl[28] = tm.origin[0];
|
|
2503
2469
|
tpl[29] = tm.origin[1];
|
|
2504
2470
|
tpl[30] = tm.origin[2];
|
|
2471
|
+
relativeToEyeFrame.packDrawableOrigin(tm.origin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2472
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2505
2473
|
tpl[32] = txOverride ? txOverride[0] : tm.color[0];
|
|
2506
2474
|
tpl[33] = txOverride ? txOverride[1] : tm.color[1];
|
|
2507
2475
|
tpl[34] = txOverride ? txOverride[2] : tm.color[2];
|
|
@@ -2648,7 +2616,7 @@ export class Renderer {
|
|
|
2648
2616
|
if (visibleInstanced.length > 0 &&
|
|
2649
2617
|
this.scene.hasTransparentInstances() &&
|
|
2650
2618
|
instancedTransparentPipeline !== null) {
|
|
2651
|
-
this.pipeline.writeRawUniforms(tpl, 0x4 | 0x8);
|
|
2619
|
+
this.pipeline.writeRawUniforms(tpl, MESH_FLAG_RTE_DRAWABLE | 0x4 | 0x8);
|
|
2652
2620
|
pass.setPipeline(instancedTransparentPipeline);
|
|
2653
2621
|
pass.setBindGroup(0, this.pipeline.getBindGroup());
|
|
2654
2622
|
pass.setBindGroup(1, this.pipeline.getEnvironmentBindGroup());
|
|
@@ -2705,6 +2673,10 @@ export class Renderer {
|
|
|
2705
2673
|
tplClipBit;
|
|
2706
2674
|
tplFlags[2] = edgeEnabledU32;
|
|
2707
2675
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
2676
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
|
|
2677
|
+
const transparentOrigin = mesh.rteOrigin ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
|
|
2678
|
+
relativeToEyeFrame.packDrawableOrigin(transparentOrigin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2679
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2708
2680
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
|
|
2709
2681
|
pass.setBindGroup(0, mesh.bindGroup);
|
|
2710
2682
|
pass.setVertexBuffer(0, mesh.vertexBuffer);
|
|
@@ -2765,6 +2737,10 @@ export class Renderer {
|
|
|
2765
2737
|
tplClipBit;
|
|
2766
2738
|
tplFlags[2] = edgeEnabledU32;
|
|
2767
2739
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
2740
|
+
packRteFragmentSpace(relativeToEyeFrame, sectionPlaneData, options.clipBox, tpl);
|
|
2741
|
+
const selectedOrigin = mesh.rteOrigin ?? [mesh.transform.m[12], mesh.transform.m[13], mesh.transform.m[14]];
|
|
2742
|
+
relativeToEyeFrame.packDrawableOrigin(selectedOrigin, tpl, MESH_UNIFORM_OFFSET.drawableDelta);
|
|
2743
|
+
tplFlags[0] |= MESH_FLAG_RTE_DRAWABLE;
|
|
2768
2744
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
|
|
2769
2745
|
pass.setPipeline(this.pipeline.getSelectionPipeline());
|
|
2770
2746
|
pass.setBindGroup(0, mesh.bindGroup);
|
|
@@ -2812,65 +2788,55 @@ export class Renderer {
|
|
|
2812
2788
|
// viewport size to convert pixel sizes into clip-space offsets.
|
|
2813
2789
|
if (this.pointCloudRenderer && this.pointCloudRenderer.hasAssets()) {
|
|
2814
2790
|
this.pointCloudRenderer.draw(pass, {
|
|
2815
|
-
viewProj,
|
|
2791
|
+
viewProj: relativeToEyeFrame.getViewProjection().m,
|
|
2792
|
+
relativeToEyeFrame,
|
|
2816
2793
|
sectionPlane: sectionPlaneData
|
|
2817
2794
|
? { ...sectionPlaneData, flipped: options.sectionPlane?.flipped === true }
|
|
2818
2795
|
: null,
|
|
2819
|
-
|
|
2796
|
+
clipBox: options.clipBox,
|
|
2797
|
+
viewport: { width: this.canvas.width / this.pixelRatio, height: this.canvas.height / this.pixelRatio },
|
|
2820
2798
|
});
|
|
2821
2799
|
}
|
|
2822
2800
|
// Section-plane gizmo, 2D section cap and every standalone 3D
|
|
2823
2801
|
// overlay (annotation / alignment / grid / DXF / clash / symbolic
|
|
2824
2802
|
// text). One draw call into the pass — see RendererOverlays.draw().
|
|
2825
|
-
this.referenceImages.draw(pass, viewProj);
|
|
2803
|
+
this.referenceImages.draw(pass, viewProj, relativeToEyeFrame.getViewProjection().m, relativeToEyeFrame.getCameraWorld());
|
|
2826
2804
|
this.overlays.draw(pass, {
|
|
2827
2805
|
options,
|
|
2828
2806
|
viewProj,
|
|
2829
2807
|
modelBounds: this.getModelBounds(),
|
|
2830
2808
|
camera: this.camera,
|
|
2831
|
-
canvasWidth: this.canvas.width,
|
|
2832
|
-
canvasHeight: this.canvas.height,
|
|
2809
|
+
canvasWidth: this.canvas.width / this.pixelRatio, // CSS px: glyph sizes are CSS px
|
|
2810
|
+
canvasHeight: this.canvas.height / this.pixelRatio, pixelRatio: this.pixelRatio,
|
|
2811
|
+
relativeToEyeFrame,
|
|
2812
|
+
rteViewProj: relativeToEyeFrame.getViewProjection().m,
|
|
2813
|
+
rteCamera: relativeToEyeFrame.getCameraWorld(),
|
|
2833
2814
|
});
|
|
2834
2815
|
pass.end();
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
this.
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
separationRadius: Math.min(2.0, Math.max(1.0, visualEnhancement.separationLines.radius)),
|
|
2854
|
-
separationIntensity: separationEnabled ? Math.min(1.0, Math.max(0.0, visualEnhancement.separationLines.intensity)) : 0.0,
|
|
2855
|
-
enableSeparationLines: separationEnabled,
|
|
2856
|
-
});
|
|
2857
|
-
}
|
|
2858
|
-
// Eye-Dome Lighting — runs AFTER contact/separation so it darkens
|
|
2859
|
-
// every layer uniformly. Cheap (~9 depth taps), only active when
|
|
2860
|
-
// there are point clouds in the scene and the user has enabled it.
|
|
2861
|
-
if (this.edlPass
|
|
2862
|
-
&& this.edlOptions.enabled
|
|
2863
|
-
&& this.pointCloudRenderer?.hasAssets()) {
|
|
2864
|
-
this.edlPass.apply(encoder, {
|
|
2865
|
-
targetView: textureView,
|
|
2866
|
-
depthView: this.pipeline.getDepthOnlyTextureView(),
|
|
2867
|
-
}, {
|
|
2868
|
-
strength: this.edlOptions.strength,
|
|
2869
|
-
radiusPx: this.edlOptions.radiusPx,
|
|
2870
|
-
highQuality: this.edlOptions.highQuality,
|
|
2871
|
-
});
|
|
2872
|
-
}
|
|
2816
|
+
// Created lazily like the sky/shadow passes; each pass inside is too.
|
|
2817
|
+
this.postPasses ??= new PostPassChain(this.device, this.pipeline.getSampleCount());
|
|
2818
|
+
this.postPasses.encode({
|
|
2819
|
+
encoder,
|
|
2820
|
+
targetView: textureView,
|
|
2821
|
+
width: this.canvas.width,
|
|
2822
|
+
height: this.canvas.height,
|
|
2823
|
+
// Depth-only: depth24plus-stencil8 cannot be sampled as texture_depth_* with aspect 'all'.
|
|
2824
|
+
depthView: this.pipeline.getDepthOnlyTextureView(),
|
|
2825
|
+
objectIdView,
|
|
2826
|
+
projection: this.camera.getProjMatrix(),
|
|
2827
|
+
enhancement: visualEnhancement,
|
|
2828
|
+
effectsLive,
|
|
2829
|
+
pixelRatio: this.pixelRatio,
|
|
2830
|
+
// EDL only when point clouds are loaded and the user enabled it.
|
|
2831
|
+
edl: this.edlOptions.enabled && this.pointCloudRenderer?.hasAssets() ? this.edlOptions : null,
|
|
2832
|
+
});
|
|
2833
|
+
colorReadback = colorCapture && encodeRendererColorFrameCapture(device, encoder, colorCapture);
|
|
2873
2834
|
device.queue.submit([encoder.finish()]);
|
|
2835
|
+
if (colorReadback && colorCapture) {
|
|
2836
|
+
settleRendererColorFrameCapture(this, colorCapture, colorReadback);
|
|
2837
|
+
colorCapture = null;
|
|
2838
|
+
colorReadback = null;
|
|
2839
|
+
}
|
|
2874
2840
|
this._lastFrameStats = {
|
|
2875
2841
|
drawCalls: frameDrawCalls,
|
|
2876
2842
|
batchesDrawn: frameBatchesDrawn,
|
|
@@ -2894,6 +2860,7 @@ export class Renderer {
|
|
|
2894
2860
|
}
|
|
2895
2861
|
}
|
|
2896
2862
|
catch (error) {
|
|
2863
|
+
discardRendererColorFrameReadback(colorReadback);
|
|
2897
2864
|
// Balance the validation scope if we threw before popping it above —
|
|
2898
2865
|
// an unpopped scope would capture every later frame's errors silently.
|
|
2899
2866
|
// drainErrorScope logs a pop rejection (device loss) rather than
|
|
@@ -2913,7 +2880,9 @@ export class Renderer {
|
|
|
2913
2880
|
// typed-array views — plus one 5-argument call in
|
|
2914
2881
|
// `point-cloud-uniforms.ts` whose offset and size are compile-time
|
|
2915
2882
|
// constants matching its scratch array — so the spec's
|
|
2916
|
-
// `OperationError` preconditions are unreachable
|
|
2883
|
+
// `OperationError` preconditions are unreachable (the partial-batch
|
|
2884
|
+
// upload's `createStaticGpuBuffer` also pads its whole-view payload
|
|
2885
|
+
// to the 4-byte multiple the spec demands, #5429); the one
|
|
2917
2886
|
// `copyExternalImageToTexture` copies the glyph atlas's own
|
|
2918
2887
|
// never-externally-drawn canvas at its full fixed size, so neither
|
|
2919
2888
|
// `SecurityError` nor a zero-size `OperationError` can arise; and
|
|
@@ -2922,10 +2891,11 @@ export class Renderer {
|
|
|
2922
2891
|
// draws, `finish`, `submit`, `createBindGroup`) reports failure as
|
|
2923
2892
|
// an asynchronous `GPUValidationError` through the error scope, not
|
|
2924
2893
|
// as a throw. The region's real healthy-device failure is
|
|
2925
|
-
// `getOrCreatePartialBatch`'s
|
|
2926
|
-
//
|
|
2894
|
+
// `getOrCreatePartialBatch`'s CPU-side geometry merge running out of
|
|
2895
|
+
// host memory, and that throws a `RangeError` — which is exactly why
|
|
2927
2896
|
// the discriminator keys on the TYPE and not on "a frame threw".
|
|
2928
2897
|
this.containFrameThrow(error, 'encode');
|
|
2898
|
+
cancelRendererColorFrame(this);
|
|
2929
2899
|
}
|
|
2930
2900
|
}
|
|
2931
2901
|
/**
|
|
@@ -3252,30 +3222,9 @@ export class Renderer {
|
|
|
3252
3222
|
}
|
|
3253
3223
|
return this.device.getDevice();
|
|
3254
3224
|
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
* @returns PNG data URL or null if capture failed
|
|
3259
|
-
*/
|
|
3260
|
-
async captureScreenshot() {
|
|
3261
|
-
if (!this.device.isInitialized()) {
|
|
3262
|
-
console.warn('[Renderer] Cannot capture screenshot: not initialized');
|
|
3263
|
-
return null;
|
|
3264
|
-
}
|
|
3265
|
-
try {
|
|
3266
|
-
// Wait for any pending GPU work to complete before capturing
|
|
3267
|
-
// This ensures we capture the fully rendered frame
|
|
3268
|
-
const device = this.device.getDevice();
|
|
3269
|
-
await device.queue.onSubmittedWorkDone();
|
|
3270
|
-
// Capture exactly what's displayed on the canvas
|
|
3271
|
-
const dataUrl = this.canvas.toDataURL('image/png');
|
|
3272
|
-
return dataUrl;
|
|
3273
|
-
}
|
|
3274
|
-
catch (error) {
|
|
3275
|
-
console.error('[Renderer] Screenshot capture failed:', error);
|
|
3276
|
-
return null;
|
|
3277
|
-
}
|
|
3278
|
-
}
|
|
3225
|
+
captureScreenshot() { return captureRendererScreenshot(this.device, this.canvas); }
|
|
3226
|
+
/** Capture the next submitted frame's bounded color pixels, not compositor state. */
|
|
3227
|
+
captureColorFrame() { return requestRendererColorFrame(this, !this.destroyed && !this.deviceLost && this.device.isInitialized(), () => this.requestRender()); }
|
|
3279
3228
|
/**
|
|
3280
3229
|
* Destroy the renderer and release all GPU resources.
|
|
3281
3230
|
*
|
|
@@ -3310,6 +3259,7 @@ export class Renderer {
|
|
|
3310
3259
|
destroy() {
|
|
3311
3260
|
this.initGeneration++;
|
|
3312
3261
|
this.destroyed = true;
|
|
3262
|
+
cancelRendererColorFrame(this);
|
|
3313
3263
|
this.teardown();
|
|
3314
3264
|
this.rejectReadyWaiters(rendererDestroyedError());
|
|
3315
3265
|
}
|
|
@@ -3323,6 +3273,7 @@ export class Renderer {
|
|
|
3323
3273
|
// go back to waiting. Set first: every release below is synchronous, but
|
|
3324
3274
|
// the flag is what a caller holding a live reference actually reads.
|
|
3325
3275
|
this.ready = false;
|
|
3276
|
+
cancelRendererColorFrame(this);
|
|
3326
3277
|
// Scene mesh GPU buffers
|
|
3327
3278
|
if (clearScene) {
|
|
3328
3279
|
this.scene.clear();
|
|
@@ -3339,11 +3290,8 @@ export class Renderer {
|
|
|
3339
3290
|
this.picker?.destroy();
|
|
3340
3291
|
this.picker = null;
|
|
3341
3292
|
this.pickingManager.setPicker(null);
|
|
3342
|
-
|
|
3343
|
-
this.
|
|
3344
|
-
this.postProcessor = null;
|
|
3345
|
-
this.edlPass?.destroy();
|
|
3346
|
-
this.edlPass = null;
|
|
3293
|
+
this.postPasses?.destroy();
|
|
3294
|
+
this.postPasses = null;
|
|
3347
3295
|
this.skyPass?.destroy();
|
|
3348
3296
|
this.skyPass = null;
|
|
3349
3297
|
this.shadowPass?.destroy();
|
|
@@ -3353,6 +3301,7 @@ export class Renderer {
|
|
|
3353
3301
|
this.overlays.destroy();
|
|
3354
3302
|
this.referenceImages.destroy();
|
|
3355
3303
|
// Point cloud GPU resources
|
|
3304
|
+
this.pointCloudNextHandleId = this.pointCloudRenderer?.handleIds?.current() ?? this.pointCloudNextHandleId;
|
|
3356
3305
|
this.pointCloudStreamEpoch++;
|
|
3357
3306
|
this.pointCloudStreamEpochs.clear();
|
|
3358
3307
|
this.pointCloudRenderer?.clear();
|