@ifc-lite/renderer 3.1.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/README.md +22 -0
- 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/appearance-preview.d.ts +2 -0
- package/dist/appearance-preview.d.ts.map +1 -1
- package/dist/appearance-preview.js +4 -0
- package/dist/appearance-preview.js.map +1 -1
- package/dist/camera-animation-immediate.d.ts +5 -0
- package/dist/camera-animation-immediate.d.ts.map +1 -0
- package/dist/camera-animation-immediate.js +16 -0
- package/dist/camera-animation-immediate.js.map +1 -0
- package/dist/camera-animation.d.ts +3 -9
- package/dist/camera-animation.d.ts.map +1 -1
- package/dist/camera-animation.js +14 -9
- package/dist/camera-animation.js.map +1 -1
- 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-recovery.d.ts +60 -0
- package/dist/device-recovery.d.ts.map +1 -0
- package/dist/device-recovery.js +153 -0
- package/dist/device-recovery.js.map +1 -0
- package/dist/device.d.ts +2 -0
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +13 -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 +95 -0
- package/dist/gpu-upload-guard.d.ts.map +1 -0
- package/dist/gpu-upload-guard.js +88 -0
- package/dist/gpu-upload-guard.js.map +1 -0
- package/dist/index.d.ts +71 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +340 -375
- package/dist/index.js.map +1 -1
- package/dist/instanced-override-color.d.ts +8 -0
- package/dist/instanced-override-color.d.ts.map +1 -1
- package/dist/instanced-override-color.js +10 -0
- package/dist/instanced-override-color.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/merged-mesh-extract.d.ts.map +1 -1
- package/dist/merged-mesh-extract.js +5 -0
- package/dist/merged-mesh-extract.js.map +1 -1
- 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 +40 -2
- package/dist/model-translation.d.ts.map +1 -1
- package/dist/model-translation.js +152 -17
- 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 +10 -1
- package/dist/picking-manager.d.ts.map +1 -1
- package/dist/picking-manager.js +57 -21
- 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 +3 -1
- package/dist/reference-images.d.ts.map +1 -1
- package/dist/reference-images.js +15 -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 +15 -3
- package/dist/renderer-overlays.d.ts.map +1 -1
- package/dist/renderer-overlays.js +46 -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 +5 -2
- package/dist/renderer-symbolic-overlays.d.ts.map +1 -1
- package/dist/renderer-symbolic-overlays.js +36 -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-appearance-preview.d.ts +2 -0
- package/dist/scene-appearance-preview.d.ts.map +1 -1
- package/dist/scene-appearance-preview.js +9 -0
- package/dist/scene-appearance-preview.js.map +1 -1
- package/dist/scene-authored-owner.d.ts +10 -0
- package/dist/scene-authored-owner.d.ts.map +1 -1
- package/dist/scene-authored-owner.js +44 -5
- package/dist/scene-authored-owner.js.map +1 -1
- package/dist/scene-batch-rebuild.d.ts +18 -0
- package/dist/scene-batch-rebuild.d.ts.map +1 -0
- package/dist/scene-batch-rebuild.js +49 -0
- package/dist/scene-batch-rebuild.js.map +1 -0
- package/dist/scene-batch-upload.d.ts +4 -1
- package/dist/scene-batch-upload.d.ts.map +1 -1
- package/dist/scene-batch-upload.js +31 -38
- package/dist/scene-batch-upload.js.map +1 -1
- package/dist/scene-bucket-routing.d.ts +21 -0
- package/dist/scene-bucket-routing.d.ts.map +1 -0
- package/dist/scene-bucket-routing.js +44 -0
- package/dist/scene-bucket-routing.js.map +1 -0
- package/dist/scene-contents.d.ts +8 -0
- package/dist/scene-contents.d.ts.map +1 -1
- package/dist/scene-derived-mesh-provenance.d.ts +13 -0
- package/dist/scene-derived-mesh-provenance.d.ts.map +1 -0
- package/dist/scene-derived-mesh-provenance.js +30 -0
- package/dist/scene-derived-mesh-provenance.js.map +1 -0
- package/dist/scene-device-recovery.d.ts +103 -0
- package/dist/scene-device-recovery.d.ts.map +1 -0
- package/dist/scene-device-recovery.js +295 -0
- package/dist/scene-device-recovery.js.map +1 -0
- 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-geometry.d.ts +1 -0
- package/dist/scene-geometry.d.ts.map +1 -1
- package/dist/scene-geometry.js +3 -23
- package/dist/scene-geometry.js.map +1 -1
- 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 +13 -1
- package/dist/scene-model-translation.d.ts.map +1 -1
- package/dist/scene-model-translation.js +23 -1
- package/dist/scene-model-translation.js.map +1 -1
- package/dist/scene-precision.d.ts +16 -0
- package/dist/scene-precision.d.ts.map +1 -0
- package/dist/scene-precision.js +73 -0
- package/dist/scene-precision.js.map +1 -0
- 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 +42 -39
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +258 -375
- 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/scene.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
import { materializeInstances } from './scene-instance-materialization.js';
|
|
5
5
|
import { InstanceSuppression } from './scene-instance-suppression.js';
|
|
6
6
|
import { createSceneBatch } from './scene-batch-upload.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createStaticGpuBuffer } from './gpu-static-upload.js';
|
|
8
|
+
import { createSceneAppearancePreview, rebindSceneAppearanceAccess } from './scene-appearance-preview.js';
|
|
8
9
|
import { AppearanceBuckets } from './scene-appearance-buckets.js';
|
|
9
|
-
import { prepareSceneAuthoredOwner } from './scene-authored-owner.js';
|
|
10
|
+
import { AuthoredPreparationRegistry, prepareSceneAuthoredOwner } from './scene-authored-owner.js';
|
|
10
11
|
import { interleaveTexturedVertices } from './textured-vertices.js';
|
|
11
12
|
import { RgbaTexturePool } from './rgba-texture-pool.js';
|
|
12
13
|
import { splitMeshForStreaming } from './scene-stream-split.js';
|
|
@@ -14,9 +15,10 @@ import { hostsOtherEntities } from './mesh-entity-hosting.js';
|
|
|
14
15
|
import { BATCH_CONSTANTS } from './constants.js';
|
|
15
16
|
import { prepareRayDirInv, raycastBoundingBoxes, raycastTriangles, rayIntersectsBox, } from './scene-raycaster.js';
|
|
16
17
|
import { selectBoundingBoxesInRect } from './scene-rect-select.js';
|
|
17
|
-
import { splitMeshDataForBufferLimit, cachedWorldAabb, worldAabbFromPieces, destroyGpuResources } from './scene-geometry.js';
|
|
18
|
+
import { splitMeshDataForBufferLimit, cachedWorldAabb, worldAabbFromPieces, destroyGpuResources, topologySafeBatchOrigin } from './scene-geometry.js';
|
|
19
|
+
import { resolvePrecisionBucket } from './scene-bucket-routing.js';
|
|
18
20
|
import { sumResidentGpuBytes } from './render-stats.js';
|
|
19
|
-
import { composeInstancedOverrideColor } from './instanced-override-color.js';
|
|
21
|
+
import { composeInstancedOverrideColor, writeOriginalInstancedColors } from './instanced-override-color.js';
|
|
20
22
|
import { bucketBaseKeyFor } from './chunk-grid.js';
|
|
21
23
|
import { inheritedQuantization, groupOverridePieces, cloneOverrides, overlaysInvalidatedBy } from './scene-derived-batches.js';
|
|
22
24
|
import { VisibilityEpochTracker } from './visibility-epoch.js';
|
|
@@ -24,11 +26,16 @@ import { isEntityVisible } from './entity-visibility.js';
|
|
|
24
26
|
import { planInstancedGhosting } from './instanced-ghost-plan.js';
|
|
25
27
|
import { selectEvictions } from './residency.js';
|
|
26
28
|
import { OPAQUE_ALPHA_CUTOFF } from './overlay-routing.js';
|
|
27
|
-
import { translateSceneModel, releaseInstanceVertices, refreshTexturedBounds } from './scene-model-translation.js';
|
|
29
|
+
import { translateSceneModel, rotateSceneModelInstances, releaseInstanceVertices, refreshTexturedBounds } from './scene-model-translation.js';
|
|
28
30
|
import { ModelTranslations } from './model-translation.js';
|
|
29
|
-
import {
|
|
31
|
+
import { unionInstancedWorldAabb as unionInstanceBounds } from './scene-instance-bounds.js';
|
|
32
|
+
import { DerivedMeshProvenance } from './scene-derived-mesh-provenance.js';
|
|
33
|
+
import { rebuildSceneBatches } from './scene-batch-rebuild.js';
|
|
34
|
+
import { regroupStreamedBuckets } from './scene-finalize-regroup.js';
|
|
30
35
|
import { dropAllPartialCaches as dropAllPartialCachesIn, dropPartialCacheForBatch as dropPartialCacheForBatchIn, retireUnusedAlphaSlots as retireUnusedAlphaSlotsIn, } from './partial-batch-cache.js';
|
|
31
36
|
import { prepareInstancedRender, foldOccurrenceWorldBox, INSTANCE_STRIDE_BYTES, INSTANCE_COLOR_OFFSET, INSTANCE_FLAGS_OFFSET, INSTANCE_FLAG_SELECTED, INSTANCE_FLAG_HIDDEN, } from './instanced-render.js';
|
|
37
|
+
import { translateInstanceRecord } from './scene-instance-translation.js';
|
|
38
|
+
import { discardSceneGpuResourcesForRecovery, prepareSceneDeviceRecovery, repartitionHydratedRecoveryBucket, restoreSceneGpuResourcesAfterRecovery } from './scene-device-recovery.js';
|
|
32
39
|
const EMPTY_INSTANCED_TEMPLATES = [];
|
|
33
40
|
/**
|
|
34
41
|
* Pure helper: compute the exclusive end index of the next flushPending()
|
|
@@ -84,6 +91,7 @@ export class Scene {
|
|
|
84
91
|
batchedMeshes = []; // flat render array (rebuilt from buckets)
|
|
85
92
|
buckets = new Map(); // bucketKey -> consolidated bucket state
|
|
86
93
|
meshDataBucket = new Map(); // reverse lookup: MeshData -> owning bucket
|
|
94
|
+
derivedMeshProvenance = new DerivedMeshProvenance();
|
|
87
95
|
modelTranslations = new ModelTranslations();
|
|
88
96
|
meshDataMap = new Map(); // Map expressId -> MeshData[] (for lazy buffer creation, accumulates multiple pieces)
|
|
89
97
|
boundingBoxes = new Map(); // Map expressId -> bounding box (computed lazily)
|
|
@@ -95,7 +103,9 @@ export class Scene {
|
|
|
95
103
|
rgbaTexturePool = new RgbaTexturePool();
|
|
96
104
|
appearanceController;
|
|
97
105
|
appearanceBuckets;
|
|
106
|
+
appearanceAccessState;
|
|
98
107
|
authoredGeneration = 0;
|
|
108
|
+
authoredPreparations = new AuthoredPreparationRegistry();
|
|
99
109
|
appearanceAccess(device, pipeline) {
|
|
100
110
|
return {
|
|
101
111
|
meshes: () => this.texturedMeshes, data: this.meshDataMap,
|
|
@@ -135,24 +145,23 @@ export class Scene {
|
|
|
135
145
|
},
|
|
136
146
|
};
|
|
137
147
|
}
|
|
148
|
+
bindAppearanceAccess(device, pipeline) { return this.appearanceAccessState = rebindSceneAppearanceAccess(this.appearanceAccessState, this.appearanceAccess(device, pipeline)); }
|
|
138
149
|
sharedAppearanceBuckets(access) {
|
|
139
150
|
return this.appearanceBuckets ??= new AppearanceBuckets(access.buckets, id => this.meshDataMap.get(id));
|
|
140
151
|
}
|
|
141
152
|
appearancePreview(device, pipeline) {
|
|
142
|
-
const access = this.
|
|
153
|
+
const access = this.bindAppearanceAccess(device, pipeline);
|
|
143
154
|
return this.appearanceController ??= createSceneAppearancePreview(access, this.sharedAppearanceBuckets(access));
|
|
144
155
|
}
|
|
145
|
-
/** Place canonical native appearance source geometry in this model's live frame. */
|
|
146
156
|
placeAppearanceSource(mesh) { return this.modelTranslations.placeMesh(mesh); }
|
|
147
|
-
/** Retain model-local source coordinates when publishing a placed appearance mesh. */
|
|
148
157
|
appearanceSourceMesh(mesh) { return this.modelTranslations.sourceFromPlaced(mesh); }
|
|
149
158
|
/** Stage one new IFC owner with all its coloured or textured geometry parts. */
|
|
150
159
|
prepareAuthoredOwner(parts, device, pipeline) {
|
|
151
|
-
const access = this.
|
|
152
|
-
return prepareSceneAuthoredOwner(access, this.sharedAppearanceBuckets(access), parts, () => {
|
|
160
|
+
const access = this.bindAppearanceAccess(device, pipeline), generation = this.authoredGeneration;
|
|
161
|
+
return this.authoredPreparations.track(prepareSceneAuthoredOwner(access, this.sharedAppearanceBuckets(access), parts, () => {
|
|
153
162
|
if (generation !== this.authoredGeneration)
|
|
154
163
|
throw new Error('The scene changed while preparing the object.');
|
|
155
|
-
});
|
|
164
|
+
}));
|
|
156
165
|
}
|
|
157
166
|
/** Compatibility entry point for an image-backed single-part owner. */
|
|
158
167
|
prepareTexturedOwner(mesh, device, pipeline) {
|
|
@@ -295,18 +304,20 @@ export class Scene {
|
|
|
295
304
|
// Temporary fragment batches created during streaming for immediate rendering.
|
|
296
305
|
// Destroyed and replaced by proper merged batches in finalizeStreaming().
|
|
297
306
|
streamingFragments = [];
|
|
307
|
+
// Buckets that received streamed meshes since the last finalize — the only
|
|
308
|
+
// ones a finalize re-groups and rebuilds (#5358).
|
|
309
|
+
streamedBucketKeys = new Set();
|
|
298
310
|
// ─── Mesh command queue ────────────────────────────────────────────
|
|
299
311
|
// Decouples React state updates from GPU work. Callers push meshes
|
|
300
312
|
// via queueMeshes() (instant, no GPU), and the animation loop drains
|
|
301
313
|
// the queue via flushPending() with a per-frame time budget.
|
|
302
314
|
meshQueue = [];
|
|
303
315
|
meshQueueReadIndex = 0;
|
|
304
|
-
// ─── GPU-resident mode ──────────────────────────────────────────────
|
|
305
|
-
// After releaseGeometryData(), JS-side typed arrays are freed.
|
|
306
|
-
// Only lightweight metadata is retained for operations that don't need
|
|
307
|
-
// raw vertex data (bounding boxes, color key lookups, expressId sets).
|
|
308
316
|
geometryReleased = false;
|
|
309
317
|
ephemeralStreamingMode = false;
|
|
318
|
+
prepareDeviceRecovery() { return prepareSceneDeviceRecovery(this); }
|
|
319
|
+
discardGpuResourcesForRecovery() { discardSceneGpuResourcesForRecovery(this); }
|
|
320
|
+
restoreGpuResourcesAfterRecovery(device, pipeline) { restoreSceneGpuResourcesAfterRecovery(this, device, pipeline); }
|
|
310
321
|
/**
|
|
311
322
|
* Add mesh to scene
|
|
312
323
|
*/
|
|
@@ -329,8 +340,10 @@ export class Scene {
|
|
|
329
340
|
/** The shared local-frame origin all batches relativize against (null until
|
|
330
341
|
* the first batch is built). Per-mesh highlight/picker VBOs replicate the
|
|
331
342
|
* batch's exact f32 path against this so they render bit-coincident. */
|
|
332
|
-
getSharedFrameOrigin(modelIndex = 0) {
|
|
333
|
-
|
|
343
|
+
getSharedFrameOrigin(modelIndex = 0, meshData) {
|
|
344
|
+
const placed = meshData ? this.derivedMeshProvenance.placedSourceFor(meshData, this.modelTranslations) : undefined;
|
|
345
|
+
const bucket = placed ? this.meshDataBucket.get(placed) : undefined;
|
|
346
|
+
return bucket?.batchedMesh?.origin ?? bucket?.frameOrigin ?? this.modelTranslations.frameOrigin(this.sharedFrameOrigins.get(modelIndex) ?? null, modelIndex) ?? null;
|
|
334
347
|
}
|
|
335
348
|
/**
|
|
336
349
|
* Enable/disable spatial chunk bucketing (issue #1682 phase 2). When set,
|
|
@@ -428,6 +441,7 @@ export class Scene {
|
|
|
428
441
|
}
|
|
429
442
|
const rebuilt = this.createBatchedMesh(bucket.meshData, bucket.meshData[0].color, device, pipeline, key);
|
|
430
443
|
bucket.batchedMesh = rebuilt;
|
|
444
|
+
bucket.frameOrigin = rebuilt.origin;
|
|
431
445
|
const idx = this.batchedMeshes.indexOf(old);
|
|
432
446
|
if (idx >= 0)
|
|
433
447
|
this.batchedMeshes[idx] = rebuilt;
|
|
@@ -474,7 +488,8 @@ export class Scene {
|
|
|
474
488
|
* lattice snap in createMeshFromData (an f32 batch must NOT snap). Same rule
|
|
475
489
|
* as overlay/partial batches; global flag while unbucketed. */
|
|
476
490
|
isMeshQuantized(meshData) {
|
|
477
|
-
|
|
491
|
+
const source = this.derivedMeshProvenance.placedSourceFor(meshData, this.modelTranslations);
|
|
492
|
+
return inheritedQuantization(this.quantizedBatchesEnabled, this.meshDataBucket.get(source)?.batchedMesh) !== 'off';
|
|
478
493
|
}
|
|
479
494
|
/** Set (or clear) the HOST budget in bytes for bucket CPU geometry. */
|
|
480
495
|
setHostResidencyBudget(bytes) {
|
|
@@ -629,10 +644,10 @@ export class Scene {
|
|
|
629
644
|
this.meshDataBucket.set(m, bucket);
|
|
630
645
|
this.addMeshData(m);
|
|
631
646
|
}
|
|
632
|
-
this.coldBuckets.delete(key);
|
|
633
647
|
if (members.length > 0) {
|
|
634
|
-
|
|
635
|
-
this
|
|
648
|
+
this.coldBuckets.delete(key);
|
|
649
|
+
for (const restoredKey of repartitionHydratedRecoveryBucket(this, key, bucket, shell))
|
|
650
|
+
this.residencyRestoreQueue.add(restoredKey);
|
|
636
651
|
}
|
|
637
652
|
else {
|
|
638
653
|
console.warn(`[Scene] cold restore for ${key} found no members — bucket stays a shell`);
|
|
@@ -664,6 +679,7 @@ export class Scene {
|
|
|
664
679
|
continue;
|
|
665
680
|
const rebuilt = this.createBatchedMesh(bucket.meshData, bucket.meshData[0].color, device, pipeline, bucket.key);
|
|
666
681
|
bucket.batchedMesh = rebuilt;
|
|
682
|
+
bucket.frameOrigin = rebuilt.origin;
|
|
667
683
|
const idx = this.batchedMeshes.indexOf(old);
|
|
668
684
|
if (idx >= 0)
|
|
669
685
|
this.batchedMeshes[idx] = rebuilt;
|
|
@@ -863,7 +879,7 @@ export class Scene {
|
|
|
863
879
|
// this expressId so selection highlighting is per-entity, not the
|
|
864
880
|
// entire merged batch.
|
|
865
881
|
if (single.entityIds) {
|
|
866
|
-
return
|
|
882
|
+
return this.derivedMeshProvenance.extract(single, expressId);
|
|
867
883
|
}
|
|
868
884
|
return single;
|
|
869
885
|
}
|
|
@@ -873,7 +889,7 @@ export class Scene {
|
|
|
873
889
|
const extracted = [];
|
|
874
890
|
for (const piece of pieces) {
|
|
875
891
|
if (piece.entityIds) {
|
|
876
|
-
const ex =
|
|
892
|
+
const ex = this.derivedMeshProvenance.extract(piece, expressId);
|
|
877
893
|
if (ex)
|
|
878
894
|
extracted.push(ex);
|
|
879
895
|
}
|
|
@@ -888,6 +904,18 @@ export class Scene {
|
|
|
888
904
|
pieces = extracted;
|
|
889
905
|
// Fall through to the normal multi-piece merge below
|
|
890
906
|
}
|
|
907
|
+
// A cross-bucket singular result keeps the historical representative merge
|
|
908
|
+
// (all triangles, no provenance); precise callers use the pieces accessor.
|
|
909
|
+
const firstOrigin = pieces[0].origin;
|
|
910
|
+
const firstSource = this.derivedMeshProvenance.placedSourceFor(pieces[0], this.modelTranslations);
|
|
911
|
+
const firstBucket = this.meshDataBucket.get(firstSource);
|
|
912
|
+
const sameBucket = pieces.every(piece => this.meshDataBucket.get(this.derivedMeshProvenance.placedSourceFor(piece, this.modelTranslations)) === firstBucket);
|
|
913
|
+
const sameFrame = pieces.every(piece => piece.origin?.[0] === firstOrigin?.[0]
|
|
914
|
+
&& piece.origin?.[1] === firstOrigin?.[1] && piece.origin?.[2] === firstOrigin?.[2]);
|
|
915
|
+
const precisionPlaceable = sameBucket || (!firstBucket && sameFrame);
|
|
916
|
+
const mergedOrigin = precisionPlaceable
|
|
917
|
+
? firstBucket?.batchedMesh?.origin ?? firstBucket?.frameOrigin ?? firstOrigin ?? [0, 0, 0]
|
|
918
|
+
: undefined;
|
|
891
919
|
// Check if all pieces have the same color (within tolerance)
|
|
892
920
|
// This handles multi-material elements like windows (frame vs glass)
|
|
893
921
|
const firstColor = pieces[0].color;
|
|
@@ -921,8 +949,17 @@ export class Scene {
|
|
|
921
949
|
let idxOffset = 0;
|
|
922
950
|
let vertexOffset = 0;
|
|
923
951
|
for (const piece of pieces) {
|
|
924
|
-
|
|
925
|
-
|
|
952
|
+
if (mergedOrigin) {
|
|
953
|
+
const ox = (piece.origin?.[0] ?? 0) - mergedOrigin[0], oy = (piece.origin?.[1] ?? 0) - mergedOrigin[1], oz = (piece.origin?.[2] ?? 0) - mergedOrigin[2];
|
|
954
|
+
for (let i = 0; i < piece.positions.length; i += 3) {
|
|
955
|
+
mergedPositions[posOffset + i] = piece.positions[i] + ox;
|
|
956
|
+
mergedPositions[posOffset + i + 1] = piece.positions[i + 1] + oy;
|
|
957
|
+
mergedPositions[posOffset + i + 2] = piece.positions[i + 2] + oz;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
mergedPositions.set(piece.positions, posOffset);
|
|
962
|
+
}
|
|
926
963
|
mergedNormals.set(piece.normals, posOffset);
|
|
927
964
|
// Copy indices with offset
|
|
928
965
|
for (let i = 0; i < piece.indices.length; i++) {
|
|
@@ -933,7 +970,7 @@ export class Scene {
|
|
|
933
970
|
vertexOffset += piece.positions.length / 3;
|
|
934
971
|
}
|
|
935
972
|
// Return merged MeshData (all pieces have same color)
|
|
936
|
-
|
|
973
|
+
const merged = {
|
|
937
974
|
expressId,
|
|
938
975
|
modelIndex: pieces[0].modelIndex, // Preserve modelIndex for multi-model support
|
|
939
976
|
positions: mergedPositions,
|
|
@@ -941,7 +978,15 @@ export class Scene {
|
|
|
941
978
|
indices: mergedIndices,
|
|
942
979
|
color: firstColor,
|
|
943
980
|
ifcType: pieces[0].ifcType,
|
|
981
|
+
...(mergedOrigin ? { origin: mergedOrigin } : {}),
|
|
944
982
|
};
|
|
983
|
+
// The common frame is explicit above. Keep a source only when every part
|
|
984
|
+
// belongs to the same live bucket; an arbitrary source would give a merged
|
|
985
|
+
// result the wrong quantization/frame after a re-batch.
|
|
986
|
+
if (firstBucket && precisionPlaceable) {
|
|
987
|
+
this.derivedMeshProvenance.remember(merged, firstSource);
|
|
988
|
+
}
|
|
989
|
+
return merged;
|
|
945
990
|
}
|
|
946
991
|
/**
|
|
947
992
|
* Check if MeshData exists for an expressId
|
|
@@ -1013,7 +1058,7 @@ export class Scene {
|
|
|
1013
1058
|
const extracted = [];
|
|
1014
1059
|
for (const piece of pieces) {
|
|
1015
1060
|
if (piece.entityIds) {
|
|
1016
|
-
const ex =
|
|
1061
|
+
const ex = this.derivedMeshProvenance.extract(piece, expressId);
|
|
1017
1062
|
if (ex)
|
|
1018
1063
|
extracted.push(ex);
|
|
1019
1064
|
}
|
|
@@ -1046,13 +1091,23 @@ export class Scene {
|
|
|
1046
1091
|
* STREAMING OPTIMIZATION: During streaming, creates lightweight "fragment"
|
|
1047
1092
|
* batches from ONLY the new meshes instead of re-merging all accumulated
|
|
1048
1093
|
* data. This reduces streaming from O(N²) to O(N). Call finalizeStreaming()
|
|
1049
|
-
* when streaming completes to
|
|
1094
|
+
* when streaming completes to merge the streamed buckets (only those, #5358).
|
|
1050
1095
|
*/
|
|
1051
1096
|
appendToBatches(meshDataArray, device, pipeline, isStreaming = false) {
|
|
1097
|
+
meshDataArray = meshDataArray.map((mesh) => this.modelTranslations.placeMesh(mesh));
|
|
1098
|
+
// Validate every input before cancelling appearance work, publishing a
|
|
1099
|
+
// bucket, or touching GPU state. A single impossible f32 frame must leave
|
|
1100
|
+
// an already-valid scene usable and allow the next safe append.
|
|
1101
|
+
for (const meshData of meshDataArray) {
|
|
1102
|
+
const modelIndex = meshData.modelIndex ?? 0;
|
|
1103
|
+
const shared = this.modelTranslations.frameOrigin(this.sharedFrameOrigins.get(modelIndex) ?? null, modelIndex);
|
|
1104
|
+
if (!topologySafeBatchOrigin([meshData], undefined, shared)) {
|
|
1105
|
+
throw new Error('Unable to resolve a topology-safe GPU frame for mesh geometry.');
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1052
1108
|
if (this.appearanceController)
|
|
1053
1109
|
for (const part of meshDataArray)
|
|
1054
1110
|
this.appearanceController.cancelFor(part.expressId);
|
|
1055
|
-
meshDataArray = meshDataArray.map((mesh) => this.modelTranslations.placeMesh(mesh));
|
|
1056
1111
|
// Cache max buffer size on first call
|
|
1057
1112
|
if (this.cachedMaxBufferSize === 0) {
|
|
1058
1113
|
this.cachedMaxBufferSize = this.getMaxBufferSize(device);
|
|
@@ -1081,6 +1136,9 @@ export class Scene {
|
|
|
1081
1136
|
for (const meshData of renderable) {
|
|
1082
1137
|
const baseKey = this.bucketBaseKey(meshData);
|
|
1083
1138
|
const bucketKey = this.resolveActiveBucket(baseKey, meshData);
|
|
1139
|
+
const resolvedBucket = this.buckets.get(bucketKey);
|
|
1140
|
+
if (resolvedBucket)
|
|
1141
|
+
this.meshDataBucket.set(meshData, resolvedBucket);
|
|
1084
1142
|
if (retainStreamingGeometry || !isStreaming) {
|
|
1085
1143
|
// Accumulate mesh data in the bucket when we need later rebatching or
|
|
1086
1144
|
// CPU-side lookups. Huge-file mode intentionally skips this to keep JS
|
|
@@ -1095,16 +1153,17 @@ export class Scene {
|
|
|
1095
1153
|
this.meshDataBucket.set(meshData, bucket);
|
|
1096
1154
|
// Also store individual mesh data for visibility filtering
|
|
1097
1155
|
this.addMeshData(meshData);
|
|
1098
|
-
//
|
|
1099
|
-
if (
|
|
1156
|
+
// Non-streaming rebuilds now; streamed buckets wait for finalize.
|
|
1157
|
+
if (isStreaming)
|
|
1158
|
+
this.streamedBucketKeys.add(bucketKey);
|
|
1159
|
+
else
|
|
1100
1160
|
this.pendingBatchKeys.add(bucketKey);
|
|
1101
|
-
}
|
|
1102
1161
|
}
|
|
1103
1162
|
}
|
|
1104
1163
|
if (isStreaming) {
|
|
1105
1164
|
// STREAMING: Create small fragment batches from ONLY the new meshes.
|
|
1106
1165
|
// Avoids the O(N²) cost of re-merging all accumulated data every batch.
|
|
1107
|
-
// finalizeStreaming() destroys fragments and
|
|
1166
|
+
// finalizeStreaming() destroys fragments and merges the streamed buckets.
|
|
1108
1167
|
// `renderable` excludes textured meshes (drawn via the textured pipeline).
|
|
1109
1168
|
this.createStreamingFragments(renderable, device, pipeline);
|
|
1110
1169
|
return;
|
|
@@ -1112,48 +1171,15 @@ export class Scene {
|
|
|
1112
1171
|
// NON-STREAMING: Rebuild full batches immediately
|
|
1113
1172
|
this.rebuildPendingBatches(device, pipeline);
|
|
1114
1173
|
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Rebuild all pending batches (call this after streaming completes)
|
|
1117
|
-
*
|
|
1118
|
-
* Each bucket key already maps to data that fits within the GPU buffer
|
|
1119
|
-
* limit (enforced at accumulation time by resolveActiveBucket), so no
|
|
1120
|
-
* splitting is needed here — just create one batch per key.
|
|
1121
|
-
*/
|
|
1174
|
+
/** Rebuild pending buckets after streaming or a geometry mutation. */
|
|
1122
1175
|
rebuildPendingBatches(device, pipeline) {
|
|
1123
1176
|
if (this.pendingBatchKeys.size === 0)
|
|
1124
1177
|
return;
|
|
1125
|
-
const rebuilt =
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
if (bucket?.batchedMesh) {
|
|
1131
|
-
// Slot keys embed the batch id and the replacement gets a fresh one, so
|
|
1132
|
-
// drop this batch's cached sub-batch clones first or they are stranded
|
|
1133
|
-
// with live GPU buffers. Every other batch-destroying path already
|
|
1134
|
-
// clears the cache (eviction per batch; finalize/release/clear wholesale).
|
|
1135
|
-
this.dropPartialCacheForBatch(bucket.batchedMesh);
|
|
1136
|
-
destroyGpuResources(bucket.batchedMesh);
|
|
1137
|
-
bucket.batchedMesh = null;
|
|
1138
|
-
}
|
|
1139
|
-
if (!bucket || bucket.meshData.length === 0) {
|
|
1140
|
-
// Bucket is empty — clean up
|
|
1141
|
-
this.buckets.delete(key);
|
|
1142
|
-
continue;
|
|
1143
|
-
}
|
|
1144
|
-
// Create new batch with all accumulated meshes for this bucket
|
|
1145
|
-
const color = bucket.meshData[0].color;
|
|
1146
|
-
const batchedMesh = this.createBatchedMesh(bucket.meshData, color, device, pipeline, key);
|
|
1147
|
-
bucket.batchedMesh = batchedMesh;
|
|
1148
|
-
rebuilt.push({ bucket, previousQuantized });
|
|
1149
|
-
}
|
|
1150
|
-
// Rebuild the flat render array from all buckets (148 max batches — not perf critical)
|
|
1151
|
-
this.batchedMeshes = [];
|
|
1152
|
-
for (const bucket of this.buckets.values()) {
|
|
1153
|
-
if (bucket.batchedMesh) {
|
|
1154
|
-
this.batchedMeshes.push(bucket.batchedMesh);
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1178
|
+
const rebuilt = rebuildSceneBatches({ pendingKeys: this.pendingBatchKeys, buckets: this.buckets,
|
|
1179
|
+
create: (meshes, color, target, renderPipeline, key) => this.createBatchedMesh(meshes, color, target, renderPipeline, key),
|
|
1180
|
+
dropPartial: batch => this.dropPartialCacheForBatch(batch),
|
|
1181
|
+
}, device, pipeline);
|
|
1182
|
+
this.batchedMeshes = [...this.buckets.values()].flatMap(bucket => bucket.batchedMesh ? [bucket.batchedMesh] : []);
|
|
1157
1183
|
this.pendingBatchKeys.clear();
|
|
1158
1184
|
// Overlays follow their depth writers only when one actually changed under
|
|
1159
1185
|
// them (flip / bucket move, #4832); finalize re-applies once itself.
|
|
@@ -1486,22 +1512,17 @@ export class Scene {
|
|
|
1486
1512
|
const cpu = this.instancedTemplateCpu[occ.templateIndex];
|
|
1487
1513
|
if (!cpu)
|
|
1488
1514
|
continue;
|
|
1489
|
-
// Column-major mat4: the translation column is floats 12,13,14, i.e. bytes
|
|
1490
|
-
// +48/+52/+56 of the occurrence's record (matches unionInstancedWorldAabb).
|
|
1491
|
-
const dv = new DataView(cpu.instanceData);
|
|
1492
1515
|
const b = occ.byteOffset;
|
|
1493
|
-
const
|
|
1494
|
-
const ty = dv.getFloat32(b + 52, true) + dy;
|
|
1495
|
-
const tz = dv.getFloat32(b + 56, true) + dz;
|
|
1496
|
-
dv.setFloat32(b + 48, tx, true);
|
|
1497
|
-
dv.setFloat32(b + 52, ty, true);
|
|
1498
|
-
dv.setFloat32(b + 56, tz, true);
|
|
1516
|
+
const translated = translateInstanceRecord(cpu, b, [dx, dy, dz]);
|
|
1499
1517
|
// Push only the 12 translation bytes to the GPU buffer (in place). Guarded
|
|
1500
1518
|
// on the cached device so CPU-only tests still exercise the matrix math.
|
|
1501
1519
|
if (device) {
|
|
1502
1520
|
const gpu = this.instancedTemplates[occ.templateIndex]?.instanceBuffer;
|
|
1503
|
-
if (gpu)
|
|
1504
|
-
device.queue.writeBuffer(gpu, b + 48,
|
|
1521
|
+
if (gpu) {
|
|
1522
|
+
device.queue.writeBuffer(gpu, b + 48, translated.translation);
|
|
1523
|
+
if (translated.legacyAnchors)
|
|
1524
|
+
device.queue.writeBuffer(gpu, b + 88, translated.legacyAnchors);
|
|
1525
|
+
}
|
|
1505
1526
|
}
|
|
1506
1527
|
moved = true;
|
|
1507
1528
|
}
|
|
@@ -1559,21 +1580,40 @@ export class Scene {
|
|
|
1559
1580
|
}
|
|
1560
1581
|
this.meshes = kept;
|
|
1561
1582
|
}
|
|
1562
|
-
/**
|
|
1563
|
-
|
|
1564
|
-
|
|
1583
|
+
/** Shared `TranslationScene` view for `setModelTranslation`/`setModelRotation`
|
|
1584
|
+
* (#4890) — the placement helpers in scene-model-translation.ts read scene
|
|
1585
|
+
* state through this narrow interface instead of the full `Scene`. */
|
|
1586
|
+
translationScope() {
|
|
1565
1587
|
const batches = this.finalizeInProgress ? [...new Set([...this.batchedMeshes,
|
|
1566
1588
|
...[...this.buckets.values()].flatMap((bucket) => bucket.batchedMesh ? [bucket.batchedMesh] : [])])] : this.batchedMeshes;
|
|
1567
|
-
|
|
1589
|
+
return { translations: this.modelTranslations, pieces: this.meshDataMap,
|
|
1568
1590
|
bounds: this.boundingBoxes, batches, meshes: this.meshes, overrides: this.overrideBatches, textured: this.texturedMeshes,
|
|
1569
1591
|
templates: this.instancedTemplates, cpu: this.instancedTemplateCpu, occurrences: this.instancedEntityMap,
|
|
1570
1592
|
device: this.instancedDevice, evictHighlight: (id) => this.evictHighlightMeshes(id, true),
|
|
1571
1593
|
clearPartial: () => this.dropAllPartialCaches(),
|
|
1572
|
-
unionBounds: (id, view, offset, min, max) => this.unionInstancedWorldAabb(id, view, offset, ...min, ...max)
|
|
1573
|
-
|
|
1594
|
+
unionBounds: (id, view, offset, min, max) => this.unionInstancedWorldAabb(id, view, offset, ...min, ...max) };
|
|
1595
|
+
}
|
|
1596
|
+
dropOrphanedSuppressedBounds() {
|
|
1574
1597
|
for (const id of this.instanceSuppression.suppressedIds())
|
|
1575
1598
|
if (!this.meshDataMap.has(id))
|
|
1576
1599
|
this.boundingBoxes.delete(id);
|
|
1600
|
+
}
|
|
1601
|
+
/** Absolute workspace translation, renderer Y-up metres (#4226). */
|
|
1602
|
+
getModelTranslation(modelIndex) { return this.modelTranslations.get(modelIndex); }
|
|
1603
|
+
setModelTranslation(modelIndex, translation) {
|
|
1604
|
+
const changed = translateSceneModel(this.translationScope(), modelIndex, translation);
|
|
1605
|
+
this.dropOrphanedSuppressedBounds();
|
|
1606
|
+
return changed;
|
|
1607
|
+
}
|
|
1608
|
+
/** Absolute workspace yaw, renderer Y-up radians about the render-frame
|
|
1609
|
+
* pivot `(pivot[0], *, pivot[2])` — the GPU-instanced half of a whole-model
|
|
1610
|
+
* rotation (#4890); `angle === 0` clears it. Flat/authored/batched geometry
|
|
1611
|
+
* is rotated by the viewer's bake, not here. */
|
|
1612
|
+
getModelRotation(modelIndex) { return this.modelTranslations.getYaw(modelIndex); }
|
|
1613
|
+
setModelRotation(modelIndex, angle, pivot) {
|
|
1614
|
+
const yaw = angle === 0 ? null : { angle, px: pivot[0], pz: pivot[2] };
|
|
1615
|
+
const changed = rotateSceneModelInstances(this.translationScope(), modelIndex, yaw);
|
|
1616
|
+
this.dropOrphanedSuppressedBounds();
|
|
1577
1617
|
return changed;
|
|
1578
1618
|
}
|
|
1579
1619
|
/** Bulk variant of `translateMeshesForEntity`. */
|
|
@@ -1736,7 +1776,7 @@ export class Scene {
|
|
|
1736
1776
|
// yielding the frame back at the TOP of the loop. The mesh-count-only chunker
|
|
1737
1777
|
// could merge a 512-mesh chunk of high-poly meshes (e.g. 899 Velux roof
|
|
1738
1778
|
// windows at 7624 tris each → ~3.9M tris) in ONE indivisible mergeGeometry +
|
|
1739
|
-
//
|
|
1779
|
+
// GPU buffer upload copy — hundreds of ms that parked the main thread
|
|
1740
1780
|
// past the 16s stream watchdog. Capping each appendToBatches by index volume
|
|
1741
1781
|
// keeps every synchronous slice ≈ one bounded fragment merge (~12-15ms).
|
|
1742
1782
|
const MAX_MESHES_PER_FLUSH = 4096;
|
|
@@ -1801,7 +1841,7 @@ export class Scene {
|
|
|
1801
1841
|
// applies to cells exactly like it does to buckets.
|
|
1802
1842
|
const colorGroups = new Map();
|
|
1803
1843
|
for (const meshData of meshDataArray) {
|
|
1804
|
-
const key = this.bucketBaseKey(meshData);
|
|
1844
|
+
const key = this.meshDataBucket.get(meshData)?.key ?? this.bucketBaseKey(meshData);
|
|
1805
1845
|
for (const fragment of this.splitMeshForStreaming(meshData)) {
|
|
1806
1846
|
let group = colorGroups.get(key);
|
|
1807
1847
|
if (!group) {
|
|
@@ -1812,11 +1852,11 @@ export class Scene {
|
|
|
1812
1852
|
}
|
|
1813
1853
|
}
|
|
1814
1854
|
// Create one fragment batch per color group (with buffer limit splitting)
|
|
1815
|
-
for (const [, group] of colorGroups) {
|
|
1855
|
+
for (const [key, group] of colorGroups) {
|
|
1816
1856
|
const chunks = this.splitMeshDataForBufferLimit(group, this.cachedMaxBufferSize);
|
|
1817
1857
|
for (const chunk of chunks) {
|
|
1818
1858
|
const color = chunk[0].color;
|
|
1819
|
-
const fragment = this.createBatchedMesh(chunk, color, device, pipeline);
|
|
1859
|
+
const fragment = this.createBatchedMesh(chunk, color, device, pipeline, undefined, undefined, this.buckets.get(key)?.frameOrigin);
|
|
1820
1860
|
this.batchedMeshes.push(fragment);
|
|
1821
1861
|
this.streamingFragments.push(fragment);
|
|
1822
1862
|
}
|
|
@@ -1826,15 +1866,14 @@ export class Scene {
|
|
|
1826
1866
|
return splitMeshForStreaming(meshData, Scene.STREAMING_FRAGMENT_MAX_INDICES, Scene.STREAMING_FRAGMENT_MAX_VERTEX_BYTES);
|
|
1827
1867
|
}
|
|
1828
1868
|
/**
|
|
1829
|
-
* Finalize streaming: destroy temporary fragment batches and
|
|
1830
|
-
*
|
|
1869
|
+
* Finalize streaming: destroy the temporary fragment batches and merge the
|
|
1870
|
+
* meshes streamed since the last finalize into proper batches.
|
|
1831
1871
|
* Call this when streaming completes instead of rebuildPendingBatches().
|
|
1832
1872
|
*
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
1836
|
-
*
|
|
1837
|
-
* meshData by their CURRENT color to produce correct batches.
|
|
1873
|
+
* Only the buckets that received streamed meshes are re-grouped and rebuilt
|
|
1874
|
+
* (plus any key already pending); every other model's batches stay as they
|
|
1875
|
+
* are (#5358). See `regroupStreamedBuckets` for why the streamed meshes are
|
|
1876
|
+
* re-grouped by their CURRENT colour.
|
|
1838
1877
|
*/
|
|
1839
1878
|
finalizeStreaming(device, pipeline) {
|
|
1840
1879
|
if (this.streamingFragments.length === 0)
|
|
@@ -1847,6 +1886,27 @@ export class Scene {
|
|
|
1847
1886
|
this.finalizeInProgress = false;
|
|
1848
1887
|
}
|
|
1849
1888
|
}
|
|
1889
|
+
regroupStreamed() {
|
|
1890
|
+
return regroupStreamedBuckets({
|
|
1891
|
+
buckets: this.buckets,
|
|
1892
|
+
meshDataBucket: this.meshDataBucket,
|
|
1893
|
+
activeBucketKey: this.activeBucketKey,
|
|
1894
|
+
coldBuckets: this.coldBuckets,
|
|
1895
|
+
pendingBatchKeys: this.pendingBatchKeys,
|
|
1896
|
+
streamedBucketKeys: this.streamedBucketKeys,
|
|
1897
|
+
bucketBaseKey: (md) => this.bucketBaseKey(md),
|
|
1898
|
+
resolveActiveBucket: (base, md) => this.resolveActiveBucket(base, md),
|
|
1899
|
+
createBucket: (key) => ({ key, meshData: [], batchedMesh: null, vertexBytes: 0 }),
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
/** Free batches the finalize swap replaced, with their per-batch caches. */
|
|
1903
|
+
retireFinalizedBatches(batches) {
|
|
1904
|
+
for (const batch of batches) {
|
|
1905
|
+
this.dropPartialCacheForBatch(batch);
|
|
1906
|
+
this.lastDrawnFrame.delete(batch.id);
|
|
1907
|
+
destroyGpuResources(batch);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1850
1910
|
finalizeStreamingInner(device, pipeline) {
|
|
1851
1911
|
// Save references to old fragments/batches — keep them rendering
|
|
1852
1912
|
// until the new proper batches are fully built (no visual gap).
|
|
@@ -1854,103 +1914,47 @@ export class Scene {
|
|
|
1854
1914
|
const oldBatches = this.batchedMeshes;
|
|
1855
1915
|
const fragmentSet = new Set(oldFragments);
|
|
1856
1916
|
const oldBatchSet = new Set(oldBatches);
|
|
1857
|
-
//
|
|
1858
|
-
//
|
|
1859
|
-
//
|
|
1860
|
-
//
|
|
1861
|
-
|
|
1917
|
+
// The re-group detaches the streamed buckets BEFORE the replacement GPU
|
|
1918
|
+
// buffers exist. If a createBuffer fails part-way through, callers that
|
|
1919
|
+
// CONTAIN the throw to keep the canvas alive would otherwise be left
|
|
1920
|
+
// rendering a half-built scene, turning a crash into a silently blank model.
|
|
1921
|
+
let regroup = null;
|
|
1862
1922
|
let rebuilt = false;
|
|
1863
1923
|
try {
|
|
1864
1924
|
this.streamingFragments = [];
|
|
1865
|
-
|
|
1866
|
-
//
|
|
1867
|
-
//
|
|
1868
|
-
// sealed shells instead of being re-grouped (re-grouping would
|
|
1869
|
-
// silently drop them).
|
|
1870
|
-
const allMeshData = [];
|
|
1871
|
-
const carriedCold = [];
|
|
1872
|
-
for (const [key, bucket] of this.buckets) {
|
|
1873
|
-
if (this.coldBuckets.has(key) && bucket.meshData.length === 0 && bucket.batchedMesh) {
|
|
1874
|
-
carriedCold.push([key, bucket]);
|
|
1875
|
-
continue;
|
|
1876
|
-
}
|
|
1877
|
-
for (const md of bucket.meshData)
|
|
1878
|
-
allMeshData.push(md);
|
|
1879
|
-
}
|
|
1880
|
-
// 2. Clear all bucket/batch state for a clean rebuild
|
|
1881
|
-
// NOTE: batchedMeshes keeps the OLD array reference — the renderer
|
|
1882
|
-
// continues to draw from it until we swap in the new array below.
|
|
1883
|
-
this.buckets.clear();
|
|
1884
|
-
this.meshDataBucket = new Map();
|
|
1885
|
-
this.activeBucketKey.clear();
|
|
1886
|
-
this.lastDrawnFrame.clear();
|
|
1887
|
-
this.residencyRestoreQueue.clear();
|
|
1888
|
-
this.pendingBatchKeys.clear();
|
|
1889
|
-
// Re-seat the carried cold shells in the fresh bucket map (their GPU
|
|
1890
|
-
// shells re-enter the flat array via rebuildPendingBatches below).
|
|
1891
|
-
for (const [key, bucket] of carriedCold)
|
|
1892
|
-
this.buckets.set(key, bucket);
|
|
1893
|
-
// 3. Re-group ALL meshData by their CURRENT color (and grid cell).
|
|
1894
|
-
// meshData.color may have been mutated in-place since the mesh was
|
|
1895
|
-
// first bucketed, so the original bucket key is stale. Re-grouping
|
|
1896
|
-
// by current color ensures batches render with correct colors.
|
|
1897
|
-
for (const meshData of allMeshData) {
|
|
1898
|
-
const baseKey = this.bucketBaseKey(meshData);
|
|
1899
|
-
const bucketKey = this.resolveActiveBucket(baseKey, meshData);
|
|
1900
|
-
let bucket = this.buckets.get(bucketKey);
|
|
1901
|
-
if (!bucket) {
|
|
1902
|
-
bucket = { key: bucketKey, meshData: [], batchedMesh: null, vertexBytes: 0 };
|
|
1903
|
-
this.buckets.set(bucketKey, bucket);
|
|
1904
|
-
}
|
|
1905
|
-
bucket.meshData.push(meshData);
|
|
1906
|
-
this.meshDataBucket.set(meshData, bucket);
|
|
1907
|
-
this.pendingBatchKeys.add(bucketKey);
|
|
1908
|
-
}
|
|
1909
|
-
// 4. Build new proper batches into a fresh array
|
|
1925
|
+
regroup = this.regroupStreamed();
|
|
1926
|
+
// Build into a fresh array; rebuildPendingBatches republishes every
|
|
1927
|
+
// bucket's batch, untouched ones included.
|
|
1910
1928
|
this.batchedMeshes = [];
|
|
1911
1929
|
this.rebuildPendingBatches(device, pipeline);
|
|
1912
1930
|
rebuilt = true;
|
|
1913
|
-
// Cached partial (filtered-visibility) batches are keyed by their SOURCE
|
|
1914
|
-
// batch, so they only go stale once the replacement batches are live.
|
|
1915
|
-
// Dropping them up in step 2 destroyed their GPU resources BEFORE the
|
|
1916
|
-
// rebuild could fail, and the rollback cannot bring them back — an active
|
|
1917
|
-
// hide/isolate view lost its visible subset and had to recreate it
|
|
1918
|
-
// against the very device that just failed. On the failure path they now
|
|
1919
|
-
// survive, still matching the restored batches.
|
|
1920
|
-
this.dropAllPartialCaches();
|
|
1921
1931
|
}
|
|
1922
1932
|
finally {
|
|
1923
1933
|
if (!rebuilt) {
|
|
1924
|
-
// Free ONLY what this attempt created
|
|
1925
|
-
//
|
|
1926
|
-
// live before the rebuild must be left alone — destroying it would
|
|
1927
|
-
// leave the restored arrays pointing at dead buffers.
|
|
1934
|
+
// Free ONLY what this attempt created: anything live before the
|
|
1935
|
+
// rebuild is what the restored arrays point back at.
|
|
1928
1936
|
for (const created of this.batchedMeshes) {
|
|
1929
1937
|
if (!oldBatchSet.has(created) && !fragmentSet.has(created)) {
|
|
1930
1938
|
destroyGpuResources(created);
|
|
1931
1939
|
}
|
|
1932
1940
|
}
|
|
1933
|
-
|
|
1934
|
-
// resource: putting the arrays back restores exactly what was on
|
|
1935
|
-
// screen.
|
|
1941
|
+
regroup?.rollback();
|
|
1936
1942
|
this.streamingFragments = oldFragments;
|
|
1937
1943
|
this.batchedMeshes = oldBatches;
|
|
1938
1944
|
}
|
|
1939
1945
|
}
|
|
1940
|
-
//
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
destroyGpuResources(batch);
|
|
1946
|
-
}
|
|
1946
|
+
// NOW free the fragments and the dissolved buckets' batches (the
|
|
1947
|
+
// replacements are live). Batches of rebuilt surviving buckets were
|
|
1948
|
+
// already retired by rebuildPendingBatches.
|
|
1949
|
+
this.retireFinalizedBatches(oldFragments);
|
|
1950
|
+
this.retireFinalizedBatches(regroup?.retired ?? []);
|
|
1947
1951
|
this.reapplyColorOverrides(device, pipeline);
|
|
1948
1952
|
}
|
|
1949
1953
|
/**
|
|
1950
1954
|
* Time-sliced version of finalizeStreaming.
|
|
1951
|
-
*
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
1955
|
+
* Rebuilds the streamed buckets' batches in small chunks, yielding to the
|
|
1956
|
+
* event loop between chunks so orbit/pan stays responsive. The fragments
|
|
1957
|
+
* keep rendering until the rebuilt batches are swapped in.
|
|
1954
1958
|
*
|
|
1955
1959
|
* @param device GPU device
|
|
1956
1960
|
* @param pipeline Render pipeline
|
|
@@ -1982,40 +1986,32 @@ export class Scene {
|
|
|
1982
1986
|
// spec). Every entry point that can fail — the synchronous preamble AND
|
|
1983
1987
|
// each chunked continuation — must therefore run under its own try/catch
|
|
1984
1988
|
// that explicitly calls `reject`, mirroring finalizeStreamingInner's
|
|
1985
|
-
// try/finally contract: restore oldFragments/oldBatches
|
|
1986
|
-
// this attempt created,
|
|
1987
|
-
//
|
|
1989
|
+
// try/finally contract: restore oldFragments/oldBatches and the bucket
|
|
1990
|
+
// map, free only what this attempt created, and always clear
|
|
1991
|
+
// finalizeInProgress.
|
|
1988
1992
|
return new Promise((resolve, reject) => {
|
|
1989
|
-
const newBatches = [];
|
|
1990
|
-
// Every batch this attempt creates, paired with the bucket that now owns
|
|
1991
|
-
// it and the value it displaced. `newBatches` alone is not enough to roll
|
|
1992
|
-
// back: processChunk publishes each batch into `bucket.batchedMesh`, so
|
|
1993
|
-
// freeing it without repairing the owner leaves the bucket map pointing
|
|
1994
|
-
// at destroyed GPU resources (use-after-free on the next bucket-driven
|
|
1995
|
-
// access). `previous` is null for the freshly built buckets and, for a
|
|
1996
|
-
// carried COLD bucket a re-grouped meshData landed in, the shell that the
|
|
1997
|
-
// restored `batchedMeshes` still holds — which must be put back, not
|
|
1998
|
-
// nulled.
|
|
1999
1993
|
const createdOwned = [];
|
|
2000
|
-
|
|
1994
|
+
// Pending keys whose bucket ended up empty: deleted (and their batch
|
|
1995
|
+
// retired) at the swap, exactly as rebuildPendingBatches would.
|
|
1996
|
+
const emptied = [];
|
|
1997
|
+
let regroup = null;
|
|
2001
1998
|
let pendingKeys = [];
|
|
2002
1999
|
let keyIdx = 0;
|
|
2003
2000
|
function rollback() {
|
|
2004
|
-
|
|
2005
|
-
// anything already live before the rebuild must be left alone (they
|
|
2006
|
-
// are what the restored arrays point back at). Iterating the owned
|
|
2007
|
-
// pairs rather than `newBatches` also skips the carried cold shells
|
|
2008
|
-
// appended just before the swap, which this attempt did not create.
|
|
2009
|
-
for (const { bucket, previous, batch } of createdOwned) {
|
|
2001
|
+
for (const { bucket, previous, previousFrameOrigin, batch } of createdOwned) {
|
|
2010
2002
|
// Repair the owner BEFORE the free, so no bucket is ever observable
|
|
2011
|
-
// holding a destroyed batch
|
|
2012
|
-
|
|
2003
|
+
// holding a destroyed batch; its frame origin (what a later
|
|
2004
|
+
// createBatchedMesh seeds from) must describe the restored batch.
|
|
2005
|
+
if (bucket.batchedMesh === batch) {
|
|
2013
2006
|
bucket.batchedMesh = previous;
|
|
2007
|
+
bucket.frameOrigin = previousFrameOrigin;
|
|
2008
|
+
}
|
|
2014
2009
|
if (!oldBatchSet.has(batch) && !fragmentSet.has(batch)) {
|
|
2015
2010
|
destroyGpuResources(batch);
|
|
2016
2011
|
}
|
|
2017
2012
|
}
|
|
2018
|
-
|
|
2013
|
+
regroup?.rollback();
|
|
2014
|
+
scene.streamingFragments = [...oldFragments, ...scene.streamingFragments]; // + any streamed in meanwhile
|
|
2019
2015
|
scene.batchedMeshes = oldBatches;
|
|
2020
2016
|
scene.finalizeInProgress = false;
|
|
2021
2017
|
}
|
|
@@ -2026,43 +2022,41 @@ export class Scene {
|
|
|
2026
2022
|
const key = pendingKeys[keyIdx++];
|
|
2027
2023
|
const bucket = scene.buckets.get(key);
|
|
2028
2024
|
if (!bucket || bucket.meshData.length === 0) {
|
|
2029
|
-
|
|
2025
|
+
emptied.push(key);
|
|
2030
2026
|
continue;
|
|
2031
2027
|
}
|
|
2032
2028
|
const color = bucket.meshData[0].color;
|
|
2033
2029
|
const previous = bucket.batchedMesh;
|
|
2030
|
+
const previousFrameOrigin = bucket.frameOrigin;
|
|
2034
2031
|
const batchedMesh = scene.createBatchedMesh(bucket.meshData, color, device, pipeline, key);
|
|
2035
2032
|
bucket.batchedMesh = batchedMesh;
|
|
2036
|
-
|
|
2037
|
-
|
|
2033
|
+
bucket.frameOrigin = batchedMesh.origin;
|
|
2034
|
+
createdOwned.push({ bucket, previous, previousFrameOrigin, batch: batchedMesh });
|
|
2038
2035
|
// Check time budget — yield if exceeded
|
|
2039
2036
|
if (performance.now() - chunkStart >= budgetMs) {
|
|
2040
2037
|
setTimeout(processChunk, 0);
|
|
2041
2038
|
return;
|
|
2042
2039
|
}
|
|
2043
2040
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
// Cached partial (filtered-visibility) batches are keyed by their
|
|
2054
|
-
// SOURCE batch, so they only go stale once the replacement batches
|
|
2055
|
-
// are live — dropping them any earlier destroys GPU resources a
|
|
2056
|
-
// mid-rebuild failure could never get back (same rationale as
|
|
2057
|
-
// finalizeStreamingInner).
|
|
2058
|
-
scene.dropAllPartialCaches();
|
|
2059
|
-
// Destroy old fragment/batch GPU resources
|
|
2060
|
-
for (const fragment of oldFragments)
|
|
2061
|
-
destroyGpuResources(fragment);
|
|
2062
|
-
for (const batch of oldBatches) {
|
|
2063
|
-
if (!fragmentSet.has(batch))
|
|
2064
|
-
destroyGpuResources(batch);
|
|
2041
|
+
const retired = [...oldFragments, ...(regroup?.retired ?? [])];
|
|
2042
|
+
for (const { previous } of createdOwned)
|
|
2043
|
+
if (previous)
|
|
2044
|
+
retired.push(previous);
|
|
2045
|
+
for (const key of emptied) {
|
|
2046
|
+
const batch = scene.buckets.get(key)?.batchedMesh;
|
|
2047
|
+
if (batch)
|
|
2048
|
+
retired.push(batch);
|
|
2049
|
+
scene.buckets.delete(key);
|
|
2065
2050
|
}
|
|
2051
|
+
// Atomic swap so the renderer never sees an empty array: every
|
|
2052
|
+
// bucket's live batch (untouched models included, evicted shells
|
|
2053
|
+
// too — the draw loop skips gpuResident === false and the restore
|
|
2054
|
+
// path revives them) plus any fragment streamed in meanwhile.
|
|
2055
|
+
scene.batchedMeshes = [
|
|
2056
|
+
...[...scene.buckets.values()].flatMap((b) => (b.batchedMesh ? [b.batchedMesh] : [])),
|
|
2057
|
+
...scene.streamingFragments,
|
|
2058
|
+
];
|
|
2059
|
+
scene.retireFinalizedBatches(retired);
|
|
2066
2060
|
scene.finalizeInProgress = false;
|
|
2067
2061
|
scene.reapplyColorOverrides(device, pipeline); // never throws: the swap above is committed
|
|
2068
2062
|
resolve();
|
|
@@ -2073,45 +2067,9 @@ export class Scene {
|
|
|
2073
2067
|
}
|
|
2074
2068
|
}
|
|
2075
2069
|
try {
|
|
2076
|
-
// --- Synchronous preamble
|
|
2070
|
+
// --- Synchronous preamble: re-group the streamed meshes only ---
|
|
2077
2071
|
scene.streamingFragments = [];
|
|
2078
|
-
|
|
2079
|
-
// shells — see the sync finalize for the rationale)
|
|
2080
|
-
const allMeshData = [];
|
|
2081
|
-
for (const [key, bucket] of scene.buckets) {
|
|
2082
|
-
if (scene.coldBuckets.has(key) && bucket.meshData.length === 0 && bucket.batchedMesh) {
|
|
2083
|
-
carriedCold.push([key, bucket]);
|
|
2084
|
-
continue;
|
|
2085
|
-
}
|
|
2086
|
-
for (const md of bucket.meshData)
|
|
2087
|
-
allMeshData.push(md);
|
|
2088
|
-
}
|
|
2089
|
-
// 2. Clear bucket/batch state. dropAllPartialCaches() is deliberately
|
|
2090
|
-
// NOT called here — see the success path above.
|
|
2091
|
-
scene.buckets.clear();
|
|
2092
|
-
scene.meshDataBucket = new Map();
|
|
2093
|
-
scene.activeBucketKey.clear();
|
|
2094
|
-
scene.lastDrawnFrame.clear();
|
|
2095
|
-
scene.residencyRestoreQueue.clear();
|
|
2096
|
-
scene.pendingBatchKeys.clear();
|
|
2097
|
-
// Re-seat the carried cold shells in the fresh bucket map.
|
|
2098
|
-
for (const [key, bucket] of carriedCold)
|
|
2099
|
-
scene.buckets.set(key, bucket);
|
|
2100
|
-
// 3. Re-group meshData by current color (and grid cell) — fast
|
|
2101
|
-
for (const meshData of allMeshData) {
|
|
2102
|
-
const baseKey = scene.bucketBaseKey(meshData);
|
|
2103
|
-
const bucketKey = scene.resolveActiveBucket(baseKey, meshData);
|
|
2104
|
-
let bucket = scene.buckets.get(bucketKey);
|
|
2105
|
-
if (!bucket) {
|
|
2106
|
-
bucket = { key: bucketKey, meshData: [], batchedMesh: null, vertexBytes: 0 };
|
|
2107
|
-
scene.buckets.set(bucketKey, bucket);
|
|
2108
|
-
}
|
|
2109
|
-
bucket.meshData.push(meshData);
|
|
2110
|
-
scene.meshDataBucket.set(meshData, bucket);
|
|
2111
|
-
scene.pendingBatchKeys.add(bucketKey);
|
|
2112
|
-
}
|
|
2113
|
-
// Build new batches into a temporary array so the old batchedMeshes
|
|
2114
|
-
// (streaming fragments) keep rendering until the swap is complete.
|
|
2072
|
+
regroup = scene.regroupStreamed();
|
|
2115
2073
|
pendingKeys = Array.from(scene.pendingBatchKeys);
|
|
2116
2074
|
scene.pendingBatchKeys.clear();
|
|
2117
2075
|
}
|
|
@@ -2152,6 +2110,7 @@ export class Scene {
|
|
|
2152
2110
|
this.coldBuckets.clear();
|
|
2153
2111
|
this.dirtyBuckets.clear();
|
|
2154
2112
|
this.pendingBatchKeys.clear();
|
|
2113
|
+
this.streamedBucketKeys.clear();
|
|
2155
2114
|
this.dropAllPartialCaches();
|
|
2156
2115
|
this.geometryReleased = true;
|
|
2157
2116
|
this.ephemeralStreamingMode = false;
|
|
@@ -2172,7 +2131,6 @@ export class Scene {
|
|
|
2172
2131
|
* Call this after finalizeStreaming() when all color updates have been applied.
|
|
2173
2132
|
*/
|
|
2174
2133
|
releaseGeometryData() {
|
|
2175
|
-
this.authoredGeneration++;
|
|
2176
2134
|
if (this.geometryReleased)
|
|
2177
2135
|
return;
|
|
2178
2136
|
if (this.instanceSuppression.retained) {
|
|
@@ -2186,6 +2144,8 @@ export class Scene {
|
|
|
2186
2144
|
`Call finalizeStreaming()/rebuildPendingBatches() first.`);
|
|
2187
2145
|
return;
|
|
2188
2146
|
}
|
|
2147
|
+
this.authoredGeneration++;
|
|
2148
|
+
this.authoredPreparations.invalidate();
|
|
2189
2149
|
this.appearanceController?.forget();
|
|
2190
2150
|
// 1. Precompute and cache ALL entity bounding boxes before releasing data.
|
|
2191
2151
|
// Same rule as `finishEphemeralStreaming`: an entity with no usable vertex
|
|
@@ -2328,14 +2288,17 @@ export class Scene {
|
|
|
2328
2288
|
* participate in the main buckets map).
|
|
2329
2289
|
* @param quantization - 'auto' for bucket batches; derived batches pass `inheritedQuantization(source)` (#4832).
|
|
2330
2290
|
*/
|
|
2331
|
-
createBatchedMesh(meshes, color, device, pipeline, bucketKey, quantization = this.quantizedBatchesEnabled ? 'auto' : 'off') {
|
|
2291
|
+
createBatchedMesh(meshes, color, device, pipeline, bucketKey, quantization = this.quantizedBatchesEnabled ? 'auto' : 'off', frameOrigin) {
|
|
2332
2292
|
// Keep main's model-local frame and translation registration while staging
|
|
2333
2293
|
// every GPU allocation before publishing Scene state.
|
|
2334
2294
|
const modelIndex = meshes[0]?.modelIndex ?? 0;
|
|
2335
2295
|
const offset = this.modelTranslations.get(modelIndex);
|
|
2296
|
+
const origin = topologySafeBatchOrigin(meshes, frameOrigin ?? this.meshDataBucket.get(meshes[0])?.batchedMesh?.origin, this.modelTranslations.frameOrigin(this.sharedFrameOrigins.get(modelIndex) ?? null, modelIndex), this.meshDataBucket.get(meshes[0])?.frameOrigin);
|
|
2297
|
+
if (!origin)
|
|
2298
|
+
throw new Error('Unable to resolve a topology-safe GPU frame for mesh geometry.');
|
|
2336
2299
|
const result = createSceneBatch(meshes, color, device, pipeline, {
|
|
2337
2300
|
id: this.nextBatchId, colorKey: bucketKey ?? this.colorKey(color),
|
|
2338
|
-
origin
|
|
2301
|
+
origin,
|
|
2339
2302
|
quantized: quantization, lod: this.lodBuildsEnabled,
|
|
2340
2303
|
}, bucketKey);
|
|
2341
2304
|
this.nextBatchId++;
|
|
@@ -2365,38 +2328,15 @@ export class Scene {
|
|
|
2365
2328
|
* Returns the bucket key to use (may be the base key or a suffixed key).
|
|
2366
2329
|
*/
|
|
2367
2330
|
resolveActiveBucket(baseColorKey, meshData) {
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
bucketKey = `${baseColorKey}#${this.nextSplitId++}`;
|
|
2378
|
-
this.activeBucketKey.set(baseColorKey, bucketKey);
|
|
2379
|
-
let target = this.buckets.get(bucketKey);
|
|
2380
|
-
if (!target) {
|
|
2381
|
-
target = { key: bucketKey, meshData: [], batchedMesh: null, vertexBytes: 0 };
|
|
2382
|
-
this.buckets.set(bucketKey, target);
|
|
2383
|
-
}
|
|
2384
|
-
target.vertexBytes += meshBytes;
|
|
2385
|
-
return bucketKey;
|
|
2386
|
-
}
|
|
2387
|
-
if (currentBytes > 0 && currentBytes + meshBytes > this.cachedMaxBufferSize) {
|
|
2388
|
-
// Overflow — create a new sub-bucket
|
|
2389
|
-
bucketKey = `${baseColorKey}#${this.nextSplitId++}`;
|
|
2390
|
-
this.activeBucketKey.set(baseColorKey, bucketKey);
|
|
2391
|
-
}
|
|
2392
|
-
// Update size tracking on the bucket (create if needed)
|
|
2393
|
-
let targetBucket = this.buckets.get(bucketKey);
|
|
2394
|
-
if (!targetBucket) {
|
|
2395
|
-
targetBucket = { key: bucketKey, meshData: [], batchedMesh: null, vertexBytes: 0 };
|
|
2396
|
-
this.buckets.set(bucketKey, targetBucket);
|
|
2397
|
-
}
|
|
2398
|
-
targetBucket.vertexBytes += meshBytes;
|
|
2399
|
-
return bucketKey;
|
|
2331
|
+
const modelIndex = meshData.modelIndex ?? 0;
|
|
2332
|
+
return resolvePrecisionBucket({
|
|
2333
|
+
buckets: this.buckets,
|
|
2334
|
+
activeKeys: this.activeBucketKey,
|
|
2335
|
+
coldKeys: this.coldBuckets,
|
|
2336
|
+
maxBufferSize: this.cachedMaxBufferSize,
|
|
2337
|
+
sharedOrigin: this.modelTranslations.frameOrigin(this.sharedFrameOrigins.get(modelIndex) ?? null, modelIndex),
|
|
2338
|
+
nextSplitKey: () => `${baseColorKey}#${this.nextSplitId++}`,
|
|
2339
|
+
}, baseColorKey, meshData);
|
|
2400
2340
|
}
|
|
2401
2341
|
/**
|
|
2402
2342
|
* Extract the base color key from a bucket key (strips "#N" suffix if present).
|
|
@@ -2524,7 +2464,7 @@ export class Scene {
|
|
|
2524
2464
|
// Drawn INSTEAD of its source batch, so it inherits the source's f32/quantized
|
|
2525
2465
|
// decision (#4832): the overlay built from the same source must match its depth.
|
|
2526
2466
|
const color = visibleMeshData[0].color;
|
|
2527
|
-
const partialBatch = this.createBatchedMesh(visibleMeshData, color, device, pipeline, undefined, inheritedQuantization(this.quantizedBatchesEnabled, bucket?.batchedMesh));
|
|
2467
|
+
const partialBatch = this.createBatchedMesh(visibleMeshData, color, device, pipeline, undefined, inheritedQuantization(this.quantizedBatchesEnabled, bucket?.batchedMesh), bucket?.batchedMesh?.origin);
|
|
2528
2468
|
// Cache it
|
|
2529
2469
|
this.partialBatchCache.set(cacheKey, partialBatch);
|
|
2530
2470
|
this.partialBatchCacheKeys.set(sourceBatchKey, cacheKey);
|
|
@@ -2574,7 +2514,7 @@ export class Scene {
|
|
|
2574
2514
|
this.overlaySources.set(piece, sourceKey);
|
|
2575
2515
|
const quantization = inheritedQuantization(this.quantizedBatchesEnabled, sourceBatch);
|
|
2576
2516
|
for (const chunk of this.splitMeshDataForBufferLimit(meshData, maxBufferSize)) {
|
|
2577
|
-
this.overrideBatches.push(this.createBatchedMesh(chunk, color, device, pipeline, undefined, quantization));
|
|
2517
|
+
this.overrideBatches.push(this.createBatchedMesh(chunk, color, device, pipeline, undefined, quantization, sourceBatch?.origin));
|
|
2578
2518
|
}
|
|
2579
2519
|
}
|
|
2580
2520
|
}
|
|
@@ -2873,31 +2813,13 @@ export class Scene {
|
|
|
2873
2813
|
vf[o + 5] = i * 3 + 2 < t.normals.length ? t.normals[i * 3 + 2] : 0;
|
|
2874
2814
|
// vf[o + 6] (entityId lane) stays 0.
|
|
2875
2815
|
}
|
|
2876
|
-
const vertexBuffer = device.
|
|
2877
|
-
|
|
2878
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
2879
|
-
mappedAtCreation: true,
|
|
2880
|
-
});
|
|
2881
|
-
new Uint8Array(vertexBuffer.getMappedRange()).set(new Uint8Array(vtx));
|
|
2882
|
-
vertexBuffer.unmap();
|
|
2883
|
-
const indexBuffer = device.createBuffer({
|
|
2884
|
-
size: t.indices.byteLength,
|
|
2885
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
2886
|
-
mappedAtCreation: true,
|
|
2887
|
-
});
|
|
2888
|
-
new Uint32Array(indexBuffer.getMappedRange()).set(t.indices);
|
|
2889
|
-
indexBuffer.unmap();
|
|
2816
|
+
const vertexBuffer = createStaticGpuBuffer(device, vtx, GPUBufferUsage.VERTEX);
|
|
2817
|
+
const indexBuffer = createStaticGpuBuffer(device, t.indices, GPUBufferUsage.INDEX);
|
|
2890
2818
|
// instanceBuffer is already the interleaved mat4 + entityId + rgba block
|
|
2891
2819
|
// (INSTANCE_STRIDE_BYTES per occurrence) from prepareInstancedRender.
|
|
2892
|
-
this.modelTranslations.placeInstances(t.instanceBuffer, modelIndex, INSTANCE_STRIDE_BYTES);
|
|
2820
|
+
this.modelTranslations.placeInstances(t.instanceBuffer, modelIndex, INSTANCE_STRIDE_BYTES, t.canonicalAnchors, t.canonicalMatrixTranslations);
|
|
2893
2821
|
const instSize = t.instanceCount * INSTANCE_STRIDE_BYTES;
|
|
2894
|
-
const instanceBuffer = device.
|
|
2895
|
-
size: instSize,
|
|
2896
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
2897
|
-
mappedAtCreation: true,
|
|
2898
|
-
});
|
|
2899
|
-
new Uint8Array(instanceBuffer.getMappedRange()).set(new Uint8Array(t.instanceBuffer, 0, instSize));
|
|
2900
|
-
instanceBuffer.unmap();
|
|
2822
|
+
const instanceBuffer = createStaticGpuBuffer(device, new Uint8Array(t.instanceBuffer, 0, instSize), GPUBufferUsage.VERTEX);
|
|
2901
2823
|
// Always append: slots are stable identities, never recycled.
|
|
2902
2824
|
const templateIndex = this.instancedTemplates.length;
|
|
2903
2825
|
const template = {
|
|
@@ -2907,6 +2829,7 @@ export class Scene {
|
|
|
2907
2829
|
indexCount: t.indices.length,
|
|
2908
2830
|
instanceBuffer,
|
|
2909
2831
|
instanceCount: t.instanceCount,
|
|
2832
|
+
canonicalAnchors: t.canonicalAnchors,
|
|
2910
2833
|
bounds: null,
|
|
2911
2834
|
maxOccRadius: 0,
|
|
2912
2835
|
selectedCount: 0,
|
|
@@ -2940,6 +2863,8 @@ export class Scene {
|
|
|
2940
2863
|
normals: t.normals,
|
|
2941
2864
|
indices: t.indices,
|
|
2942
2865
|
instanceData: t.instanceBuffer,
|
|
2866
|
+
canonicalAnchors: t.canonicalAnchors,
|
|
2867
|
+
canonicalMatrixTranslations: t.canonicalMatrixTranslations,
|
|
2943
2868
|
localMin: [lmnx, lmny, lmnz],
|
|
2944
2869
|
localMax: [lmxx, lmxy, lmxz],
|
|
2945
2870
|
};
|
|
@@ -3010,51 +2935,10 @@ export class Scene {
|
|
|
3010
2935
|
this.instancedVisibilityDirty = true;
|
|
3011
2936
|
this.instancedGhostDirty = true;
|
|
3012
2937
|
}
|
|
3013
|
-
/** Transform a template's local AABB by an occurrence's column-major mat4 (read
|
|
3014
|
-
* from the packed instance record at `matOffset`) and union the world box into
|
|
3015
|
-
* boundingBoxes[eid]. Returns the occurrence's world box so the caller can also
|
|
3016
|
-
* fold it into the template's cull metadata. */
|
|
3017
2938
|
unionInstancedWorldAabb(eid, dv, matOffset, lmnx, lmny, lmnz, lmxx, lmxy, lmxz) {
|
|
3018
|
-
|
|
3019
|
-
const m4 = dv.getFloat32(matOffset + 16, true), m5 = dv.getFloat32(matOffset + 20, true), m6 = dv.getFloat32(matOffset + 24, true);
|
|
3020
|
-
const m8 = dv.getFloat32(matOffset + 32, true), m9 = dv.getFloat32(matOffset + 36, true), m10 = dv.getFloat32(matOffset + 40, true);
|
|
3021
|
-
const m12 = dv.getFloat32(matOffset + 48, true), m13 = dv.getFloat32(matOffset + 52, true), m14 = dv.getFloat32(matOffset + 56, true);
|
|
3022
|
-
let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
3023
|
-
for (let c = 0; c < 8; c++) {
|
|
3024
|
-
const x = (c & 1) ? lmxx : lmnx, y = (c & 2) ? lmxy : lmny, z = (c & 4) ? lmxz : lmnz;
|
|
3025
|
-
const wx = m0 * x + m4 * y + m8 * z + m12;
|
|
3026
|
-
const wy = m1 * x + m5 * y + m9 * z + m13;
|
|
3027
|
-
const wz = m2 * x + m6 * y + m10 * z + m14;
|
|
3028
|
-
if (wx < minX)
|
|
3029
|
-
minX = wx;
|
|
3030
|
-
if (wy < minY)
|
|
3031
|
-
minY = wy;
|
|
3032
|
-
if (wz < minZ)
|
|
3033
|
-
minZ = wz;
|
|
3034
|
-
if (wx > maxX)
|
|
3035
|
-
maxX = wx;
|
|
3036
|
-
if (wy > maxY)
|
|
3037
|
-
maxY = wy;
|
|
3038
|
-
if (wz > maxZ)
|
|
3039
|
-
maxZ = wz;
|
|
3040
|
-
}
|
|
3041
|
-
const existing = this.boundingBoxes.get(eid);
|
|
3042
|
-
if (existing) {
|
|
3043
|
-
existing.min.x = Math.min(existing.min.x, minX);
|
|
3044
|
-
existing.min.y = Math.min(existing.min.y, minY);
|
|
3045
|
-
existing.min.z = Math.min(existing.min.z, minZ);
|
|
3046
|
-
existing.max.x = Math.max(existing.max.x, maxX);
|
|
3047
|
-
existing.max.y = Math.max(existing.max.y, maxY);
|
|
3048
|
-
existing.max.z = Math.max(existing.max.z, maxZ);
|
|
3049
|
-
}
|
|
3050
|
-
else {
|
|
3051
|
-
this.boundingBoxes.set(eid, { min: { x: minX, y: minY, z: minZ }, max: { x: maxX, y: maxY, z: maxZ } });
|
|
3052
|
-
}
|
|
3053
|
-
return { minX, minY, minZ, maxX, maxY, maxZ };
|
|
2939
|
+
return unionInstanceBounds(this.boundingBoxes, eid, dv, matOffset, lmnx, lmny, lmnz, lmxx, lmxy, lmxz);
|
|
3054
2940
|
}
|
|
3055
|
-
/** True
|
|
3056
|
-
* shard, not the flat meshDataMap). CPU consumers use this to decide whether to
|
|
3057
|
-
* fall back to the instanced accessors below. */
|
|
2941
|
+
/** True when `expressId` has a GPU-instanced occurrence. */
|
|
3058
2942
|
isInstancedEntity(expressId) {
|
|
3059
2943
|
return this.instancedEntityMap.has(expressId);
|
|
3060
2944
|
}
|
|
@@ -3317,22 +3201,17 @@ export class Scene {
|
|
|
3317
3201
|
this.restoreInstanceColor(device, eid);
|
|
3318
3202
|
}
|
|
3319
3203
|
for (const eid of toFade) {
|
|
3320
|
-
const
|
|
3321
|
-
if (
|
|
3322
|
-
|
|
3323
|
-
|
|
3204
|
+
const override = this.instancedOverrideColors?.get(eid);
|
|
3205
|
+
if (override)
|
|
3206
|
+
this.writeInstanceColor(device, eid, [override[0], override[1], override[2], ghostAlpha]);
|
|
3207
|
+
else
|
|
3208
|
+
this.writeOriginalInstanceColors(device, eid, ghostAlpha);
|
|
3324
3209
|
}
|
|
3325
3210
|
this.instancedGhosted = next;
|
|
3326
3211
|
this.lastGhostAlpha = ghostAlpha;
|
|
3327
3212
|
this.instancedGhostDirty = false;
|
|
3328
3213
|
this.instancedGhostTransparent = next.size > 0 && ghostAlpha < OPAQUE_ALPHA_CUTOFF;
|
|
3329
3214
|
}
|
|
3330
|
-
/** The colour an occurrence was uploaded with, before any override or ghost. */
|
|
3331
|
-
originalInstanceColor(eid) {
|
|
3332
|
-
const locs = this.instancedEntityMap.get(eid);
|
|
3333
|
-
const first = locs?.[0];
|
|
3334
|
-
return first ? first.originalColor : null;
|
|
3335
|
-
}
|
|
3336
3215
|
/** Write the combined flag lane (selected | hidden) for every occurrence of `eid`
|
|
3337
3216
|
* (shares one u32 lane at INSTANCE_FLAGS_OFFSET, so both bits are folded here).
|
|
3338
3217
|
* #4382: for the item-filtered eid, selected narrows per-occurrence to
|
|
@@ -3374,6 +3253,7 @@ export class Scene {
|
|
|
3374
3253
|
device.queue.writeBuffer(buf, loc.byteOffset + INSTANCE_COLOR_OFFSET, new Float32Array(loc.originalColor));
|
|
3375
3254
|
}
|
|
3376
3255
|
}
|
|
3256
|
+
writeOriginalInstanceColors(device, eid, alpha) { writeOriginalInstancedColors(device, this.instancedEntityMap.get(eid) ?? [], this.instancedTemplates, INSTANCE_COLOR_OFFSET, alpha); }
|
|
3377
3257
|
/**
|
|
3378
3258
|
* Build a textured mesh (#961): interleave position+normal+entityId+uv into one
|
|
3379
3259
|
* vertex buffer, upload the decoded RGBA8 texture, create a sampler honouring
|
|
@@ -3558,6 +3438,7 @@ export class Scene {
|
|
|
3558
3438
|
*/
|
|
3559
3439
|
clearFlatGeometry() {
|
|
3560
3440
|
this.authoredGeneration++;
|
|
3441
|
+
this.authoredPreparations.invalidate();
|
|
3561
3442
|
this.instanceSuppression.restore();
|
|
3562
3443
|
this.appearanceController?.forget();
|
|
3563
3444
|
this.clearFlatBuffers();
|
|
@@ -3566,6 +3447,7 @@ export class Scene {
|
|
|
3566
3447
|
* owners keep their original-instance history. Full reset remains separate. */
|
|
3567
3448
|
clearFlatGeometryForRebuild(geometry, models, sourceGeometry = geometry) {
|
|
3568
3449
|
this.authoredGeneration++;
|
|
3450
|
+
this.authoredPreparations.invalidate();
|
|
3569
3451
|
const retained = this.appearanceController?.prepareRebuild(sourceGeometry, models) ?? new Set();
|
|
3570
3452
|
const discarded = this.appearanceController?.discardedForRebuild(retained) ?? [];
|
|
3571
3453
|
// A discarded converted owner must not resurrect its obsolete type instance.
|
|
@@ -3623,6 +3505,7 @@ export class Scene {
|
|
|
3623
3505
|
this.dirtyBuckets.clear();
|
|
3624
3506
|
this.cachedMaxBufferSize = 0;
|
|
3625
3507
|
this.pendingBatchKeys.clear();
|
|
3508
|
+
this.streamedBucketKeys.clear();
|
|
3626
3509
|
this.meshQueue = [];
|
|
3627
3510
|
this.meshQueueReadIndex = 0;
|
|
3628
3511
|
this.geometryReleased = false;
|
|
@@ -3921,7 +3804,7 @@ export class Scene {
|
|
|
3921
3804
|
* covered: their world AABBs are registered in `boundingBoxes` when the
|
|
3922
3805
|
* instanced shard is built.
|
|
3923
3806
|
*/
|
|
3924
|
-
selectRect(x0, y0, x1, y1, viewportWidth, viewportHeight, viewProj, hiddenIds, isolatedIds, clip) {
|
|
3807
|
+
selectRect(x0, y0, x1, y1, viewportWidth, viewportHeight, viewProj, hiddenIds, isolatedIds, clip, rte) {
|
|
3925
3808
|
// After release the cache is already the complete set; before it, boxes are
|
|
3926
3809
|
// computed lazily, so make sure every entity that still has mesh data has
|
|
3927
3810
|
// one. Same authoritative id set pick() uses, so colour-fused fillers whose
|
|
@@ -3939,7 +3822,7 @@ export class Scene {
|
|
|
3939
3822
|
this.getEntityBoundingBox(expressId);
|
|
3940
3823
|
}
|
|
3941
3824
|
}
|
|
3942
|
-
return selectBoundingBoxesInRect(this.boundingBoxes, viewProj, { x0, y0, x1, y1 }, viewportWidth, viewportHeight, hiddenIds, isolatedIds, clip);
|
|
3825
|
+
return selectBoundingBoxesInRect(this.boundingBoxes, viewProj, { x0, y0, x1, y1 }, viewportWidth, viewportHeight, hiddenIds, isolatedIds, clip, rte);
|
|
3943
3826
|
}
|
|
3944
3827
|
}
|
|
3945
3828
|
//# sourceMappingURL=scene.js.map
|