@pascal-app/viewer 1.0.7 → 1.1.1

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 (270) hide show
  1. package/README.md +209 -19
  2. package/dist/components/viewer/drone-controller.d.ts.map +1 -1
  3. package/dist/components/viewer/drone-controller.js +1 -5
  4. package/dist/components/viewer/first-person/build-collider-world.d.ts +14 -0
  5. package/dist/components/viewer/first-person/build-collider-world.d.ts.map +1 -0
  6. package/dist/components/viewer/first-person/build-collider-world.js +426 -0
  7. package/dist/components/viewer/first-person/ground-holes.d.ts +22 -0
  8. package/dist/components/viewer/first-person/ground-holes.d.ts.map +1 -0
  9. package/dist/components/viewer/first-person/ground-holes.js +116 -0
  10. package/dist/components/viewer/first-person/mechanism-targets.d.ts +27 -0
  11. package/dist/components/viewer/first-person/mechanism-targets.d.ts.map +1 -0
  12. package/dist/components/viewer/first-person/mechanism-targets.js +79 -0
  13. package/dist/components/viewer/first-person/terrain-collider.d.ts +32 -0
  14. package/dist/components/viewer/first-person/terrain-collider.d.ts.map +1 -0
  15. package/dist/components/viewer/first-person/terrain-collider.js +175 -0
  16. package/dist/components/viewer/glb-replace-instances.d.ts +6 -3
  17. package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -1
  18. package/dist/components/viewer/glb-replace-instances.js +5 -4
  19. package/dist/components/viewer/glb-scene.d.ts +3 -1
  20. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  21. package/dist/components/viewer/glb-scene.js +2 -1
  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 +3 -4
  25. package/dist/components/viewer/index.d.ts.map +1 -1
  26. package/dist/components/viewer/index.js +8 -4
  27. package/dist/components/viewer/live-walkthrough-controller.d.ts +17 -0
  28. package/dist/components/viewer/live-walkthrough-controller.d.ts.map +1 -0
  29. package/dist/components/viewer/live-walkthrough-controller.js +1449 -0
  30. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  31. package/dist/components/viewer/post-processing.js +22 -1
  32. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  33. package/dist/components/viewer/registered-systems.js +2 -2
  34. package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -1
  35. package/dist/components/viewer/unsupported-gpu-fallback.js +21 -1
  36. package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -1
  37. package/dist/components/viewer/use-daylight-rooms.js +75 -33
  38. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  39. package/dist/hooks/use-gltf-ktx2.js +2 -1
  40. package/dist/index.d.ts +8 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +8 -1
  43. package/dist/lib/asset-url.d.ts +0 -1
  44. package/dist/lib/asset-url.d.ts.map +1 -1
  45. package/dist/lib/asset-url.js +3 -3
  46. package/dist/lib/daylight/room-context.d.ts.map +1 -1
  47. package/dist/lib/daylight/room-context.js +3 -2
  48. package/dist/lib/daylight/room-map.d.ts +149 -0
  49. package/dist/lib/daylight/room-map.d.ts.map +1 -0
  50. package/dist/lib/daylight/room-map.js +813 -0
  51. package/dist/lib/daylight/room-scene.d.ts +4 -2
  52. package/dist/lib/daylight/room-scene.d.ts.map +1 -1
  53. package/dist/lib/daylight/room-scene.js +81 -40
  54. package/dist/lib/daylight/rooms.d.ts +71 -32
  55. package/dist/lib/daylight/rooms.d.ts.map +1 -1
  56. package/dist/lib/daylight/rooms.js +371 -286
  57. package/dist/lib/ktx2-loader.d.ts.map +1 -1
  58. package/dist/lib/ktx2-loader.js +2 -1
  59. package/dist/lib/measurement-label.d.ts +4 -0
  60. package/dist/lib/measurement-label.d.ts.map +1 -0
  61. package/dist/lib/measurement-label.js +54 -0
  62. package/dist/lib/measurement-presentation.d.ts +7 -0
  63. package/dist/lib/measurement-presentation.d.ts.map +1 -0
  64. package/dist/lib/measurement-presentation.js +14 -0
  65. package/dist/lib/pointer-events.d.ts.map +1 -1
  66. package/dist/lib/pointer-events.js +3 -3
  67. package/dist/lib/texture-reference.d.ts.map +1 -1
  68. package/dist/lib/texture-reference.js +4 -3
  69. package/dist/lib/typing-target.d.ts +2 -0
  70. package/dist/lib/typing-target.d.ts.map +1 -0
  71. package/dist/lib/typing-target.js +7 -0
  72. package/dist/lib/viewer-config.d.ts +27 -0
  73. package/dist/lib/viewer-config.d.ts.map +1 -0
  74. package/dist/lib/viewer-config.js +23 -0
  75. package/dist/lib/viewer-preference-storage.d.ts +7 -0
  76. package/dist/lib/viewer-preference-storage.d.ts.map +1 -0
  77. package/dist/lib/viewer-preference-storage.js +23 -0
  78. package/dist/sfx/audio-settings.d.ts +81 -0
  79. package/dist/sfx/audio-settings.d.ts.map +1 -0
  80. package/dist/sfx/audio-settings.js +166 -0
  81. package/dist/sfx/index.d.ts +6 -0
  82. package/dist/sfx/index.d.ts.map +1 -0
  83. package/dist/sfx/index.js +5 -0
  84. package/dist/sfx/interaction-sounds.d.ts +7 -0
  85. package/dist/sfx/interaction-sounds.d.ts.map +1 -0
  86. package/dist/sfx/interaction-sounds.js +39 -0
  87. package/dist/sfx/player.d.ts +31 -0
  88. package/dist/sfx/player.d.ts.map +1 -0
  89. package/dist/sfx/player.js +255 -0
  90. package/dist/sfx/sounds.d.ts +60 -0
  91. package/dist/sfx/sounds.d.ts.map +1 -0
  92. package/dist/sfx/sounds.js +25 -0
  93. package/dist/sfx/use-viewer-ui-sounds.d.ts +8 -0
  94. package/dist/sfx/use-viewer-ui-sounds.d.ts.map +1 -0
  95. package/dist/sfx/use-viewer-ui-sounds.js +42 -0
  96. package/dist/store/use-first-person-hud.d.ts +13 -0
  97. package/dist/store/use-first-person-hud.d.ts.map +1 -0
  98. package/dist/store/use-first-person-hud.js +21 -0
  99. package/dist/store/use-viewer.d.ts +2 -56
  100. package/dist/store/use-viewer.d.ts.map +1 -1
  101. package/dist/store/use-viewer.js +3 -1
  102. package/dist/systems/elevator/elevator-interaction-system.d.ts +16 -2
  103. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
  104. package/dist/systems/elevator/elevator-interaction-system.js +41 -8
  105. package/dist/systems/geometry/geometry-status.d.ts +8 -0
  106. package/dist/systems/geometry/geometry-status.d.ts.map +1 -0
  107. package/dist/systems/geometry/geometry-status.js +24 -0
  108. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  109. package/dist/systems/roof/roof-system.js +134 -78
  110. package/dist/systems/wall/wall-system.d.ts +11 -0
  111. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  112. package/dist/systems/wall/wall-system.js +56 -10
  113. package/dist/ui/components/action-button.d.ts +14 -0
  114. package/dist/ui/components/action-button.d.ts.map +1 -0
  115. package/dist/ui/components/action-button.js +21 -0
  116. package/dist/ui/components/action-menu-placement.d.ts +7 -0
  117. package/dist/ui/components/action-menu-placement.d.ts.map +1 -0
  118. package/dist/ui/components/action-menu-placement.js +10 -0
  119. package/dist/ui/components/floorplan-compass-button.d.ts +10 -0
  120. package/dist/ui/components/floorplan-compass-button.d.ts.map +1 -0
  121. package/dist/ui/components/floorplan-compass-button.js +22 -0
  122. package/dist/ui/components/follow-pascal-toggle.d.ts +6 -0
  123. package/dist/ui/components/follow-pascal-toggle.d.ts.map +1 -0
  124. package/dist/ui/components/follow-pascal-toggle.js +21 -0
  125. package/dist/ui/components/localized-html.d.ts +9 -0
  126. package/dist/ui/components/localized-html.d.ts.map +1 -0
  127. package/dist/ui/components/localized-html.js +16 -0
  128. package/dist/ui/components/render-settings-menu.d.ts +9 -0
  129. package/dist/ui/components/render-settings-menu.d.ts.map +1 -0
  130. package/dist/ui/components/render-settings-menu.js +145 -0
  131. package/dist/ui/components/rooms/room-highlight.d.ts +10 -0
  132. package/dist/ui/components/rooms/room-highlight.d.ts.map +1 -0
  133. package/dist/ui/components/rooms/room-highlight.js +56 -0
  134. package/dist/ui/components/rooms/room-labels.d.ts +278 -0
  135. package/dist/ui/components/rooms/room-labels.d.ts.map +1 -0
  136. package/dist/ui/components/rooms/room-labels.js +1295 -0
  137. package/dist/ui/components/rooms/viewer-rooms.d.ts +14 -0
  138. package/dist/ui/components/rooms/viewer-rooms.d.ts.map +1 -0
  139. package/dist/ui/components/rooms/viewer-rooms.js +123 -0
  140. package/dist/ui/components/scene-loader.d.ts +18 -0
  141. package/dist/ui/components/scene-loader.d.ts.map +1 -0
  142. package/dist/ui/components/scene-loader.js +29 -0
  143. package/dist/ui/components/sky-control.d.ts +37 -0
  144. package/dist/ui/components/sky-control.d.ts.map +1 -0
  145. package/dist/ui/components/sky-control.js +382 -0
  146. package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts +4 -0
  147. package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts.map +1 -0
  148. package/dist/ui/components/use-viewer-camera-navigation-sync.js +117 -0
  149. package/dist/ui/components/viewer-controls-bar.d.ts +20 -0
  150. package/dist/ui/components/viewer-controls-bar.d.ts.map +1 -0
  151. package/dist/ui/components/viewer-controls-bar.js +130 -0
  152. package/dist/ui/components/viewer-hover-label.d.ts +14 -0
  153. package/dist/ui/components/viewer-hover-label.d.ts.map +1 -0
  154. package/dist/ui/components/viewer-hover-label.js +28 -0
  155. package/dist/ui/components/viewer-scene-header.d.ts +34 -0
  156. package/dist/ui/components/viewer-scene-header.d.ts.map +1 -0
  157. package/dist/ui/components/viewer-scene-header.js +92 -0
  158. package/dist/ui/components/viewer-selection-manager.d.ts +7 -0
  159. package/dist/ui/components/viewer-selection-manager.d.ts.map +1 -0
  160. package/dist/ui/components/viewer-selection-manager.js +116 -0
  161. package/dist/ui/components/viewer-stage-modes.d.ts +7 -0
  162. package/dist/ui/components/viewer-stage-modes.d.ts.map +1 -0
  163. package/dist/ui/components/viewer-stage-modes.js +42 -0
  164. package/dist/ui/components/viewer-stage-switcher.d.ts +10 -0
  165. package/dist/ui/components/viewer-stage-switcher.d.ts.map +1 -0
  166. package/dist/ui/components/viewer-stage-switcher.js +16 -0
  167. package/dist/ui/components/viewer-stage.d.ts +25 -0
  168. package/dist/ui/components/viewer-stage.d.ts.map +1 -0
  169. package/dist/ui/components/viewer-stage.js +107 -0
  170. package/dist/ui/components/walkthrough-hud.d.ts +29 -0
  171. package/dist/ui/components/walkthrough-hud.d.ts.map +1 -0
  172. package/dist/ui/components/walkthrough-hud.js +55 -0
  173. package/dist/ui/floorplan/floorplan-camera-sync.d.ts +22 -0
  174. package/dist/ui/floorplan/floorplan-camera-sync.d.ts.map +1 -0
  175. package/dist/ui/floorplan/floorplan-camera-sync.js +157 -0
  176. package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts +61 -0
  177. package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts.map +1 -0
  178. package/dist/ui/floorplan/floorplan-dimension-renderer.js +280 -0
  179. package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts +54 -0
  180. package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts.map +1 -0
  181. package/dist/ui/floorplan/floorplan-geometry-renderer.js +317 -0
  182. package/dist/ui/floorplan/floorplan-label-angle.d.ts +24 -0
  183. package/dist/ui/floorplan/floorplan-label-angle.d.ts.map +1 -0
  184. package/dist/ui/floorplan/floorplan-label-angle.js +60 -0
  185. package/dist/ui/floorplan/floorplan-navigation-sync.d.ts +18 -0
  186. package/dist/ui/floorplan/floorplan-navigation-sync.d.ts.map +1 -0
  187. package/dist/ui/floorplan/floorplan-navigation-sync.js +41 -0
  188. package/dist/ui/floorplan/floorplan-preview-geometry.d.ts +26 -0
  189. package/dist/ui/floorplan/floorplan-preview-geometry.d.ts.map +1 -0
  190. package/dist/ui/floorplan/floorplan-preview-geometry.js +173 -0
  191. package/dist/ui/floorplan/floorplan-preview-navigation.d.ts +22 -0
  192. package/dist/ui/floorplan/floorplan-preview-navigation.d.ts.map +1 -0
  193. package/dist/ui/floorplan/floorplan-preview-navigation.js +41 -0
  194. package/dist/ui/floorplan/floorplan-preview-visibility.d.ts +8 -0
  195. package/dist/ui/floorplan/floorplan-preview-visibility.d.ts.map +1 -0
  196. package/dist/ui/floorplan/floorplan-preview-visibility.js +21 -0
  197. package/dist/ui/floorplan/floorplan-preview.d.ts +19 -0
  198. package/dist/ui/floorplan/floorplan-preview.d.ts.map +1 -0
  199. package/dist/ui/floorplan/floorplan-preview.js +700 -0
  200. package/dist/ui/hooks/use-mobile.d.ts +2 -0
  201. package/dist/ui/hooks/use-mobile.d.ts.map +1 -0
  202. package/dist/ui/hooks/use-mobile.js +14 -0
  203. package/dist/ui/index.d.ts +49 -0
  204. package/dist/ui/index.d.ts.map +1 -0
  205. package/dist/ui/index.js +48 -0
  206. package/dist/ui/lib/below-grade.d.ts +38 -0
  207. package/dist/ui/lib/below-grade.d.ts.map +1 -0
  208. package/dist/ui/lib/below-grade.js +102 -0
  209. package/dist/ui/lib/camera-pose.d.ts +19 -0
  210. package/dist/ui/lib/camera-pose.d.ts.map +1 -0
  211. package/dist/ui/lib/camera-pose.js +113 -0
  212. package/dist/ui/lib/cn.d.ts +3 -0
  213. package/dist/ui/lib/cn.d.ts.map +1 -0
  214. package/dist/ui/lib/cn.js +5 -0
  215. package/dist/ui/lib/level-name.d.ts +10 -0
  216. package/dist/ui/lib/level-name.d.ts.map +1 -0
  217. package/dist/ui/lib/level-name.js +33 -0
  218. package/dist/ui/lib/node-display-label.d.ts +8 -0
  219. package/dist/ui/lib/node-display-label.d.ts.map +1 -0
  220. package/dist/ui/lib/node-display-label.js +36 -0
  221. package/dist/ui/lib/room-area.d.ts +3 -0
  222. package/dist/ui/lib/room-area.d.ts.map +1 -0
  223. package/dist/ui/lib/room-area.js +8 -0
  224. package/dist/ui/lib/room-assembly-overlay.d.ts +46 -0
  225. package/dist/ui/lib/room-assembly-overlay.d.ts.map +1 -0
  226. package/dist/ui/lib/room-assembly-overlay.js +222 -0
  227. package/dist/ui/lib/room-name-messages.d.ts +28 -0
  228. package/dist/ui/lib/room-name-messages.d.ts.map +1 -0
  229. package/dist/ui/lib/room-name-messages.js +112 -0
  230. package/dist/ui/lib/room-selection.d.ts +58 -0
  231. package/dist/ui/lib/room-selection.d.ts.map +1 -0
  232. package/dist/ui/lib/room-selection.js +297 -0
  233. package/dist/ui/lib/scene-theme-name.d.ts +4 -0
  234. package/dist/ui/lib/scene-theme-name.d.ts.map +1 -0
  235. package/dist/ui/lib/scene-theme-name.js +22 -0
  236. package/dist/ui/lib/selection-modifiers.d.ts +20 -0
  237. package/dist/ui/lib/selection-modifiers.d.ts.map +1 -0
  238. package/dist/ui/lib/selection-modifiers.js +15 -0
  239. package/dist/ui/lib/viewer-selection.d.ts +132 -0
  240. package/dist/ui/lib/viewer-selection.d.ts.map +1 -0
  241. package/dist/ui/lib/viewer-selection.js +462 -0
  242. package/dist/ui/lib/zone-content.d.ts +6 -0
  243. package/dist/ui/lib/zone-content.d.ts.map +1 -0
  244. package/dist/ui/lib/zone-content.js +96 -0
  245. package/dist/ui/primitives/button.d.ts +11 -0
  246. package/dist/ui/primitives/button.d.ts.map +1 -0
  247. package/dist/ui/primitives/button.js +35 -0
  248. package/dist/ui/primitives/dropdown-menu.d.ts +26 -0
  249. package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
  250. package/dist/ui/primitives/dropdown-menu.js +53 -0
  251. package/dist/ui/primitives/popover.d.ts +8 -0
  252. package/dist/ui/primitives/popover.d.ts.map +1 -0
  253. package/dist/ui/primitives/popover.js +17 -0
  254. package/dist/ui/primitives/switch.d.ts +5 -0
  255. package/dist/ui/primitives/switch.d.ts.map +1 -0
  256. package/dist/ui/primitives/switch.js +8 -0
  257. package/dist/ui/primitives/tooltip.d.ts +8 -0
  258. package/dist/ui/primitives/tooltip.d.ts.map +1 -0
  259. package/dist/ui/primitives/tooltip.js +17 -0
  260. package/dist/ui/store/camera-pose-store.d.ts +17 -0
  261. package/dist/ui/store/camera-pose-store.d.ts.map +1 -0
  262. package/dist/ui/store/camera-pose-store.js +15 -0
  263. package/dist/ui/store/navigation-sync-pose-store.d.ts +26 -0
  264. package/dist/ui/store/navigation-sync-pose-store.d.ts.map +1 -0
  265. package/dist/ui/store/navigation-sync-pose-store.js +16 -0
  266. package/dist/ui/store/use-follow-pascal.d.ts +30 -0
  267. package/dist/ui/store/use-follow-pascal.d.ts.map +1 -0
  268. package/dist/ui/store/use-follow-pascal.js +28 -0
  269. package/dist/ui/theme.css +339 -0
  270. package/package.json +34 -7
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The sculpted ground as walkthrough collision geometry.
3
+ *
4
+ * The first-person collider world is a single merged BVH mesh, and before terrain
5
+ * the ground contributed one flat 2 km box to it. That box is wrong in both
6
+ * directions on a sculpted site: it holds the player up at the datum inside an
7
+ * excavation, and buries them inside a hill. This module replaces it.
8
+ *
9
+ * Two properties it has to hold, because the whole point is that walking agrees
10
+ * with every other consumer of the field:
11
+ *
12
+ * - **The surface is the field**, sampled at every vertex — the same
13
+ * `heightAtSample`/`normalAt` the rendered mesh uses (`terrain-geometry.ts`),
14
+ * so what you see is what you stand on. It is deliberately not a decimated
15
+ * proxy at realistic field sizes.
16
+ * - **It extends past the field**, because `heightAt` clamps to the border
17
+ * outside it. A collider that stopped at the field edge would drop the player
18
+ * into the void one step past the site, which is exactly the bug the flat box
19
+ * existed to prevent. The skirt extrudes each border sample outward at its own
20
+ * height, so the collider and `heightAt` agree everywhere, not just inside.
21
+ *
22
+ * Non-indexed triangles with position + normal, matching what the other collider
23
+ * contributors emit — `mergeGeometries` requires every input to agree on both.
24
+ */
25
+ import { type TerrainField } from '@pascal-app/core';
26
+ import * as THREE from 'three';
27
+ /**
28
+ * Terrain collision geometry in the site's own frame — the caller applies the
29
+ * site's world matrix, exactly as it did for the flat box it replaces.
30
+ */
31
+ export declare function createTerrainColliderGeometry(field: TerrainField): THREE.BufferGeometry | null;
32
+ //# sourceMappingURL=terrain-collider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-collider.d.ts","sourceRoot":"","sources":["../../../../src/components/viewer/first-person/terrain-collider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2E9B;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,CAAC,cAAc,GAAG,IAAI,CA0G9F"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The sculpted ground as walkthrough collision geometry.
3
+ *
4
+ * The first-person collider world is a single merged BVH mesh, and before terrain
5
+ * the ground contributed one flat 2 km box to it. That box is wrong in both
6
+ * directions on a sculpted site: it holds the player up at the datum inside an
7
+ * excavation, and buries them inside a hill. This module replaces it.
8
+ *
9
+ * Two properties it has to hold, because the whole point is that walking agrees
10
+ * with every other consumer of the field:
11
+ *
12
+ * - **The surface is the field**, sampled at every vertex — the same
13
+ * `heightAtSample`/`normalAt` the rendered mesh uses (`terrain-geometry.ts`),
14
+ * so what you see is what you stand on. It is deliberately not a decimated
15
+ * proxy at realistic field sizes.
16
+ * - **It extends past the field**, because `heightAt` clamps to the border
17
+ * outside it. A collider that stopped at the field edge would drop the player
18
+ * into the void one step past the site, which is exactly the bug the flat box
19
+ * existed to prevent. The skirt extrudes each border sample outward at its own
20
+ * height, so the collider and `heightAt` agree everywhere, not just inside.
21
+ *
22
+ * Non-indexed triangles with position + normal, matching what the other collider
23
+ * contributors emit — `mergeGeometries` requires every input to agree on both.
24
+ */
25
+ import { heightAtSample, normalAt } from '@pascal-app/core';
26
+ import * as THREE from 'three';
27
+ /**
28
+ * How far past the field the ground keeps holding the player, in metres. Matches
29
+ * the flat box's half-extent so a site's collider bounds do not change size when
30
+ * it gains terrain.
31
+ */
32
+ const SKIRT_REACH = 1000;
33
+ /**
34
+ * Cell budget before the collider decimates.
35
+ *
36
+ * 66k cells is a 257² field — the largest anything realistic produces, and ~131k
37
+ * triangles, which the BVH builds in well under a frame's worth of budget. The
38
+ * schema allows up to 4097² (a decode-bomb guard, not a real terrain); at that
39
+ * size an exact collider would be 100M triangles, so past the budget this steps
40
+ * the sample grid and the collider becomes a faithful-but-coarser version of the
41
+ * same surface rather than an out-of-memory crash.
42
+ */
43
+ const MAX_COLLIDER_CELLS = 66_000;
44
+ function colliderStride(field) {
45
+ const cells = Math.max(1, (field.cols - 1) * (field.rows - 1));
46
+ if (cells <= MAX_COLLIDER_CELLS)
47
+ return 1;
48
+ return Math.ceil(Math.sqrt(cells / MAX_COLLIDER_CELLS));
49
+ }
50
+ /** The sample indices the collider walks: every `stride`-th, always including the border. */
51
+ function sampleLine(count, stride) {
52
+ const line = [];
53
+ for (let i = 0; i < count - 1; i += stride)
54
+ line.push(i);
55
+ line.push(count - 1);
56
+ return line;
57
+ }
58
+ class TriangleSink {
59
+ field;
60
+ positions = [];
61
+ normals = [];
62
+ constructor(field) {
63
+ this.field = field;
64
+ }
65
+ push(v) {
66
+ this.positions.push(v.x, v.y, v.z);
67
+ const [nx, ny, nz] = normalAt(this.field, v.x, v.z);
68
+ this.normals.push(nx, ny, nz);
69
+ }
70
+ /**
71
+ * One cell, given its four corners named by axis order. Winding matches
72
+ * `buildTerrainMesh`: `(p00, p01, p10)` and `(p10, p01, p11)` faces +Y, which is
73
+ * what the spawn resolver's `normal.dot(UP) > 0.2` floor test needs.
74
+ */
75
+ quad(p00, p10, p01, p11) {
76
+ this.push(p00);
77
+ this.push(p01);
78
+ this.push(p10);
79
+ this.push(p10);
80
+ this.push(p01);
81
+ this.push(p11);
82
+ }
83
+ build() {
84
+ if (this.positions.length === 0)
85
+ return null;
86
+ const geometry = new THREE.BufferGeometry();
87
+ geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(this.positions), 3));
88
+ geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.normals), 3));
89
+ return geometry;
90
+ }
91
+ }
92
+ /**
93
+ * Terrain collision geometry in the site's own frame — the caller applies the
94
+ * site's world matrix, exactly as it did for the flat box it replaces.
95
+ */
96
+ export function createTerrainColliderGeometry(field) {
97
+ if (field.cols < 2 || field.rows < 2)
98
+ return null;
99
+ const stride = colliderStride(field);
100
+ const cols = sampleLine(field.cols, stride);
101
+ const rows = sampleLine(field.rows, stride);
102
+ const sink = new TriangleSink(field);
103
+ const xAt = (col) => field.origin[0] + col * field.spacing;
104
+ const zAt = (row) => field.origin[1] + row * field.spacing;
105
+ const at = (col, row) => ({
106
+ x: xAt(col),
107
+ y: heightAtSample(field, col, row),
108
+ z: zAt(row),
109
+ });
110
+ for (let ri = 0; ri < rows.length - 1; ri++) {
111
+ const r0 = rows[ri];
112
+ const r1 = rows[ri + 1];
113
+ for (let ci = 0; ci < cols.length - 1; ci++) {
114
+ const c0 = cols[ci];
115
+ const c1 = cols[ci + 1];
116
+ sink.quad(at(c0, r0), at(c1, r0), at(c0, r1), at(c1, r1));
117
+ }
118
+ }
119
+ // The skirt: each border sample extruded outward at its own height, so stepping
120
+ // off the field lands on the border height rather than on nothing. This is the
121
+ // collider spelling of `heightAt`'s edge clamp.
122
+ const firstCol = 0;
123
+ const lastCol = field.cols - 1;
124
+ const firstRow = 0;
125
+ const lastRow = field.rows - 1;
126
+ const westX = xAt(firstCol) - SKIRT_REACH;
127
+ const eastX = xAt(lastCol) + SKIRT_REACH;
128
+ const northZ = zAt(firstRow) - SKIRT_REACH;
129
+ const southZ = zAt(lastRow) + SKIRT_REACH;
130
+ for (let ci = 0; ci < cols.length - 1; ci++) {
131
+ const c0 = cols[ci];
132
+ const c1 = cols[ci + 1];
133
+ const north0 = at(c0, firstRow);
134
+ const north1 = at(c1, firstRow);
135
+ sink.quad({ x: north0.x, y: north0.y, z: northZ }, { x: north1.x, y: north1.y, z: northZ }, north0, north1);
136
+ const south0 = at(c0, lastRow);
137
+ const south1 = at(c1, lastRow);
138
+ sink.quad(south0, south1, { x: south0.x, y: south0.y, z: southZ }, {
139
+ x: south1.x,
140
+ y: south1.y,
141
+ z: southZ,
142
+ });
143
+ }
144
+ for (let ri = 0; ri < rows.length - 1; ri++) {
145
+ const r0 = rows[ri];
146
+ const r1 = rows[ri + 1];
147
+ const west0 = at(firstCol, r0);
148
+ const west1 = at(firstCol, r1);
149
+ sink.quad({ x: westX, y: west0.y, z: west0.z }, west0, { x: westX, y: west1.y, z: west1.z }, west1);
150
+ const east0 = at(lastCol, r0);
151
+ const east1 = at(lastCol, r1);
152
+ sink.quad(east0, { x: eastX, y: east0.y, z: east0.z }, east1, {
153
+ x: eastX,
154
+ y: east1.y,
155
+ z: east1.z,
156
+ });
157
+ }
158
+ // Corner quads, flat at the corner sample's height — the diagonal region no edge
159
+ // strip covers, and where `heightAt` also reports the corner value.
160
+ const corners = [
161
+ [at(firstCol, firstRow), westX, northZ],
162
+ [at(lastCol, firstRow), eastX, northZ],
163
+ [at(firstCol, lastRow), westX, southZ],
164
+ [at(lastCol, lastRow), eastX, southZ],
165
+ ];
166
+ for (const [corner, farX, farZ] of corners) {
167
+ const minX = Math.min(corner.x, farX);
168
+ const maxX = Math.max(corner.x, farX);
169
+ const minZ = Math.min(corner.z, farZ);
170
+ const maxZ = Math.max(corner.z, farZ);
171
+ const y = corner.y;
172
+ sink.quad({ x: minX, y, z: minZ }, { x: maxX, y, z: minZ }, { x: minX, y, z: maxZ }, { x: maxX, y, z: maxZ });
173
+ }
174
+ return sink.build();
175
+ }
@@ -1,4 +1,4 @@
1
- import { type AnyNode } from '@pascal-app/core';
1
+ import { type AnyNode, type GeometryContext } from '@pascal-app/core';
2
2
  import type { Object3D } from 'three';
3
3
  /**
4
4
  * Re-renders `bake: 'replace'` kinds (e.g. plugin trees) live over the baked GLB.
@@ -9,10 +9,13 @@ import type { Object3D } from 'three';
9
9
  * free, and a forest stays a few instanced draw calls.
10
10
  *
11
11
  * Memoized: `GlbScene` re-renders each frame on camera move; `nodes` and
12
- * `identity` are stable refs, so this whole subtree short-circuits.
12
+ * `identity` are stable refs, so this whole subtree short-circuits; `resolve`
13
+ * must be stable too.
13
14
  */
14
- export declare const GlbReplaceInstances: import("react").MemoExoticComponent<({ nodes, identity, }: {
15
+ export declare const GlbReplaceInstances: import("react").MemoExoticComponent<({ nodes, identity, resolve, }: {
15
16
  nodes: AnyNode[];
16
17
  identity: Map<string, Object3D>;
18
+ /** Looks up nodes of the published scene graph; handed to every renderer. */
19
+ resolve?: GeometryContext['resolve'];
17
20
  }) => import("react").JSX.Element>;
18
21
  //# sourceMappingURL=glb-replace-instances.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"glb-replace-instances.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-replace-instances.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAA0C,MAAM,kBAAkB,CAAA;AAGvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAerC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,6DAG7B;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAChC,iCAyCC,CAAA"}
1
+ {"version":3,"file":"glb-replace-instances.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-replace-instances.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,eAAe,EAErB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAiBrC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,sEAI7B;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;CACrC,iCAyCC,CAAA"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
- import { nodeRegistry } from '@pascal-app/core';
3
+ import { nodeRegistry, } from '@pascal-app/core';
4
4
  import { createPortal } from '@react-three/fiber';
5
5
  import { Fragment, lazy, memo, Suspense, useMemo } from 'react';
6
6
  // Lazy components cached by their source so React.lazy isn't re-invoked per render.
@@ -22,9 +22,10 @@ function getReplaceRenderer(source) {
22
22
  * free, and a forest stays a few instanced draw calls.
23
23
  *
24
24
  * Memoized: `GlbScene` re-renders each frame on camera move; `nodes` and
25
- * `identity` are stable refs, so this whole subtree short-circuits.
25
+ * `identity` are stable refs, so this whole subtree short-circuits; `resolve`
26
+ * must be stable too.
26
27
  */
27
- export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, identity, }) {
28
+ export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, identity, resolve, }) {
28
29
  const byLevel = useMemo(() => {
29
30
  const levels = new Map();
30
31
  for (const node of nodes) {
@@ -54,7 +55,7 @@ export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, id
54
55
  if (!source)
55
56
  return;
56
57
  const Renderer = getReplaceRenderer(source);
57
- portals.push(_jsx(Fragment, { children: createPortal(_jsx(Suspense, { fallback: null, children: _jsx(Renderer, { nodes: kindNodes }) }), anchor) }, `${parentId}:${kind}`));
58
+ portals.push(_jsx(Fragment, { children: createPortal(_jsx(Suspense, { fallback: null, children: _jsx(Renderer, { nodes: kindNodes, resolve: resolve }) }), anchor) }, `${parentId}:${kind}`));
58
59
  });
59
60
  });
60
61
  return _jsx(_Fragment, { children: portals });
@@ -67,7 +67,9 @@ export declare function GlbScene({ url, interactiveItems, referenceNodes, replac
67
67
  /** Clicks and hovers on the building; without it the scene is look-only. */
68
68
  onPick?: (event: GlbPickEvent) => void;
69
69
  onWalkthroughChange?: (state: GlbWalkthrough) => void;
70
- /** The scene graph's nodes, so the selection callout can say what each element is. */
70
+ /** Every node of the scene graph the artifact was baked from: the selection
71
+ * callout says what each element is, and `replace` renderers resolve their
72
+ * parent and siblings. A stable reference. */
71
73
  sceneNodes?: Readonly<Record<string, AnyNode>>;
72
74
  }): import("react").JSX.Element;
73
75
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAab,MAAM,kBAAkB,CAAA;AASzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoB9B,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAuB3E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/D,SAAS,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;CAC1E,CAAA;AAKD;0EAC0E;AAC1E,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/D,GAAG,IAAI,CAAA;AAqDR,KAAK,YAAY,GAAG;IAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAA;CAAE,CAAA;AA4CpE;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,GAC5C,CAAC,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG;IAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAA;CAAE,CAAC,GAAG,IAAI,CAkB5E;AASD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,UAAU,GACX,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;IACX;oFACgF;IAChF,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,CAAA;IACvC;mFAC+E;IAC/E,cAAc,CAAC,EAAE,OAAO,EAAE,CAAA;IAC1B;kFAC8E;IAC9E,YAAY,CAAC,EAAE,OAAO,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAA;IACxE,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACtC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;IACrD,sFAAsF;IACtF,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC/C,+BAu7BA"}
1
+ {"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAcb,MAAM,kBAAkB,CAAA;AASzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoB9B,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAuB3E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/D,SAAS,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;CAC1E,CAAA;AAKD;0EAC0E;AAC1E,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/D,GAAG,IAAI,CAAA;AAqDR,KAAK,YAAY,GAAG;IAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAA;CAAE,CAAA;AA4CpE;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,GAC5C,CAAC,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG;IAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAA;CAAE,CAAC,GAAG,IAAI,CAkB5E;AASD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,UAAU,GACX,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;IACX;oFACgF;IAChF,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,CAAA;IACvC;mFAC+E;IAC/E,cAAc,CAAC,EAAE,OAAO,EAAE,CAAA;IAC1B;kFAC8E;IAC9E,YAAY,CAAC,EAAE,OAAO,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAA;IACxE,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACtC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;IACrD;;mDAE+C;IAC/C,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC/C,+BA47BA"}
@@ -127,6 +127,7 @@ export function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes,
127
127
  const gltf = useGLTFKTX2(url);
128
128
  const rootRef = useRef(null);
129
129
  const actions = useClipActions(gltf.animations, rootRef);
130
+ const resolveNode = useMemo(() => sceneNodes ? (id) => sceneNodes[id] : undefined, [sceneNodes]);
130
131
  const proceduralPlayback = useMemo(() => {
131
132
  const byNode = new Map();
132
133
  gltf.scene.traverse((object) => {
@@ -987,7 +988,7 @@ export function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes,
987
988
  for (const mesh of occluderOwnMeshes)
988
989
  clearShadowOnly(mesh);
989
990
  }, [occluderOwnMeshes]);
990
- return (_jsxs("group", { ref: rootRef, children: [walkingOnFoot && shuttersInView ? _jsx(GlbShuttersPrompt, {}) : null, _jsx("primitive", { object: gltf.scene, onClick: handleClick, onPointerMissed: handlePointerMissed, onPointerMove: handlePointerMove, onPointerOut: handlePointerOut }), _jsx(GlbSelectionCallout, { identity: identity, isOpen: isOpen, items: interactiveItems, loopMechanisms: loopMechanisms, sceneNodes: sceneNodes, toggleOpen: toggleOpenable }), interactiveItems?.length ? (_jsx(GlbInteractive, { actions: actions, identity: identity, items: interactiveItems })) : null, referenceNodes?.length ? (_jsx(GlbReferenceNodes, { identity: identity, nodes: referenceNodes })) : null, replaceNodes?.length ? (_jsx(GlbReplaceInstances, { identity: identity, nodes: replaceNodes })) : null, _jsx(GlbDaylightGround, { root: gltf.scene })] }));
991
+ return (_jsxs("group", { ref: rootRef, children: [walkingOnFoot && shuttersInView ? _jsx(GlbShuttersPrompt, {}) : null, _jsx("primitive", { object: gltf.scene, onClick: handleClick, onPointerMissed: handlePointerMissed, onPointerMove: handlePointerMove, onPointerOut: handlePointerOut }), _jsx(GlbSelectionCallout, { identity: identity, isOpen: isOpen, items: interactiveItems, loopMechanisms: loopMechanisms, sceneNodes: sceneNodes, toggleOpen: toggleOpenable }), interactiveItems?.length ? (_jsx(GlbInteractive, { actions: actions, identity: identity, items: interactiveItems })) : null, referenceNodes?.length ? (_jsx(GlbReferenceNodes, { identity: identity, nodes: referenceNodes })) : null, replaceNodes?.length ? (_jsx(GlbReplaceInstances, { identity: identity, nodes: replaceNodes, resolve: resolveNode })) : null, _jsx(GlbDaylightGround, { root: gltf.scene })] }));
991
992
  }
992
993
  function playShutterSound(node) {
993
994
  const id = node.userData.pascalId;
@@ -9,7 +9,7 @@ export declare const CROUCH_RUN_SPEED = 1.4;
9
9
  export declare const STAND_CLEARANCE = 1.25;
10
10
  export declare const EYE_LERP_SPEED = 12;
11
11
  /**
12
- * First-person walkthrough controller for the baked GLB. Reuses the editor's
12
+ * First-person walkthrough controller for the baked GLB. Uses the shared
13
13
  * `BVHEcctrl` capsule character controller (gravity, jump, sprint, ground-float,
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
@@ -1 +1 @@
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;AAmMhC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,sCA0PhE"}
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;AAiBjC,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;AAmMhC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,sCA0PhE"}
@@ -16,8 +16,7 @@ import { applyWalkthroughCameraClipping } from './viewer-camera';
16
16
  // awareness without wide-angle distortion. Applied only while walking — both
17
17
  // walkthrough controllers read this and restore the orbit FOV on exit.
18
18
  export const WALKTHROUGH_FOV = 60;
19
- // Eye/capsule geometry mirrors the editor's first-person controller so the
20
- // baked walkthrough feels identical. The capsule centre sits below the eye; the
19
+ // Shared with the live walkthrough so baked and parametric scenes feel identical. The capsule centre sits below the eye; the
21
20
  // camera rides the capsule with a small offset and the controller floats it to
22
21
  // the ground.
23
22
  const CAMERA_EYE_OFFSET = 0.45;
@@ -109,7 +108,7 @@ function boxFloorGeometry(cx, topY, cz, width, depth) {
109
108
  // Fallback ground so the player never falls into the void: a single large
110
109
  // ground plane at the lowest level (level 0). Upper levels rely on their own
111
110
  // baked slabs — a slab-less upper floor lets you fall down to the ground, which
112
- // the ground plane catches. (Mirrors the editor walkthrough's site ground.)
111
+ // the ground plane catches. (Mirrors the live walkthrough's site ground.)
113
112
  function addFallbackFloors(scene, geometries) {
114
113
  const sceneBounds = new Box3();
115
114
  for (const geometry of geometries) {
@@ -224,7 +223,7 @@ function resolveGlbSpawn(scene) {
224
223
  };
225
224
  }
226
225
  /**
227
- * First-person walkthrough controller for the baked GLB. Reuses the editor's
226
+ * First-person walkthrough controller for the baked GLB. Uses the shared
228
227
  * `BVHEcctrl` capsule character controller (gravity, jump, sprint, ground-float,
229
228
  * mesh collision) fed a collider built from the artifact's own geometry — so the
230
229
  * baked viewer walks the building with the same physics as the editor, without
@@ -1 +1 @@
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;AAMrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,EAAuB,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAI5F,OAAkB,EAAE,KAAK,aAAa,EAAsB,MAAM,wBAAwB,CAAA;AAe1F,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;AASD,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;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;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,sGA6SV,CAAA;eA+Ea,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAId,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;AAMrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,EAAuB,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAK5F,OAAkB,EAAE,KAAK,aAAa,EAAsB,MAAM,wBAAwB,CAAA;AAe1F,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;AASD,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;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;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,sGA+SV,CAAA;eA+Ea,MAAM"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
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';
3
+ import { isDevelopmentBuild, 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';
@@ -13,6 +13,7 @@ import { choosePointerEvents } from '../../lib/pointer-events';
13
13
  import { initializeGpuRenderer } from '../../lib/renderer-capability';
14
14
  import { getSceneTheme } from '../../lib/scene-themes';
15
15
  import { installTextureNodeNullGuard } from '../../lib/texture-node-guard';
16
+ import { viewerPreferenceStorage } from '../../lib/viewer-preference-storage';
16
17
  import useViewer, {} from '../../store/use-viewer';
17
18
  import { FloorElevationSystem } from '../../systems/floor-elevation/floor-elevation-system';
18
19
  import { GeometrySystem } from '../../systems/geometry/geometry-system';
@@ -68,7 +69,7 @@ const DIRTY_BUILD_KINDS = new Set([
68
69
  'wall',
69
70
  'window',
70
71
  ]);
71
- const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakSet();
72
+ const warnedEmptyDraw = isDevelopmentBuild() ? new WeakSet() : null;
72
73
  /**
73
74
  * Renderer-level safety net against the empty-vertex-buffer crash.
74
75
  *
@@ -375,7 +376,7 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
375
376
  if (!hasDefaultRender || typeof window === 'undefined')
376
377
  return;
377
378
  let persistedState = {};
378
- const rawPreferences = window.localStorage.getItem('viewer-preferences');
379
+ const rawPreferences = viewerPreferenceStorage.getItem('viewer-preferences');
379
380
  if (rawPreferences) {
380
381
  try {
381
382
  const parsed = JSON.parse(rawPreferences);
@@ -421,7 +422,10 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
421
422
  if (showGpuFallback) {
422
423
  return _jsx(UnsupportedGpuViewerFallback, {});
423
424
  }
424
- return (_jsxs(_Fragment, { children: [(perf || PERF_OVERLAY_ENABLED) && _jsx(PerfPanel, {}), _jsx(Canvas, { ref: subscribeWallBuildInteractions, camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${transparentBackground ? 'bg-transparent' : isDark ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, maxDpr], events: pointerEvents, frameloop: "never", gl: ((props) => {
425
+ return (_jsxs(_Fragment, { children: [(perf || PERF_OVERLAY_ENABLED) && _jsx(PerfPanel, {}), _jsx(Canvas, { ref: subscribeWallBuildInteractions, camera: { position: [50, 50, 50], fov: 50 }, style: {
426
+ backgroundColor: transparentBackground ? 'transparent' : isDark ? '#1f2433' : '#fafafa',
427
+ transition: 'background-color 700ms cubic-bezier(0.4, 0, 0.2, 1), color 700ms cubic-bezier(0.4, 0, 0.2, 1)',
428
+ }, dpr: [1, maxDpr], events: pointerEvents, frameloop: "never", gl: ((props) => {
425
429
  const canvas = props.canvas;
426
430
  const cached = canvas ? WEBGPU_RENDERER_CACHE.get(canvas) : undefined;
427
431
  if (cached)
@@ -0,0 +1,17 @@
1
+ export type LiveWalkthroughControllerProps = {
2
+ /** Freeze movement and look while a host renders a still. */
3
+ paused?: boolean;
4
+ /**
5
+ * Whether the host owns camera FOV right now (e.g. a capture rig). Read when walkthrough
6
+ * starts and when it ends, so the answer at teardown decides whether the orbit FOV returns.
7
+ */
8
+ externalFov?: () => boolean;
9
+ /** Allow locked-pointer clicks to operate the targeted object (E/R still work). */
10
+ pointerInteraction?: boolean;
11
+ /** Keep walkthrough active when pointer lock is lost; Escape with a free cursor exits. */
12
+ suspendOnUnlock?: boolean;
13
+ /** Called after walkthroughMode is cleared, including on Escape or pointer unlock. */
14
+ onExit?: () => void;
15
+ };
16
+ export declare function LiveWalkthroughController(props: LiveWalkthroughControllerProps): import("react").JSX.Element | null;
17
+ //# sourceMappingURL=live-walkthrough-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-walkthrough-controller.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/live-walkthrough-controller.tsx"],"names":[],"mappings":"AA8uBA,MAAM,MAAM,8BAA8B,GAAG;IAC3C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAA;IAC3B,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,0FAA0F;IAC1F,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAKD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,8BAA8B,sCAG9E"}