@pascal-app/viewer 1.0.3 → 1.0.4
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/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +8 -3
- 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 +252 -85
- 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-scene.d.ts +45 -27
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +420 -506
- 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 +12 -1
- package/dist/components/viewer/index.d.ts +14 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +84 -26
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +6 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +8 -3
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +16 -2
- 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-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 +10 -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 +11 -1
- package/dist/index.d.ts +22 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -14
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +7 -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/hero-pose.d.ts +12 -2
- 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 +4 -2
- 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/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 +14 -7
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +31 -5
- 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 +8 -0
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +27 -4
- 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/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-visibility.d.ts +6 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +15 -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/snapshot-pipeline.d.ts +32 -2
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +215 -105
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
- package/dist/lib/terrain-perimeter-fill.js +22 -14
- 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 +11 -0
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +13 -6
- 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 +86 -6
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +55 -17
- 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/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +40 -15
- 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 +47 -52
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +3 -5
- package/dist/systems/level/level-utils.d.ts +7 -4
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +17 -6
- 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/roof-materials.d.ts +14 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +54 -15
- package/dist/systems/roof/roof-system.d.ts +13 -3
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +188 -13
- 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 +7 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +46 -17
- 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-materials.d.ts +9 -4
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +99 -152
- package/dist/systems/wall/wall-system.d.ts +28 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +582 -143
- 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 +41 -22
- package/package.json +5 -5
- 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;
|
|
@@ -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) {
|
|
@@ -10,6 +10,13 @@ declare module '@react-three/fiber' {
|
|
|
10
10
|
lineBasicNodeMaterial: ThreeElement<typeof THREE.LineBasicNodeMaterial>;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The scene-ready gate's own test as a count — nodes still to build or load
|
|
15
|
+
* (an item holds its mark until its model settles), 1 while the graph has
|
|
16
|
+
* not mounted. A capture waits on it, and reads a count that stops moving
|
|
17
|
+
* as a node no system will ever settle.
|
|
18
|
+
*/
|
|
19
|
+
export declare function pendingSceneBuildCount(): number;
|
|
13
20
|
export interface ViewerImmersiveSession {
|
|
14
21
|
Session: ComponentType<{
|
|
15
22
|
children: React.ReactNode;
|
|
@@ -49,6 +56,13 @@ interface ViewerProps {
|
|
|
49
56
|
*/
|
|
50
57
|
sceneReadyKey?: string | number | null;
|
|
51
58
|
onSceneReadyChange?: (ready: boolean) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Called when something in the scene throws while rendering (a node
|
|
61
|
+
* renderer or system, such as a plugin's lazy chunk that failed to load).
|
|
62
|
+
* The scene boundary still renders nothing in its place; this lets a host
|
|
63
|
+
* that must not carry on silently (a bake) fail with its own error.
|
|
64
|
+
*/
|
|
65
|
+
onRenderError?: (cause: unknown) => void;
|
|
52
66
|
/**
|
|
53
67
|
* Wall-clock give-up cap for scene readiness, replacing the default
|
|
54
68
|
* 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,EAOf,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;AAKrE,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;;;;;;;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;;;;;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,sGAwQV,CAAA;AAyEF,eAAe,MAAM,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { nodeRegistry, RoofElevationSystem, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
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)) {
|
|
@@ -155,31 +185,51 @@ function ImmersiveXRBackground() {
|
|
|
155
185
|
const background = useViewer((state) => getSceneTheme(state.sceneTheme).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, isolate, sceneReadyKey, onSceneReadyChange, onRenderError, 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>.');
|
|
@@ -259,6 +309,14 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
259
309
|
const [pointerEvents] = useState(() => choosePointerEvents());
|
|
260
310
|
const [rendererInitFailed, setRendererInitFailed] = useState(false);
|
|
261
311
|
const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
|
|
312
|
+
const sceneTheme = useViewer((state) => state.sceneTheme);
|
|
313
|
+
const previousLampTheme = useRef(sceneTheme);
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
useInteractive
|
|
316
|
+
.getState()
|
|
317
|
+
.setLampDefault(getSceneTheme(sceneTheme).appearance === 'dark', previousLampTheme.current !== sceneTheme);
|
|
318
|
+
previousLampTheme.current = sceneTheme;
|
|
319
|
+
}, [sceneTheme]);
|
|
262
320
|
const transparentBackground = useViewer((state) => state.transparentBackground);
|
|
263
321
|
// The shadows toggle drives `renderer.shadowMap.enabled` (via the Canvas
|
|
264
322
|
// `shadows` prop) rather than the lights' `castShadow`: toggling castShadow
|
|
@@ -382,11 +440,11 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
382
440
|
}, shadows: {
|
|
383
441
|
type: THREE.PCFShadowMap,
|
|
384
442
|
enabled: shadowsEnabled,
|
|
385
|
-
}, children: _jsx(ImmersiveXRPresentationProvider, { enabled: immersiveActive, children: ImmersiveSession ? (_jsx(ImmersiveSession, { children: _jsx(ViewerScene, { disablePostFx: true, hoverStyles: hoverStyles, immersiveXR: true,
|
|
443
|
+
}, 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
444
|
});
|
|
387
|
-
function ViewerScene({ children, disablePostFx, hoverStyles, immersiveXR = false,
|
|
445
|
+
function ViewerScene({ children, disablePostFx, hoverStyles, immersiveXR = false, renderErrorHandlers, onSceneReadyChange, perf, sceneReadyKey, sceneReadyMaxWaitMs, selectionManager, useBvh, SceneWrapper, }) {
|
|
388
446
|
const renderedScene = useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {}));
|
|
389
447
|
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(
|
|
448
|
+
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
449
|
}
|
|
392
450
|
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":"AA8EA,wBAAgB,MAAM,gCAwQrB"}
|
|
@@ -3,6 +3,7 @@ import { sceneRegistry } from '@pascal-app/core';
|
|
|
3
3
|
import { useFrame } from '@react-three/fiber';
|
|
4
4
|
import { 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
8
|
import { getSceneTheme } from '../../lib/scene-themes';
|
|
8
9
|
import useViewer from '../../store/use-viewer';
|
|
@@ -141,7 +142,11 @@ export function Lights() {
|
|
|
141
142
|
const distance = size + SHADOW_BACKOFF;
|
|
142
143
|
const near = SHADOW_BACKOFF;
|
|
143
144
|
const far = distance + size;
|
|
144
|
-
|
|
145
|
+
const directionOverride = getKeyLightDirectionOverride();
|
|
146
|
+
if (directionOverride) {
|
|
147
|
+
shadowDir.current.set(...directionOverride);
|
|
148
|
+
}
|
|
149
|
+
else if (atmosphere) {
|
|
145
150
|
shadowDir.current.copy(atmosphere.sunDirection);
|
|
146
151
|
}
|
|
147
152
|
else if (themeKey) {
|
|
@@ -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":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA+CA,eAAO,MAAM,YAAY;;;CAGxB,CAAA;AAGD,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAgED,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AA+CD,QAAA,MAAM,oBAAoB,GAAI,iCAG3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,SAmnBA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -8,10 +8,11 @@ import { RenderPipeline, TimestampQuery } from 'three/webgpu';
|
|
|
8
8
|
import { backdropGradient, deepSkyColor, horizonHazeColor } from '../../lib/backdrop';
|
|
9
9
|
import { edgeColorFor, edgeOpacityScaleFor } from '../../lib/edge-style';
|
|
10
10
|
import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
|
|
11
|
-
import { inkedEdges } from '../../lib/ink-edges';
|
|
11
|
+
import { createEdgeDepthSampler, inkedEdges } from '../../lib/ink-edges';
|
|
12
12
|
import { refreshIsolation } from '../../lib/isolation';
|
|
13
13
|
import { LayerPassIndex, LayerPassNode } from '../../lib/layer-pass';
|
|
14
14
|
import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
15
|
+
import { isLiveFrameHeld } from '../../lib/live-frame-hold';
|
|
15
16
|
import { mergedOutline } from '../../lib/merged-outline-node';
|
|
16
17
|
import { recordPerfSample, timeSpan } from '../../lib/perf-tracks';
|
|
17
18
|
import { PostProcessingResources } from '../../lib/post-processing-resources';
|
|
@@ -371,6 +372,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
371
372
|
// Scene depth is shared by SSGI, ink, and outlines.
|
|
372
373
|
// The normal MRT is only built when SSGI or ink needs it.
|
|
373
374
|
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
375
|
+
const sampleEdgeDepth = createEdgeDepthSampler(scenePassDepth, camera);
|
|
374
376
|
let scenePassNormal = null;
|
|
375
377
|
if (needsNormalMRT) {
|
|
376
378
|
scenePass.setMRT(mrt({
|
|
@@ -428,7 +430,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
428
430
|
// (same ≈150→350 m window as ink-edges' distanceFade) so the horizon
|
|
429
431
|
// disc and the geometry↔sky depth cliff never grow an AO band — that
|
|
430
432
|
// band read as a visible line along the horizon.
|
|
431
|
-
const aoFarFade = smoothstep(tslFloat(0.9994), tslFloat(0.9998),
|
|
433
|
+
const aoFarFade = smoothstep(tslFloat(0.9994), tslFloat(0.9998), sampleEdgeDepth(screenUV));
|
|
432
434
|
ao = mix(ao, tslFloat(1), aoFarFade);
|
|
433
435
|
// Composite: scene * AO + diffuse * GI
|
|
434
436
|
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
@@ -439,7 +441,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
439
441
|
if (inkEnabled && scenePassDepth && scenePassNormal) {
|
|
440
442
|
sceneColor = vec4(inkedEdges({
|
|
441
443
|
sceneRgb: sceneColor.rgb,
|
|
442
|
-
|
|
444
|
+
sampleDepth: sampleEdgeDepth,
|
|
443
445
|
normalTex: scenePassNormal,
|
|
444
446
|
inkColor: inkColorUniform.current,
|
|
445
447
|
radius: inkRadius,
|
|
@@ -574,6 +576,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
|
|
|
574
576
|
if (size.width < 1 || size.height < 1) {
|
|
575
577
|
return;
|
|
576
578
|
}
|
|
579
|
+
// A capture is working on the shared scene: the canvas keeps its last frame.
|
|
580
|
+
if (isLiveFrameHeld())
|
|
581
|
+
return;
|
|
577
582
|
// `?disable=draw`: nothing downstream wants pixels — render an EMPTY scene
|
|
578
583
|
// instead of the real one. This is the only render call (positive-priority
|
|
579
584
|
// useFrame subscribers already disable R3F's automatic render), so the real
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registered-systems.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/registered-systems.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registered-systems.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/registered-systems.tsx"],"names":[],"mappings":"AAoDA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,uCAkChC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { createSceneApi, isNodeKindEnabled, nodeRegistry, useRegistryVersion, useScene, } from '@pascal-app/core';
|
|
4
|
-
import { lazy, Suspense, useMemo } from 'react';
|
|
4
|
+
import { Component, lazy, Suspense, useMemo } from 'react';
|
|
5
5
|
const DEFAULT_PRIORITY = 5;
|
|
6
6
|
const lazyCache = new WeakMap();
|
|
7
7
|
function loadSystem(def) {
|
|
@@ -14,6 +14,20 @@ function loadSystem(def) {
|
|
|
14
14
|
lazyCache.set(def.system.module, Comp);
|
|
15
15
|
return Comp;
|
|
16
16
|
}
|
|
17
|
+
class SystemErrorBoundary extends Component {
|
|
18
|
+
state = { failed: false };
|
|
19
|
+
static getDerivedStateFromError() {
|
|
20
|
+
return { failed: true };
|
|
21
|
+
}
|
|
22
|
+
componentDidCatch(error) {
|
|
23
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
24
|
+
console.error(`[viewer] Disabled system ${this.props.systemId}`, error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
render() {
|
|
28
|
+
return this.state.failed ? null : this.props.children;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
17
31
|
/**
|
|
18
32
|
* Mounts every registered node kind's system component, ordered by
|
|
19
33
|
* `system.priority` (default {@link DEFAULT_PRIORITY}).
|
|
@@ -56,6 +70,6 @@ export function RegisteredSystems() {
|
|
|
56
70
|
const Comp = loadSystem(def);
|
|
57
71
|
if (!Comp)
|
|
58
72
|
return null;
|
|
59
|
-
return (_jsx(Suspense, { fallback: null, children: _jsx(Comp, { sceneApi: sceneApi }) }, `registered-system:${kind}`));
|
|
73
|
+
return (_jsx(SystemErrorBoundary, { systemId: kind, children: _jsx(Suspense, { fallback: null, children: _jsx(Comp, { sceneApi: sceneApi }) }) }, `registered-system:${kind}`));
|
|
60
74
|
}) }));
|
|
61
75
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
type RenderErrorHandler = ((cause: unknown) => void) | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* One handler that notifies every given handler (the immersive session's and
|
|
5
|
+
* the host's), or undefined when there is none. A handler that throws is
|
|
6
|
+
* logged and does not stop the others.
|
|
7
|
+
*/
|
|
8
|
+
export declare function composeRenderErrorHandlers(...handlers: RenderErrorHandler[]): RenderErrorHandler;
|
|
9
|
+
/**
|
|
10
|
+
* The viewer-scene error boundary: a node renderer or system that throws while
|
|
11
|
+
* rendering renders nothing in its place, and every handler hears about it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function SceneErrorBoundary({ handlers, children, }: {
|
|
14
|
+
handlers: RenderErrorHandler[];
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=render-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-error.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/render-error.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,KAAK,kBAAkB,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;AAEhE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,QAAQ,EAAE,kBAAkB,EAAE,GAAG,kBAAkB,CAYhG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,QAAQ,EAAE,SAAS,CAAA;CACpB,+BAUA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ErrorBoundary } from '../error-boundary';
|
|
3
|
+
/**
|
|
4
|
+
* One handler that notifies every given handler (the immersive session's and
|
|
5
|
+
* the host's), or undefined when there is none. A handler that throws is
|
|
6
|
+
* logged and does not stop the others.
|
|
7
|
+
*/
|
|
8
|
+
export function composeRenderErrorHandlers(...handlers) {
|
|
9
|
+
const present = handlers.filter((handler) => handler !== undefined);
|
|
10
|
+
if (present.length === 0)
|
|
11
|
+
return;
|
|
12
|
+
return (cause) => {
|
|
13
|
+
for (const handler of present) {
|
|
14
|
+
try {
|
|
15
|
+
handler(cause);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error('[viewer] a render-error handler threw', error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The viewer-scene error boundary: a node renderer or system that throws while
|
|
25
|
+
* rendering renders nothing in its place, and every handler hears about it.
|
|
26
|
+
*/
|
|
27
|
+
export function SceneErrorBoundary({ handlers, children, }) {
|
|
28
|
+
return (_jsx(ErrorBoundary, { fallback: null, onError: composeRenderErrorHandlers(...handlers), scope: "viewer-scene", children: children }));
|
|
29
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import type { Scene } from 'three';
|
|
1
2
|
/** Whether modeled exterior ground replaces this scene's fallback horizon disc. */
|
|
2
3
|
export declare function useSceneGroundReplacement(): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* The same claim from outside React (a map layer that hangs its own meshes on
|
|
6
|
+
* the scene): returns the release. The last release restores the fallback.
|
|
7
|
+
*/
|
|
8
|
+
export declare function claimSceneGround(scene: Scene): () => void;
|
|
3
9
|
/** Mount alongside replacement ground. The last release restores the fallback. */
|
|
4
10
|
export declare function SceneGroundReplacement(): null;
|
|
5
11
|
//# sourceMappingURL=scene-ground-replacement.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-ground-replacement.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-ground-replacement.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scene-ground-replacement.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-ground-replacement.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAgBlC,mFAAmF;AACnF,wBAAgB,yBAAyB,IAAI,OAAO,CAInD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,IAAI,CASzD;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,SAarC"}
|
|
@@ -18,6 +18,21 @@ export function useSceneGroundReplacement() {
|
|
|
18
18
|
const store = useMemo(() => groundStore(scene), [scene]);
|
|
19
19
|
return useStore(store, (state) => state.owners > 0);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The same claim from outside React (a map layer that hangs its own meshes on
|
|
23
|
+
* the scene): returns the release. The last release restores the fallback.
|
|
24
|
+
*/
|
|
25
|
+
export function claimSceneGround(scene) {
|
|
26
|
+
const store = groundStore(scene);
|
|
27
|
+
store.setState((state) => ({ owners: state.owners + 1 }));
|
|
28
|
+
let released = false;
|
|
29
|
+
return () => {
|
|
30
|
+
if (released)
|
|
31
|
+
return;
|
|
32
|
+
released = true;
|
|
33
|
+
store.setState((state) => ({ owners: Math.max(0, state.owners - 1) }));
|
|
34
|
+
};
|
|
35
|
+
}
|
|
21
36
|
/** Mount alongside replacement ground. The last release restores the fallback. */
|
|
22
37
|
export function SceneGroundReplacement() {
|
|
23
38
|
const scene = useThree((state) => state.scene);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAqSA,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAqSA,eAAO,MAAM,gBAAgB,mCA+G5B,CAAA"}
|
|
@@ -233,6 +233,10 @@ export const SelectionManager = () => {
|
|
|
233
233
|
// re-subscribe when plugin kinds register after mount (async plugin load)
|
|
234
234
|
void registryVersion;
|
|
235
235
|
const onEnter = (event) => {
|
|
236
|
+
// Walkthrough is camera-only: first-person controls own hover for the
|
|
237
|
+
// interactable target, and pointer lock keeps raycasting from a frozen cursor.
|
|
238
|
+
if (useViewer.getState().walkthroughMode)
|
|
239
|
+
return;
|
|
236
240
|
const strategy = getStrategy();
|
|
237
241
|
if (!strategy)
|
|
238
242
|
return;
|
|
@@ -254,6 +258,8 @@ export const SelectionManager = () => {
|
|
|
254
258
|
}
|
|
255
259
|
};
|
|
256
260
|
const onLeave = (event) => {
|
|
261
|
+
if (useViewer.getState().walkthroughMode)
|
|
262
|
+
return;
|
|
257
263
|
const strategy = getStrategy();
|
|
258
264
|
if (!strategy)
|
|
259
265
|
return;
|
|
@@ -268,6 +274,8 @@ export const SelectionManager = () => {
|
|
|
268
274
|
}
|
|
269
275
|
};
|
|
270
276
|
const onClick = (event) => {
|
|
277
|
+
if (useViewer.getState().walkthroughMode)
|
|
278
|
+
return;
|
|
271
279
|
const strategy = getStrategy();
|
|
272
280
|
if (!strategy)
|
|
273
281
|
return;
|
|
@@ -324,6 +332,8 @@ const PointerMissedHandler = ({ clickHandledRef, }) => {
|
|
|
324
332
|
const viewerState = useViewer.getState();
|
|
325
333
|
if (viewerState.cameraDragging || viewerState.inputDragging)
|
|
326
334
|
return;
|
|
335
|
+
if (viewerState.walkthroughMode)
|
|
336
|
+
return;
|
|
327
337
|
if (event.button !== 0)
|
|
328
338
|
return;
|
|
329
339
|
// Use requestAnimationFrame to check after R3F event handlers
|
|
@@ -9,6 +9,10 @@ export declare function applyViewerCameraClipping(camera: {
|
|
|
9
9
|
near: number;
|
|
10
10
|
updateProjectionMatrix(): void;
|
|
11
11
|
}, immersiveXR: boolean): void;
|
|
12
|
+
export declare function applyWalkthroughCameraClipping(camera: {
|
|
13
|
+
near: number;
|
|
14
|
+
updateProjectionMatrix(): void;
|
|
15
|
+
}): () => void;
|
|
12
16
|
export declare function viewerUsesPerspectiveCamera(cameraMode: string, immersiveXR: boolean): boolean;
|
|
13
17
|
export declare const ViewerCamera: ({ immersiveXR }: {
|
|
14
18
|
immersiveXR?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewer-camera.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-camera.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAOnC,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAWxF;AAcD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,OAAO;;;EAExD;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,sBAAsB,IAAI,IAAI,CAAA;CAAE,EACrE,WAAW,EAAE,OAAO,QAMrB;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,WAEnF;AAED,eAAO,MAAM,YAAY,GAAI,iBAAyB;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,gCAqB9E,CAAA"}
|
|
1
|
+
{"version":3,"file":"viewer-camera.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-camera.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAOnC,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAWxF;AAcD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,OAAO;;;EAExD;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,sBAAsB,IAAI,IAAI,CAAA;CAAE,EACrE,WAAW,EAAE,OAAO,QAMrB;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,sBAAsB,IAAI,IAAI,CAAA;CAC/B,cASA;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,WAEnF;AAED,eAAO,MAAM,YAAY,GAAI,iBAAyB;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,gCAqB9E,CAAA"}
|
|
@@ -29,7 +29,7 @@ function ImmersiveXRViewLayers({ enabled }) {
|
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
export function viewerCameraClipping(immersiveXR) {
|
|
32
|
-
return immersiveXR ? { far: 10_000, near: 0.001 } : { far: 1000, near: 0.
|
|
32
|
+
return immersiveXR ? { far: 10_000, near: 0.001 } : { far: 1000, near: 0.3 };
|
|
33
33
|
}
|
|
34
34
|
export function applyViewerCameraClipping(camera, immersiveXR) {
|
|
35
35
|
const clipping = viewerCameraClipping(immersiveXR);
|
|
@@ -37,6 +37,16 @@ export function applyViewerCameraClipping(camera, immersiveXR) {
|
|
|
37
37
|
camera.near = clipping.near;
|
|
38
38
|
camera.updateProjectionMatrix();
|
|
39
39
|
}
|
|
40
|
+
export function applyWalkthroughCameraClipping(camera) {
|
|
41
|
+
const previousNear = camera.near;
|
|
42
|
+
// The eye can approach a wall to the capsule's 0.25 m radius.
|
|
43
|
+
camera.near = Math.min(previousNear, 0.1);
|
|
44
|
+
camera.updateProjectionMatrix();
|
|
45
|
+
return () => {
|
|
46
|
+
camera.near = previousNear;
|
|
47
|
+
camera.updateProjectionMatrix();
|
|
48
|
+
};
|
|
49
|
+
}
|
|
40
50
|
export function viewerUsesPerspectiveCamera(cameraMode, immersiveXR) {
|
|
41
51
|
return immersiveXR || cameraMode === 'perspective';
|
|
42
52
|
}
|