@pascal-app/viewer 1.0.3 → 1.0.5

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