@pascal-app/viewer 1.0.3 → 1.0.5
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/capture/capture-runtime.js +1 -1
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.d.ts +1 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +12 -4
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.d.ts +1 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +22 -3
- package/dist/components/viewer/glb-interactive.d.ts +25 -1
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +253 -86
- package/dist/components/viewer/glb-mechanisms.d.ts +10 -0
- package/dist/components/viewer/glb-mechanisms.d.ts.map +1 -0
- package/dist/components/viewer/glb-mechanisms.js +19 -0
- package/dist/components/viewer/glb-reference-nodes.js +1 -1
- package/dist/components/viewer/glb-replace-instances.js +1 -1
- package/dist/components/viewer/glb-scene.d.ts +45 -27
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +421 -508
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +13 -2
- package/dist/components/viewer/index.d.ts +24 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +111 -37
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +113 -22
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +6 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -1
- package/dist/components/viewer/perf-panel.js +1 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +104 -36
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +17 -3
- package/dist/components/viewer/render-error.d.ts +18 -0
- package/dist/components/viewer/render-error.d.ts.map +1 -0
- package/dist/components/viewer/render-error.js +29 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +1 -1
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +31 -11
- package/dist/components/viewer/scene-ground-replacement.d.ts +6 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -1
- package/dist/components/viewer/scene-ground-replacement.js +15 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +25 -7
- package/dist/components/viewer/underground-ambiance.d.ts +9 -0
- package/dist/components/viewer/underground-ambiance.d.ts.map +1 -0
- package/dist/components/viewer/underground-ambiance.js +105 -0
- package/dist/components/viewer/viewer-camera.d.ts +4 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -2
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/index.d.ts +36 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -19
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +7 -1
- package/dist/lib/backdrop.d.ts +17 -0
- package/dist/lib/backdrop.d.ts.map +1 -1
- package/dist/lib/backdrop.js +18 -1
- package/dist/lib/construction-dust.d.ts +57 -0
- package/dist/lib/construction-dust.d.ts.map +1 -0
- package/dist/lib/construction-dust.js +187 -0
- package/dist/lib/csg-utils.js +1 -1
- package/dist/lib/display-state.d.ts +25 -0
- package/dist/lib/display-state.d.ts.map +1 -0
- package/dist/lib/display-state.js +1 -0
- package/dist/lib/foundation-materials.d.ts +26 -0
- package/dist/lib/foundation-materials.d.ts.map +1 -0
- package/dist/lib/foundation-materials.js +65 -0
- package/dist/lib/gltf-unlit.d.ts +10 -0
- package/dist/lib/gltf-unlit.d.ts.map +1 -0
- package/dist/lib/gltf-unlit.js +23 -0
- package/dist/lib/ground-window.d.ts +33 -0
- package/dist/lib/ground-window.d.ts.map +1 -0
- package/dist/lib/ground-window.js +114 -0
- package/dist/lib/hero-pose.d.ts +13 -3
- package/dist/lib/hero-pose.d.ts.map +1 -1
- package/dist/lib/hero-pose.js +72 -5
- package/dist/lib/ink-edges.d.ts +6 -3
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +17 -7
- package/dist/lib/key-light-override.d.ts +11 -0
- package/dist/lib/key-light-override.d.ts.map +1 -0
- package/dist/lib/key-light-override.js +14 -0
- package/dist/lib/ktx2-loader.d.ts +7 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +35 -0
- package/dist/lib/layer-pass.d.ts.map +1 -1
- package/dist/lib/layers.d.ts +11 -7
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +16 -6
- package/dist/lib/live-frame-hold.d.ts +14 -0
- package/dist/lib/live-frame-hold.d.ts.map +1 -0
- package/dist/lib/live-frame-hold.js +28 -0
- package/dist/lib/materials.d.ts +19 -7
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +65 -9
- package/dist/lib/merged-outline-node.d.ts +5 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +80 -2
- package/dist/lib/node-top-surface-height.d.ts +6 -0
- package/dist/lib/node-top-surface-height.d.ts.map +1 -0
- package/dist/lib/node-top-surface-height.js +74 -0
- package/dist/lib/perf-probe-state.d.ts +39 -0
- package/dist/lib/perf-probe-state.d.ts.map +1 -0
- package/dist/lib/perf-probe-state.js +17 -0
- package/dist/lib/pointer-events.d.ts +26 -2
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +99 -12
- package/dist/lib/post-processing-resources.d.ts.map +1 -1
- package/dist/lib/procedural-emission.d.ts +7 -0
- package/dist/lib/procedural-emission.d.ts.map +1 -0
- package/dist/lib/procedural-emission.js +54 -0
- package/dist/lib/reveal-candidates.d.ts +80 -0
- package/dist/lib/reveal-candidates.d.ts.map +1 -0
- package/dist/lib/reveal-candidates.js +524 -0
- package/dist/lib/reveal-ghost.d.ts +9 -0
- package/dist/lib/reveal-ghost.d.ts.map +1 -0
- package/dist/lib/reveal-ghost.js +62 -0
- package/dist/lib/reveal-pending.d.ts +12 -0
- package/dist/lib/reveal-pending.d.ts.map +1 -0
- package/dist/lib/reveal-pending.js +12 -0
- package/dist/lib/reveal-pose.d.ts +70 -0
- package/dist/lib/reveal-pose.d.ts.map +1 -0
- package/dist/lib/reveal-pose.js +248 -0
- package/dist/lib/reveal-schedule.d.ts +208 -0
- package/dist/lib/reveal-schedule.d.ts.map +1 -0
- package/dist/lib/reveal-schedule.js +410 -0
- package/dist/lib/reveal-texture-scale.d.ts +5 -0
- package/dist/lib/reveal-texture-scale.d.ts.map +1 -0
- package/dist/lib/reveal-texture-scale.js +113 -0
- package/dist/lib/reveal-windows.d.ts +23 -0
- package/dist/lib/reveal-windows.d.ts.map +1 -0
- package/dist/lib/reveal-windows.js +31 -0
- package/dist/lib/reveal-wipe.d.ts +71 -0
- package/dist/lib/reveal-wipe.d.ts.map +1 -0
- package/dist/lib/reveal-wipe.js +105 -0
- package/dist/lib/scene-pass-mrt.d.ts +12 -0
- package/dist/lib/scene-pass-mrt.d.ts.map +1 -0
- package/dist/lib/scene-pass-mrt.js +23 -0
- package/dist/lib/scene-support-height.d.ts +4 -0
- package/dist/lib/scene-support-height.d.ts.map +1 -0
- package/dist/lib/scene-support-height.js +34 -0
- package/dist/lib/scene-themes.d.ts +30 -8
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +36 -0
- package/dist/lib/scene-visibility.d.ts +17 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +53 -3
- package/dist/lib/scripted-opening.d.ts +11 -0
- package/dist/lib/scripted-opening.d.ts.map +1 -0
- package/dist/lib/scripted-opening.js +20 -0
- package/dist/lib/site-surfaces.d.ts +32 -0
- package/dist/lib/site-surfaces.d.ts.map +1 -0
- package/dist/lib/site-surfaces.js +70 -0
- package/dist/lib/snapshot-pipeline.d.ts +34 -3
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +247 -117
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
- package/dist/lib/terrain-perimeter-fill.js +22 -14
- package/dist/lib/tsl-compat.d.ts +2 -2
- package/dist/lib/tsl-compat.d.ts.map +1 -1
- package/dist/lib/underground-ambiance.d.ts +168 -0
- package/dist/lib/underground-ambiance.d.ts.map +1 -0
- package/dist/lib/underground-ambiance.js +334 -0
- package/dist/lib/zone-geometry.d.ts +11 -0
- package/dist/lib/zone-geometry.d.ts.map +1 -0
- package/dist/lib/zone-geometry.js +46 -0
- package/dist/store/use-item-light-pool.d.ts +14 -10
- package/dist/store/use-item-light-pool.d.ts.map +1 -1
- package/dist/store/use-item-light-pool.js +52 -26
- package/dist/store/use-viewer.d.ts +194 -26
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +164 -21
- package/dist/systems/ceiling/ceiling-system.d.ts +15 -1
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +88 -6
- package/dist/systems/construction-reveal/construction-dust.d.ts +16 -0
- package/dist/systems/construction-reveal/construction-dust.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-dust.js +92 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts +276 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-reveal.js +2383 -0
- package/dist/systems/construction-reveal/dust-material.d.ts +10 -0
- package/dist/systems/construction-reveal/dust-material.d.ts.map +1 -0
- package/dist/systems/construction-reveal/dust-material.js +17 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts +10 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts.map +1 -0
- package/dist/systems/construction-reveal/ghost-dress.js +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +55 -17
- package/dist/systems/elevator/elevator-interaction-system.d.ts +7 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-interaction-system.js +18 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +3 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +33 -5
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +71 -11
- package/dist/systems/interactive/control-widget.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +52 -17
- package/dist/systems/interactive/procedural-controls.d.ts +13 -0
- package/dist/systems/interactive/procedural-controls.d.ts.map +1 -0
- package/dist/systems/interactive/procedural-controls.js +17 -0
- package/dist/systems/interactive/scripted-clips.d.ts +19 -0
- package/dist/systems/interactive/scripted-clips.d.ts.map +1 -0
- package/dist/systems/interactive/scripted-clips.js +134 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +9 -3
- package/dist/systems/item/object-cut-invalidation.d.ts +3 -0
- package/dist/systems/item/object-cut-invalidation.d.ts.map +1 -0
- package/dist/systems/item/object-cut-invalidation.js +68 -0
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +48 -53
- package/dist/systems/level/level-hold.d.ts +4 -0
- package/dist/systems/level/level-hold.d.ts.map +1 -0
- package/dist/systems/level/level-hold.js +14 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -6
- package/dist/systems/level/level-utils.d.ts +19 -6
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +68 -9
- package/dist/systems/level/shadow-only-levels.d.ts +9 -0
- package/dist/systems/level/shadow-only-levels.d.ts.map +1 -0
- package/dist/systems/level/shadow-only-levels.js +7 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts +103 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts.map +1 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.js +317 -0
- package/dist/systems/roof/roof-materials.d.ts +16 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +64 -31
- package/dist/systems/roof/roof-system.d.ts +21 -4
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +374 -49
- package/dist/systems/slab/slab-system.d.ts +1 -1
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +49 -19
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +133 -354
- package/dist/systems/wall/wall-cutout-cache.d.ts +39 -3
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +141 -19
- package/dist/systems/wall/wall-cutout.d.ts +6 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +42 -28
- package/dist/systems/wall/wall-finish-data.d.ts +22 -0
- package/dist/systems/wall/wall-finish-data.d.ts.map +1 -0
- package/dist/systems/wall/wall-finish-data.js +17 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.d.ts +9 -4
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +135 -153
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.d.ts +33 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +621 -159
- package/dist/systems/window/corner-frame.d.ts +10 -0
- package/dist/systems/window/corner-frame.d.ts.map +1 -0
- package/dist/systems/window/corner-frame.js +17 -0
- package/dist/systems/window/window-system.d.ts +1 -1
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -27
- package/package.json +10 -10
- package/dist/systems/fence/fence-system.d.ts +0 -22
- package/dist/systems/fence/fence-system.d.ts.map +0 -1
- package/dist/systems/fence/fence-system.js +0 -357
|
@@ -14,7 +14,7 @@ export declare const EYE_LERP_SPEED = 12;
|
|
|
14
14
|
* mesh collision) fed a collider built from the artifact's own geometry — so the
|
|
15
15
|
* baked viewer walks the building with the same physics as the editor, without
|
|
16
16
|
* the parametric scene. Pointer-lock drives look; WASD moves; Space jumps; Shift
|
|
17
|
-
* sprints.
|
|
17
|
+
* sprints. Interaction stays in `GlbScene` (its centre-ray HUD).
|
|
18
18
|
*/
|
|
19
19
|
export declare function GlbWalkthroughController({ url }: {
|
|
20
20
|
url: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glb-walkthrough-controller.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-walkthrough-controller.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"glb-walkthrough-controller.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-walkthrough-controller.tsx"],"names":[],"mappings":"AAkCA,eAAO,MAAM,eAAe,KAAK,CAAA;AAkBjC,eAAO,MAAM,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAqB,CAAA;AAChF,eAAO,MAAM,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAqB,CAAA;AACjF,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,eAAO,MAAM,mBAAmB,OAAO,CAAA;AACvC,eAAO,MAAM,iBAAiB,MAAM,CAAA;AACpC,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAInC,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,cAAc,KAAK,CAAA;AAiMhC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,sCA0PhE"}
|
|
@@ -10,6 +10,7 @@ import { useGLTFKTX2 } from '../../hooks/use-gltf-ktx2';
|
|
|
10
10
|
import { SCENE_LAYER } from '../../lib/layers';
|
|
11
11
|
import useViewer from '../../store/use-viewer';
|
|
12
12
|
import BVHEcctrl, {} from './bvh-ecctrl';
|
|
13
|
+
import { applyWalkthroughCameraClipping } from './viewer-camera';
|
|
13
14
|
// First-person FOV. The orbit camera is 50° (set on the Canvas), which feels
|
|
14
15
|
// cramped on foot; ~60° vertical (~90° horizontal at 16:9) restores peripheral
|
|
15
16
|
// awareness without wide-angle distortion. Applied only while walking — both
|
|
@@ -145,6 +146,11 @@ function buildGlbColliderWorld(scene) {
|
|
|
145
146
|
return;
|
|
146
147
|
if (!isEffectivelyVisible(mesh))
|
|
147
148
|
return;
|
|
149
|
+
let ancestor = mesh;
|
|
150
|
+
while (ancestor && !ancestor.userData.proceduralMotion)
|
|
151
|
+
ancestor = ancestor.parent;
|
|
152
|
+
if (ancestor)
|
|
153
|
+
return;
|
|
148
154
|
const kind = kindOf(mesh);
|
|
149
155
|
if (kind && COLLIDER_EXCLUDED_KINDS.has(kind))
|
|
150
156
|
return;
|
|
@@ -172,7 +178,7 @@ function buildGlbColliderWorld(scene) {
|
|
|
172
178
|
;
|
|
173
179
|
merged.computeBoundsTree = computeBoundsTree;
|
|
174
180
|
merged.disposeBoundsTree = disposeBoundsTree;
|
|
175
|
-
merged.computeBoundsTree({
|
|
181
|
+
merged.computeBoundsTree({ targetLeafSize: 12, strategy: 0 });
|
|
176
182
|
merged.computeBoundingBox();
|
|
177
183
|
const mesh = new Mesh(merged, colliderMaterial);
|
|
178
184
|
mesh.raycast = acceleratedRaycast;
|
|
@@ -220,7 +226,7 @@ function resolveGlbSpawn(scene) {
|
|
|
220
226
|
* mesh collision) fed a collider built from the artifact's own geometry — so the
|
|
221
227
|
* baked viewer walks the building with the same physics as the editor, without
|
|
222
228
|
* the parametric scene. Pointer-lock drives look; WASD moves; Space jumps; Shift
|
|
223
|
-
* sprints.
|
|
229
|
+
* sprints. Interaction stays in `GlbScene` (its centre-ray HUD).
|
|
224
230
|
*/
|
|
225
231
|
export function GlbWalkthroughController({ url }) {
|
|
226
232
|
const { camera, gl } = useThree();
|
|
@@ -273,6 +279,11 @@ export function GlbWalkthroughController({ url }) {
|
|
|
273
279
|
cam.updateProjectionMatrix();
|
|
274
280
|
};
|
|
275
281
|
}, [camera]);
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (!camera.isPerspectiveCamera)
|
|
284
|
+
return;
|
|
285
|
+
return applyWalkthroughCameraClipping(camera);
|
|
286
|
+
}, [camera]);
|
|
276
287
|
useEffect(() => {
|
|
277
288
|
worldRef.current = world;
|
|
278
289
|
if (world) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type AnyNodeId } from '@pascal-app/core';
|
|
1
|
+
import { type AnyNodeId, type SitePresentation } from '@pascal-app/core';
|
|
2
2
|
import { type ThreeElement } from '@react-three/fiber';
|
|
3
3
|
import { type ComponentType } from 'react';
|
|
4
4
|
import * as THREE from 'three/webgpu';
|
|
5
5
|
import type { ColorPreset, RenderShading } from '../../lib/materials';
|
|
6
|
+
import { type ViewerBreadcrumbHandler } from '../../lib/pointer-events';
|
|
6
7
|
import { type RenderContext } from '../../store/use-viewer';
|
|
7
8
|
import { type HoverStyles } from './post-processing';
|
|
8
9
|
declare module '@react-three/fiber' {
|
|
@@ -10,6 +11,13 @@ declare module '@react-three/fiber' {
|
|
|
10
11
|
lineBasicNodeMaterial: ThreeElement<typeof THREE.LineBasicNodeMaterial>;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The scene-ready gate's own test as a count — nodes still to build or load
|
|
16
|
+
* (an item holds its mark until its model settles), 1 while the graph has
|
|
17
|
+
* not mounted. A capture waits on it, and reads a count that stops moving
|
|
18
|
+
* as a node no system will ever settle.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pendingSceneBuildCount(): number;
|
|
13
21
|
export interface ViewerImmersiveSession {
|
|
14
22
|
Session: ComponentType<{
|
|
15
23
|
children: React.ReactNode;
|
|
@@ -32,6 +40,8 @@ interface ViewerProps {
|
|
|
32
40
|
textures?: boolean;
|
|
33
41
|
colorPreset?: ColorPreset;
|
|
34
42
|
};
|
|
43
|
+
/** Saved site appearance when rendering a baked asset without its graph. Null ignores it. */
|
|
44
|
+
presentation?: SitePresentation | null;
|
|
35
45
|
/**
|
|
36
46
|
* Visibility filter on the live canvas. When non-null, every registered
|
|
37
47
|
* node group whose id is not in `isolate` (or in the isolated set's
|
|
@@ -49,6 +59,19 @@ interface ViewerProps {
|
|
|
49
59
|
*/
|
|
50
60
|
sceneReadyKey?: string | number | null;
|
|
51
61
|
onSceneReadyChange?: (ready: boolean) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Called when something in the scene throws while rendering (a node
|
|
64
|
+
* renderer or system, such as a plugin's lazy chunk that failed to load).
|
|
65
|
+
* The scene boundary still renders nothing in its place; this lets a host
|
|
66
|
+
* that must not carry on silently (a bake) fail with its own error.
|
|
67
|
+
*/
|
|
68
|
+
onRenderError?: (cause: unknown) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Called with diagnostics worth keeping on a host's error-reporter trail,
|
|
71
|
+
* such as `raycast_invalid_material` when a pointer raycast skips a mesh
|
|
72
|
+
* whose geometry groups point past its material array.
|
|
73
|
+
*/
|
|
74
|
+
onBreadcrumb?: ViewerBreadcrumbHandler;
|
|
52
75
|
/**
|
|
53
76
|
* Wall-clock give-up cap for scene readiness, replacing the default
|
|
54
77
|
* frame-count cap. Set it on hosts whose frame cadence is decoupled from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAGd,KAAK,gBAAgB,EAKtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAkB,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAC1F,OAAO,EACL,KAAK,aAAa,EAOnB,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,EAAuB,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAI5F,OAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AActE,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAa1F,OAAO,QAAQ,oBAAoB,CAAC;IAOlC,UAAU,aAAa;QACrB,qBAAqB,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,qBAAqB,CAAC,CAAA;KACxE;CACF;AAsPD;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAS/C;AAsED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,CAAC,CAAA;IACrD,KAAK,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,CAAC,CAAA;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,WAAW,CAAC,EAAE,WAAW,CAAA;KAC1B,CAAA;IACD,6FAA6F;IAC7F,YAAY,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACtC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACtC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACxC;;;;OAIG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAA;IACtC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,sBAAsB,CAAA;CACnC;AAED,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;OAKG;IACH,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED,QAAA,MAAM,MAAM,sGA2RV,CAAA;eAyEa,MAAM"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import { nodeRegistry, RoofElevationSystem, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { nodeRegistry, RoofElevationSystem, StairOpeningSystem, sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
|
|
5
5
|
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
|
|
6
6
|
import * as THREE from 'three/webgpu';
|
|
@@ -16,9 +16,9 @@ import useViewer, {} from '../../store/use-viewer';
|
|
|
16
16
|
import { FloorElevationSystem } from '../../systems/floor-elevation/floor-elevation-system';
|
|
17
17
|
import { GeometrySystem } from '../../systems/geometry/geometry-system';
|
|
18
18
|
import { PerfActionSettleSystem } from '../../systems/perf-action-settle/perf-action-settle-system';
|
|
19
|
+
import { getPendingRoofMergeCount } from '../../systems/roof/roof-system';
|
|
19
20
|
import { subscribeWallBuildInteractions } from '../../systems/wall/wall-build-lifecycle';
|
|
20
21
|
import { ImmersiveXRPresentationProvider } from '../../xr/presentation-context';
|
|
21
|
-
import { ErrorBoundary } from '../error-boundary';
|
|
22
22
|
import { SceneRenderer } from '../renderers/scene-renderer';
|
|
23
23
|
import { BATCH_SPIKE_ENABLED, BatchedMeshSpike } from './batched-mesh-spike';
|
|
24
24
|
import FrameLimiter from './frame-limiter';
|
|
@@ -28,6 +28,7 @@ import { PerfPanel } from './perf-panel';
|
|
|
28
28
|
import { PointerRaycastLayers } from './pointer-raycast-layers';
|
|
29
29
|
import PostProcessing, { DEFAULT_HOVER_STYLES } from './post-processing';
|
|
30
30
|
import { RegisteredSystems } from './registered-systems';
|
|
31
|
+
import { composeRenderErrorHandlers, SceneErrorBoundary } from './render-error';
|
|
31
32
|
import { useSceneAtmosphere } from './scene-atmosphere';
|
|
32
33
|
import { SceneBvh } from './scene-bvh';
|
|
33
34
|
import { SelectionManager } from './selection-manager';
|
|
@@ -83,6 +84,35 @@ const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakS
|
|
|
83
84
|
* carry the same check inline).
|
|
84
85
|
*/
|
|
85
86
|
function installEmptyDrawGuard(renderer) {
|
|
87
|
+
// The render-object hook is one funnel; the post-processing passes and the
|
|
88
|
+
// outline node reach the backend by other paths, and a geometry whose
|
|
89
|
+
// draw range is empty still slips past a position count. The backend's
|
|
90
|
+
// own `draw` is the last gate before the command encoder — a zero-vertex
|
|
91
|
+
// draw leaves a vertex-buffer slot unbound ("Vertex buffer slot 1 required
|
|
92
|
+
// by [RenderPipeline "…MeshLambertNodeMaterial…"] was not set … Draw(0, …)",
|
|
93
|
+
// 2026-09-10) and poisons the whole encoder, so it is dropped here.
|
|
94
|
+
const backend = renderer.backend;
|
|
95
|
+
if (backend && typeof backend.draw === 'function' && !backend.__pascalDrawGuard) {
|
|
96
|
+
const draw = backend.draw.bind(backend);
|
|
97
|
+
backend.draw = (renderObject, ...rest) => {
|
|
98
|
+
const geometry = renderObject?.geometry;
|
|
99
|
+
const range = geometry?.drawRange;
|
|
100
|
+
if (!hasDrawableGeometry(geometry) || (range && range.count === 0)) {
|
|
101
|
+
if (warnedEmptyDraw && geometry && !warnedEmptyDraw.has(geometry)) {
|
|
102
|
+
warnedEmptyDraw.add(geometry);
|
|
103
|
+
console.warn('[viewer] dropped a zero-vertex draw at the backend', {
|
|
104
|
+
name: renderObject?.object
|
|
105
|
+
?.name,
|
|
106
|
+
type: renderObject?.object
|
|
107
|
+
?.type,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
return draw(renderObject, ...rest);
|
|
113
|
+
};
|
|
114
|
+
backend.__pascalDrawGuard = true;
|
|
115
|
+
}
|
|
86
116
|
renderer.setRenderObjectFunction((object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId) => {
|
|
87
117
|
if (!hasDrawableGeometry(geometry)) {
|
|
88
118
|
if (warnedEmptyDraw && !warnedEmptyDraw.has(geometry ?? object)) {
|
|
@@ -134,16 +164,16 @@ function GPUDeviceWatcher({ intentionalWebGL = false }) {
|
|
|
134
164
|
return null;
|
|
135
165
|
}
|
|
136
166
|
function ToneMappingExposure() {
|
|
137
|
-
const
|
|
167
|
+
const appearance = useViewer((state) => state.sceneAppearance);
|
|
138
168
|
const atmosphere = useSceneAtmosphere();
|
|
139
169
|
const gl = useThree((state) => state.gl);
|
|
140
170
|
const invalidate = useThree((state) => state.invalidate);
|
|
141
171
|
useEffect(() => {
|
|
142
172
|
if (atmosphere)
|
|
143
173
|
return;
|
|
144
|
-
gl.toneMappingExposure =
|
|
174
|
+
gl.toneMappingExposure = appearance.toneMappingExposure;
|
|
145
175
|
invalidate();
|
|
146
|
-
}, [atmosphere, gl, invalidate,
|
|
176
|
+
}, [atmosphere, gl, invalidate, appearance]);
|
|
147
177
|
useFrame(() => {
|
|
148
178
|
if (!atmosphere)
|
|
149
179
|
return;
|
|
@@ -152,34 +182,54 @@ function ToneMappingExposure() {
|
|
|
152
182
|
return null;
|
|
153
183
|
}
|
|
154
184
|
function ImmersiveXRBackground() {
|
|
155
|
-
const background = useViewer((state) =>
|
|
185
|
+
const background = useViewer((state) => state.sceneAppearance.background);
|
|
156
186
|
return _jsx("color", { args: [background], attach: "background" });
|
|
157
187
|
}
|
|
188
|
+
function isPendingSceneBuild(id, { nodes, rootNodeIds }) {
|
|
189
|
+
const node = nodes[id];
|
|
190
|
+
if (!node)
|
|
191
|
+
return false;
|
|
192
|
+
// Unreachable nodes (orphaned by a broken detach: the parent doesn't list
|
|
193
|
+
// them in `children`, or no parent and not a root) never render — every
|
|
194
|
+
// renderer enumerates the parent's `children` array — so no system will
|
|
195
|
+
// ever build them or clear their mark. They must not hold scene-ready
|
|
196
|
+
// hostage (observed in prod scene data: two dangling windows kept every
|
|
197
|
+
// bake of that scene waiting out the full readiness cap).
|
|
198
|
+
const parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
199
|
+
const reachable = parent
|
|
200
|
+
? parent.children?.includes(id) === true
|
|
201
|
+
: rootNodeIds.includes(id);
|
|
202
|
+
if (!reachable)
|
|
203
|
+
return false;
|
|
204
|
+
const def = nodeRegistry.get(node.type);
|
|
205
|
+
return Boolean(def?.geometry || def?.capabilities?.floorPlaced || DIRTY_BUILD_KINDS.has(node.type));
|
|
206
|
+
}
|
|
158
207
|
function hasPendingSceneBuildWork() {
|
|
159
|
-
const
|
|
160
|
-
for (const id of dirtyNodes) {
|
|
161
|
-
|
|
162
|
-
if (!node)
|
|
163
|
-
continue;
|
|
164
|
-
// Unreachable nodes (orphaned by a broken detach: the parent doesn't list
|
|
165
|
-
// them in `children`, or no parent and not a root) never render — every
|
|
166
|
-
// renderer enumerates the parent's `children` array — so no system will
|
|
167
|
-
// ever build them or clear their mark. They must not hold scene-ready
|
|
168
|
-
// hostage (observed in prod scene data: two dangling windows kept every
|
|
169
|
-
// bake of that scene waiting out the full readiness cap).
|
|
170
|
-
const parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
171
|
-
const reachable = parent
|
|
172
|
-
? parent.children?.includes(id) === true
|
|
173
|
-
: rootNodeIds.includes(id);
|
|
174
|
-
if (!reachable)
|
|
175
|
-
continue;
|
|
176
|
-
const def = nodeRegistry.get(node.type);
|
|
177
|
-
if (def?.geometry || def?.capabilities?.floorPlaced || DIRTY_BUILD_KINDS.has(node.type)) {
|
|
208
|
+
const state = useScene.getState();
|
|
209
|
+
for (const id of state.dirtyNodes) {
|
|
210
|
+
if (isPendingSceneBuild(id, state))
|
|
178
211
|
return true;
|
|
179
|
-
}
|
|
180
212
|
}
|
|
181
213
|
return false;
|
|
182
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* The scene-ready gate's own test as a count — nodes still to build or load
|
|
217
|
+
* (an item holds its mark until its model settles), 1 while the graph has
|
|
218
|
+
* not mounted. A capture waits on it, and reads a count that stops moving
|
|
219
|
+
* as a node no system will ever settle.
|
|
220
|
+
*/
|
|
221
|
+
export function pendingSceneBuildCount() {
|
|
222
|
+
if (!hasCommittedSceneRoot())
|
|
223
|
+
return 1;
|
|
224
|
+
const state = useScene.getState();
|
|
225
|
+
// A roof's dirty mark clears when its merged shell is queued, frames before it is built.
|
|
226
|
+
let count = getPendingRoofMergeCount();
|
|
227
|
+
for (const id of state.dirtyNodes) {
|
|
228
|
+
if (isPendingSceneBuild(id, state))
|
|
229
|
+
count++;
|
|
230
|
+
}
|
|
231
|
+
return count;
|
|
232
|
+
}
|
|
183
233
|
function hasCommittedSceneRoot() {
|
|
184
234
|
const { nodes, rootNodeIds } = useScene.getState();
|
|
185
235
|
if (rootNodeIds.length === 0)
|
|
@@ -233,7 +283,7 @@ function SceneReadyTracker({ onSceneReadyChange, sceneReadyKey, sceneReadyMaxWai
|
|
|
233
283
|
}, 10);
|
|
234
284
|
return null;
|
|
235
285
|
}
|
|
236
|
-
const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, renderContext = 'editor', transparent, defaultRender, isolate, sceneReadyKey, onSceneReadyChange, sceneReadyMaxWaitMs, maxFps = 50, disablePostFx = false, renderPaused = false, immersive, }, ref) {
|
|
286
|
+
const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, renderContext = 'editor', transparent, defaultRender, presentation: suppliedPresentation, isolate, sceneReadyKey, onSceneReadyChange, onRenderError, onBreadcrumb, sceneReadyMaxWaitMs, maxFps = 50, disablePostFx = false, renderPaused = false, immersive, }, ref) {
|
|
237
287
|
useEffect(() => {
|
|
238
288
|
if (nodeRegistry.size === 0) {
|
|
239
289
|
console.warn('[viewer] Node registry is empty. Install @pascal-app/nodes and call await loadPlugin(builtinPlugin) before mounting <Viewer>.');
|
|
@@ -256,9 +306,19 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
256
306
|
clearIsolation();
|
|
257
307
|
};
|
|
258
308
|
}, [isolate]);
|
|
259
|
-
const
|
|
309
|
+
const onBreadcrumbRef = useRef(onBreadcrumb);
|
|
310
|
+
onBreadcrumbRef.current = onBreadcrumb;
|
|
311
|
+
const [pointerEvents] = useState(() => choosePointerEvents(undefined, (breadcrumb) => onBreadcrumbRef.current?.(breadcrumb)));
|
|
260
312
|
const [rendererInitFailed, setRendererInitFailed] = useState(false);
|
|
261
|
-
const isDark = useViewer((state) =>
|
|
313
|
+
const isDark = useViewer((state) => state.sceneAppearance.appearance === 'dark');
|
|
314
|
+
const sceneTheme = useViewer((state) => state.sceneTheme);
|
|
315
|
+
const previousLampTheme = useRef(sceneTheme);
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
useInteractive
|
|
318
|
+
.getState()
|
|
319
|
+
.setLampDefault(getSceneTheme(sceneTheme).appearance === 'dark', previousLampTheme.current !== sceneTheme);
|
|
320
|
+
previousLampTheme.current = sceneTheme;
|
|
321
|
+
}, [sceneTheme]);
|
|
262
322
|
const transparentBackground = useViewer((state) => state.transparentBackground);
|
|
263
323
|
// The shadows toggle drives `renderer.shadowMap.enabled` (via the Canvas
|
|
264
324
|
// `shadows` prop) rather than the lights' `castShadow`: toggling castShadow
|
|
@@ -276,15 +336,28 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
276
336
|
useViewer.getState().setTransparentBackground(false);
|
|
277
337
|
};
|
|
278
338
|
}, [transparent]);
|
|
339
|
+
const scenePresentation = useScene((state) => {
|
|
340
|
+
for (const id of state.rootNodeIds) {
|
|
341
|
+
const node = state.nodes[id];
|
|
342
|
+
if (node?.type === 'site')
|
|
343
|
+
return node.presentation;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
});
|
|
347
|
+
const presentation = suppliedPresentation === undefined ? scenePresentation : (suppliedPresentation ?? undefined);
|
|
348
|
+
const projectId = useViewer((state) => state.projectId);
|
|
349
|
+
useLayoutEffect(() => {
|
|
350
|
+
// Project selection clears the previous look even when both graphs share a preset.
|
|
351
|
+
if (useViewer.getState().projectId === projectId)
|
|
352
|
+
useViewer.getState().setProjectPresentation(presentation);
|
|
353
|
+
}, [presentation, projectId]);
|
|
279
354
|
const defaultShading = defaultRender?.shading;
|
|
280
355
|
const defaultTextures = defaultRender?.textures;
|
|
281
356
|
const defaultColorPreset = defaultRender?.colorPreset;
|
|
282
357
|
const hasDefaultRender = defaultRender != null;
|
|
283
358
|
useEffect(() => {
|
|
284
359
|
const ctx = renderContext;
|
|
285
|
-
useViewer.getState().setRenderContext(ctx);
|
|
286
|
-
const { shading, shadingByContext, setShading } = useViewer.getState();
|
|
287
|
-
setShading(shadingByContext[ctx] ?? defaultShading ?? shading);
|
|
360
|
+
useViewer.getState().setRenderContext(ctx, defaultShading);
|
|
288
361
|
if (!hasDefaultRender || typeof window === 'undefined')
|
|
289
362
|
return;
|
|
290
363
|
let persistedState = {};
|
|
@@ -354,7 +427,8 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
354
427
|
trackTimestamp: PERF_OVERLAY_ENABLED,
|
|
355
428
|
});
|
|
356
429
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
357
|
-
renderer.toneMappingExposure =
|
|
430
|
+
renderer.toneMappingExposure =
|
|
431
|
+
useViewer.getState().sceneAppearance.toneMappingExposure;
|
|
358
432
|
return renderer;
|
|
359
433
|
},
|
|
360
434
|
});
|
|
@@ -382,11 +456,11 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
382
456
|
}, shadows: {
|
|
383
457
|
type: THREE.PCFShadowMap,
|
|
384
458
|
enabled: shadowsEnabled,
|
|
385
|
-
}, children: _jsx(ImmersiveXRPresentationProvider, { enabled: immersiveActive, children: ImmersiveSession ? (_jsx(ImmersiveSession, { children: _jsx(ViewerScene, { disablePostFx: true, hoverStyles: hoverStyles, immersiveXR: true,
|
|
459
|
+
}, children: _jsx(ImmersiveXRPresentationProvider, { enabled: immersiveActive, children: ImmersiveSession ? (_jsx(ImmersiveSession, { children: _jsx(ViewerScene, { disablePostFx: true, hoverStyles: hoverStyles, immersiveXR: true, renderErrorHandlers: [immersive.onError, onRenderError], onSceneReadyChange: onSceneReadyChange, perf: perf, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs, selectionManager: selectionManager, useBvh: useBvh, SceneWrapper: immersive.Scene, children: children }) })) : (_jsxs(_Fragment, { children: [_jsx(FrameLimiter, { fps: maxFps, onFrameError: composeRenderErrorHandlers(onRenderError), paused: renderPaused }), _jsx(ViewerScene, { disablePostFx: disablePostFx, hoverStyles: hoverStyles, renderErrorHandlers: [onRenderError], onSceneReadyChange: onSceneReadyChange, perf: perf, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs, selectionManager: selectionManager, useBvh: useBvh, children: children })] })) }) }, immersiveActive ? 'webgl-xr' : 'webgpu')] }));
|
|
386
460
|
});
|
|
387
|
-
function ViewerScene({ children, disablePostFx, hoverStyles, immersiveXR = false,
|
|
461
|
+
function ViewerScene({ children, disablePostFx, hoverStyles, immersiveXR = false, renderErrorHandlers, onSceneReadyChange, perf, sceneReadyKey, sceneReadyMaxWaitMs, selectionManager, useBvh, SceneWrapper, }) {
|
|
388
462
|
const renderedScene = useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {}));
|
|
389
463
|
const spatialScene = (_jsxs(_Fragment, { children: [renderedScene, _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RoofElevationSystem, {}), _jsx(RegisteredSystems, {}), BATCH_SPIKE_ENABLED && _jsx(BatchedMeshSpike, {}), children] }));
|
|
390
|
-
return (_jsxs(_Fragment, { children: [_jsx(ViewerCamera, { immersiveXR: immersiveXR }), immersiveXR && _jsx(ImmersiveXRBackground, {}), _jsx(PointerRaycastLayers, {}), _jsx(GPUDeviceWatcher, { intentionalWebGL: immersiveXR }), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(
|
|
464
|
+
return (_jsxs(_Fragment, { children: [_jsx(ViewerCamera, { immersiveXR: immersiveXR }), immersiveXR && _jsx(ImmersiveXRBackground, {}), _jsx(PointerRaycastLayers, {}), _jsx(GPUDeviceWatcher, { intentionalWebGL: immersiveXR }), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(SceneErrorBoundary, { handlers: renderErrorHandlers, children: [_jsx(Lights, {}), SceneWrapper ? _jsx(SceneWrapper, { children: spatialScene }) : spatialScene, !immersiveXR && _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfActionSettleSystem, {})] })] }));
|
|
391
465
|
}
|
|
392
466
|
export default Viewer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AAmGA,wBAAgB,MAAM,gCAiVrB"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { sceneRegistry } from '@pascal-app/core';
|
|
3
3
|
import { useFrame } from '@react-three/fiber';
|
|
4
|
-
import { useMemo, useRef } from 'react';
|
|
4
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
5
5
|
import * as THREE from 'three/webgpu';
|
|
6
|
+
import { getKeyLightDirectionOverride } from '../../lib/key-light-override';
|
|
6
7
|
import { SHADOW_ONLY_LAYER } from '../../lib/layers';
|
|
7
|
-
import {
|
|
8
|
-
import useViewer from '../../store/use-viewer';
|
|
8
|
+
import { registerSkyLights } from '../../lib/underground-ambiance';
|
|
9
|
+
import useViewer, { groundVeiled } from '../../store/use-viewer';
|
|
9
10
|
import { useSceneAtmosphere } from './scene-atmosphere';
|
|
11
|
+
import { UndergroundAmbiance } from './underground-ambiance';
|
|
10
12
|
// Diagnostic toggle: `?disable=shadows` skips the shadow-map render pass
|
|
11
13
|
// (which doubles draw calls for every shadow-casting mesh) so you can
|
|
12
14
|
// isolate how much of the baseline GPU cost is shadows vs. raw geometry.
|
|
@@ -58,10 +60,42 @@ const SHADOW_MARGIN = 3;
|
|
|
58
60
|
const SHADOW_BACKOFF = 10;
|
|
59
61
|
// Fallback radius when the scene has no building geometry yet (empty scene).
|
|
60
62
|
const SHADOW_FALLBACK_RADIUS = 30;
|
|
63
|
+
/**
|
|
64
|
+
* The live counterpart of the interior capture's lighting: a light that shines from the eye along
|
|
65
|
+
* the view, casting no shadow. Over a veiled ground the floor below grade still lies in the
|
|
66
|
+
* shadow of the storeys above and has no sky: this keeps it from reading as a cave. The
|
|
67
|
+
* underground ambiance owns its intensity (the veil's 1 at rest, brought up as the sky's light
|
|
68
|
+
* comes down when the eye dives, back to rest in a capture); the hemisphere's ground side is
|
|
69
|
+
* lifted in `Lights`.
|
|
70
|
+
*/
|
|
71
|
+
function UndergroundFill({ lightRef }) {
|
|
72
|
+
const look = useMemo(() => new THREE.Vector3(), []);
|
|
73
|
+
useFrame(({ camera }) => {
|
|
74
|
+
const fill = lightRef.current;
|
|
75
|
+
if (!fill)
|
|
76
|
+
return;
|
|
77
|
+
camera.getWorldPosition(fill.position);
|
|
78
|
+
fill.target.position.copy(fill.position).add(camera.getWorldDirection(look));
|
|
79
|
+
fill.target.updateMatrixWorld();
|
|
80
|
+
});
|
|
81
|
+
return _jsx("directionalLight", { intensity: 0, ref: lightRef });
|
|
82
|
+
}
|
|
61
83
|
export function Lights() {
|
|
62
|
-
const
|
|
63
|
-
const theme = getSceneTheme(sceneTheme);
|
|
84
|
+
const theme = useViewer((state) => state.sceneAppearance);
|
|
64
85
|
const shadows = useViewer((state) => state.shadows);
|
|
86
|
+
const veiled = useViewer(groundVeiled);
|
|
87
|
+
// The eye's fill is mounted once, and stays: adding or taking away a light changes every lit
|
|
88
|
+
// material's shader (a runtime compile that lagged the switch to a basement), so it is there
|
|
89
|
+
// from the moment the scene has a level below grade or the eye may dive (or a view first needs
|
|
90
|
+
// it) and only its intensity, the ambiance's, moves.
|
|
91
|
+
const wantsFill = useViewer((state) => state.hasLevelBelowGrade ||
|
|
92
|
+
state.diveAvailable ||
|
|
93
|
+
groundVeiled(state) ||
|
|
94
|
+
state.ambianceActive);
|
|
95
|
+
const fillMounted = useRef(false);
|
|
96
|
+
if (wantsFill)
|
|
97
|
+
fillMounted.current = true;
|
|
98
|
+
const filled = fillMounted.current;
|
|
65
99
|
const atmosphere = useSceneAtmosphere();
|
|
66
100
|
const lightSlots = useMemo(() => Array.from({ length: atmosphere ? 2 : theme.lights.length }, (_, index) => index), [atmosphere, theme.lights.length]);
|
|
67
101
|
const lightRefs = useRef([]);
|
|
@@ -78,6 +112,17 @@ export function Lights() {
|
|
|
78
112
|
const shadowHelpers = useRef([]);
|
|
79
113
|
const hemiRef = useRef(null);
|
|
80
114
|
const ambientRef = useRef(null);
|
|
115
|
+
const fillRef = useRef(null);
|
|
116
|
+
// What the eye's fill shines at with no ambiance: the veil's own 1 over a veiled ground.
|
|
117
|
+
const fillRest = useRef(0);
|
|
118
|
+
fillRest.current = veiled ? 1 : 0;
|
|
119
|
+
// The underground ambiance moves the sky's own light and the eye's fill for the frame it draws.
|
|
120
|
+
useEffect(() => registerSkyLights(() => ({
|
|
121
|
+
ambient: ambientRef.current,
|
|
122
|
+
hemisphere: hemiRef.current,
|
|
123
|
+
fill: fillRef.current,
|
|
124
|
+
fillRest: fillRest.current,
|
|
125
|
+
})), []);
|
|
81
126
|
const initialized = useRef(false);
|
|
82
127
|
const lightTargets = useRef([]);
|
|
83
128
|
const targets = useMemo(() => ({
|
|
@@ -88,6 +133,8 @@ export function Lights() {
|
|
|
88
133
|
useFrame((state, delta) => {
|
|
89
134
|
// Clamp delta to avoid huge jumps on tab switch.
|
|
90
135
|
const dt = Math.min(delta, 0.1) * 4;
|
|
136
|
+
const sourceShadow = atmosphere ? undefined : theme.lights[0]?.shadow;
|
|
137
|
+
const sourceCamera = sourceShadow?.camera;
|
|
91
138
|
// Atmosphere directions are stable mutable vectors. Keep both light
|
|
92
139
|
// resources mounted and move them imperatively so time/angle changes never
|
|
93
140
|
// rebuild materials or shadow resources.
|
|
@@ -102,12 +149,23 @@ export function Lights() {
|
|
|
102
149
|
light.target.updateMatrixWorld();
|
|
103
150
|
}
|
|
104
151
|
}
|
|
152
|
+
else {
|
|
153
|
+
for (const index of lightSlots) {
|
|
154
|
+
const light = lightRefs.current[index];
|
|
155
|
+
const config = theme.lights[index];
|
|
156
|
+
if (!light || !config || (index === 0 && shadows && !sourceCamera))
|
|
157
|
+
continue;
|
|
158
|
+
light.position.set(...config.position);
|
|
159
|
+
light.target.position.set(...(config.target ?? [0, 0, 0]));
|
|
160
|
+
light.target.updateMatrixWorld();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
105
163
|
// Fit the single key-light shadow frustum to the BUILDING geometry rather
|
|
106
164
|
// than the camera. The source controls its direction; only its distance and
|
|
107
165
|
// frustum extents are derived here.
|
|
108
166
|
if (shadows) {
|
|
109
167
|
const now = state.clock.elapsedTime;
|
|
110
|
-
if (now - lastBoundsTime.current >= BOUNDS_REFRESH_INTERVAL) {
|
|
168
|
+
if (!sourceCamera && now - lastBoundsTime.current >= BOUNDS_REFRESH_INTERVAL) {
|
|
111
169
|
lastBoundsTime.current = now;
|
|
112
170
|
const box = boundsBox.current.makeEmpty();
|
|
113
171
|
for (const [id, obj] of sceneRegistry.nodes) {
|
|
@@ -137,15 +195,27 @@ export function Lights() {
|
|
|
137
195
|
const castsShadow = atmosphere ? true : Boolean(themeKey?.castShadow);
|
|
138
196
|
if (light && castsShadow) {
|
|
139
197
|
const focus = shadowFocus.current;
|
|
140
|
-
const size = shadowRadius.current * SHADOW_MARGIN_SCALE +
|
|
198
|
+
const size = shadowRadius.current * SHADOW_MARGIN_SCALE +
|
|
199
|
+
Math.min(SHADOW_MARGIN, shadowRadius.current * 0.25);
|
|
141
200
|
const distance = size + SHADOW_BACKOFF;
|
|
142
201
|
const near = SHADOW_BACKOFF;
|
|
143
|
-
|
|
144
|
-
|
|
202
|
+
// Receivers can lie beyond the caster's bounds, especially under a low sun.
|
|
203
|
+
const far = distance + shadowRadius.current * SHADOW_MARGIN_SCALE + SHADOW_MARGIN;
|
|
204
|
+
const directionOverride = getKeyLightDirectionOverride();
|
|
205
|
+
if (directionOverride) {
|
|
206
|
+
shadowDir.current.set(...directionOverride);
|
|
207
|
+
}
|
|
208
|
+
else if (atmosphere) {
|
|
145
209
|
shadowDir.current.copy(atmosphere.sunDirection);
|
|
146
210
|
}
|
|
147
211
|
else if (themeKey) {
|
|
148
212
|
shadowDir.current.set(...themeKey.position);
|
|
213
|
+
if (themeKey.target) {
|
|
214
|
+
const [x, y, z] = themeKey.target;
|
|
215
|
+
shadowDir.current.x -= x;
|
|
216
|
+
shadowDir.current.y -= y;
|
|
217
|
+
shadowDir.current.z -= z;
|
|
218
|
+
}
|
|
149
219
|
}
|
|
150
220
|
else {
|
|
151
221
|
shadowDir.current.set(0, 1, 0);
|
|
@@ -153,18 +223,38 @@ export function Lights() {
|
|
|
153
223
|
if (shadowDir.current.lengthSq() === 0)
|
|
154
224
|
shadowDir.current.set(0, 1, 0);
|
|
155
225
|
shadowDir.current.normalize().multiplyScalar(distance);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
226
|
+
if (!sourceCamera) {
|
|
227
|
+
light.position.copy(focus).add(shadowDir.current);
|
|
228
|
+
light.target.position.copy(focus);
|
|
229
|
+
light.target.updateMatrixWorld();
|
|
230
|
+
}
|
|
231
|
+
else if (directionOverride && themeKey) {
|
|
232
|
+
const [x, y, z] = themeKey.position;
|
|
233
|
+
const [tx, ty, tz] = themeKey.target ?? [0, 0, 0];
|
|
234
|
+
shadowDir.current.normalize().multiplyScalar(Math.hypot(x - tx, y - ty, z - tz) || 1);
|
|
235
|
+
light.position.set(tx, ty, tz).add(shadowDir.current);
|
|
236
|
+
}
|
|
159
237
|
const cam = light.shadow?.camera;
|
|
160
238
|
if (cam) {
|
|
239
|
+
// The calibrated building bias detaches furniture-scale contact shadows.
|
|
240
|
+
// Small scenes need finer shadow edges too; retain the building map budget.
|
|
241
|
+
const smallScene = !sourceCamera && shadowRadius.current < 1.5;
|
|
242
|
+
light.shadow.mapSize.setScalar(sourceShadow?.mapSize ?? (smallScene ? 2048 : 1024));
|
|
243
|
+
light.shadow.bias = sourceShadow?.bias ?? SHADOW_DEPTH_BIAS;
|
|
244
|
+
light.shadow.radius = sourceShadow?.radius ?? 2;
|
|
245
|
+
light.shadow.normalBias =
|
|
246
|
+
sourceShadow?.normalBias ??
|
|
247
|
+
(smallScene
|
|
248
|
+
? Math.min(SHADOW_NORMAL_BIAS, (4 * size) / light.shadow.mapSize.x)
|
|
249
|
+
: SHADOW_NORMAL_BIAS);
|
|
161
250
|
cam.layers.enable(SHADOW_ONLY_LAYER);
|
|
162
|
-
|
|
163
|
-
cam.
|
|
164
|
-
cam.
|
|
165
|
-
cam.
|
|
166
|
-
cam.
|
|
167
|
-
cam.
|
|
251
|
+
// A retained source frustum keeps distant context from diluting the building's shadows.
|
|
252
|
+
cam.left = sourceCamera?.left ?? -size;
|
|
253
|
+
cam.right = sourceCamera?.right ?? size;
|
|
254
|
+
cam.top = sourceCamera?.top ?? size;
|
|
255
|
+
cam.bottom = sourceCamera?.bottom ?? -size;
|
|
256
|
+
cam.near = sourceCamera?.near ?? near;
|
|
257
|
+
cam.far = sourceCamera?.far ?? far;
|
|
168
258
|
cam.updateProjectionMatrix();
|
|
169
259
|
if (SHADOW_CAMERA_DEBUG) {
|
|
170
260
|
let helper = shadowHelpers.current[0];
|
|
@@ -210,7 +300,7 @@ export function Lights() {
|
|
|
210
300
|
light.color.lerp(target, dt);
|
|
211
301
|
}
|
|
212
302
|
if (index === 0 && light.shadow?.intensity !== undefined) {
|
|
213
|
-
const shadowIntensity = intensity <= 1 ? intensity : MAX_SHADOW_INTENSITY;
|
|
303
|
+
const shadowIntensity = sourceShadow?.intensity ?? (intensity <= 1 ? intensity : MAX_SHADOW_INTENSITY);
|
|
214
304
|
light.shadow.intensity =
|
|
215
305
|
atmosphere || !initialized.current
|
|
216
306
|
? shadowIntensity
|
|
@@ -219,7 +309,8 @@ export function Lights() {
|
|
|
219
309
|
}
|
|
220
310
|
const hemiIntensity = atmosphere ? atmosphere.hemisphereIntensity : theme.hemi?.intensity;
|
|
221
311
|
const hemiSky = atmosphere ? atmosphere.skyColor : theme.hemi?.sky;
|
|
222
|
-
|
|
312
|
+
// Over a veiled ground the floor is lit from below as from above, as in an interior capture.
|
|
313
|
+
const hemiGround = veiled ? hemiSky : atmosphere ? atmosphere.groundColor : theme.hemi?.ground;
|
|
223
314
|
if (hemiRef.current && hemiIntensity !== undefined && hemiSky && hemiGround) {
|
|
224
315
|
if (atmosphere || !initialized.current) {
|
|
225
316
|
hemiRef.current.intensity = hemiIntensity;
|
|
@@ -262,5 +353,5 @@ export function Lights() {
|
|
|
262
353
|
: (themeLight?.position ?? [0, 1, 0]), ref: (light) => {
|
|
263
354
|
lightRefs.current[index] = light;
|
|
264
355
|
}, "shadow-bias": SHADOW_DEPTH_BIAS, "shadow-mapSize": [1024, 1024], "shadow-normalBias": SHADOW_NORMAL_BIAS, "shadow-radius": 2, children: index === 0 && keyCastsShadow ? (_jsx("orthographicCamera", { attach: "shadow-camera", bottom: -shadowCameraSize, far: 400, left: -shadowCameraSize, near: 1, ref: shadowCamera, right: shadowCameraSize, top: shadowCameraSize })) : null }, index));
|
|
265
|
-
}), atmosphere || theme.hemi ? _jsx("hemisphereLight", { ref: hemiRef }) : null, _jsx("ambientLight", { ref: ambientRef })] }));
|
|
356
|
+
}), atmosphere || theme.hemi ? _jsx("hemisphereLight", { ref: hemiRef }) : null, _jsx("ambientLight", { ref: ambientRef }), filled && _jsx(UndergroundFill, { lightRef: fillRef }), _jsx(UndergroundAmbiance, {})] }));
|
|
266
357
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perf-monitor.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-monitor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"perf-monitor.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-monitor.tsx"],"names":[],"mappings":"AA6CA;;;;GAIG;AACH,eAAO,MAAM,WAAW,YA2TvB,CAAA"}
|
|
@@ -4,7 +4,9 @@ import { useEffect, useRef } from 'react';
|
|
|
4
4
|
import { Vector3 } from 'three';
|
|
5
5
|
import { initPerfObservers } from '../../lib/perf-observers';
|
|
6
6
|
import { publishPerfStats, readPerfBatchStats } from '../../lib/perf-panel-store';
|
|
7
|
+
import { readPerfHistory, readPerfSelection } from '../../lib/perf-probe-state';
|
|
7
8
|
import { clearPerfMeasures, drainPerfCounters } from '../../lib/perf-tracks';
|
|
9
|
+
import useViewer from '../../store/use-viewer';
|
|
8
10
|
const SAMPLE_INTERVAL = 0.5; // seconds between display updates
|
|
9
11
|
// Walking the scene graph is the overlay's own biggest cost on large projects,
|
|
10
12
|
// and the counts barely move between ticks — sample it at 2s instead of 0.5s.
|
|
@@ -73,6 +75,10 @@ export const PerfMonitor = () => {
|
|
|
73
75
|
useEffect(() => {
|
|
74
76
|
const probe = {
|
|
75
77
|
batchStats: readPerfBatchStats,
|
|
78
|
+
// Undo/redo entry counts and history pause state, and the selection path: what a
|
|
79
|
+
// gesture receipt needs on a production build, where the stores have no handle.
|
|
80
|
+
history: () => readPerfHistory(useScene),
|
|
81
|
+
selection: () => readPerfSelection(useViewer),
|
|
76
82
|
listNodes(type) {
|
|
77
83
|
return Object.values(useScene.getState().nodes)
|
|
78
84
|
.filter((n) => n.type === type)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perf-panel.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-panel.tsx"],"names":[],"mappings":"AA2GA,eAAO,MAAM,SAAS,0CA2MrB,CAAA;
|
|
1
|
+
{"version":3,"file":"perf-panel.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-panel.tsx"],"names":[],"mappings":"AA2GA,eAAO,MAAM,SAAS,0CA2MrB,CAAA;eAEc,SAAS"}
|