@pascal-app/viewer 1.0.0-beta.4 → 1.0.0-beta.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/README.md +52 -0
- package/dist/components/viewer/frame-limiter.d.ts +11 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +58 -18
- package/dist/components/viewer/index.d.ts +15 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +34 -50
- package/dist/components/viewer/perf-monitor.js +2 -2
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +7 -2
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
- package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/lib/dispose-object3d.d.ts +4 -0
- package/dist/lib/dispose-object3d.d.ts.map +1 -0
- package/dist/lib/dispose-object3d.js +27 -0
- package/dist/lib/geometry-groups.d.ts +17 -0
- package/dist/lib/geometry-groups.d.ts.map +1 -0
- package/dist/lib/geometry-groups.js +53 -0
- package/dist/lib/isolation.d.ts +8 -8
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +18 -30
- package/dist/lib/layers.d.ts +16 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/renderer-capability.d.ts +56 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -0
- package/dist/lib/renderer-capability.js +114 -0
- package/dist/lib/scene-visibility.d.ts +14 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -0
- package/dist/lib/scene-visibility.js +48 -0
- package/dist/lib/shadow-only.d.ts +16 -0
- package/dist/lib/shadow-only.d.ts.map +1 -1
- package/dist/lib/shadow-only.js +8 -17
- package/dist/lib/snapshot-pipeline.d.ts +9 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +33 -9
- package/dist/store/use-viewer.d.ts +5 -4
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +2 -26
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +7 -1
- package/dist/systems/wall/level-miter-cache.d.ts +10 -0
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
- package/dist/systems/wall/level-miter-cache.js +40 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +41 -11
- package/dist/systems/wall/wall-system.d.ts +4 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +31 -18
- package/package.json +4 -2
- package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
- package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
- package/dist/components/viewer/scene-bvh.test.js +0 -13
- package/dist/lib/csg-utils.test.d.ts +0 -2
- package/dist/lib/csg-utils.test.d.ts.map +0 -1
- package/dist/lib/csg-utils.test.js +0 -45
- package/dist/lib/materials.test.d.ts +0 -2
- package/dist/lib/materials.test.d.ts.map +0 -1
- package/dist/lib/materials.test.js +0 -25
- package/dist/lib/merged-outline-node.test.d.ts +0 -2
- package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
- package/dist/lib/merged-outline-node.test.js +0 -22
- package/dist/lib/polygon-union.test.d.ts +0 -2
- package/dist/lib/polygon-union.test.d.ts.map +0 -1
- package/dist/lib/polygon-union.test.js +0 -105
- package/dist/lib/texture-reference.test.d.ts +0 -2
- package/dist/lib/texture-reference.test.d.ts.map +0 -1
- package/dist/lib/texture-reference.test.js +0 -86
- package/dist/store/use-viewer.test.d.ts +0 -2
- package/dist/store/use-viewer.test.d.ts.map +0 -1
- package/dist/store/use-viewer.test.js +0 -61
- package/dist/systems/door/door-animation.test.d.ts +0 -2
- package/dist/systems/door/door-animation.test.d.ts.map +0 -1
- package/dist/systems/door/door-animation.test.js +0 -51
- package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
- package/dist/systems/door/door-floor-alignment.test.js +0 -50
- package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
- package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
- package/dist/systems/geometry/geometry-system.test.js +0 -30
- package/dist/systems/slab/slab-system.test.d.ts +0 -2
- package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
- package/dist/systems/slab/slab-system.test.js +0 -102
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
- package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
- package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-support-extension.test.js +0 -116
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
package/README.md
CHANGED
|
@@ -68,6 +68,58 @@ function App() {
|
|
|
68
68
|
}
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
## 2D and Split-View Embeds
|
|
72
|
+
|
|
73
|
+
`@pascal-app/viewer` owns the 3D canvas. The npm-facing multi-view shell lives in
|
|
74
|
+
`@pascal-app/editor`, where it can compose that canvas with the read-only SVG floor plan without
|
|
75
|
+
coupling editor-only floor-plan state into the viewer runtime.
|
|
76
|
+
|
|
77
|
+
Use `modes` to expose any combination of `3d`, `2d`, and `split`. A single enabled mode hides the
|
|
78
|
+
switcher automatically. `mode` and `onModeChange` can be supplied for controlled embeds; otherwise
|
|
79
|
+
`defaultMode` is used.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { ViewerStage, useViewerCameraNavigationSync } from '@pascal-app/editor'
|
|
83
|
+
import { Viewer } from '@pascal-app/viewer'
|
|
84
|
+
import { CameraControls, type CameraControlsImpl } from '@react-three/drei'
|
|
85
|
+
import { useRef } from 'react'
|
|
86
|
+
|
|
87
|
+
function SyncedCameraControls() {
|
|
88
|
+
const controls = useRef<CameraControlsImpl>(null)
|
|
89
|
+
const publishCameraPose = useViewerCameraNavigationSync(controls)
|
|
90
|
+
|
|
91
|
+
return <CameraControls makeDefault onUpdate={publishCameraPose} ref={controls} />
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function EmbeddedViewer() {
|
|
95
|
+
return (
|
|
96
|
+
<div style={{ width: 960, height: 640 }}>
|
|
97
|
+
<ViewerStage defaultMode="3d" modes={['3d', '2d']}>
|
|
98
|
+
<Viewer>
|
|
99
|
+
<SyncedCameraControls />
|
|
100
|
+
</Viewer>
|
|
101
|
+
</ViewerStage>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Common configurations:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<ViewerStage modes={['3d']}>{viewer}</ViewerStage>
|
|
111
|
+
<ViewerStage modes={['2d']} />
|
|
112
|
+
<ViewerStage modes={['3d', '2d']}>{viewer}</ViewerStage>
|
|
113
|
+
<ViewerStage modes={['3d', 'split']}>{viewer}</ViewerStage>
|
|
114
|
+
<ViewerStage modes={['3d', '2d', 'split']}>{viewer}</ViewerStage>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For a 2D-only embed, no 3D canvas is mounted. When 3D or split is enabled, the 3D canvas stays
|
|
118
|
+
mounted while 2D is active, avoiding renderer reinitialization. Camera poses,
|
|
119
|
+
floor-plan pan/zoom/rotation, and the compass synchronize through transient subscriptions; live
|
|
120
|
+
navigation does not require a React render per frame. Set `showCompass={false}` or
|
|
121
|
+
`showSwitcher={false}` when the host supplies its own controls.
|
|
122
|
+
|
|
71
123
|
## Viewer State
|
|
72
124
|
|
|
73
125
|
```typescript
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
type FrameLimiterProps = {
|
|
2
2
|
fps?: number;
|
|
3
|
+
paused?: boolean;
|
|
3
4
|
};
|
|
5
|
+
export type FrameClock = {
|
|
6
|
+
sample: (wallTimeMs: number, intervalMs: number) => number | null;
|
|
7
|
+
step: (seconds: number) => number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Keeps R3F's manual clock monotonic while each limiter effect owns a fresh
|
|
11
|
+
* wall-time baseline. The first rAF sample establishes that baseline instead
|
|
12
|
+
* of treating the browser's process uptime as elapsed frame time.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createFrameClock(initialTime?: number): FrameClock;
|
|
4
15
|
declare const FrameLimiter: React.FC<FrameLimiterProps>;
|
|
5
16
|
export default FrameLimiter;
|
|
6
17
|
//# sourceMappingURL=frame-limiter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"AAIA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"AAIA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACjE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,SAAI,GAAG,UAAU,CAwB5D;AAgBD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoF7C,CAAA;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { useThree } from '@react-three/fiber';
|
|
2
|
-
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
3
3
|
import useViewer from '../../store/use-viewer';
|
|
4
|
+
/**
|
|
5
|
+
* Keeps R3F's manual clock monotonic while each limiter effect owns a fresh
|
|
6
|
+
* wall-time baseline. The first rAF sample establishes that baseline instead
|
|
7
|
+
* of treating the browser's process uptime as elapsed frame time.
|
|
8
|
+
*/
|
|
9
|
+
export function createFrameClock(initialTime = 0) {
|
|
10
|
+
let frameTime = initialTime;
|
|
11
|
+
let previousWallTime = null;
|
|
12
|
+
return {
|
|
13
|
+
sample(wallTimeMs, intervalMs) {
|
|
14
|
+
if (previousWallTime === null) {
|
|
15
|
+
previousWallTime = wallTimeMs;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const elapsedMs = wallTimeMs - previousWallTime;
|
|
19
|
+
if (elapsedMs < intervalMs)
|
|
20
|
+
return null;
|
|
21
|
+
const remainderMs = elapsedMs % intervalMs;
|
|
22
|
+
frameTime += (elapsedMs - remainderMs) / 1000;
|
|
23
|
+
previousWallTime = wallTimeMs - remainderMs;
|
|
24
|
+
return frameTime;
|
|
25
|
+
},
|
|
26
|
+
step(seconds) {
|
|
27
|
+
frameTime += seconds;
|
|
28
|
+
return frameTime;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
4
32
|
// `?disable=draw` (see post-processing.tsx): the page renders no real frames,
|
|
5
33
|
// and Chromium's no-damage scheduler then throttles requestAnimationFrame to
|
|
6
34
|
// 1Hz on Linux (measured in the headless bake worker — every useFrame system
|
|
@@ -11,23 +39,23 @@ const DRAW_DISABLED = typeof window !== 'undefined' &&
|
|
|
11
39
|
new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
|
|
12
40
|
.split(',')
|
|
13
41
|
.map((s) => s.trim())).has('draw');
|
|
14
|
-
const FrameLimiter = ({ fps = 50 }) => {
|
|
42
|
+
const FrameLimiter = ({ fps = 50, paused = false }) => {
|
|
15
43
|
const { advance, set, frameloop: initFrameloop } = useThree();
|
|
44
|
+
const nextFrameTimeRef = useRef(0);
|
|
16
45
|
const renderer = useThree((state) => state.gl);
|
|
17
46
|
const size = useThree((state) => state.size);
|
|
18
47
|
const dpr = useThree((state) => state.viewport.dpr);
|
|
19
48
|
// Fully covered canvas (e.g. studio gallery) → stop advancing frames
|
|
20
49
|
const renderPaused = useViewer((s) => s.renderPaused);
|
|
21
50
|
useLayoutEffect(() => {
|
|
22
|
-
if (renderPaused)
|
|
51
|
+
if (renderPaused || paused)
|
|
23
52
|
return;
|
|
24
|
-
|
|
25
|
-
let then = 0;
|
|
26
|
-
let i = 0;
|
|
53
|
+
const clock = createFrameClock(nextFrameTimeRef.current);
|
|
27
54
|
let raf = null;
|
|
28
55
|
let timer = null;
|
|
29
56
|
let sizeSynced = false;
|
|
30
|
-
const
|
|
57
|
+
const effectiveFps = Number.isFinite(fps) && fps > 0 ? fps : 50;
|
|
58
|
+
const interval = 1000 / effectiveFps;
|
|
31
59
|
function syncSize() {
|
|
32
60
|
if (sizeSynced)
|
|
33
61
|
return;
|
|
@@ -38,17 +66,17 @@ const FrameLimiter = ({ fps = 50 }) => {
|
|
|
38
66
|
function tick(t) {
|
|
39
67
|
raf = requestAnimationFrame(tick);
|
|
40
68
|
syncSize();
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
69
|
+
const frameTime = clock.sample(t, interval);
|
|
70
|
+
if (frameTime === null)
|
|
71
|
+
return;
|
|
72
|
+
nextFrameTimeRef.current = frameTime;
|
|
73
|
+
advance(frameTime);
|
|
47
74
|
}
|
|
48
75
|
function kick() {
|
|
49
76
|
syncSize();
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
const frameTime = clock.step(1 / 1000);
|
|
78
|
+
nextFrameTimeRef.current = frameTime;
|
|
79
|
+
advance(frameTime);
|
|
52
80
|
}
|
|
53
81
|
function onVisibilityChange() {
|
|
54
82
|
if (document.visibilityState === 'visible')
|
|
@@ -58,8 +86,9 @@ const FrameLimiter = ({ fps = 50 }) => {
|
|
|
58
86
|
set({ frameloop: 'never' });
|
|
59
87
|
if (DRAW_DISABLED) {
|
|
60
88
|
timer = setInterval(() => {
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
const frameTime = clock.step(interval / 1000);
|
|
90
|
+
nextFrameTimeRef.current = frameTime;
|
|
91
|
+
advance(frameTime);
|
|
63
92
|
}, interval);
|
|
64
93
|
}
|
|
65
94
|
else {
|
|
@@ -84,7 +113,18 @@ const FrameLimiter = ({ fps = 50 }) => {
|
|
|
84
113
|
window.removeEventListener('pageshow', kick);
|
|
85
114
|
set({ frameloop: initFrameloop });
|
|
86
115
|
};
|
|
87
|
-
}, [
|
|
116
|
+
}, [
|
|
117
|
+
advance,
|
|
118
|
+
dpr,
|
|
119
|
+
fps,
|
|
120
|
+
initFrameloop,
|
|
121
|
+
paused,
|
|
122
|
+
renderPaused,
|
|
123
|
+
renderer,
|
|
124
|
+
set,
|
|
125
|
+
size.height,
|
|
126
|
+
size.width,
|
|
127
|
+
]);
|
|
88
128
|
return null;
|
|
89
129
|
};
|
|
90
130
|
export default FrameLimiter;
|
|
@@ -46,6 +46,19 @@ interface ViewerProps {
|
|
|
46
46
|
* 180-frame cap in ~3.6s — shorter than a cold item-model download).
|
|
47
47
|
*/
|
|
48
48
|
sceneReadyMaxWaitMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Frame cap for the render loop, in frames per second. Defaults to 50, the
|
|
51
|
+
* value the viewer has always used.
|
|
52
|
+
*
|
|
53
|
+
* The viewer runs `frameloop="never"` and advances frames itself through
|
|
54
|
+
* `<FrameLimiter>`, so this cap is the only thing setting the cadence and a
|
|
55
|
+
* host cannot raise it from the outside. Hosts that animate the scene on
|
|
56
|
+
* their own clock — a timeline scrubbing node transforms, a walkthrough
|
|
57
|
+
* camera — are pinned to it and cannot reach display refresh, which reads as
|
|
58
|
+
* judder against a 60Hz+ monitor. Raise it for those; lower it to spare the
|
|
59
|
+
* GPU on a passive or background canvas.
|
|
60
|
+
*/
|
|
61
|
+
maxFps?: number;
|
|
49
62
|
/**
|
|
50
63
|
* Skip the TSL post-processing pipeline (SSGI/denoise/ink/outline) and render
|
|
51
64
|
* the scene directly. For headless/capture surfaces (the bake page) where
|
|
@@ -54,6 +67,8 @@ interface ViewerProps {
|
|
|
54
67
|
* `?disable=postFx` diagnostic URL flag, but host-controlled.
|
|
55
68
|
*/
|
|
56
69
|
disablePostFx?: boolean;
|
|
70
|
+
/** Keep the mounted renderer/context warm without advancing scene frames. */
|
|
71
|
+
renderPaused?: boolean;
|
|
57
72
|
}
|
|
58
73
|
/** Imperative handle exposed via `ref` on `<Viewer>`. */
|
|
59
74
|
export type ViewerHandle = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAkB,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAS1F,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAkB,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAS1F,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAIrE,OAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAQtE,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAW1F,OAAO,QAAQ,oBAAoB,CAAC;IAOlC,UAAU,aAAa;QACrB,qBAAqB,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,qBAAqB,CAAC,CAAA;KACxE;CACF;AAuQD,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,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;CACvB;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,sGAoOV,CAAA;AAkBF,eAAe,MAAM,CAAA"}
|
|
@@ -8,6 +8,7 @@ import { hasDrawableGeometry } from '../../lib/drawable-geometry';
|
|
|
8
8
|
import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf';
|
|
9
9
|
import { applyIsolation, clearIsolation } from '../../lib/isolation';
|
|
10
10
|
import { ensureKtx2Support } from '../../lib/ktx2-loader';
|
|
11
|
+
import { initializeGpuRenderer } from '../../lib/renderer-capability';
|
|
11
12
|
import { getSceneTheme } from '../../lib/scene-themes';
|
|
12
13
|
import { installTextureNodeNullGuard } from '../../lib/texture-node-guard';
|
|
13
14
|
import useViewer, {} from '../../store/use-viewer';
|
|
@@ -22,6 +23,7 @@ import PostProcessing, { DEFAULT_HOVER_STYLES } from './post-processing';
|
|
|
22
23
|
import { RegisteredSystems } from './registered-systems';
|
|
23
24
|
import { SceneBvh } from './scene-bvh';
|
|
24
25
|
import { SelectionManager } from './selection-manager';
|
|
26
|
+
import { UnsupportedGpuViewerFallback } from './unsupported-gpu-fallback';
|
|
25
27
|
import { ViewerCamera } from './viewer-camera';
|
|
26
28
|
// Must be in place before any node material builds — a null texture pulled by
|
|
27
29
|
// a shared override-material pass otherwise kills the render pass outright.
|
|
@@ -56,27 +58,6 @@ const DIRTY_BUILD_KINDS = new Set([
|
|
|
56
58
|
'window',
|
|
57
59
|
]);
|
|
58
60
|
const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakSet();
|
|
59
|
-
function canCreateWebGLContext() {
|
|
60
|
-
if (typeof document === 'undefined')
|
|
61
|
-
return false;
|
|
62
|
-
const canvas = document.createElement('canvas');
|
|
63
|
-
try {
|
|
64
|
-
return Boolean(canvas.getContext('webgl2') ?? canvas.getContext('webgl'));
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function canMountGpuViewer() {
|
|
71
|
-
if (typeof window === 'undefined')
|
|
72
|
-
return false;
|
|
73
|
-
if (!('gpu' in navigator) && !canCreateWebGLContext())
|
|
74
|
-
return false;
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
function UnsupportedGpuViewerFallback() {
|
|
78
|
-
return (_jsx("div", { className: "flex h-full min-h-64 w-full items-center justify-center bg-[#fafafa] p-6 text-center text-neutral-900", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-lg", children: "3D viewer unavailable" }), _jsx("p", { className: "mt-2 text-neutral-600 text-sm", children: "This browser or environment does not expose WebGPU or WebGL, so Pascal cannot render the 3D scene here. Try opening the editor in a browser with hardware acceleration enabled." })] }) }));
|
|
79
|
-
}
|
|
80
61
|
/**
|
|
81
62
|
* Renderer-level safety net against the empty-vertex-buffer crash.
|
|
82
63
|
*
|
|
@@ -230,7 +211,7 @@ function SceneReadyTracker({ onSceneReadyChange, sceneReadyKey, sceneReadyMaxWai
|
|
|
230
211
|
}, 10);
|
|
231
212
|
return null;
|
|
232
213
|
}
|
|
233
|
-
const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, renderContext = 'editor', transparent, defaultRender, isolate, sceneReadyKey, onSceneReadyChange, sceneReadyMaxWaitMs, disablePostFx = false, }, ref) {
|
|
214
|
+
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, }, ref) {
|
|
234
215
|
useEffect(() => {
|
|
235
216
|
if (nodeRegistry.size === 0) {
|
|
236
217
|
console.warn('[viewer] Node registry is empty. Install @pascal-app/nodes and call await loadPlugin(builtinPlugin) before mounting <Viewer>.');
|
|
@@ -254,15 +235,6 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
254
235
|
};
|
|
255
236
|
}, [isolate]);
|
|
256
237
|
const [rendererInitFailed, setRendererInitFailed] = useState(false);
|
|
257
|
-
// Capability detection runs after mount. We start optimistic (true) so the
|
|
258
|
-
// server-rendered markup and the first client render agree (no hydration
|
|
259
|
-
// mismatch); the effect flips it to false only on environments that expose
|
|
260
|
-
// neither WebGPU nor WebGL.
|
|
261
|
-
const [canMountViewer, setCanMountViewer] = useState(true);
|
|
262
|
-
useEffect(() => {
|
|
263
|
-
if (!canMountGpuViewer())
|
|
264
|
-
setCanMountViewer(false);
|
|
265
|
-
}, []);
|
|
266
238
|
const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
|
|
267
239
|
const transparentBackground = useViewer((state) => state.transparentBackground);
|
|
268
240
|
// The shadows toggle drives `renderer.shadowMap.enabled` (via the Canvas
|
|
@@ -317,7 +289,7 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
317
289
|
// fragment-shader cost down — saves another ~30% over 1.5x on high-DPI mobile.
|
|
318
290
|
// Desktops (fine pointer) keep the original 1.5 cap.
|
|
319
291
|
const maxDpr = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches ? 1.25 : 1.5;
|
|
320
|
-
const showGpuFallback =
|
|
292
|
+
const showGpuFallback = rendererInitFailed;
|
|
321
293
|
// When we can't mount the GPU canvas, the SceneReadyTracker never mounts and
|
|
322
294
|
// the host editor would otherwise wait on its scene-readiness timeout. Signal
|
|
323
295
|
// readiness explicitly so the host can drop its loader immediately.
|
|
@@ -334,24 +306,36 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
334
306
|
if (cached)
|
|
335
307
|
return cached;
|
|
336
308
|
const promise = (async () => {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
309
|
+
const result = await initializeGpuRenderer({
|
|
310
|
+
// Supplying `device` makes three skip its own `requestAdapter`,
|
|
311
|
+
// so R3F's `powerPreference` only reaches the GPU if we forward it.
|
|
312
|
+
powerPreference: props.powerPreference,
|
|
313
|
+
createRenderer: (backendParameters) => {
|
|
314
|
+
const renderer = new THREE.WebGPURenderer({
|
|
315
|
+
...props,
|
|
316
|
+
...backendParameters,
|
|
317
|
+
alpha: true,
|
|
318
|
+
});
|
|
319
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
320
|
+
renderer.toneMappingExposure = getSceneTheme(useViewer.getState().sceneTheme).toneMappingExposure;
|
|
321
|
+
return renderer;
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
if (result.status === 'ready') {
|
|
325
|
+
installEmptyDrawGuard(result.renderer);
|
|
326
|
+
return result.renderer;
|
|
354
327
|
}
|
|
328
|
+
if (canvas)
|
|
329
|
+
WEBGPU_RENDERER_CACHE.delete(canvas);
|
|
330
|
+
console.error('[viewer] WebGPURenderer init failed', result.error);
|
|
331
|
+
setRendererInitFailed(true);
|
|
332
|
+
// Never settles on purpose. Rejecting is what produced
|
|
333
|
+
// MONOREPO-EDITOR-59: R3F awaits this inside its own configure()
|
|
334
|
+
// with no catch, so a rejection surfaces as an unhandled rejection.
|
|
335
|
+
// Resolving is worse still — R3F would call render() on a renderer
|
|
336
|
+
// that has no context. The state update above unmounts this Canvas,
|
|
337
|
+
// which is what releases the pending configure().
|
|
338
|
+
return new Promise(() => undefined);
|
|
355
339
|
})();
|
|
356
340
|
if (canvas)
|
|
357
341
|
WEBGPU_RENDERER_CACHE.set(canvas, promise);
|
|
@@ -361,7 +345,7 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
361
345
|
}, shadows: {
|
|
362
346
|
type: THREE.PCFShadowMap,
|
|
363
347
|
enabled: shadowsEnabled,
|
|
364
|
-
}, children: [_jsx(FrameLimiter, { fps:
|
|
348
|
+
}, children: [_jsx(FrameLimiter, { fps: maxFps, paused: renderPaused }), _jsx(ViewerCamera, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {})), _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RegisteredSystems, {}), _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), children] })] }));
|
|
365
349
|
});
|
|
366
350
|
const DebugRenderer = () => {
|
|
367
351
|
useFrame(({ gl, scene, camera }) => {
|
|
@@ -43,7 +43,7 @@ export const PerfMonitor = () => {
|
|
|
43
43
|
gl.info.autoReset = previousAutoReset;
|
|
44
44
|
};
|
|
45
45
|
}, [gl]);
|
|
46
|
-
useFrame(({ gl, scene, clock }) => {
|
|
46
|
+
useFrame(({ gl, scene, clock }, delta) => {
|
|
47
47
|
frameCount.current++;
|
|
48
48
|
const now = clock.elapsedTime;
|
|
49
49
|
const dt = now - elapsed.current;
|
|
@@ -122,7 +122,7 @@ export const PerfMonitor = () => {
|
|
|
122
122
|
frameCount.current = 0;
|
|
123
123
|
elapsed.current = now;
|
|
124
124
|
}
|
|
125
|
-
lastMs.current = Math.round(
|
|
125
|
+
lastMs.current = Math.round(delta * 1000 * 10) / 10;
|
|
126
126
|
});
|
|
127
127
|
return (_jsx(Html, { position: [0, 0, 0], style: { position: 'fixed', top: 8, left: 8, pointerEvents: 'none' }, zIndexRange: [100, 100], children: _jsx("div", { style: {
|
|
128
128
|
fontFamily: 'monospace',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAkTA,eAAO,MAAM,gBAAgB,mCA0G5B,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 { emitter, getSelectableKinds, pointInPolygon, resolveSelectionProxyId, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
3
|
+
import { emitter, getSelectableKinds, pointInPolygon, resolveSelectionProxyId, sceneRegistry, useRegistryVersion, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { useThree } from '@react-three/fiber';
|
|
5
5
|
import { useEffect, useRef } from 'react';
|
|
6
6
|
import { Vector3 } from 'three';
|
|
@@ -234,7 +234,12 @@ const getStrategy = () => {
|
|
|
234
234
|
export const SelectionManager = () => {
|
|
235
235
|
const selection = useViewer((s) => s.selection);
|
|
236
236
|
const clickHandledRef = useRef(false);
|
|
237
|
+
// Plugin kinds register AFTER mount (async dynamic-import discovery) —
|
|
238
|
+
// re-derive the `getSelectableKinds()` subscription list when they land.
|
|
239
|
+
const registryVersion = useRegistryVersion();
|
|
237
240
|
useEffect(() => {
|
|
241
|
+
// re-subscribe when plugin kinds register after mount (async plugin load)
|
|
242
|
+
void registryVersion;
|
|
238
243
|
const onEnter = (event) => {
|
|
239
244
|
const strategy = getStrategy();
|
|
240
245
|
if (!strategy)
|
|
@@ -316,7 +321,7 @@ export const SelectionManager = () => {
|
|
|
316
321
|
emitter.off(`${type}:click`, onClick);
|
|
317
322
|
}
|
|
318
323
|
};
|
|
319
|
-
}, []);
|
|
324
|
+
}, [registryVersion]);
|
|
320
325
|
return (_jsxs(_Fragment, { children: [_jsx(PointerMissedHandler, { clickHandledRef: clickHandledRef }), _jsx(OutlinerSync, {})] }));
|
|
321
326
|
};
|
|
322
327
|
const PointerMissedHandler = ({ clickHandledRef, }) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsupported-gpu-fallback.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/unsupported-gpu-fallback.tsx"],"names":[],"mappings":"AAAA,wBAAgB,4BAA4B,gCAY3C"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function UnsupportedGpuViewerFallback() {
|
|
3
|
+
return (_jsx("div", { className: "flex h-full min-h-64 w-full items-center justify-center bg-[#fafafa] p-6 text-center text-neutral-900", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-lg", children: "3D viewer unavailable" }), _jsx("p", { className: "mt-2 text-neutral-600 text-sm", children: "This browser or environment could not initialize WebGPU or WebGL, so Pascal cannot render the 3D scene here. Try opening the editor in a browser with hardware acceleration enabled." })] }) }));
|
|
4
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,21 +17,24 @@ export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url'
|
|
|
17
17
|
export { backdropGradient, deepSkyColor, horizonHazeColor } from './lib/backdrop';
|
|
18
18
|
export { applyWorldScaleBoxUVs } from './lib/box-uv';
|
|
19
19
|
export { ADDITION, Brush, computeGeometryBoundsTree, csgEvaluator, csgGeometry, csgMaterials, INTERSECTION, prepareBrushForCSG, SUBTRACTION, } from './lib/csg-utils';
|
|
20
|
+
export { disposeObject3DResources } from './lib/dispose-object3d';
|
|
20
21
|
export type { EdgeMode } from './lib/edge-style';
|
|
21
22
|
export { computeHeroFraming, DEFAULT_FRAMING_EXCLUDED_TYPES, type HeroFraming, heroCameraPose, temporarilyHideNodeTypes, unionRegisteredNodeBounds, } from './lib/hero-pose';
|
|
22
23
|
export { applyIsolation, clearIsolation, collectIsolationSubtree, isIsolationActive, } from './lib/isolation';
|
|
23
24
|
export { configureKtx2Support, ensureKtx2Support } from './lib/ktx2-loader';
|
|
24
|
-
export { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from './lib/layers';
|
|
25
|
+
export { BATCHED_LAYER, GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, setSurfaceRaycastLayers, ZONE_LAYER, } from './lib/layers';
|
|
25
26
|
export { applyMaterialPresetToMaterials, BLUEPRINT_PALETTE, baseMaterial, CLAY_PALETTE, type ColorPreset, clearMaterialCache, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, DEFAULT_CEILING_MATERIAL, DEFAULT_DOOR_MATERIAL, DEFAULT_ROOF_MATERIAL, DEFAULT_SHELF_MATERIAL, DEFAULT_SLAB_MATERIAL, DEFAULT_STAIR_MATERIAL, DEFAULT_WALL_MATERIAL, DEFAULT_WINDOW_MATERIAL, disposeMaterial, glassMaterial, MONO_PALETTE, PRESET_PALETTES, type RenderShading, resolveMaterialRef, resolveSlotDefaultMaterial, resolveSurfaceColor, WHITE_PALETTE, } from './lib/materials';
|
|
26
27
|
export { mergedOutline } from './lib/merged-outline-node';
|
|
27
28
|
export { unionPolygons } from './lib/polygon-union';
|
|
29
|
+
export { detectRendererCapability, initializeGpuRenderer, type RendererBackendParameters, type RendererCapability, type RendererCapabilityCanvas, type RendererInitializationResult, type RendererPowerPreference, } from './lib/renderer-capability';
|
|
28
30
|
export { getSceneTheme, SCENE_THEME_IDS, SCENE_THEMES, type SceneTheme, } from './lib/scene-themes';
|
|
29
|
-
export {
|
|
31
|
+
export { type HiddenReason, hideFromScene, showInScene } from './lib/scene-visibility';
|
|
32
|
+
export { createSnapshotPipeline, SNAPSHOT_MAX_EDGE, SNAPSHOT_MIME, SNAPSHOT_QUALITY, type SnapshotCaptureMode, type SnapshotCaptureResult, type SnapshotCropRegion, type SnapshotPipeline, type SnapshotSize, THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH, } from './lib/snapshot-pipeline';
|
|
30
33
|
export { buildTerrainPerimeterFillGeometry, type TerrainPerimeterPoint, } from './lib/terrain-perimeter-fill';
|
|
31
34
|
export { getPascalTextureRef, type PascalTextureColorSpace, type PascalTextureMap, type PascalTextureRef, stampPascalTextureRef, textureMapForSlot, } from './lib/texture-reference';
|
|
32
35
|
export { packNormalToRGB, unpackRGBToNormal } from './lib/tsl-compat';
|
|
33
36
|
export { useItemLightPool } from './store/use-item-light-pool';
|
|
34
|
-
export { applyCountryUnitDefault, default as useViewer, type MetricNotation, } from './store/use-viewer';
|
|
37
|
+
export { applyCountryUnitDefault, default as useViewer, type MetricNotation, type WallMode, } from './store/use-viewer';
|
|
35
38
|
export { CeilingSystem } from './systems/ceiling/ceiling-system';
|
|
36
39
|
export { createColumnBoxGeometry, createColumnCylinderGeometry, createColumnSphereGeometry, createColumnTorusGeometry, } from './systems/column/column-geometry';
|
|
37
40
|
export { DoorAnimationSystem } from './systems/door/door-animation-system';
|
|
@@ -52,9 +55,9 @@ export { generateSlabGeometry } from './systems/slab/slab-system';
|
|
|
52
55
|
export { getStairBodyMaterials, getStairRailingMaterial, getStraightStairSegmentBodyMaterials, type StairBodyMaterials, } from './systems/stair/stair-materials';
|
|
53
56
|
export { StairSystem } from './systems/stair/stair-system';
|
|
54
57
|
export { buildOpeningCutoutGeometry, getOpeningCutoutBottomPadding, hasFlatOpeningCutoutBottom, } from './systems/wall/opening-cutout-geometry';
|
|
55
|
-
export { WallCutout } from './systems/wall/wall-cutout';
|
|
58
|
+
export { getWallHideState, WallCutout } from './systems/wall/wall-cutout';
|
|
56
59
|
export { getVisibleWallMaterials } from './systems/wall/wall-materials';
|
|
57
|
-
export { WallSystem } from './systems/wall/wall-system';
|
|
60
|
+
export { drainRebuiltWalls, getPendingWallRebuildCount, WallSystem, } from './systems/wall/wall-system';
|
|
58
61
|
export { poseWindowMovingParts, WindowAnimationSystem, } from './systems/window/window-animation-system';
|
|
59
62
|
export { buildWindowPreviewMesh, WindowSystem } from './systems/window/window-system';
|
|
60
63
|
export { ZoneSystem } from './systems/zone/zone-system';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAO3D,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AACnE,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,EACL,KAAK,YAAY,EACjB,OAAO,IAAI,SAAS,EACpB,KAAK,aAAa,GACnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,KAAK,kBAAkB,GACxB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,yCAAyC,CAAA;AAChD,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,QAAQ,EACR,KAAK,cAAc,GACpB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,eAAe,GAChB,MAAM,gDAAgD,CAAA;AACvD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AAClF,OAAO,EACL,oBAAoB,EACpB,WAAW,GACZ,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAChF,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAIpD,OAAO,EACL,QAAQ,EACR,KAAK,EACL,yBAAyB,EACzB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,GACZ,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EACL,kBAAkB,EAClB,8BAA8B,EAC9B,KAAK,WAAW,EAChB,cAAc,EACd,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,cAAc,EACd,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAO3D,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AACnE,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,EACL,KAAK,YAAY,EACjB,OAAO,IAAI,SAAS,EACpB,KAAK,aAAa,GACnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,KAAK,kBAAkB,GACxB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,yCAAyC,CAAA;AAChD,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,QAAQ,EACR,KAAK,cAAc,GACpB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,eAAe,GAChB,MAAM,gDAAgD,CAAA;AACvD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AAClF,OAAO,EACL,oBAAoB,EACpB,WAAW,GACZ,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAChF,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAIpD,OAAO,EACL,QAAQ,EACR,KAAK,EACL,yBAAyB,EACzB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,GACZ,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,YAAY,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EACL,kBAAkB,EAClB,8BAA8B,EAC9B,KAAK,WAAW,EAChB,cAAc,EACd,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,cAAc,EACd,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,EACL,aAAa,EACb,UAAU,EACV,aAAa,EACb,WAAW,EACX,uBAAuB,EACvB,UAAU,GACX,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,8BAA8B,EAC9B,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,KAAK,WAAW,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,eAAe,EACf,KAAK,aAAa,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,mBAAmB,EACnB,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,uBAAuB,GAC7B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,aAAa,EACb,eAAe,EACf,YAAY,EACZ,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACtF,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,gBAAgB,EAChB,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,iCAAiC,EACjC,KAAK,qBAAqB,GAC3B,MAAM,8BAA8B,CAAA;AACrC,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAC9D,OAAO,EACL,uBAAuB,EACvB,OAAO,IAAI,SAAS,EACpB,KAAK,cAAc,EACnB,KAAK,QAAQ,GACd,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAChE,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAA;AAC1E,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAClG,OAAO,EAAE,yBAAyB,EAAE,MAAM,gDAAgD,CAAA;AAI1F,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAA;AAKrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAA;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAA;AAI5E,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AAKpE,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,+BAA+B,EAC/B,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAGvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,oCAAoC,EACpC,KAAK,kBAAkB,GACxB,MAAM,iCAAiC,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAI1D,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAA;AAKvE,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,UAAU,GACX,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0CAA0C,CAAA;AACjD,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -28,15 +28,18 @@ export { applyWorldScaleBoxUVs } from './lib/box-uv';
|
|
|
28
28
|
// geometry subtracts pieces against their host. Lives in viewer
|
|
29
29
|
// because three-bvh-csg / three-mesh-bvh are viewer-only deps.
|
|
30
30
|
export { ADDITION, Brush, computeGeometryBoundsTree, csgEvaluator, csgGeometry, csgMaterials, INTERSECTION, prepareBrushForCSG, SUBTRACTION, } from './lib/csg-utils';
|
|
31
|
+
export { disposeObject3DResources } from './lib/dispose-object3d';
|
|
31
32
|
export { computeHeroFraming, DEFAULT_FRAMING_EXCLUDED_TYPES, heroCameraPose, temporarilyHideNodeTypes, unionRegisteredNodeBounds, } from './lib/hero-pose';
|
|
32
33
|
export { applyIsolation, clearIsolation, collectIsolationSubtree, isIsolationActive, } from './lib/isolation';
|
|
33
34
|
export { configureKtx2Support, ensureKtx2Support } from './lib/ktx2-loader';
|
|
34
|
-
export { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from './lib/layers';
|
|
35
|
+
export { BATCHED_LAYER, GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, setSurfaceRaycastLayers, ZONE_LAYER, } from './lib/layers';
|
|
35
36
|
export { applyMaterialPresetToMaterials, BLUEPRINT_PALETTE, baseMaterial, CLAY_PALETTE, clearMaterialCache, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, DEFAULT_CEILING_MATERIAL, DEFAULT_DOOR_MATERIAL, DEFAULT_ROOF_MATERIAL, DEFAULT_SHELF_MATERIAL, DEFAULT_SLAB_MATERIAL, DEFAULT_STAIR_MATERIAL, DEFAULT_WALL_MATERIAL, DEFAULT_WINDOW_MATERIAL, disposeMaterial, glassMaterial, MONO_PALETTE, PRESET_PALETTES, resolveMaterialRef, resolveSlotDefaultMaterial, resolveSurfaceColor, WHITE_PALETTE, } from './lib/materials';
|
|
36
37
|
export { mergedOutline } from './lib/merged-outline-node';
|
|
37
38
|
export { unionPolygons } from './lib/polygon-union';
|
|
39
|
+
export { detectRendererCapability, initializeGpuRenderer, } from './lib/renderer-capability';
|
|
38
40
|
export { getSceneTheme, SCENE_THEME_IDS, SCENE_THEMES, } from './lib/scene-themes';
|
|
39
|
-
export {
|
|
41
|
+
export { hideFromScene, showInScene } from './lib/scene-visibility';
|
|
42
|
+
export { createSnapshotPipeline, SNAPSHOT_MAX_EDGE, SNAPSHOT_MIME, SNAPSHOT_QUALITY, THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH, } from './lib/snapshot-pipeline';
|
|
40
43
|
export { buildTerrainPerimeterFillGeometry, } from './lib/terrain-perimeter-fill';
|
|
41
44
|
export { getPascalTextureRef, stampPascalTextureRef, textureMapForSlot, } from './lib/texture-reference';
|
|
42
45
|
export { packNormalToRGB, unpackRGBToNormal } from './lib/tsl-compat';
|
|
@@ -81,13 +84,13 @@ export { StairSystem } from './systems/stair/stair-system';
|
|
|
81
84
|
// roof-wall opening cuts in `@pascal-app/nodes` — keeps shaped holes
|
|
82
85
|
// (arch / rounded / frameless opening) identical across both hosts.
|
|
83
86
|
export { buildOpeningCutoutGeometry, getOpeningCutoutBottomPadding, hasFlatOpeningCutoutBottom, } from './systems/wall/opening-cutout-geometry';
|
|
84
|
-
export { WallCutout } from './systems/wall/wall-cutout';
|
|
87
|
+
export { getWallHideState, WallCutout } from './systems/wall/wall-cutout';
|
|
85
88
|
export { getVisibleWallMaterials } from './systems/wall/wall-materials';
|
|
86
89
|
// Wall internals re-exported so `@pascal-app/nodes`' registry-driven wall
|
|
87
90
|
// definition can compose them into `def.system` without duplicating the
|
|
88
91
|
// 800+ lines of CSG / mitering logic during Phase 3. These exports are
|
|
89
92
|
// removed in Phase 6 when the legacy mount points are deleted.
|
|
90
|
-
export { WallSystem } from './systems/wall/wall-system';
|
|
93
|
+
export { drainRebuiltWalls, getPendingWallRebuildCount, WallSystem, } from './systems/wall/wall-system';
|
|
91
94
|
export { poseWindowMovingParts, WindowAnimationSystem, } from './systems/window/window-animation-system';
|
|
92
95
|
export { buildWindowPreviewMesh, WindowSystem } from './systems/window/window-system';
|
|
93
96
|
export { ZoneSystem } from './systems/zone/zone-system';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispose-object3d.d.ts","sourceRoot":"","sources":["../../src/lib/dispose-object3d.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA4B,QAAQ,EAAE,MAAM,OAAO,CAAA;AAM/D,8EAA8E;AAC9E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAsB7D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function isCachedMaterial(material) {
|
|
2
|
+
return Boolean(material.userData?.__pascalCachedMaterial);
|
|
3
|
+
}
|
|
4
|
+
/** Dispose geometry and non-cached materials owned by an Object3D subtree. */
|
|
5
|
+
export function disposeObject3DResources(root) {
|
|
6
|
+
const geometries = new Set();
|
|
7
|
+
const materials = new Set();
|
|
8
|
+
root.traverse((object) => {
|
|
9
|
+
const renderable = object;
|
|
10
|
+
if (renderable.geometry)
|
|
11
|
+
geometries.add(renderable.geometry);
|
|
12
|
+
const objectMaterials = renderable.material;
|
|
13
|
+
if (Array.isArray(objectMaterials)) {
|
|
14
|
+
for (const material of objectMaterials)
|
|
15
|
+
materials.add(material);
|
|
16
|
+
}
|
|
17
|
+
else if (objectMaterials) {
|
|
18
|
+
materials.add(objectMaterials);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
for (const geometry of geometries)
|
|
22
|
+
geometry.dispose();
|
|
23
|
+
for (const material of materials) {
|
|
24
|
+
if (!isCachedMaterial(material))
|
|
25
|
+
material.dispose();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Rewrites a geometry's material groups so every material is drawn exactly once.
|
|
4
|
+
*
|
|
5
|
+
* A group is a contiguous slice of the index buffer, so a mesh whose triangles
|
|
6
|
+
* alternate between materials pays a draw call per *run*, not per material.
|
|
7
|
+
* Extruded walls hit this hard: `ExtrudeGeometry` emits the cap and side faces
|
|
8
|
+
* interleaved, so run-length grouping produces four groups for two materials —
|
|
9
|
+
* multiplied by a thousand walls, that is thousands of avoidable draw calls.
|
|
10
|
+
* Bucketing the triangles by material first makes each material one run.
|
|
11
|
+
*
|
|
12
|
+
* The geometry gains an index buffer if it had none. Triangle winding, vertex
|
|
13
|
+
* data and material assignment are untouched, so the rendered image is
|
|
14
|
+
* unchanged; only the order in which the GPU is asked to draw it differs.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setGroupsSortedByMaterial(geometry: THREE.BufferGeometry, triangleMaterials: ArrayLike<number>): void;
|
|
17
|
+
//# sourceMappingURL=geometry-groups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-groups.d.ts","sourceRoot":"","sources":["../../src/lib/geometry-groups.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,iBAAiB,EAAE,SAAS,CAAC,MAAM,CAAC,GACnC,IAAI,CA2CN"}
|