@pascal-app/core 0.9.1 → 1.0.0-beta.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 (378) hide show
  1. package/README.md +17 -10
  2. package/dist/events/bus.d.ts +49 -2
  3. package/dist/events/bus.d.ts.map +1 -1
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  6. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  7. package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
  8. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  9. package/dist/hooks/scene-registry/scene-registry.js +27 -1
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +135 -28
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +427 -271
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +208 -9
  19. package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
  20. package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
  21. package/dist/hooks/spatial-grid/support-host-id.js +2 -0
  22. package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
  24. package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
  25. package/dist/index.d.ts +33 -11
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +32 -10
  28. package/dist/lib/level-name.d.ts +4 -0
  29. package/dist/lib/level-name.d.ts.map +1 -0
  30. package/dist/lib/level-name.js +10 -0
  31. package/dist/lib/measurement-geometry.d.ts +23 -0
  32. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  33. package/dist/lib/measurement-geometry.js +249 -0
  34. package/dist/lib/polygon-geometry.d.ts +0 -1
  35. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  36. package/dist/lib/polygon-geometry.js +0 -18
  37. package/dist/lib/selection-proxy.d.ts +4 -0
  38. package/dist/lib/selection-proxy.d.ts.map +1 -0
  39. package/dist/lib/selection-proxy.js +12 -0
  40. package/dist/lib/slab-polygon.d.ts +31 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +557 -31
  43. package/dist/lib/slots.d.ts +14 -0
  44. package/dist/lib/slots.d.ts.map +1 -0
  45. package/dist/lib/slots.js +26 -0
  46. package/dist/lib/space-detection.d.ts +40 -6
  47. package/dist/lib/space-detection.d.ts.map +1 -1
  48. package/dist/lib/space-detection.js +574 -192
  49. package/dist/lib/terrain-brush.d.ts +163 -0
  50. package/dist/lib/terrain-brush.d.ts.map +1 -0
  51. package/dist/lib/terrain-brush.js +359 -0
  52. package/dist/lib/terrain-codec.d.ts +51 -0
  53. package/dist/lib/terrain-codec.d.ts.map +1 -0
  54. package/dist/lib/terrain-codec.js +167 -0
  55. package/dist/lib/terrain-field.d.ts +150 -0
  56. package/dist/lib/terrain-field.d.ts.map +1 -0
  57. package/dist/lib/terrain-field.js +248 -0
  58. package/dist/lib/terrain-raycast.d.ts +44 -0
  59. package/dist/lib/terrain-raycast.d.ts.map +1 -0
  60. package/dist/lib/terrain-raycast.js +188 -0
  61. package/dist/lib/terrain-source.d.ts +60 -0
  62. package/dist/lib/terrain-source.d.ts.map +1 -0
  63. package/dist/lib/terrain-source.js +90 -0
  64. package/dist/lib/terrain-support.d.ts +60 -0
  65. package/dist/lib/terrain-support.d.ts.map +1 -0
  66. package/dist/lib/terrain-support.js +150 -0
  67. package/dist/lib/wall-distance.d.ts +60 -0
  68. package/dist/lib/wall-distance.d.ts.map +1 -0
  69. package/dist/lib/wall-distance.js +85 -0
  70. package/dist/lib/zone-quantities.d.ts +21 -0
  71. package/dist/lib/zone-quantities.d.ts.map +1 -0
  72. package/dist/lib/zone-quantities.js +428 -0
  73. package/dist/material-library.d.ts +28 -1
  74. package/dist/material-library.d.ts.map +1 -1
  75. package/dist/material-library.js +2233 -441
  76. package/dist/registry/handles.d.ts +91 -3
  77. package/dist/registry/handles.d.ts.map +1 -1
  78. package/dist/registry/index.d.ts +3 -3
  79. package/dist/registry/index.d.ts.map +1 -1
  80. package/dist/registry/index.js +1 -1
  81. package/dist/registry/registry.d.ts +46 -3
  82. package/dist/registry/registry.d.ts.map +1 -1
  83. package/dist/registry/registry.js +82 -1
  84. package/dist/registry/subtree.d.ts.map +1 -1
  85. package/dist/registry/subtree.js +8 -1
  86. package/dist/registry/types.d.ts +781 -39
  87. package/dist/registry/types.d.ts.map +1 -1
  88. package/dist/schema/index.d.ts +27 -8
  89. package/dist/schema/index.d.ts.map +1 -1
  90. package/dist/schema/index.js +23 -6
  91. package/dist/schema/material.d.ts +3 -2
  92. package/dist/schema/material.d.ts.map +1 -1
  93. package/dist/schema/material.js +7 -5
  94. package/dist/schema/nodes/box-vent.d.ts +2 -2
  95. package/dist/schema/nodes/cabinet.d.ts +536 -0
  96. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  97. package/dist/schema/nodes/cabinet.js +139 -0
  98. package/dist/schema/nodes/ceiling.d.ts +4 -3
  99. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  100. package/dist/schema/nodes/ceiling.js +11 -1
  101. package/dist/schema/nodes/chimney.d.ts +4 -4
  102. package/dist/schema/nodes/column.d.ts +4 -2
  103. package/dist/schema/nodes/column.d.ts.map +1 -1
  104. package/dist/schema/nodes/column.js +5 -0
  105. package/dist/schema/nodes/construction-dimension.d.ts +195 -0
  106. package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
  107. package/dist/schema/nodes/construction-dimension.js +151 -0
  108. package/dist/schema/nodes/cupola.d.ts +2 -2
  109. package/dist/schema/nodes/door.d.ts +42 -5
  110. package/dist/schema/nodes/door.d.ts.map +1 -1
  111. package/dist/schema/nodes/door.js +32 -0
  112. package/dist/schema/nodes/dormer.d.ts +8 -8
  113. package/dist/schema/nodes/dormer.js +1 -1
  114. package/dist/schema/nodes/downspout.d.ts +2 -2
  115. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  116. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  117. package/dist/schema/nodes/duct-fitting.js +92 -0
  118. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  119. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  120. package/dist/schema/nodes/duct-segment.js +73 -0
  121. package/dist/schema/nodes/duct-terminal.d.ts +58 -0
  122. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  123. package/dist/schema/nodes/duct-terminal.js +53 -0
  124. package/dist/schema/nodes/elevator.d.ts +3 -2
  125. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  126. package/dist/schema/nodes/elevator.js +3 -0
  127. package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
  128. package/dist/schema/nodes/fence.d.ts +20 -2
  129. package/dist/schema/nodes/fence.d.ts.map +1 -1
  130. package/dist/schema/nodes/fence.js +33 -2
  131. package/dist/schema/nodes/gutter.d.ts +2 -2
  132. package/dist/schema/nodes/hvac-equipment.d.ts +64 -0
  133. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  134. package/dist/schema/nodes/hvac-equipment.js +57 -0
  135. package/dist/schema/nodes/item.d.ts +9 -0
  136. package/dist/schema/nodes/item.d.ts.map +1 -1
  137. package/dist/schema/nodes/item.js +24 -0
  138. package/dist/schema/nodes/level.d.ts +2 -1
  139. package/dist/schema/nodes/level.d.ts.map +1 -1
  140. package/dist/schema/nodes/level.js +12 -31
  141. package/dist/schema/nodes/lineset.d.ts +42 -0
  142. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  143. package/dist/schema/nodes/lineset.js +38 -0
  144. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  145. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  146. package/dist/schema/nodes/liquid-line.js +24 -0
  147. package/dist/schema/nodes/measurement.d.ts +306 -0
  148. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  149. package/dist/schema/nodes/measurement.js +87 -0
  150. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  151. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  152. package/dist/schema/nodes/pipe-fitting.js +45 -0
  153. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  154. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  155. package/dist/schema/nodes/pipe-segment.js +36 -0
  156. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  157. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  158. package/dist/schema/nodes/pipe-trap.js +36 -0
  159. package/dist/schema/nodes/ridge-vent.d.ts +18 -3
  160. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  161. package/dist/schema/nodes/ridge-vent.js +221 -3
  162. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  163. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  164. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  165. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  166. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  167. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  168. package/dist/schema/nodes/roof-segment.d.ts +84 -8
  169. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  170. package/dist/schema/nodes/roof-segment.js +290 -5
  171. package/dist/schema/nodes/roof.d.ts +8 -8
  172. package/dist/schema/nodes/shelf.d.ts +4 -2
  173. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  174. package/dist/schema/nodes/shelf.js +8 -0
  175. package/dist/schema/nodes/site.d.ts +9 -0
  176. package/dist/schema/nodes/site.d.ts.map +1 -1
  177. package/dist/schema/nodes/site.js +8 -6
  178. package/dist/schema/nodes/skylight.d.ts +10 -10
  179. package/dist/schema/nodes/slab.d.ts +8 -2
  180. package/dist/schema/nodes/slab.d.ts.map +1 -1
  181. package/dist/schema/nodes/slab.js +18 -2
  182. package/dist/schema/nodes/solar-panel.d.ts +4 -4
  183. package/dist/schema/nodes/spawn.d.ts +1 -0
  184. package/dist/schema/nodes/spawn.d.ts.map +1 -1
  185. package/dist/schema/nodes/spawn.js +2 -0
  186. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  187. package/dist/schema/nodes/stair.d.ts +16 -13
  188. package/dist/schema/nodes/stair.d.ts.map +1 -1
  189. package/dist/schema/nodes/stair.js +11 -1
  190. package/dist/schema/nodes/structural-grid.d.ts +27 -0
  191. package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
  192. package/dist/schema/nodes/structural-grid.js +17 -0
  193. package/dist/schema/nodes/turbine-vent.d.ts +2 -2
  194. package/dist/schema/nodes/wall.d.ts +248 -6
  195. package/dist/schema/nodes/wall.d.ts.map +1 -1
  196. package/dist/schema/nodes/wall.js +266 -0
  197. package/dist/schema/nodes/window.d.ts +40 -3
  198. package/dist/schema/nodes/window.d.ts.map +1 -1
  199. package/dist/schema/nodes/window.js +30 -0
  200. package/dist/schema/nodes/zone.d.ts +22 -0
  201. package/dist/schema/nodes/zone.d.ts.map +1 -1
  202. package/dist/schema/nodes/zone.js +21 -0
  203. package/dist/schema/scene-material.d.ts +41 -0
  204. package/dist/schema/scene-material.d.ts.map +1 -0
  205. package/dist/schema/scene-material.js +9 -0
  206. package/dist/schema/terrain.d.ts +28 -0
  207. package/dist/schema/terrain.d.ts.map +1 -0
  208. package/dist/schema/terrain.js +30 -0
  209. package/dist/schema/types.d.ts +1300 -97
  210. package/dist/schema/types.d.ts.map +1 -1
  211. package/dist/schema/types.js +27 -0
  212. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
  213. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
  214. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
  215. package/dist/services/alignment-anchors.d.ts.map +1 -1
  216. package/dist/services/alignment-anchors.js +30 -1
  217. package/dist/services/alignment.d.ts +5 -0
  218. package/dist/services/alignment.d.ts.map +1 -1
  219. package/dist/services/alignment.js +2 -0
  220. package/dist/services/hosting.d.ts +8 -0
  221. package/dist/services/hosting.d.ts.map +1 -1
  222. package/dist/services/hosting.js +13 -4
  223. package/dist/services/index.d.ts +9 -2
  224. package/dist/services/index.d.ts.map +1 -1
  225. package/dist/services/index.js +9 -2
  226. package/dist/services/level-height.d.ts +27 -0
  227. package/dist/services/level-height.d.ts.map +1 -0
  228. package/dist/services/level-height.js +94 -0
  229. package/dist/services/opening-guides.d.ts +141 -0
  230. package/dist/services/opening-guides.d.ts.map +1 -0
  231. package/dist/services/opening-guides.js +267 -0
  232. package/dist/services/port-connectivity.d.ts +115 -0
  233. package/dist/services/port-connectivity.d.ts.map +1 -0
  234. package/dist/services/port-connectivity.js +300 -0
  235. package/dist/services/riser-diagram.d.ts +40 -0
  236. package/dist/services/riser-diagram.d.ts.map +1 -0
  237. package/dist/services/riser-diagram.js +110 -0
  238. package/dist/services/snap.d.ts +9 -1
  239. package/dist/services/snap.d.ts.map +1 -1
  240. package/dist/services/snap.js +21 -1
  241. package/dist/services/storey.d.ts +103 -0
  242. package/dist/services/storey.d.ts.map +1 -0
  243. package/dist/services/storey.js +296 -0
  244. package/dist/services/system-graph.d.ts +30 -0
  245. package/dist/services/system-graph.d.ts.map +1 -0
  246. package/dist/services/system-graph.js +164 -0
  247. package/dist/services/validate-dwv.d.ts +32 -0
  248. package/dist/services/validate-dwv.d.ts.map +1 -0
  249. package/dist/services/validate-dwv.js +118 -0
  250. package/dist/store/actions/node-actions.d.ts.map +1 -1
  251. package/dist/store/actions/node-actions.js +141 -4
  252. package/dist/store/history-control.d.ts +31 -0
  253. package/dist/store/history-control.d.ts.map +1 -1
  254. package/dist/store/history-control.js +130 -0
  255. package/dist/store/use-live-terrain.d.ts +65 -0
  256. package/dist/store/use-live-terrain.d.ts.map +1 -0
  257. package/dist/store/use-live-terrain.js +31 -0
  258. package/dist/store/use-live-transforms.d.ts +8 -0
  259. package/dist/store/use-live-transforms.d.ts.map +1 -1
  260. package/dist/store/use-scene.d.ts +47 -3
  261. package/dist/store/use-scene.d.ts.map +1 -1
  262. package/dist/store/use-scene.js +992 -55
  263. package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
  264. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  265. package/dist/systems/elevator/elevator-opening-system.js +27 -21
  266. package/dist/systems/elevator/elevator-service.d.ts +0 -2
  267. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  268. package/dist/systems/elevator/elevator-service.js +2 -24
  269. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  270. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  271. package/dist/systems/fence/fence-centerline.js +37 -0
  272. package/dist/systems/fence/fence-spline.d.ts +39 -0
  273. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  274. package/dist/systems/fence/fence-spline.js +196 -0
  275. package/dist/systems/slab/slab-placement.d.ts +11 -0
  276. package/dist/systems/slab/slab-placement.d.ts.map +1 -0
  277. package/dist/systems/slab/slab-placement.js +14 -0
  278. package/dist/systems/slab/slab-support.d.ts +137 -0
  279. package/dist/systems/slab/slab-support.d.ts.map +1 -0
  280. package/dist/systems/slab/slab-support.js +557 -0
  281. package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
  282. package/dist/systems/stair/stair-footprint.js +21 -11
  283. package/dist/systems/stair/stair-opening-preview.d.ts +1300 -505
  284. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  285. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  286. package/dist/systems/stair/stair-opening-sync.js +19 -33
  287. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  288. package/dist/systems/stair/stair-opening-system.js +32 -4
  289. package/dist/systems/stair/stair-rise.d.ts +22 -0
  290. package/dist/systems/stair/stair-rise.d.ts.map +1 -0
  291. package/dist/systems/stair/stair-rise.js +79 -0
  292. package/dist/systems/wall/wall-curve.d.ts +10 -0
  293. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  294. package/dist/systems/wall/wall-curve.js +1 -1
  295. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  296. package/dist/systems/wall/wall-mitering.js +44 -8
  297. package/dist/systems/wall/wall-top.d.ts +40 -0
  298. package/dist/systems/wall/wall-top.d.ts.map +1 -0
  299. package/dist/systems/wall/wall-top.js +46 -0
  300. package/dist/utils/clone-scene-graph.d.ts +1 -0
  301. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  302. package/dist/utils/clone-scene-graph.js +56 -5
  303. package/dist/utils/heal-scene-graph.d.ts +18 -0
  304. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  305. package/dist/utils/heal-scene-graph.js +106 -0
  306. package/dist/validation/validate-build-json.d.ts +3 -0
  307. package/dist/validation/validate-build-json.d.ts.map +1 -1
  308. package/dist/validation/validate-build-json.js +90 -5
  309. package/package.json +4 -4
  310. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
  311. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
  312. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -273
  313. package/dist/lib/space-detection-pause.test.d.ts +0 -2
  314. package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
  315. package/dist/lib/space-detection-pause.test.js +0 -42
  316. package/dist/lib/space-detection.test.d.ts +0 -2
  317. package/dist/lib/space-detection.test.d.ts.map +0 -1
  318. package/dist/lib/space-detection.test.js +0 -77
  319. package/dist/registry/registry.test.d.ts +0 -2
  320. package/dist/registry/registry.test.d.ts.map +0 -1
  321. package/dist/registry/registry.test.js +0 -183
  322. package/dist/registry/relations-resolver.test.d.ts +0 -2
  323. package/dist/registry/relations-resolver.test.d.ts.map +0 -1
  324. package/dist/registry/relations-resolver.test.js +0 -183
  325. package/dist/registry/scene-api.test.d.ts +0 -2
  326. package/dist/registry/scene-api.test.d.ts.map +0 -1
  327. package/dist/registry/scene-api.test.js +0 -183
  328. package/dist/registry/subtree.test.d.ts +0 -2
  329. package/dist/registry/subtree.test.d.ts.map +0 -1
  330. package/dist/registry/subtree.test.js +0 -133
  331. package/dist/schema/asset-url.test.d.ts +0 -2
  332. package/dist/schema/asset-url.test.d.ts.map +0 -1
  333. package/dist/schema/asset-url.test.js +0 -134
  334. package/dist/services/alignment-anchors.test.d.ts +0 -2
  335. package/dist/services/alignment-anchors.test.d.ts.map +0 -1
  336. package/dist/services/alignment-anchors.test.js +0 -383
  337. package/dist/services/alignment.test.d.ts +0 -2
  338. package/dist/services/alignment.test.d.ts.map +0 -1
  339. package/dist/services/alignment.test.js +0 -92
  340. package/dist/services/drag-session.test.d.ts +0 -2
  341. package/dist/services/drag-session.test.d.ts.map +0 -1
  342. package/dist/services/drag-session.test.js +0 -219
  343. package/dist/services/hosting.test.d.ts +0 -2
  344. package/dist/services/hosting.test.d.ts.map +0 -1
  345. package/dist/services/hosting.test.js +0 -199
  346. package/dist/services/movement.test.d.ts +0 -2
  347. package/dist/services/movement.test.d.ts.map +0 -1
  348. package/dist/services/movement.test.js +0 -133
  349. package/dist/services/single-undo-dance.test.d.ts +0 -2
  350. package/dist/services/single-undo-dance.test.d.ts.map +0 -1
  351. package/dist/services/single-undo-dance.test.js +0 -210
  352. package/dist/services/snap.test.d.ts +0 -2
  353. package/dist/services/snap.test.d.ts.map +0 -1
  354. package/dist/services/snap.test.js +0 -95
  355. package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
  356. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
  357. package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
  358. package/dist/store/actions/reparent.test.d.ts +0 -2
  359. package/dist/store/actions/reparent.test.d.ts.map +0 -1
  360. package/dist/store/actions/reparent.test.js +0 -212
  361. package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
  362. package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
  363. package/dist/store/use-scene-elevator-migration.test.js +0 -109
  364. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
  365. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
  366. package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
  367. package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
  368. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
  369. package/dist/systems/elevator/elevator-runtime.test.js +0 -64
  370. package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
  371. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
  372. package/dist/systems/stair/stair-opening-preview.test.js +0 -80
  373. package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
  374. package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
  375. package/dist/systems/stair/stair-opening-sync.test.js +0 -446
  376. package/dist/utils/clone-scene-graph.test.d.ts +0 -2
  377. package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
  378. package/dist/utils/clone-scene-graph.test.js +0 -59
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Ray → terrain intersection.
3
+ *
4
+ * This is the function that replaces `t = -oy / dy` (the flat-ground plane
5
+ * solve) everywhere the editor asks "where on the ground is the pointer?".
6
+ * It lives in `core` with no Three.js so the 2D view, the tools, the placement
7
+ * coordinator, and the tests all call the same code — the failure mode this
8
+ * avoids is each surface having its own idea of where the ground is, which on
9
+ * flat ground is invisible and on a slope is a perspective-skewed offset that
10
+ * only some tools correct for.
11
+ *
12
+ * Method: march the ray in `spacing`-sized steps, watching the sign of
13
+ * `rayY - terrainY`, then bisect the bracketing interval. Marching (rather than
14
+ * per-triangle intersection over the whole grid) is what keeps this O(path
15
+ * length) instead of O(samples) — a 129² field is 32 768 triangles, and testing
16
+ * all of them per pointer-move is not affordable at 50 FPS.
17
+ *
18
+ * The bisection is exact to `spacing / 2^ITERATIONS`; at 0.5 m spacing and 20
19
+ * iterations that is under half a micrometre, i.e. far below the quantization
20
+ * ladder, so the returned point is on the same rendered triangle plane
21
+ * `heightAt` reports.
22
+ */
23
+ import { heightAt } from './terrain-field';
24
+ /** Bisection depth. 20 halvings of one cell is sub-micrometre at any sane spacing. */
25
+ const REFINE_ITERATIONS = 20;
26
+ /**
27
+ * How far to march before giving up, in multiples of `spacing`.
28
+ *
29
+ * A ray aimed just above a distant ridge can travel a long way before it dips
30
+ * below the surface, but an unbounded march is a hang risk if a caller passes a
31
+ * near-horizontal ray. The bound is generous relative to any site: at 0.5 m
32
+ * spacing this is 4 km of travel.
33
+ */
34
+ const MAX_STEPS = 8192;
35
+ /**
36
+ * World-XZ bounds of the field, in metres. The march is clipped to this so a ray
37
+ * pointing away from the site terminates immediately instead of stepping
38
+ * MAX_STEPS times through empty space.
39
+ */
40
+ function fieldBounds(field) {
41
+ return {
42
+ minX: field.origin[0],
43
+ minZ: field.origin[1],
44
+ maxX: field.origin[0] + (field.cols - 1) * field.spacing,
45
+ maxZ: field.origin[1] + (field.rows - 1) * field.spacing,
46
+ };
47
+ }
48
+ /**
49
+ * Min/max height in metres, cached per `heights` buffer.
50
+ *
51
+ * The vertical extent is what bounds the march for rays that never leave the
52
+ * field in XZ — a straight-down pointer ray, which is the single most common
53
+ * case, has `dx = dz = 0` and therefore an infinite XZ exit. Without a Y bound
54
+ * such a ray relies on `MAX_STEPS` to terminate, and an *upward* one burns all
55
+ * 8192 steps before returning null.
56
+ *
57
+ * The scan is O(samples), so it is cached on the buffer rather than repeated per
58
+ * ray. `applyHeightPatch` allocates a new `Int16Array` for every mutation, which
59
+ * makes the buffer identity a correct version key: a patched field misses the
60
+ * cache and rescans exactly once, and a stale entry is unreachable. `WeakMap`
61
+ * keeps it from retaining fields past their use.
62
+ */
63
+ const heightRangeCache = new WeakMap();
64
+ function heightRange(field) {
65
+ const cached = heightRangeCache.get(field.heights);
66
+ if (cached)
67
+ return cached;
68
+ let min = 0;
69
+ let max = 0;
70
+ if (field.heights.length > 0) {
71
+ min = field.heights[0] ?? 0;
72
+ max = min;
73
+ for (let i = 1; i < field.heights.length; i++) {
74
+ const h = field.heights[i] ?? 0;
75
+ if (h < min)
76
+ min = h;
77
+ if (h > max)
78
+ max = h;
79
+ }
80
+ }
81
+ const range = { minY: min * field.step, maxY: max * field.step };
82
+ heightRangeCache.set(field.heights, range);
83
+ return range;
84
+ }
85
+ /**
86
+ * Slab-method clip of the ray against the field's bounding box, returning the
87
+ * `[tEnter, tExit]` interval to march. Null when the ray misses the box.
88
+ *
89
+ * All three axes are clipped, not just XZ. The Y slab is what bounds a
90
+ * straight-down ray — `dx = dz = 0` makes both XZ slabs "parallel and inside",
91
+ * so an XZ-only clip returns an infinite exit and the march has to fall back on
92
+ * `MAX_STEPS`. The Y slab is padded because the surface is bilinear between
93
+ * samples and a tangent ray must not be clipped exactly at the extremum.
94
+ */
95
+ function clipToField(field, origin, direction) {
96
+ const bounds = fieldBounds(field);
97
+ const { minY, maxY } = heightRange(field);
98
+ const pad = field.spacing;
99
+ let tEnter = 0;
100
+ let tExit = Number.POSITIVE_INFINITY;
101
+ const axes = [
102
+ [origin[0], direction[0], bounds.minX, bounds.maxX],
103
+ [origin[1], direction[1], minY - pad, maxY + pad],
104
+ [origin[2], direction[2], bounds.minZ, bounds.maxZ],
105
+ ];
106
+ for (const [o, d, lo, hi] of axes) {
107
+ if (Math.abs(d) < 1e-12) {
108
+ // Parallel to this slab: either always inside or never.
109
+ if (o < lo || o > hi)
110
+ return null;
111
+ continue;
112
+ }
113
+ const t1 = (lo - o) / d;
114
+ const t2 = (hi - o) / d;
115
+ tEnter = Math.max(tEnter, Math.min(t1, t2));
116
+ tExit = Math.min(tExit, Math.max(t1, t2));
117
+ if (tEnter > tExit)
118
+ return null;
119
+ }
120
+ return { tEnter, tExit };
121
+ }
122
+ /**
123
+ * First intersection of a ray with the terrain surface, or `null` if it misses.
124
+ *
125
+ * `direction` need not be normalized; `t` is in units of `direction`'s length,
126
+ * matching `Ray.at(t)` semantics so a viewer-side caller can pass a three.js ray
127
+ * through unchanged.
128
+ *
129
+ * The march tracks the *sign change* of `rayY - surfaceY` rather than assuming
130
+ * the ray starts above the ground, so a ray fired from inside a hill or from a
131
+ * basement finds the surface it exits through. A ray whose origin is already
132
+ * exactly on the surface returns `t = 0`.
133
+ */
134
+ export function raycastTerrain(field, origin, direction) {
135
+ const dirLength = Math.hypot(direction[0], direction[1], direction[2]);
136
+ if (dirLength < 1e-12)
137
+ return null;
138
+ const clip = clipToField(field, origin, direction);
139
+ if (!clip)
140
+ return null;
141
+ const { tEnter, tExit } = clip;
142
+ // Step in world-space multiples of one cell, converted into t units. Half a
143
+ // cell, not a full one, so a ray crossing a single-cell spike cannot step over
144
+ // its peak and report the ground beyond it.
145
+ const stepT = field.spacing / 2 / dirLength;
146
+ const at = (t) => ({
147
+ x: origin[0] + direction[0] * t,
148
+ y: origin[1] + direction[1] * t,
149
+ z: origin[2] + direction[2] * t,
150
+ });
151
+ const surfaceGap = (t) => {
152
+ const p = at(t);
153
+ return p.y - heightAt(field, p.x, p.z);
154
+ };
155
+ const hitAt = (t) => {
156
+ const p = at(t);
157
+ return { t, x: p.x, y: heightAt(field, p.x, p.z), z: p.z };
158
+ };
159
+ let prevT = tEnter;
160
+ let prevGap = surfaceGap(tEnter);
161
+ // Entering exactly on the surface counts as a hit — a pointer ray grazing the
162
+ // ground plane must not fall through to the caller's flat-plane fallback.
163
+ if (prevGap === 0)
164
+ return hitAt(tEnter);
165
+ const startedAbove = prevGap > 0;
166
+ for (let step = 1; step <= MAX_STEPS; step++) {
167
+ const t = Math.min(tExit, tEnter + step * stepT);
168
+ const gap = surfaceGap(t);
169
+ // A crossing in either direction is a surface hit.
170
+ if (gap === 0 || gap > 0 !== startedAbove) {
171
+ let lo = prevT;
172
+ let hi = t;
173
+ for (let i = 0; i < REFINE_ITERATIONS; i++) {
174
+ const mid = (lo + hi) / 2;
175
+ if (surfaceGap(mid) > 0 === startedAbove)
176
+ lo = mid;
177
+ else
178
+ hi = mid;
179
+ }
180
+ return hitAt(hi);
181
+ }
182
+ prevT = t;
183
+ prevGap = gap;
184
+ if (t >= tExit)
185
+ break;
186
+ }
187
+ return null;
188
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Scene → `TerrainField`, cached.
3
+ *
4
+ * Every consumer that needs the ground — the mesh builder, the raycast, the
5
+ * placement predicate, the collider, the 2D view — starts here. It exists to
6
+ * solve one problem: the scene stores terrain as base64 (`site.terrain`), and
7
+ * decoding that per frame, or per pointer-move, would be absurd.
8
+ *
9
+ * The cache is a `WeakMap` keyed on the **`TerrainData` object identity**, which
10
+ * is a correct version key because the scene store is immutable-by-convention:
11
+ * `updateNode` produces a new node object with a new `terrain` object, so a
12
+ * changed terrain necessarily misses the cache and a stale entry is unreachable.
13
+ * No invalidation call, no version counter, nothing to forget to bump.
14
+ *
15
+ * `commitTerrainField` closes the loop: it encodes a field *and* primes the cache
16
+ * with the field it just encoded. That is what makes a sculpt stroke free — after
17
+ * the commit, the next read is a cache hit, so a stroke never decodes base64 even
18
+ * once, let alone per dab.
19
+ */
20
+ import type { SiteNode } from '../schema/nodes/site';
21
+ import type { TerrainData } from '../schema/terrain';
22
+ import { createTerrainField, type TerrainField } from './terrain-field';
23
+ /**
24
+ * The terrain field for a site, or `null` when the site has no sculpted terrain.
25
+ *
26
+ * `null` means flat ground at the datum. It is deliberately not "a flat field":
27
+ * callers use `null` to take their existing flat-ground fast path, which keeps
28
+ * every scene that has never touched terrain — the overwhelming majority —
29
+ * running exactly the code it ran before terrain existed. That property is worth
30
+ * more than the uniformity of always having a field.
31
+ *
32
+ * An in-flight sculpt stroke wins over the persisted data. Resolving that *here*
33
+ * rather than per caller is what makes a stroke consistent: the mesh, the pointer
34
+ * raycast, the placement predicate, the collider, and the 2D view all see the
35
+ * same ground mid-drag without any of them knowing a stroke exists. The
36
+ * alternative — each consumer checking the live store — is how a drag ends up
37
+ * showing a hill the raycast cannot hit.
38
+ */
39
+ export declare function terrainFieldOf(site: (Pick<SiteNode, 'terrain'> & {
40
+ id?: string;
41
+ }) | null | undefined): TerrainField | null;
42
+ /**
43
+ * The field a site *would* sculpt into: its existing terrain, or a fresh flat
44
+ * field sized to `options`.
45
+ *
46
+ * Separate from `terrainFieldOf` because the read path wants `null` for "no
47
+ * terrain" and the write path wants something to write into. Collapsing them
48
+ * would force every reader to allocate a field it does not need.
49
+ */
50
+ export declare function terrainFieldForEdit(site: Pick<SiteNode, 'terrain'> | null | undefined, options?: Parameters<typeof createTerrainField>[0]): TerrainField;
51
+ /**
52
+ * Encode a field for persistence and prime the read cache with it.
53
+ *
54
+ * Call this instead of `encodeTerrainField` whenever the result is going into the
55
+ * scene. The returned data is what to write to `site.terrain`; the cache priming
56
+ * means the very next `terrainFieldOf` on the updated node returns this exact
57
+ * field object without decoding.
58
+ */
59
+ export declare function commitTerrainField(field: TerrainField): TerrainData;
60
+ //# sourceMappingURL=terrain-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-source.d.ts","sourceRoot":"","sources":["../../src/lib/terrain-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAGpD,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAUvE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACrE,YAAY,GAAG,IAAI,CAkBrB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,EAClD,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,GACjD,YAAY,CAEd;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,CAInE"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Scene → `TerrainField`, cached.
3
+ *
4
+ * Every consumer that needs the ground — the mesh builder, the raycast, the
5
+ * placement predicate, the collider, the 2D view — starts here. It exists to
6
+ * solve one problem: the scene stores terrain as base64 (`site.terrain`), and
7
+ * decoding that per frame, or per pointer-move, would be absurd.
8
+ *
9
+ * The cache is a `WeakMap` keyed on the **`TerrainData` object identity**, which
10
+ * is a correct version key because the scene store is immutable-by-convention:
11
+ * `updateNode` produces a new node object with a new `terrain` object, so a
12
+ * changed terrain necessarily misses the cache and a stale entry is unreachable.
13
+ * No invalidation call, no version counter, nothing to forget to bump.
14
+ *
15
+ * `commitTerrainField` closes the loop: it encodes a field *and* primes the cache
16
+ * with the field it just encoded. That is what makes a sculpt stroke free — after
17
+ * the commit, the next read is a cache hit, so a stroke never decodes base64 even
18
+ * once, let alone per dab.
19
+ */
20
+ import useLiveTerrain from '../store/use-live-terrain';
21
+ import { decodeTerrainField, encodeTerrainField } from './terrain-codec';
22
+ import { createTerrainField } from './terrain-field';
23
+ const fieldCache = new WeakMap();
24
+ /**
25
+ * Sites whose `terrain` failed to decode. Prevents re-attempting a doomed decode
26
+ * on every frame — the corrupt-scene path must be cheap, not just survivable.
27
+ */
28
+ const failedDecodes = new WeakSet();
29
+ /**
30
+ * The terrain field for a site, or `null` when the site has no sculpted terrain.
31
+ *
32
+ * `null` means flat ground at the datum. It is deliberately not "a flat field":
33
+ * callers use `null` to take their existing flat-ground fast path, which keeps
34
+ * every scene that has never touched terrain — the overwhelming majority —
35
+ * running exactly the code it ran before terrain existed. That property is worth
36
+ * more than the uniformity of always having a field.
37
+ *
38
+ * An in-flight sculpt stroke wins over the persisted data. Resolving that *here*
39
+ * rather than per caller is what makes a stroke consistent: the mesh, the pointer
40
+ * raycast, the placement predicate, the collider, and the 2D view all see the
41
+ * same ground mid-drag without any of them knowing a stroke exists. The
42
+ * alternative — each consumer checking the live store — is how a drag ends up
43
+ * showing a hill the raycast cannot hit.
44
+ */
45
+ export function terrainFieldOf(site) {
46
+ if (site?.id) {
47
+ const live = useLiveTerrain.getState().fieldOf(site.id);
48
+ if (live)
49
+ return live;
50
+ }
51
+ const data = site?.terrain;
52
+ if (!data)
53
+ return null;
54
+ const cached = fieldCache.get(data);
55
+ if (cached)
56
+ return cached;
57
+ if (failedDecodes.has(data))
58
+ return null;
59
+ const field = decodeTerrainField(data);
60
+ if (!field) {
61
+ failedDecodes.add(data);
62
+ return null;
63
+ }
64
+ fieldCache.set(data, field);
65
+ return field;
66
+ }
67
+ /**
68
+ * The field a site *would* sculpt into: its existing terrain, or a fresh flat
69
+ * field sized to `options`.
70
+ *
71
+ * Separate from `terrainFieldOf` because the read path wants `null` for "no
72
+ * terrain" and the write path wants something to write into. Collapsing them
73
+ * would force every reader to allocate a field it does not need.
74
+ */
75
+ export function terrainFieldForEdit(site, options) {
76
+ return terrainFieldOf(site) ?? createTerrainField(options);
77
+ }
78
+ /**
79
+ * Encode a field for persistence and prime the read cache with it.
80
+ *
81
+ * Call this instead of `encodeTerrainField` whenever the result is going into the
82
+ * scene. The returned data is what to write to `site.terrain`; the cache priming
83
+ * means the very next `terrainFieldOf` on the updated node returns this exact
84
+ * field object without decoding.
85
+ */
86
+ export function commitTerrainField(field) {
87
+ const data = encodeTerrainField(field);
88
+ fieldCache.set(data, field);
89
+ return data;
90
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Terrain as the level base — the third and last place flat ground was assumed.
3
+ *
4
+ * `getFloorPlacedElevation` returns a *lift* added to a node's level-local Y, and
5
+ * two of its `0`-returns mean "supported by the level base". This module is what
6
+ * those two branches call so that "the level base" can mean the sculpted ground
7
+ * instead of the plane `y = 0`. The other `0`-returns are `+0` no-ops for
8
+ * wall/ceiling-attached items, non-`level` parents, and broken graphs; substituting
9
+ * terrain there would lift sconces off walls, so they stay `0` forever.
10
+ *
11
+ * The whole file is pure — nodes in, metres out — which is what lets the stacking
12
+ * matrix be tested per kind without a canvas.
13
+ */
14
+ import type { AnyNode } from '../schema';
15
+ /**
16
+ * World Y of the site datum — the plane terrain heights are measured from.
17
+ *
18
+ * Terrain vertices are absolute world coordinates (the site group renders at the
19
+ * origin and `terrain-geometry` writes `origin + col * spacing` straight into the
20
+ * buffer), so the datum is a constant rather than a site property. It is named
21
+ * because the *comparison* against it is load-bearing, not the value: it is what
22
+ * separates "the ground" from every other horizontal surface in the scene.
23
+ */
24
+ export declare const SITE_DATUM_Y = 0;
25
+ /**
26
+ * How close a surface must sit to the datum to count as the ground.
27
+ *
28
+ * Loose enough to absorb float drift through a level's world matrix, tight enough
29
+ * that a ground slab (5 cm) still reads as a built surface rather than as terrain.
30
+ */
31
+ export declare const SITE_DATUM_EPSILON = 0.0001;
32
+ /** True when a world-space height is the site datum — i.e. the terrain's plane. */
33
+ export declare function isSiteDatum(worldY: number): boolean;
34
+ /**
35
+ * Whether the sculpted ground is what a node on `levelId` stands on — i.e.
36
+ * whether moving the terrain moves that node.
37
+ *
38
+ * Split out of `terrainSupportLift` so the invalidation rule that re-elevates
39
+ * nodes after a sculpt (`markTerrainSupportDependents`) tests grade-ness with
40
+ * the *same* predicate the resolver does. Two copies of this test would drift,
41
+ * and the failure is silent: a level the resolver drapes but the dirty rule
42
+ * skips leaves its contents frozen at the height the ground used to be.
43
+ *
44
+ * Deliberately independent of whether a field exists. A stroke that clears the
45
+ * terrain has to re-elevate exactly the nodes a stroke that creates it does, and
46
+ * asking about the field here would mark none of them on the way back down.
47
+ */
48
+ export declare function isLevelAtSiteDatum(nodes: Record<string, AnyNode>, levelId: string): boolean;
49
+ /**
50
+ * The lift, in level-local metres, that the sculpted ground provides to a node on
51
+ * `levelId` at level-local `x`/`z`. Null when terrain does not support this node —
52
+ * no sculpted ground, an unresolvable level, or a storey whose floor is not at
53
+ * grade.
54
+ *
55
+ * Null rather than 0 so callers keep their existing flat-ground path verbatim.
56
+ * That is what makes this change invisible to every scene that never touched
57
+ * terrain, which is nearly all of them.
58
+ */
59
+ export declare function terrainSupportLift(nodes: Record<string, AnyNode>, levelId: string, x: number, z: number): number | null;
60
+ //# sourceMappingURL=terrain-support.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-support.d.ts","sourceRoot":"","sources":["../../src/lib/terrain-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,WAAW,CAAA;AAOnD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SAAO,CAAA;AAEtC,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAqFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,MAAM,GAAG,IAAI,CAYf"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Terrain as the level base — the third and last place flat ground was assumed.
3
+ *
4
+ * `getFloorPlacedElevation` returns a *lift* added to a node's level-local Y, and
5
+ * two of its `0`-returns mean "supported by the level base". This module is what
6
+ * those two branches call so that "the level base" can mean the sculpted ground
7
+ * instead of the plane `y = 0`. The other `0`-returns are `+0` no-ops for
8
+ * wall/ceiling-attached items, non-`level` parents, and broken graphs; substituting
9
+ * terrain there would lift sconces off walls, so they stay `0` forever.
10
+ *
11
+ * The whole file is pure — nodes in, metres out — which is what lets the stacking
12
+ * matrix be tested per kind without a canvas.
13
+ */
14
+ import { getLevelElevations } from '../services/storey';
15
+ import { heightAt } from './terrain-field';
16
+ import { terrainFieldOf } from './terrain-source';
17
+ /**
18
+ * World Y of the site datum — the plane terrain heights are measured from.
19
+ *
20
+ * Terrain vertices are absolute world coordinates (the site group renders at the
21
+ * origin and `terrain-geometry` writes `origin + col * spacing` straight into the
22
+ * buffer), so the datum is a constant rather than a site property. It is named
23
+ * because the *comparison* against it is load-bearing, not the value: it is what
24
+ * separates "the ground" from every other horizontal surface in the scene.
25
+ */
26
+ export const SITE_DATUM_Y = 0;
27
+ /**
28
+ * How close a surface must sit to the datum to count as the ground.
29
+ *
30
+ * Loose enough to absorb float drift through a level's world matrix, tight enough
31
+ * that a ground slab (5 cm) still reads as a built surface rather than as terrain.
32
+ */
33
+ export const SITE_DATUM_EPSILON = 1e-4;
34
+ /** True when a world-space height is the site datum — i.e. the terrain's plane. */
35
+ export function isSiteDatum(worldY) {
36
+ return Math.abs(worldY - SITE_DATUM_Y) < SITE_DATUM_EPSILON;
37
+ }
38
+ /**
39
+ * `getLevelElevations` memoized on the `nodes` record identity.
40
+ *
41
+ * Without this the stacking resolver is O(N) per node and therefore O(N²) per
42
+ * frame, since `FloorElevationSystem` calls it for every dirty floor-placed node.
43
+ * The scene store is immutable-by-convention, so the record identity is a correct
44
+ * version key — the same reasoning (and the same `WeakMap` shape) as
45
+ * `terrain-source`'s field cache and the spatial grid's `levelWallsCache`.
46
+ */
47
+ const levelElevationCache = new WeakMap();
48
+ function cachedLevelElevations(nodes) {
49
+ const cached = levelElevationCache.get(nodes);
50
+ if (cached)
51
+ return cached;
52
+ const elevations = getLevelElevations(nodes);
53
+ levelElevationCache.set(nodes, elevations);
54
+ return elevations;
55
+ }
56
+ function siteOf(nodes) {
57
+ for (const node of Object.values(nodes)) {
58
+ if (node?.type === 'site')
59
+ return node;
60
+ }
61
+ return null;
62
+ }
63
+ /**
64
+ * Level-local XZ → world XZ, through the building's transform.
65
+ *
66
+ * A level group carries only a Y offset (`LevelSystem` writes `position.y` and
67
+ * nothing else), so level-local XZ is building-local XZ; the building carries the
68
+ * site-space placement, and the site renders at the origin. Only the Y rotation
69
+ * matters — a building tipped in X or Z is not a thing the editor can produce.
70
+ */
71
+ function levelLocalToWorldXZ(building, x, z) {
72
+ if (!building)
73
+ return [x, z];
74
+ const angle = building.rotation?.[1] ?? 0;
75
+ const [px, , pz] = building.position ?? [0, 0, 0];
76
+ if (angle === 0)
77
+ return [x + px, z + pz];
78
+ const cos = Math.cos(angle);
79
+ const sin = Math.sin(angle);
80
+ return [cos * x + sin * z + px, -sin * x + cos * z + pz];
81
+ }
82
+ /**
83
+ * Where a level sits relative to the datum, for the levels terrain can support.
84
+ *
85
+ * **Only the storey the level stack puts at the datum follows terrain.** An upper
86
+ * storey has a real floor, and draping it would sink its contents into the
87
+ * hillside under the building. The test is that storey's own base height against
88
+ * the datum — deliberately asked of `getLevelElevations`, the same function the
89
+ * renderer positions level groups with, so placement can never hold a second
90
+ * opinion about which storey is on the ground.
91
+ *
92
+ * Which storey that is follows from the stack, not from the ordinal: today the
93
+ * lowest ordinal anchors at 0, so a building with an ordinal `-1` level has *that*
94
+ * level at grade and its ground floor a storey up. When genuine below-grade
95
+ * stacking lands, this rule tracks it with no change here — an excavated basement
96
+ * will stop being at the datum, and stop being draped, on its own.
97
+ */
98
+ function levelDatum(nodes, levelId) {
99
+ const elevation = cachedLevelElevations(nodes).get(levelId);
100
+ if (!elevation)
101
+ return null;
102
+ const building = elevation.buildingId
103
+ ? (nodes[elevation.buildingId] ?? null)
104
+ : null;
105
+ const datum = building?.position?.[1] ?? 0;
106
+ const baseWorldY = datum + elevation.baseY;
107
+ if (!isSiteDatum(baseWorldY))
108
+ return null;
109
+ return { building, baseWorldY };
110
+ }
111
+ /**
112
+ * Whether the sculpted ground is what a node on `levelId` stands on — i.e.
113
+ * whether moving the terrain moves that node.
114
+ *
115
+ * Split out of `terrainSupportLift` so the invalidation rule that re-elevates
116
+ * nodes after a sculpt (`markTerrainSupportDependents`) tests grade-ness with
117
+ * the *same* predicate the resolver does. Two copies of this test would drift,
118
+ * and the failure is silent: a level the resolver drapes but the dirty rule
119
+ * skips leaves its contents frozen at the height the ground used to be.
120
+ *
121
+ * Deliberately independent of whether a field exists. A stroke that clears the
122
+ * terrain has to re-elevate exactly the nodes a stroke that creates it does, and
123
+ * asking about the field here would mark none of them on the way back down.
124
+ */
125
+ export function isLevelAtSiteDatum(nodes, levelId) {
126
+ return levelDatum(nodes, levelId) !== null;
127
+ }
128
+ /**
129
+ * The lift, in level-local metres, that the sculpted ground provides to a node on
130
+ * `levelId` at level-local `x`/`z`. Null when terrain does not support this node —
131
+ * no sculpted ground, an unresolvable level, or a storey whose floor is not at
132
+ * grade.
133
+ *
134
+ * Null rather than 0 so callers keep their existing flat-ground path verbatim.
135
+ * That is what makes this change invisible to every scene that never touched
136
+ * terrain, which is nearly all of them.
137
+ */
138
+ export function terrainSupportLift(nodes, levelId, x, z) {
139
+ const site = siteOf(nodes);
140
+ const field = site ? terrainFieldOf(site) : null;
141
+ if (!field)
142
+ return null;
143
+ const datum = levelDatum(nodes, levelId);
144
+ if (!datum)
145
+ return null;
146
+ const [worldX, worldZ] = levelLocalToWorldXZ(datum.building, x, z);
147
+ // The lift is measured from the storey's own base, so a building sited on a
148
+ // datum other than 0 does not double-count it.
149
+ return heightAt(field, worldX, worldZ) - datum.baseWorldY;
150
+ }
@@ -0,0 +1,60 @@
1
+ import type { WallNode } from '../schema/nodes/wall';
2
+ import type { AnyNode, AnyNodeId } from '../schema/types';
3
+ /**
4
+ * Pure plan-space wall-distance math shared by the 2D opening snap
5
+ * (`findClosestWallInPlan` in @pascal-app/nodes) and the editor's 2D
6
+ * Voronoi debug overlay. One source of truth means the overlay is a
7
+ * faithful picture of what the snap actually decides.
8
+ *
9
+ * A wall segment's Voronoi cell is exactly "the points whose nearest wall
10
+ * is this segment", so nearest-segment classification == the segment
11
+ * Voronoi diagram. Curved walls are excluded (the opening snap rejects
12
+ * them — mitering + arc + opening tears in 3D).
13
+ */
14
+ /**
15
+ * Max cursor-to-wall plan distance (metres) for a 2D opening to snap onto a
16
+ * wall. Tight, because plan walls are thin and often close together — a large
17
+ * radius would let a far wall's region reach across a nearer one. Shared so the
18
+ * snap and the Voronoi debug overlay clip to the exact same range.
19
+ */
20
+ export declare const WALL_SNAP_DISTANCE_M = 0.4;
21
+ export type WallSegment = {
22
+ wall: WallNode;
23
+ /** [x, z] plan start. */
24
+ start: readonly [number, number];
25
+ /** [x, z] plan end. */
26
+ end: readonly [number, number];
27
+ /** Unit direction (start → end) in plan. */
28
+ dirX: number;
29
+ dirY: number;
30
+ /** Segment length in metres. */
31
+ length: number;
32
+ };
33
+ export type WallSegmentClosest = {
34
+ segment: WallSegment;
35
+ /** Distance from the query point to the closest point on the segment. */
36
+ distance: number;
37
+ /** Distance along the wall from `start`, clamped to [0, length]. */
38
+ along: number;
39
+ /** Signed perpendicular offset from the wall axis (+ on the front side). */
40
+ perp: number;
41
+ };
42
+ /**
43
+ * Collect the straight (non-curved) wall segments that are direct children
44
+ * of a level — the candidates an opening can snap onto.
45
+ */
46
+ export declare function collectLevelWallSegments(nodes: Record<AnyNodeId, AnyNode>, levelId: AnyNodeId | null): WallSegment[];
47
+ /** Closest point + signed offset of one query point against one segment. */
48
+ export declare function closestOnSegment(segment: WallSegment, pointX: number, pointY: number): {
49
+ distance: number;
50
+ along: number;
51
+ perp: number;
52
+ };
53
+ /**
54
+ * The single nearest wall segment to a plan point — its Voronoi cell. Returns
55
+ * null when `segments` is empty or (when `maxDistance` is given) nothing is
56
+ * within range. Ties resolve to the first segment scanned; callers pass an
57
+ * already-curved-filtered list from `collectLevelWallSegments`.
58
+ */
59
+ export declare function nearestWallSegment(segments: readonly WallSegment[], pointX: number, pointY: number, maxDistance?: number, excludeWallId?: AnyNodeId): WallSegmentClosest | null;
60
+ //# sourceMappingURL=wall-distance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-distance.d.ts","sourceRoot":"","sources":["../../src/lib/wall-distance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGzD;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,QAAQ,CAAA;IACd,yBAAyB;IACzB,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,uBAAuB;IACvB,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,WAAW,CAAA;IACpB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,SAAS,GAAG,IAAI,GACxB,WAAW,EAAE,CA0Bf;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CASnD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,SAA2B,EACtC,aAAa,CAAC,EAAE,SAAS,GACxB,kBAAkB,GAAG,IAAI,CAU3B"}