@pascal-app/viewer 1.0.0-beta.3 → 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.
Files changed (103) hide show
  1. package/README.md +52 -0
  2. package/dist/components/viewer/frame-limiter.d.ts +11 -0
  3. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  4. package/dist/components/viewer/frame-limiter.js +58 -18
  5. package/dist/components/viewer/index.d.ts +15 -0
  6. package/dist/components/viewer/index.d.ts.map +1 -1
  7. package/dist/components/viewer/index.js +34 -50
  8. package/dist/components/viewer/perf-monitor.js +2 -2
  9. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  10. package/dist/components/viewer/selection-manager.js +7 -2
  11. package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
  12. package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
  13. package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
  14. package/dist/index.d.ts +8 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -4
  17. package/dist/lib/dispose-object3d.d.ts +4 -0
  18. package/dist/lib/dispose-object3d.d.ts.map +1 -0
  19. package/dist/lib/dispose-object3d.js +27 -0
  20. package/dist/lib/geometry-groups.d.ts +17 -0
  21. package/dist/lib/geometry-groups.d.ts.map +1 -0
  22. package/dist/lib/geometry-groups.js +53 -0
  23. package/dist/lib/isolation.d.ts +8 -8
  24. package/dist/lib/isolation.d.ts.map +1 -1
  25. package/dist/lib/isolation.js +18 -30
  26. package/dist/lib/layers.d.ts +16 -0
  27. package/dist/lib/layers.d.ts.map +1 -1
  28. package/dist/lib/layers.js +18 -0
  29. package/dist/lib/renderer-capability.d.ts +56 -0
  30. package/dist/lib/renderer-capability.d.ts.map +1 -0
  31. package/dist/lib/renderer-capability.js +114 -0
  32. package/dist/lib/scene-visibility.d.ts +14 -0
  33. package/dist/lib/scene-visibility.d.ts.map +1 -0
  34. package/dist/lib/scene-visibility.js +48 -0
  35. package/dist/lib/shadow-only.d.ts +16 -0
  36. package/dist/lib/shadow-only.d.ts.map +1 -1
  37. package/dist/lib/shadow-only.js +8 -17
  38. package/dist/lib/snapshot-pipeline.d.ts +9 -0
  39. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  40. package/dist/lib/snapshot-pipeline.js +33 -9
  41. package/dist/store/use-viewer.d.ts +5 -4
  42. package/dist/store/use-viewer.d.ts.map +1 -1
  43. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  44. package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
  45. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  46. package/dist/systems/geometry/geometry-system.js +2 -26
  47. package/dist/systems/level/level-system.d.ts.map +1 -1
  48. package/dist/systems/level/level-system.js +7 -1
  49. package/dist/systems/wall/level-miter-cache.d.ts +10 -0
  50. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
  51. package/dist/systems/wall/level-miter-cache.js +40 -0
  52. package/dist/systems/wall/wall-cutout.d.ts +12 -0
  53. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  54. package/dist/systems/wall/wall-cutout.js +41 -11
  55. package/dist/systems/wall/wall-system.d.ts +4 -0
  56. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  57. package/dist/systems/wall/wall-system.js +31 -18
  58. package/package.json +4 -2
  59. package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
  60. package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
  61. package/dist/components/viewer/scene-bvh.test.js +0 -13
  62. package/dist/lib/csg-utils.test.d.ts +0 -2
  63. package/dist/lib/csg-utils.test.d.ts.map +0 -1
  64. package/dist/lib/csg-utils.test.js +0 -45
  65. package/dist/lib/materials.test.d.ts +0 -2
  66. package/dist/lib/materials.test.d.ts.map +0 -1
  67. package/dist/lib/materials.test.js +0 -25
  68. package/dist/lib/merged-outline-node.test.d.ts +0 -2
  69. package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
  70. package/dist/lib/merged-outline-node.test.js +0 -22
  71. package/dist/lib/polygon-union.test.d.ts +0 -2
  72. package/dist/lib/polygon-union.test.d.ts.map +0 -1
  73. package/dist/lib/polygon-union.test.js +0 -105
  74. package/dist/lib/texture-reference.test.d.ts +0 -2
  75. package/dist/lib/texture-reference.test.d.ts.map +0 -1
  76. package/dist/lib/texture-reference.test.js +0 -86
  77. package/dist/store/use-viewer.test.d.ts +0 -2
  78. package/dist/store/use-viewer.test.d.ts.map +0 -1
  79. package/dist/store/use-viewer.test.js +0 -61
  80. package/dist/systems/door/door-animation.test.d.ts +0 -2
  81. package/dist/systems/door/door-animation.test.d.ts.map +0 -1
  82. package/dist/systems/door/door-animation.test.js +0 -51
  83. package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
  84. package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
  85. package/dist/systems/door/door-floor-alignment.test.js +0 -50
  86. package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
  87. package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
  88. package/dist/systems/geometry/geometry-system.test.js +0 -30
  89. package/dist/systems/slab/slab-system.test.d.ts +0 -2
  90. package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
  91. package/dist/systems/slab/slab-system.test.js +0 -102
  92. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
  93. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
  94. package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
  95. package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
  96. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
  97. package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
  98. package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
  99. package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
  100. package/dist/systems/wall/wall-support-extension.test.js +0 -116
  101. package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
  102. package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
  103. 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;CACb,CAAA;AAgBD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAyE7C,CAAA;AAED,eAAe,YAAY,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
- let elapsed = 0;
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 interval = 1000 / fps;
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
- elapsed = t - then;
42
- if (elapsed > interval) {
43
- advance(i);
44
- i += elapsed / 1000 - (elapsed % interval) / 1000;
45
- then = t - (elapsed % interval);
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
- i += 1 / 1000;
51
- advance(i);
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
- i += interval / 1000;
62
- advance(i);
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
- }, [advance, dpr, fps, initFrameloop, renderPaused, renderer, set, size.height, size.width]);
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;AAGrE,OAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAQtE,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAU1F,OAAO,QAAQ,oBAAoB,CAAC;IAOlC,UAAU,aAAa;QACrB,qBAAqB,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,qBAAqB,CAAC,CAAA;KACxE;CACF;AAuSD,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;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;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,sGA4NV,CAAA;AAkBF,eAAe,MAAM,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 = !canMountViewer || rendererInitFailed;
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
- try {
338
- const renderer = new THREE.WebGPURenderer({ ...props, alpha: true });
339
- renderer.toneMapping = THREE.ACESFilmicToneMapping;
340
- renderer.toneMappingExposure = getSceneTheme(useViewer.getState().sceneTheme).toneMappingExposure;
341
- await renderer.init();
342
- installEmptyDrawGuard(renderer);
343
- return renderer;
344
- }
345
- catch (err) {
346
- // Drop the failed promise from the cache so a future Canvas
347
- // mount on the same DOM can retry instead of inheriting the
348
- // rejection forever.
349
- if (canvas)
350
- WEBGPU_RENDERER_CACHE.delete(canvas);
351
- console.error('[viewer] WebGPURenderer init failed', err);
352
- setRendererInitFailed(true);
353
- throw err;
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: 50 }), _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] })] }));
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(clock.getDelta() * 1000 * 10) / 10;
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":"AAiTA,eAAO,MAAM,gBAAgB,mCAqG5B,CAAA"}
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,2 @@
1
+ export declare function UnsupportedGpuViewerFallback(): import("react").JSX.Element;
2
+ //# sourceMappingURL=unsupported-gpu-fallback.d.ts.map
@@ -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 { createSnapshotPipeline, type SnapshotCaptureMode, type SnapshotCaptureResult, type SnapshotCropRegion, type SnapshotPipeline, type SnapshotSize, THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH, } from './lib/snapshot-pipeline';
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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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 { createSnapshotPipeline, THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH, } from './lib/snapshot-pipeline';
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,4 @@
1
+ import type { Object3D } from 'three';
2
+ /** Dispose geometry and non-cached materials owned by an Object3D subtree. */
3
+ export declare function disposeObject3DResources(root: Object3D): void;
4
+ //# sourceMappingURL=dispose-object3d.d.ts.map
@@ -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"}