@pascal-app/core 0.8.0 → 0.9.2

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 (471) hide show
  1. package/dist/events/bus.d.ts +67 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  6. package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +59 -26
  9. package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
  10. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
  11. package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  13. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  14. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  15. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  16. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  23. package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
  24. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  25. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  26. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  27. package/dist/index.d.ts +36 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +33 -6
  30. package/dist/lib/level-name.d.ts +4 -0
  31. package/dist/lib/level-name.d.ts.map +1 -0
  32. package/dist/lib/level-name.js +10 -0
  33. package/dist/lib/measurement-geometry.d.ts +19 -0
  34. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  35. package/dist/lib/measurement-geometry.js +232 -0
  36. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  37. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  38. package/dist/lib/measurement-geometry.test.js +102 -0
  39. package/dist/lib/polygon-geometry.d.ts +0 -1
  40. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  41. package/dist/lib/polygon-geometry.js +0 -18
  42. package/dist/lib/polygon-relations.d.ts +10 -0
  43. package/dist/lib/polygon-relations.d.ts.map +1 -0
  44. package/dist/lib/polygon-relations.js +73 -0
  45. package/dist/lib/selection-proxy.d.ts +4 -0
  46. package/dist/lib/selection-proxy.d.ts.map +1 -0
  47. package/dist/lib/selection-proxy.js +12 -0
  48. package/dist/lib/slab-polygon.d.ts +31 -2
  49. package/dist/lib/slab-polygon.d.ts.map +1 -1
  50. package/dist/lib/slab-polygon.js +525 -31
  51. package/dist/lib/slab-polygon.test.d.ts +2 -0
  52. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  53. package/dist/lib/slab-polygon.test.js +635 -0
  54. package/dist/lib/slots.d.ts +14 -0
  55. package/dist/lib/slots.d.ts.map +1 -0
  56. package/dist/lib/slots.js +26 -0
  57. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  58. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  59. package/dist/lib/space-detection-pause.test.js +42 -0
  60. package/dist/lib/space-detection.d.ts +54 -2
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +599 -124
  63. package/dist/lib/space-detection.test.d.ts +2 -0
  64. package/dist/lib/space-detection.test.d.ts.map +1 -0
  65. package/dist/lib/space-detection.test.js +376 -0
  66. package/dist/lib/wall-distance.d.ts +60 -0
  67. package/dist/lib/wall-distance.d.ts.map +1 -0
  68. package/dist/lib/wall-distance.js +85 -0
  69. package/dist/lib/zone-quantities.d.ts +21 -0
  70. package/dist/lib/zone-quantities.d.ts.map +1 -0
  71. package/dist/lib/zone-quantities.js +183 -0
  72. package/dist/lib/zone-quantities.test.d.ts +2 -0
  73. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  74. package/dist/lib/zone-quantities.test.js +82 -0
  75. package/dist/material-library.d.ts +20 -1
  76. package/dist/material-library.d.ts.map +1 -1
  77. package/dist/material-library.js +3737 -200
  78. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  79. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  80. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  81. package/dist/registry/handles.d.ts +394 -0
  82. package/dist/registry/handles.d.ts.map +1 -0
  83. package/dist/registry/handles.js +24 -0
  84. package/dist/registry/index.d.ts +7 -0
  85. package/dist/registry/index.d.ts.map +1 -0
  86. package/dist/registry/index.js +4 -0
  87. package/dist/registry/registry.d.ts +136 -0
  88. package/dist/registry/registry.d.ts.map +1 -0
  89. package/dist/registry/registry.js +282 -0
  90. package/dist/registry/registry.test.d.ts +2 -0
  91. package/dist/registry/registry.test.d.ts.map +1 -0
  92. package/dist/registry/registry.test.js +196 -0
  93. package/dist/registry/relations-resolver.d.ts +42 -0
  94. package/dist/registry/relations-resolver.d.ts.map +1 -0
  95. package/dist/registry/relations-resolver.js +77 -0
  96. package/dist/registry/relations-resolver.test.d.ts +2 -0
  97. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  98. package/dist/registry/relations-resolver.test.js +183 -0
  99. package/dist/registry/scene-api.d.ts +44 -0
  100. package/dist/registry/scene-api.d.ts.map +1 -0
  101. package/dist/registry/scene-api.js +111 -0
  102. package/dist/registry/scene-api.test.d.ts +2 -0
  103. package/dist/registry/scene-api.test.d.ts.map +1 -0
  104. package/dist/registry/scene-api.test.js +183 -0
  105. package/dist/registry/subtree.d.ts +67 -0
  106. package/dist/registry/subtree.d.ts.map +1 -0
  107. package/dist/registry/subtree.js +123 -0
  108. package/dist/registry/subtree.test.d.ts +2 -0
  109. package/dist/registry/subtree.test.d.ts.map +1 -0
  110. package/dist/registry/subtree.test.js +163 -0
  111. package/dist/registry/types.d.ts +2099 -0
  112. package/dist/registry/types.d.ts.map +1 -0
  113. package/dist/registry/types.js +1 -0
  114. package/dist/schema/index.d.ts +35 -4
  115. package/dist/schema/index.d.ts.map +1 -1
  116. package/dist/schema/index.js +32 -4
  117. package/dist/schema/material.d.ts +13 -2
  118. package/dist/schema/material.d.ts.map +1 -1
  119. package/dist/schema/material.js +17 -5
  120. package/dist/schema/material.test.d.ts +2 -0
  121. package/dist/schema/material.test.d.ts.map +1 -0
  122. package/dist/schema/material.test.js +43 -0
  123. package/dist/schema/nodes/box-vent.d.ts +74 -0
  124. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  125. package/dist/schema/nodes/box-vent.js +63 -0
  126. package/dist/schema/nodes/building.d.ts +1 -1
  127. package/dist/schema/nodes/building.d.ts.map +1 -1
  128. package/dist/schema/nodes/building.js +3 -2
  129. package/dist/schema/nodes/cabinet.d.ts +534 -0
  130. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  131. package/dist/schema/nodes/cabinet.js +137 -0
  132. package/dist/schema/nodes/ceiling.d.ts +5 -2
  133. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  134. package/dist/schema/nodes/ceiling.js +5 -1
  135. package/dist/schema/nodes/chimney.d.ts +158 -0
  136. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  137. package/dist/schema/nodes/chimney.js +67 -0
  138. package/dist/schema/nodes/column.d.ts +600 -7
  139. package/dist/schema/nodes/column.d.ts.map +1 -1
  140. package/dist/schema/nodes/column.js +589 -6
  141. package/dist/schema/nodes/cupola.d.ts +66 -0
  142. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  143. package/dist/schema/nodes/cupola.js +28 -0
  144. package/dist/schema/nodes/door.d.ts +13 -5
  145. package/dist/schema/nodes/door.d.ts.map +1 -1
  146. package/dist/schema/nodes/door.js +12 -0
  147. package/dist/schema/nodes/dormer.d.ts +228 -0
  148. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  149. package/dist/schema/nodes/dormer.js +119 -0
  150. package/dist/schema/nodes/downspout.d.ts +75 -0
  151. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  152. package/dist/schema/nodes/downspout.js +63 -0
  153. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  154. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  155. package/dist/schema/nodes/duct-fitting.js +92 -0
  156. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  157. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  158. package/dist/schema/nodes/duct-segment.js +73 -0
  159. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  160. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  161. package/dist/schema/nodes/duct-terminal.js +51 -0
  162. package/dist/schema/nodes/elevator.d.ts +106 -0
  163. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  164. package/dist/schema/nodes/elevator.js +57 -0
  165. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  166. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  167. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  168. package/dist/schema/nodes/fence.d.ts +19 -2
  169. package/dist/schema/nodes/fence.d.ts.map +1 -1
  170. package/dist/schema/nodes/fence.js +27 -2
  171. package/dist/schema/nodes/gutter.d.ts +84 -0
  172. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  173. package/dist/schema/nodes/gutter.js +76 -0
  174. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  175. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  176. package/dist/schema/nodes/hvac-equipment.js +55 -0
  177. package/dist/schema/nodes/item.d.ts +12 -0
  178. package/dist/schema/nodes/item.d.ts.map +1 -1
  179. package/dist/schema/nodes/item.js +20 -0
  180. package/dist/schema/nodes/level.d.ts +1 -1
  181. package/dist/schema/nodes/level.d.ts.map +1 -1
  182. package/dist/schema/nodes/level.js +4 -0
  183. package/dist/schema/nodes/lineset.d.ts +42 -0
  184. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  185. package/dist/schema/nodes/lineset.js +38 -0
  186. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  187. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  188. package/dist/schema/nodes/liquid-line.js +24 -0
  189. package/dist/schema/nodes/measurement.d.ts +306 -0
  190. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  191. package/dist/schema/nodes/measurement.js +87 -0
  192. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  193. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  194. package/dist/schema/nodes/measurement.test.js +139 -0
  195. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  196. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  197. package/dist/schema/nodes/pipe-fitting.js +45 -0
  198. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  199. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  200. package/dist/schema/nodes/pipe-segment.js +36 -0
  201. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  202. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  203. package/dist/schema/nodes/pipe-trap.js +36 -0
  204. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  205. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  206. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  207. package/dist/schema/nodes/ridge-vent.d.ts +82 -0
  208. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  209. package/dist/schema/nodes/ridge-vent.js +248 -0
  210. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  211. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  212. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  213. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  214. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  215. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  216. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  217. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  218. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  219. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  220. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  221. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  222. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  223. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  224. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  225. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  226. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  227. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  228. package/dist/schema/nodes/roof-segment.d.ts +273 -3
  229. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  230. package/dist/schema/nodes/roof-segment.js +539 -3
  231. package/dist/schema/nodes/roof.d.ts +8 -8
  232. package/dist/schema/nodes/roof.d.ts.map +1 -1
  233. package/dist/schema/nodes/roof.js +4 -16
  234. package/dist/schema/nodes/shelf.d.ts +106 -0
  235. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  236. package/dist/schema/nodes/shelf.js +90 -0
  237. package/dist/schema/nodes/site.d.ts +1 -122
  238. package/dist/schema/nodes/site.d.ts.map +1 -1
  239. package/dist/schema/nodes/site.js +2 -6
  240. package/dist/schema/nodes/skylight.d.ts +279 -0
  241. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  242. package/dist/schema/nodes/skylight.js +152 -0
  243. package/dist/schema/nodes/slab.d.ts +5 -2
  244. package/dist/schema/nodes/slab.d.ts.map +1 -1
  245. package/dist/schema/nodes/slab.js +5 -1
  246. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  247. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  248. package/dist/schema/nodes/solar-panel.js +47 -0
  249. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  250. package/dist/schema/nodes/stair.d.ts +13 -12
  251. package/dist/schema/nodes/stair.d.ts.map +1 -1
  252. package/dist/schema/nodes/stair.js +3 -0
  253. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  254. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  255. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  256. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  257. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  258. package/dist/schema/nodes/turbine-vent.js +41 -0
  259. package/dist/schema/nodes/wall.d.ts +245 -6
  260. package/dist/schema/nodes/wall.d.ts.map +1 -1
  261. package/dist/schema/nodes/wall.js +257 -0
  262. package/dist/schema/nodes/wall.test.d.ts +2 -0
  263. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  264. package/dist/schema/nodes/wall.test.js +208 -0
  265. package/dist/schema/nodes/window.d.ts +11 -3
  266. package/dist/schema/nodes/window.d.ts.map +1 -1
  267. package/dist/schema/nodes/window.js +12 -0
  268. package/dist/schema/nodes/zone.d.ts +2 -0
  269. package/dist/schema/nodes/zone.d.ts.map +1 -1
  270. package/dist/schema/nodes/zone.js +6 -0
  271. package/dist/schema/scene-material.d.ts +41 -0
  272. package/dist/schema/scene-material.d.ts.map +1 -0
  273. package/dist/schema/scene-material.js +9 -0
  274. package/dist/schema/types.d.ts +2850 -655
  275. package/dist/schema/types.d.ts.map +1 -1
  276. package/dist/schema/types.js +49 -0
  277. package/dist/services/alignment-anchors.d.ts +100 -0
  278. package/dist/services/alignment-anchors.d.ts.map +1 -0
  279. package/dist/services/alignment-anchors.js +312 -0
  280. package/dist/services/alignment-anchors.test.d.ts +2 -0
  281. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  282. package/dist/services/alignment-anchors.test.js +383 -0
  283. package/dist/services/alignment.d.ts +131 -0
  284. package/dist/services/alignment.d.ts.map +1 -0
  285. package/dist/services/alignment.js +171 -0
  286. package/dist/services/alignment.test.d.ts +2 -0
  287. package/dist/services/alignment.test.d.ts.map +1 -0
  288. package/dist/services/alignment.test.js +92 -0
  289. package/dist/services/drag-session.d.ts +47 -0
  290. package/dist/services/drag-session.d.ts.map +1 -0
  291. package/dist/services/drag-session.js +100 -0
  292. package/dist/services/drag-session.test.d.ts +2 -0
  293. package/dist/services/drag-session.test.d.ts.map +1 -0
  294. package/dist/services/drag-session.test.js +219 -0
  295. package/dist/services/hosting.d.ts +88 -0
  296. package/dist/services/hosting.d.ts.map +1 -0
  297. package/dist/services/hosting.js +131 -0
  298. package/dist/services/hosting.test.d.ts +2 -0
  299. package/dist/services/hosting.test.d.ts.map +1 -0
  300. package/dist/services/hosting.test.js +230 -0
  301. package/dist/services/index.d.ts +13 -0
  302. package/dist/services/index.d.ts.map +1 -0
  303. package/dist/services/index.js +12 -0
  304. package/dist/services/level-height.d.ts +28 -0
  305. package/dist/services/level-height.d.ts.map +1 -0
  306. package/dist/services/level-height.js +62 -0
  307. package/dist/services/movement.d.ts +48 -0
  308. package/dist/services/movement.d.ts.map +1 -0
  309. package/dist/services/movement.js +65 -0
  310. package/dist/services/movement.test.d.ts +2 -0
  311. package/dist/services/movement.test.d.ts.map +1 -0
  312. package/dist/services/movement.test.js +133 -0
  313. package/dist/services/opening-guides.d.ts +141 -0
  314. package/dist/services/opening-guides.d.ts.map +1 -0
  315. package/dist/services/opening-guides.js +267 -0
  316. package/dist/services/opening-guides.test.d.ts +2 -0
  317. package/dist/services/opening-guides.test.d.ts.map +1 -0
  318. package/dist/services/opening-guides.test.js +208 -0
  319. package/dist/services/port-connectivity.d.ts +115 -0
  320. package/dist/services/port-connectivity.d.ts.map +1 -0
  321. package/dist/services/port-connectivity.js +300 -0
  322. package/dist/services/port-connectivity.test.d.ts +2 -0
  323. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  324. package/dist/services/port-connectivity.test.js +297 -0
  325. package/dist/services/riser-diagram.d.ts +40 -0
  326. package/dist/services/riser-diagram.d.ts.map +1 -0
  327. package/dist/services/riser-diagram.js +110 -0
  328. package/dist/services/riser-diagram.test.d.ts +2 -0
  329. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  330. package/dist/services/riser-diagram.test.js +66 -0
  331. package/dist/services/single-undo-dance.test.d.ts +2 -0
  332. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  333. package/dist/services/single-undo-dance.test.js +210 -0
  334. package/dist/services/snap.d.ts +80 -0
  335. package/dist/services/snap.d.ts.map +1 -0
  336. package/dist/services/snap.js +140 -0
  337. package/dist/services/snap.test.d.ts +2 -0
  338. package/dist/services/snap.test.d.ts.map +1 -0
  339. package/dist/services/snap.test.js +137 -0
  340. package/dist/services/system-graph.d.ts +30 -0
  341. package/dist/services/system-graph.d.ts.map +1 -0
  342. package/dist/services/system-graph.js +164 -0
  343. package/dist/services/system-graph.test.d.ts +2 -0
  344. package/dist/services/system-graph.test.d.ts.map +1 -0
  345. package/dist/services/system-graph.test.js +138 -0
  346. package/dist/services/validate-dwv.d.ts +32 -0
  347. package/dist/services/validate-dwv.d.ts.map +1 -0
  348. package/dist/services/validate-dwv.js +118 -0
  349. package/dist/services/validate-dwv.test.d.ts +2 -0
  350. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  351. package/dist/services/validate-dwv.test.js +109 -0
  352. package/dist/solar-panel-presets.d.ts +17 -0
  353. package/dist/solar-panel-presets.d.ts.map +1 -0
  354. package/dist/solar-panel-presets.js +43 -0
  355. package/dist/store/actions/node-actions.d.ts +14 -2
  356. package/dist/store/actions/node-actions.d.ts.map +1 -1
  357. package/dist/store/actions/node-actions.js +585 -15
  358. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  359. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  360. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  361. package/dist/store/actions/reparent.test.d.ts +2 -0
  362. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  363. package/dist/store/actions/reparent.test.js +212 -0
  364. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  365. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  366. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  367. package/dist/store/history-control.d.ts +11 -0
  368. package/dist/store/history-control.d.ts.map +1 -1
  369. package/dist/store/history-control.js +33 -0
  370. package/dist/store/use-interactive.d.ts +39 -0
  371. package/dist/store/use-interactive.d.ts.map +1 -1
  372. package/dist/store/use-interactive.js +75 -0
  373. package/dist/store/use-live-node-overrides.d.ts +22 -0
  374. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  375. package/dist/store/use-live-node-overrides.js +59 -0
  376. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  377. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  378. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  379. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  380. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  381. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  382. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  383. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  384. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  385. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  386. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  387. package/dist/store/use-scene-plugins.test.js +51 -0
  388. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  389. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  390. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  391. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  392. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  393. package/dist/store/use-scene-window-migration.test.js +85 -0
  394. package/dist/store/use-scene.d.ts +28 -2
  395. package/dist/store/use-scene.d.ts.map +1 -1
  396. package/dist/store/use-scene.js +546 -49
  397. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  398. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  399. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  400. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  401. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  402. package/dist/systems/elevator/elevator-geometry.js +50 -0
  403. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  404. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  405. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  406. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  407. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  408. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  409. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  410. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  411. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  412. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  413. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  414. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  415. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  416. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  417. package/dist/systems/elevator/elevator-runtime.js +225 -0
  418. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  419. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  420. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  421. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  422. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  423. package/dist/systems/elevator/elevator-service.js +111 -0
  424. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  425. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  426. package/dist/systems/fence/fence-centerline.js +37 -0
  427. package/dist/systems/fence/fence-spline.d.ts +39 -0
  428. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  429. package/dist/systems/fence/fence-spline.js +196 -0
  430. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  431. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  432. package/dist/systems/fence/fence-spline.test.js +136 -0
  433. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  434. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  435. package/dist/systems/stair/stair-footprint.js +178 -0
  436. package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
  437. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  438. package/dist/systems/stair/stair-opening-preview.js +93 -0
  439. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  440. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  441. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  442. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  443. package/dist/systems/stair/stair-opening-sync.js +155 -147
  444. package/dist/systems/stair/stair-opening-sync.test.js +424 -2
  445. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  446. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  447. package/dist/systems/stair/stair-opening-system.js +104 -0
  448. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  449. package/dist/systems/wall/wall-mitering.js +38 -2
  450. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  451. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  452. package/dist/systems/wall/wall-mitering.test.js +63 -0
  453. package/dist/systems/wall/wall-move.d.ts +48 -0
  454. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  455. package/dist/systems/wall/wall-move.js +193 -0
  456. package/dist/utils/clone-scene-graph.d.ts +7 -4
  457. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  458. package/dist/utils/clone-scene-graph.js +29 -7
  459. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  460. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  461. package/dist/utils/clone-scene-graph.test.js +62 -0
  462. package/dist/utils/heal-scene-graph.d.ts +18 -0
  463. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  464. package/dist/utils/heal-scene-graph.js +90 -0
  465. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  466. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  467. package/dist/utils/heal-scene-graph.test.js +88 -0
  468. package/dist/validation/validate-build-json.d.ts +44 -0
  469. package/dist/validation/validate-build-json.d.ts.map +1 -0
  470. package/dist/validation/validate-build-json.js +256 -0
  471. package/package.json +11 -4
@@ -1,21 +1,472 @@
1
- import { insetPolygonFromCentroid, simplifyClosedPolygon } from './polygon-geometry';
2
- /** Half of default wall thickness — used to extend slab geometry under walls */
3
- const SLAB_OUTSET = 0.05;
4
- const AUTO_SLAB_INSET = 0.02;
5
- const AUTO_SLAB_SIMPLIFY_TOLERANCE = 0.08;
6
- export function getRenderableSlabPolygon(slabNode) {
7
- return slabNode.autoFromWalls
8
- ? simplifyClosedPolygon(insetPolygonFromCentroid(slabNode.polygon, AUTO_SLAB_INSET), AUTO_SLAB_SIMPLIFY_TOLERANCE)
9
- : outsetPolygon(slabNode.polygon, SLAB_OUTSET);
1
+ import { isCurvedWall, sampleWallCenterline } from '../systems/wall/wall-curve';
2
+ import { getWallThickness } from '../systems/wall/wall-footprint';
3
+ /**
4
+ * Render-time slab polygon rules.
5
+ *
6
+ * Slab nodes store the wall-centerline polygon (auto slabs) or the drawn
7
+ * polygon (manual slabs) — render offsets are NEVER stored in node data.
8
+ * At geometry build time each polygon edge is SPLIT at the clipped span
9
+ * boundaries of every overlapping candidate (sibling slab edges and wall
10
+ * centerlines in the adoption band) and each sub-edge is classified
11
+ * independently — a single stored edge can be backed differently along
12
+ * its span (two rooms offset diagonally share a wall only where they
13
+ * overlap, so one room's edge is interior beside the sibling and its own
14
+ * facade elsewhere). Each sub-edge is PROJECTED onto an absolute target
15
+ * line:
16
+ *
17
+ * - INTERIOR — a sibling slab has a collinear, overlapping edge across
18
+ * it (directly, or across the same wall's footprint band). Projected
19
+ * EXACTLY onto a shared line so both neighbours emit the same seam
20
+ * and tile with no gap or overlap. Equal-height rooms partition the
21
+ * real wall band at its centerline. At an unequal-height boundary, the
22
+ * higher slab carries the full band to the lower room's wall face and
23
+ * the lower slab terminates at that same plane. This closes the step
24
+ * below the raised wall without lowering the wall or exposing a pocket.
25
+ * Without a wall, the target is the midline between the stored edges.
26
+ * The coincident vertical seam faces carry opposite outward
27
+ * normals and every slab material is front-side, so at most one face
28
+ * renders per view — no z-fighting.
29
+ * - WALL-BACKED — no slab neighbour across the edge, but the sub-edge
30
+ * lies inside a wall's footprint band (lateral distance from the
31
+ * centerline within half-thickness + adoption tolerance). Projected
32
+ * to the wall's OUTER face line so the slab reaches flush with the
33
+ * facade regardless of where the stored edge sits inside the band —
34
+ * this self-heals legacy data stored at wall faces or with old baked
35
+ * render offsets.
36
+ * - FREE — no neighbour, no wall. Rendered exactly as drawn.
37
+ *
38
+ * Sub-edges of one edge with different projections are joined by a
39
+ * perpendicular STEP connector at the breakpoint. Breakpoints sit on
40
+ * candidate span boundaries — wall junctions — so the step's vertical
41
+ * face lands inside the crossing wall's footprint and stays hidden
42
+ * under the wall body.
43
+ */
44
+ /** Lateral distance within which a sibling slab edge counts as directly "across" an edge. */
45
+ const SLAB_NEIGHBOR_LATERAL_TOLERANCE = 0.05;
46
+ /**
47
+ * Extra lateral tolerance beyond a wall's half-thickness for band
48
+ * adoption. 0.06 keeps every previously-adopted edge adopted (the old
49
+ * fixed 0.1 tolerance equals half-thickness + 0.05 for the thinnest
50
+ * 0.1 walls, and auto slab polygons simplified with a 0.08 tolerance on
51
+ * curved walls stay inside half + 0.06), while giving hand-adjusted
52
+ * edges ~6cm of slack on either side of the wall faces.
53
+ */
54
+ const WALL_ADOPTION_TOLERANCE = 0.06;
55
+ /** Minimum collinear overlap (m) before a candidate drives the classification. */
56
+ const MIN_CLASSIFYING_OVERLAP = 0.05;
57
+ /**
58
+ * Minimum sub-edge length (m) when splitting an edge at candidate span
59
+ * boundaries. Breakpoints closer than this to the previous one or to an
60
+ * edge endpoint are dropped, so slivers never survive into the ring.
61
+ */
62
+ const MIN_SUBEDGE_LENGTH = 0.05;
63
+ /**
64
+ * Two candidate walls whose centerlines sit within this lateral
65
+ * difference count as equally near; the larger span overlap wins
66
+ * (collinear runs of different-thickness walls along one edge).
67
+ * Otherwise the nearest centerline wins (parallel close walls).
68
+ */
69
+ const WALL_LATERAL_TIE_EPSILON = 0.02;
70
+ const CURVED_WALL_SAMPLE_SEGMENTS = 32;
71
+ const SLAB_SEAM_ELEVATION_EPSILON = 1e-4;
72
+ const DEFAULT_SLAB_ELEVATION = 0.05;
73
+ /** Prevent near-parallel offset lines from producing unbounded corner spikes. */
74
+ const MAX_CORNER_MITER_RATIO = 10;
75
+ function polygonWindingSign(polygon) {
76
+ let area2 = 0;
77
+ for (let index = 0; index < polygon.length; index += 1) {
78
+ const next = (index + 1) % polygon.length;
79
+ area2 += polygon[index][0] * polygon[next][1] - polygon[next][0] * polygon[index][1];
80
+ }
81
+ return area2 >= 0 ? 1 : -1;
82
+ }
83
+ /**
84
+ * Derive a {@link SlabPolygonContext} from a registry `GeometryContext`:
85
+ * sibling slabs come from `ctx.siblings` (same kind, same parent, self
86
+ * excluded) and walls from the parent level's children.
87
+ */
88
+ export function slabPolygonContextFromGeometry(ctx) {
89
+ if (!ctx)
90
+ return { walls: [], siblingSlabs: [] };
91
+ const siblingSlabs = ctx.siblings.filter((node) => node.type === 'slab');
92
+ const walls = [];
93
+ const parentChildIds = ctx.parent?.children;
94
+ if (Array.isArray(parentChildIds)) {
95
+ for (const childId of parentChildIds) {
96
+ const child = ctx.resolve(childId);
97
+ if (child?.type === 'wall') {
98
+ walls.push(child);
99
+ }
100
+ }
101
+ }
102
+ return { walls, siblingSlabs };
103
+ }
104
+ export function getRenderableSlabPolygon(slabNode, context) {
105
+ const polygon = slabNode.polygon;
106
+ if (polygon.length < 3) {
107
+ return polygon.map(([x, z]) => [x, z]);
108
+ }
109
+ const subSpans = computeEdgeSubSpans(polygon, slabNode.elevation ?? DEFAULT_SLAB_ELEVATION, context);
110
+ if (subSpans.every((spans) => spans.length === 1 && spans[0].offset === 0)) {
111
+ return polygon.map(([x, z]) => [x, z]);
112
+ }
113
+ return offsetPolygonPerEdge(polygon, subSpans);
114
+ }
115
+ /**
116
+ * Clip the candidate segment `(px, pz) → (qx, qz)` against the span of
117
+ * the edge starting at `(ax, az)` with normalized direction
118
+ * `(dirX, dirZ)`, counting only when the candidate is collinear with
119
+ * the edge within `lateralTolerance` (both candidate endpoints within
120
+ * that distance of the edge's infinite line — testing against the
121
+ * infinite line, not the edge span, is what lets a T-junction
122
+ * sub-segment match its longer host edge).
123
+ */
124
+ function clipCollinearSegment(ax, az, dirX, dirZ, edgeLength, px, pz, qx, qz, lateralTolerance) {
125
+ const relPX = px - ax;
126
+ const relPZ = pz - az;
127
+ const latP = relPX * dirZ - relPZ * dirX;
128
+ if (Math.abs(latP) > lateralTolerance)
129
+ return null;
130
+ const relQX = qx - ax;
131
+ const relQZ = qz - az;
132
+ const latQ = relQX * dirZ - relQZ * dirX;
133
+ if (Math.abs(latQ) > lateralTolerance)
134
+ return null;
135
+ const t0 = relPX * dirX + relPZ * dirZ;
136
+ const t1 = relQX * dirX + relQZ * dirZ;
137
+ const low = Math.max(Math.min(t0, t1), 0);
138
+ const high = Math.min(Math.max(t0, t1), edgeLength);
139
+ const overlap = high - low;
140
+ if (overlap <= 0)
141
+ return null;
142
+ const tMid = (low + high) / 2;
143
+ const u = Math.abs(t1 - t0) < 1e-12 ? 0.5 : (tMid - t0) / (t1 - t0);
144
+ return { overlap, lateral: latP + (latQ - latP) * u, start: low, end: high };
145
+ }
146
+ function wallCenterlineSegments(wall) {
147
+ if (!isCurvedWall(wall)) {
148
+ return [[wall.start[0], wall.start[1], wall.end[0], wall.end[1]]];
149
+ }
150
+ const points = sampleWallCenterline(wall, CURVED_WALL_SAMPLE_SEGMENTS);
151
+ const segments = [];
152
+ for (let index = 0; index < points.length - 1; index += 1) {
153
+ const from = points[index];
154
+ const to = points[index + 1];
155
+ segments.push([from.x, from.y, to.x, to.y]);
156
+ }
157
+ return segments;
158
+ }
159
+ /**
160
+ * Best wall whose footprint band contains the edge: the centerline is
161
+ * collinear within half-thickness + {@link WALL_ADOPTION_TOLERANCE} and
162
+ * span-overlaps the edge by at least `requiredOverlap`. Nearest
163
+ * centerline wins; near-ties fall back to the larger overlap.
164
+ */
165
+ function matchEdgeWallBand(ax, az, dirX, dirZ, edgeLength, requiredOverlap, candidates) {
166
+ let best = null;
167
+ for (const candidate of candidates) {
168
+ const tolerance = candidate.halfThickness + WALL_ADOPTION_TOLERANCE;
169
+ let overlap = 0;
170
+ let weightedLateral = 0;
171
+ for (const [px, pz, qx, qz] of candidate.segments) {
172
+ const match = clipCollinearSegment(ax, az, dirX, dirZ, edgeLength, px, pz, qx, qz, tolerance);
173
+ if (!match)
174
+ continue;
175
+ overlap += match.overlap;
176
+ weightedLateral += match.lateral * match.overlap;
177
+ }
178
+ if (overlap < requiredOverlap)
179
+ continue;
180
+ const lateral = weightedLateral / overlap;
181
+ if (!best) {
182
+ best = { wall: candidate.wall, halfThickness: candidate.halfThickness, overlap, lateral };
183
+ continue;
184
+ }
185
+ const bestAbs = Math.abs(best.lateral);
186
+ const thisAbs = Math.abs(lateral);
187
+ const nearTie = Math.abs(thisAbs - bestAbs) <= WALL_LATERAL_TIE_EPSILON;
188
+ if (nearTie ? overlap > best.overlap : thisAbs < bestAbs) {
189
+ best = { wall: candidate.wall, halfThickness: candidate.halfThickness, overlap, lateral };
190
+ }
191
+ }
192
+ return best;
193
+ }
194
+ /**
195
+ * Reshape-snap counterpart of the render band rule: when the edge
196
+ * `a → b` lies inside a wall's footprint band, return the edge
197
+ * translated onto that wall's CENTERLINE — the canonical stored
198
+ * position (matching what auto slabs store); the render rule then
199
+ * places it at the face. `maxLateral` optionally tightens the stick
200
+ * distance (non-magnetic modes keep only a connect-radius stick).
201
+ */
202
+ export function snapSlabEdgeToWallBand(a, b, walls, options) {
203
+ const dx = b[0] - a[0];
204
+ const dz = b[1] - a[1];
205
+ const edgeLength = Math.hypot(dx, dz);
206
+ if (edgeLength < 1e-9)
207
+ return null;
208
+ const dirX = dx / edgeLength;
209
+ const dirZ = dz / edgeLength;
210
+ const requiredOverlap = Math.min(MIN_CLASSIFYING_OVERLAP, edgeLength * 0.5);
211
+ const candidates = walls.map((wall) => ({
212
+ wall,
213
+ segments: wallCenterlineSegments(wall),
214
+ halfThickness: getWallThickness(wall) / 2,
215
+ }));
216
+ const match = matchEdgeWallBand(a[0], a[1], dirX, dirZ, edgeLength, requiredOverlap, candidates);
217
+ if (!match)
218
+ return null;
219
+ if (options?.maxLateral !== undefined && Math.abs(match.lateral) > options.maxLateral)
220
+ return null;
221
+ const nx = dirZ * match.lateral;
222
+ const nz = -dirX * match.lateral;
223
+ return {
224
+ wallId: match.wall.id,
225
+ edge: [
226
+ [a[0] + nx, a[1] + nz],
227
+ [b[0] + nx, b[1] + nz],
228
+ ],
229
+ };
10
230
  }
11
231
  /**
12
- * Expand a polygon outward by a uniform distance.
13
- * Offsets each edge outward then intersects consecutive offset edges.
232
+ * Split every polygon edge at candidate span boundaries and classify
233
+ * each sub-span independently. Returns one non-empty span list per
234
+ * edge, covering [0, edgeLength] without gaps.
14
235
  */
15
- function outsetPolygon(polygon, amount) {
236
+ function computeEdgeSubSpans(polygon, selfElevation, context) {
237
+ const n = polygon.length;
238
+ // Winding sign: the outward normal of an edge with direction `dir` is
239
+ // `s * (dirZ, -dirX)`, so a target lateral `L` measured along
240
+ // `(dirZ, -dirX)` is `s * L` along the outward normal.
241
+ const s = polygonWindingSign(polygon);
242
+ const neighborSegments = [];
243
+ for (const sibling of context.siblingSlabs) {
244
+ const siblingPolygon = sibling.polygon;
245
+ if (siblingPolygon.length < 2)
246
+ continue;
247
+ const elevation = sibling.elevation ?? DEFAULT_SLAB_ELEVATION;
248
+ const siblingWinding = polygonWindingSign(siblingPolygon);
249
+ for (let index = 0; index < siblingPolygon.length; index += 1) {
250
+ const from = siblingPolygon[index];
251
+ const to = siblingPolygon[(index + 1) % siblingPolygon.length];
252
+ const dx = to[0] - from[0];
253
+ const dz = to[1] - from[1];
254
+ const length = Math.hypot(dx, dz);
255
+ if (length < 1e-9)
256
+ continue;
257
+ neighborSegments.push({
258
+ segment: [from[0], from[1], to[0], to[1]],
259
+ elevation,
260
+ inwardX: (-siblingWinding * dz) / length,
261
+ inwardZ: (siblingWinding * dx) / length,
262
+ });
263
+ }
264
+ }
265
+ const wallCandidates = context.walls.map((wall) => ({
266
+ wall,
267
+ segments: wallCenterlineSegments(wall),
268
+ halfThickness: getWallThickness(wall) / 2,
269
+ }));
270
+ // Sibling breakpoints also matter for legacy face-aligned polygons up
271
+ // to a full wall band away from the edge (the band-sibling interior
272
+ // rule in classifySpan), so clip them with the widest band reach any
273
+ // level wall allows. Over-collection is harmless — same-target
274
+ // neighbours re-fuse after classification.
275
+ let siblingBreakTolerance = SLAB_NEIGHBOR_LATERAL_TOLERANCE;
276
+ for (const candidate of wallCandidates) {
277
+ siblingBreakTolerance = Math.max(siblingBreakTolerance, 2 * (candidate.halfThickness + WALL_ADOPTION_TOLERANCE));
278
+ }
279
+ const subSpans = [];
280
+ for (let index = 0; index < n; index += 1) {
281
+ const a = polygon[index];
282
+ const b = polygon[(index + 1) % n];
283
+ const dx = b[0] - a[0];
284
+ const dz = b[1] - a[1];
285
+ const edgeLength = Math.hypot(dx, dz);
286
+ if (edgeLength < 1e-9) {
287
+ subSpans.push([{ start: 0, end: edgeLength, offset: 0, key: 'free' }]);
288
+ continue;
289
+ }
290
+ const dirX = dx / edgeLength;
291
+ const dirZ = dz / edgeLength;
292
+ const rawBreakpoints = [];
293
+ for (const candidate of wallCandidates) {
294
+ const tolerance = candidate.halfThickness + WALL_ADOPTION_TOLERANCE;
295
+ for (const [px, pz, qx, qz] of candidate.segments) {
296
+ const match = clipCollinearSegment(a[0], a[1], dirX, dirZ, edgeLength, px, pz, qx, qz, tolerance);
297
+ if (match)
298
+ rawBreakpoints.push(match.start, match.end);
299
+ }
300
+ }
301
+ const inwardX = -s * dirZ;
302
+ const inwardZ = s * dirX;
303
+ for (const { segment: [px, pz, qx, qz], inwardX: siblingInwardX, inwardZ: siblingInwardZ, } of neighborSegments) {
304
+ // Coincident/stacked slabs have their interiors on the same side of
305
+ // the edge. Only an edge whose sibling interior is across this edge
306
+ // can form a room-to-room seam.
307
+ if (inwardX * siblingInwardX + inwardZ * siblingInwardZ >= -0.5)
308
+ continue;
309
+ const match = clipCollinearSegment(a[0], a[1], dirX, dirZ, edgeLength, px, pz, qx, qz, siblingBreakTolerance);
310
+ if (match)
311
+ rawBreakpoints.push(match.start, match.end);
312
+ }
313
+ rawBreakpoints.sort((left, right) => left - right);
314
+ const breakpoints = [];
315
+ let previous = 0;
316
+ for (const t of rawBreakpoints) {
317
+ if (t - previous < MIN_SUBEDGE_LENGTH)
318
+ continue;
319
+ // Sorted ascending — every later breakpoint is even closer to the end.
320
+ if (edgeLength - t < MIN_SUBEDGE_LENGTH)
321
+ break;
322
+ breakpoints.push(t);
323
+ previous = t;
324
+ }
325
+ const bounds = [0, ...breakpoints, edgeLength];
326
+ const spans = [];
327
+ for (let k = 0; k + 1 < bounds.length; k += 1) {
328
+ spans.push(classifySpan(a, dirX, dirZ, bounds[k], bounds[k + 1], s, selfElevation, wallCandidates, neighborSegments));
329
+ }
330
+ // Re-fuse same-target neighbours, reclassifying over the fused span:
331
+ // curved-wall sampling collapses back to one span per contiguous
332
+ // band, and an edge fully backed by one target reproduces the
333
+ // whole-edge projection exactly.
334
+ let fused = true;
335
+ while (fused && spans.length > 1) {
336
+ fused = false;
337
+ for (let k = 0; k + 1 < spans.length; k += 1) {
338
+ if (spans[k].key !== spans[k + 1].key)
339
+ continue;
340
+ const merged = classifySpan(a, dirX, dirZ, spans[k].start, spans[k + 1].end, s, selfElevation, wallCandidates, neighborSegments);
341
+ spans.splice(k, 2, merged);
342
+ fused = true;
343
+ break;
344
+ }
345
+ }
346
+ subSpans.push(spans);
347
+ }
348
+ return subSpans;
349
+ }
350
+ /**
351
+ * Classify one span of the edge `a + t·dir`, `t ∈ [start, end]`, with
352
+ * the whole-edge rules: direct sibling wins, sibling across the same
353
+ * wall band forces interior, otherwise the matched wall's outer face,
354
+ * otherwise free. Interior spans backed by a wall project onto the
355
+ * wall centerline at equal heights. At unequal heights both slabs target
356
+ * the lower room's wall face, giving the higher slab the full band.
357
+ */
358
+ function classifySpan(a, dirX, dirZ, start, end, s, selfElevation, wallCandidates, neighborSegments) {
359
+ const sx = a[0] + dirX * start;
360
+ const sz = a[1] + dirZ * start;
361
+ const spanLength = end - start;
362
+ // Short spans still need classification — require at most half their span.
363
+ const requiredOverlap = Math.min(MIN_CLASSIFYING_OVERLAP, spanLength * 0.5);
364
+ const inwardX = -s * dirZ;
365
+ const inwardZ = s * dirX;
366
+ const wallMatch = matchEdgeWallBand(sx, sz, dirX, dirZ, spanLength, requiredOverlap, wallCandidates);
367
+ // Direct neighbour: a sibling edge collinear within the tight
368
+ // tolerance regardless of any wall (slabs butted against each other).
369
+ let directOverlap = 0;
370
+ let directWeightedLateral = 0;
371
+ let directSiblingElevation = null;
372
+ for (const { segment: [px, pz, qx, qz], elevation, inwardX: siblingInwardX, inwardZ: siblingInwardZ, } of neighborSegments) {
373
+ if (inwardX * siblingInwardX + inwardZ * siblingInwardZ >= -0.5)
374
+ continue;
375
+ const match = clipCollinearSegment(sx, sz, dirX, dirZ, spanLength, px, pz, qx, qz, SLAB_NEIGHBOR_LATERAL_TOLERANCE);
376
+ if (!match)
377
+ continue;
378
+ directOverlap += match.overlap;
379
+ directWeightedLateral += match.lateral * match.overlap;
380
+ directSiblingElevation =
381
+ directSiblingElevation === null ? elevation : Math.max(directSiblingElevation, elevation);
382
+ }
383
+ let interiorLateral = null;
384
+ let siblingElevation = null;
385
+ if (directOverlap >= requiredOverlap) {
386
+ // No-wall fallback: half the mean sibling separation — the midline
387
+ // between the two stored edges. Symmetric: the sibling measures the
388
+ // same separation with opposite sign from its own line, so both
389
+ // project onto the same line and the seam stays gapless.
390
+ interiorLateral = wallMatch ? wallMatch.lateral : directWeightedLateral / directOverlap / 2;
391
+ siblingElevation = directSiblingElevation;
392
+ }
393
+ else if (wallMatch) {
394
+ // Rooms across a shared wall: legacy face-aligned polygons sit a
395
+ // full thickness apart — far beyond the direct tolerance — but
396
+ // both edges live inside the same wall band. Both sides must
397
+ // classify interior, otherwise each would project to the opposite
398
+ // outer face and overlap under the wall.
399
+ const bandTolerance = wallMatch.halfThickness + WALL_ADOPTION_TOLERANCE;
400
+ const looseTolerance = Math.abs(wallMatch.lateral) + bandTolerance;
401
+ let bandOverlap = 0;
402
+ for (const { segment: [px, pz, qx, qz], elevation, inwardX: siblingInwardX, inwardZ: siblingInwardZ, } of neighborSegments) {
403
+ if (inwardX * siblingInwardX + inwardZ * siblingInwardZ >= -0.5)
404
+ continue;
405
+ const match = clipCollinearSegment(sx, sz, dirX, dirZ, spanLength, px, pz, qx, qz, looseTolerance);
406
+ if (!match)
407
+ continue;
408
+ if (Math.abs(match.lateral - wallMatch.lateral) > bandTolerance)
409
+ continue;
410
+ bandOverlap += match.overlap;
411
+ siblingElevation =
412
+ siblingElevation === null ? elevation : Math.max(siblingElevation, elevation);
413
+ }
414
+ if (bandOverlap >= requiredOverlap) {
415
+ interiorLateral = wallMatch.lateral;
416
+ }
417
+ else {
418
+ siblingElevation = null;
419
+ }
420
+ }
421
+ if (interiorLateral !== null) {
422
+ if (wallMatch && siblingElevation !== null) {
423
+ const elevationDelta = selfElevation - siblingElevation;
424
+ if (elevationDelta > SLAB_SEAM_ELEVATION_EPSILON) {
425
+ return {
426
+ start,
427
+ end,
428
+ offset: s * wallMatch.lateral + wallMatch.halfThickness,
429
+ key: `interior|${wallMatch.wall.id}|higher`,
430
+ };
431
+ }
432
+ if (elevationDelta < -SLAB_SEAM_ELEVATION_EPSILON) {
433
+ return {
434
+ start,
435
+ end,
436
+ offset: s * wallMatch.lateral - wallMatch.halfThickness,
437
+ key: `interior|${wallMatch.wall.id}|lower`,
438
+ };
439
+ }
440
+ }
441
+ return {
442
+ start,
443
+ end,
444
+ offset: s * interiorLateral,
445
+ key: `interior|${wallMatch ? wallMatch.wall.id : '~'}`,
446
+ };
447
+ }
448
+ if (wallMatch) {
449
+ return {
450
+ start,
451
+ end,
452
+ offset: s * wallMatch.lateral + wallMatch.halfThickness,
453
+ key: `wall|${wallMatch.wall.id}`,
454
+ };
455
+ }
456
+ return { start, end, offset: 0, key: 'free' };
457
+ }
458
+ /**
459
+ * Offset each edge's sub-spans along the edge's outward normal by their
460
+ * own amounts (negative insets), then rebuild the ring: consecutive
461
+ * sub-spans of one edge with different offsets are joined by a
462
+ * perpendicular step connector at the breakpoint (both boundary points
463
+ * share the breakpoint param), and corners between different edges
464
+ * intersect the offset lines of the adjoining sub-spans.
465
+ */
466
+ function offsetPolygonPerEdge(polygon, subSpans) {
16
467
  const n = polygon.length;
17
468
  if (n < 3)
18
- return polygon;
469
+ return polygon.map(([x, z]) => [x, z]);
19
470
  // Determine winding via signed area
20
471
  let area2 = 0;
21
472
  for (let i = 0; i < n; i++) {
@@ -23,35 +474,78 @@ function outsetPolygon(polygon, amount) {
23
474
  area2 += polygon[i][0] * polygon[j][1] - polygon[j][0] * polygon[i][1];
24
475
  }
25
476
  const s = area2 >= 0 ? 1 : -1;
26
- // Offset each edge outward by amount
27
- const offEdges = [];
477
+ const frames = [];
28
478
  for (let i = 0; i < n; i++) {
29
479
  const j = (i + 1) % n;
30
480
  const dx = polygon[j][0] - polygon[i][0];
31
481
  const dz = polygon[j][1] - polygon[i][1];
32
- const len = Math.sqrt(dx * dx + dz * dz);
33
- if (len < 1e-9) {
34
- offEdges.push([polygon[i][0], polygon[i][1], dx, dz]);
35
- continue;
36
- }
37
- const nx = ((s * dz) / len) * amount;
38
- const nz = ((s * -dx) / len) * amount;
39
- offEdges.push([polygon[i][0] + nx, polygon[i][1] + nz, dx, dz]);
482
+ const length = Math.hypot(dx, dz);
483
+ const dirX = length < 1e-9 ? 0 : dx / length;
484
+ const dirZ = length < 1e-9 ? 0 : dz / length;
485
+ frames.push({ ax: polygon[i][0], az: polygon[i][1], dx, dz, dirX, dirZ });
40
486
  }
41
- // Intersect consecutive offset edges to get new vertices
487
+ const pointAt = (edge, t, offset) => {
488
+ const frame = frames[edge];
489
+ return [
490
+ frame.ax + frame.dirX * t + s * frame.dirZ * offset,
491
+ frame.az + frame.dirZ * t - s * frame.dirX * offset,
492
+ ];
493
+ };
42
494
  const result = [];
495
+ const push = (point) => {
496
+ const previous = result[result.length - 1];
497
+ if (previous && Math.hypot(previous[0] - point[0], previous[1] - point[1]) < 1e-9)
498
+ return;
499
+ result.push(point);
500
+ };
43
501
  for (let i = 0; i < n; i++) {
502
+ const spans = subSpans[i];
503
+ // Step connectors between differently-projected sub-spans of edge i.
504
+ // Equal offsets (different walls sharing a face line) collapse to a
505
+ // single collinear vertex via the coincident-point guard in `push`.
506
+ for (let k = 1; k < spans.length; k += 1) {
507
+ push(pointAt(i, spans[k - 1].end, spans[k - 1].offset));
508
+ push(pointAt(i, spans[k].start, spans[k].offset));
509
+ }
510
+ // Corner between edge i and edge j: intersect the offset lines of
511
+ // edge i's last sub-span and edge j's first sub-span.
44
512
  const j = (i + 1) % n;
45
- const [ax, az, adx, adz] = offEdges[i];
46
- const [bx, bz, bdx, bdz] = offEdges[j];
47
- const denom = adx * bdz - adz * bdx;
513
+ const last = spans[spans.length - 1];
514
+ const first = subSpans[j][0];
515
+ const [ax, az] = pointAt(i, last.start, last.offset);
516
+ const endI = pointAt(i, last.end, last.offset);
517
+ const startJ = pointAt(j, first.start, first.offset);
518
+ const [bx, bz] = startJ;
519
+ const frameI = frames[i];
520
+ const frameJ = frames[j];
521
+ const denom = frameI.dx * frameJ.dz - frameI.dz * frameJ.dx;
48
522
  if (Math.abs(denom) < 1e-9) {
49
- // Parallel edges use offset endpoint
50
- result.push([ax + adx, az + adz]);
523
+ // Parallel edges have no unique intersection. Emit both offset
524
+ // endpoints collinear edges with different offsets need the step
525
+ // between them.
526
+ push(endI);
527
+ push(startJ);
51
528
  }
52
529
  else {
53
- const t = ((bx - ax) * bdz - (bz - az) * bdx) / denom;
54
- result.push([ax + t * adx, az + t * adz]);
530
+ const t = ((bx - ax) * frameJ.dz - (bz - az) * frameJ.dx) / denom;
531
+ const intersection = [ax + t * frameI.dx, az + t * frameI.dz];
532
+ const miterReach = Math.max(Math.hypot(intersection[0] - endI[0], intersection[1] - endI[1]), Math.hypot(intersection[0] - startJ[0], intersection[1] - startJ[1]));
533
+ const offsetScale = Math.max(Math.abs(last.offset), Math.abs(first.offset), 1e-9);
534
+ if (!(Number.isFinite(intersection[0]) && Number.isFinite(intersection[1])) ||
535
+ miterReach > offsetScale * MAX_CORNER_MITER_RATIO) {
536
+ push(endI);
537
+ push(startJ);
538
+ }
539
+ else {
540
+ push(intersection);
541
+ }
542
+ }
543
+ }
544
+ if (result.length > 1) {
545
+ const firstPoint = result[0];
546
+ const lastPoint = result[result.length - 1];
547
+ if (Math.hypot(firstPoint[0] - lastPoint[0], firstPoint[1] - lastPoint[1]) < 1e-9) {
548
+ result.pop();
55
549
  }
56
550
  }
57
551
  return result;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=slab-polygon.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slab-polygon.test.d.ts","sourceRoot":"","sources":["../../src/lib/slab-polygon.test.ts"],"names":[],"mappings":""}