@pascal-app/nodes 0.1.1 → 1.0.0-beta.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 (408) hide show
  1. package/README.md +34 -0
  2. package/dist/building/definition.js +1 -1
  3. package/dist/cabinet/definition.d.ts.map +1 -1
  4. package/dist/cabinet/definition.js +809 -72
  5. package/dist/cabinet/geometry.d.ts.map +1 -1
  6. package/dist/cabinet/geometry.js +4 -0
  7. package/dist/cabinet/move-frame.js +3 -3
  8. package/dist/cabinet/panel.d.ts.map +1 -1
  9. package/dist/cabinet/panel.js +1 -0
  10. package/dist/cabinet/placement-snap.d.ts +8 -0
  11. package/dist/cabinet/placement-snap.d.ts.map +1 -0
  12. package/dist/cabinet/placement-snap.js +19 -0
  13. package/dist/cabinet/presets.js +8 -8
  14. package/dist/cabinet/quick-actions.d.ts.map +1 -1
  15. package/dist/cabinet/quick-actions.js +9 -3
  16. package/dist/cabinet/resize-limits.d.ts +11 -0
  17. package/dist/cabinet/resize-limits.d.ts.map +1 -0
  18. package/dist/cabinet/resize-limits.js +22 -0
  19. package/dist/cabinet/run-layout.d.ts +8 -2
  20. package/dist/cabinet/run-layout.d.ts.map +1 -1
  21. package/dist/cabinet/run-layout.js +59 -7
  22. package/dist/cabinet/run-ops.d.ts +44 -3
  23. package/dist/cabinet/run-ops.d.ts.map +1 -1
  24. package/dist/cabinet/run-ops.js +646 -110
  25. package/dist/cabinet/run-panel.d.ts +2 -1
  26. package/dist/cabinet/run-panel.d.ts.map +1 -1
  27. package/dist/cabinet/run-panel.js +38 -4
  28. package/dist/cabinet/stack-transitions.js +4 -4
  29. package/dist/cabinet/tool.d.ts.map +1 -1
  30. package/dist/cabinet/tool.js +95 -17
  31. package/dist/cabinet/wall-snap.d.ts.map +1 -1
  32. package/dist/cabinet/wall-snap.js +2 -6
  33. package/dist/ceiling/boundary-editor.d.ts.map +1 -1
  34. package/dist/ceiling/boundary-editor.js +2 -2
  35. package/dist/ceiling/definition.d.ts +1 -1
  36. package/dist/ceiling/definition.d.ts.map +1 -1
  37. package/dist/ceiling/definition.js +36 -5
  38. package/dist/ceiling/floorplan-affordances.d.ts +4 -4
  39. package/dist/ceiling/floorplan-move.d.ts +4 -4
  40. package/dist/ceiling/floorplan-move.d.ts.map +1 -1
  41. package/dist/ceiling/floorplan-move.js +5 -4
  42. package/dist/ceiling/hole-editor.d.ts.map +1 -1
  43. package/dist/ceiling/hole-editor.js +2 -2
  44. package/dist/ceiling/move-tool.d.ts.map +1 -1
  45. package/dist/ceiling/move-tool.js +4 -3
  46. package/dist/ceiling/panel.d.ts.map +1 -1
  47. package/dist/ceiling/panel.js +65 -3
  48. package/dist/ceiling/renderer.d.ts.map +1 -1
  49. package/dist/ceiling/renderer.js +6 -2
  50. package/dist/ceiling/tool.d.ts.map +1 -1
  51. package/dist/ceiling/tool.js +12 -1
  52. package/dist/chimney/definition.d.ts.map +1 -1
  53. package/dist/chimney/definition.js +1 -0
  54. package/dist/chimney/tool.d.ts.map +1 -1
  55. package/dist/chimney/tool.js +16 -1
  56. package/dist/column/definition.d.ts.map +1 -1
  57. package/dist/column/definition.js +14 -2
  58. package/dist/column/floorplan-affordances.d.ts.map +1 -1
  59. package/dist/column/floorplan-affordances.js +19 -14
  60. package/dist/column/floorplan-move.d.ts.map +1 -1
  61. package/dist/column/floorplan-move.js +7 -2
  62. package/dist/column/floorplan.d.ts +9 -1
  63. package/dist/column/floorplan.d.ts.map +1 -1
  64. package/dist/column/floorplan.js +59 -4
  65. package/dist/column/tool.d.ts.map +1 -1
  66. package/dist/column/tool.js +33 -7
  67. package/dist/construction-dimension/definition.d.ts +4 -0
  68. package/dist/construction-dimension/definition.d.ts.map +1 -0
  69. package/dist/construction-dimension/definition.js +83 -0
  70. package/dist/construction-dimension/drawing-coordination.d.ts +7 -0
  71. package/dist/construction-dimension/drawing-coordination.d.ts.map +1 -0
  72. package/dist/construction-dimension/drawing-coordination.js +54 -0
  73. package/dist/construction-dimension/floorplan-affordances.d.ts +4 -0
  74. package/dist/construction-dimension/floorplan-affordances.d.ts.map +1 -0
  75. package/dist/construction-dimension/floorplan-affordances.js +114 -0
  76. package/dist/construction-dimension/floorplan-tool.d.ts +21 -0
  77. package/dist/construction-dimension/floorplan-tool.d.ts.map +1 -0
  78. package/dist/construction-dimension/floorplan-tool.js +557 -0
  79. package/dist/construction-dimension/floorplan.d.ts +3 -0
  80. package/dist/construction-dimension/floorplan.d.ts.map +1 -0
  81. package/dist/construction-dimension/floorplan.js +457 -0
  82. package/dist/construction-dimension/geometry.d.ts +31 -0
  83. package/dist/construction-dimension/geometry.d.ts.map +1 -0
  84. package/dist/construction-dimension/geometry.js +144 -0
  85. package/dist/construction-dimension/index.d.ts +4 -0
  86. package/dist/construction-dimension/index.d.ts.map +1 -0
  87. package/dist/construction-dimension/index.js +3 -0
  88. package/dist/construction-dimension/panel.d.ts +2 -0
  89. package/dist/construction-dimension/panel.d.ts.map +1 -0
  90. package/dist/construction-dimension/panel.js +128 -0
  91. package/dist/construction-dimension/parametrics.d.ts +3 -0
  92. package/dist/construction-dimension/parametrics.d.ts.map +1 -0
  93. package/dist/construction-dimension/parametrics.js +4 -0
  94. package/dist/construction-dimension/schema.d.ts +2 -0
  95. package/dist/construction-dimension/schema.d.ts.map +1 -0
  96. package/dist/construction-dimension/schema.js +1 -0
  97. package/dist/door/contextual-dimensions.d.ts +3 -0
  98. package/dist/door/contextual-dimensions.d.ts.map +1 -0
  99. package/dist/door/contextual-dimensions.js +7 -0
  100. package/dist/door/definition.d.ts.map +1 -1
  101. package/dist/door/definition.js +13 -8
  102. package/dist/door/floorplan-affordances.d.ts +2 -5
  103. package/dist/door/floorplan-affordances.d.ts.map +1 -1
  104. package/dist/door/floorplan-affordances.js +9 -18
  105. package/dist/door/floorplan.d.ts +3 -4
  106. package/dist/door/floorplan.d.ts.map +1 -1
  107. package/dist/door/floorplan.js +88 -6
  108. package/dist/door/move-tool.d.ts.map +1 -1
  109. package/dist/door/move-tool.js +2 -2
  110. package/dist/door/panel.d.ts.map +1 -1
  111. package/dist/door/panel.js +3 -1
  112. package/dist/door/schema.d.ts +1 -1
  113. package/dist/door/schema.d.ts.map +1 -1
  114. package/dist/door/schema.js +1 -1
  115. package/dist/door/tool.d.ts.map +1 -1
  116. package/dist/door/tool.js +63 -6
  117. package/dist/dormer/definition.d.ts.map +1 -1
  118. package/dist/dormer/definition.js +1 -0
  119. package/dist/dormer/tool.d.ts.map +1 -1
  120. package/dist/dormer/tool.js +23 -1
  121. package/dist/duct-segment/tool.d.ts.map +1 -1
  122. package/dist/duct-segment/tool.js +20 -9
  123. package/dist/duct-terminal/tool.d.ts.map +1 -1
  124. package/dist/duct-terminal/tool.js +9 -6
  125. package/dist/elevator/floorplan-affordances.d.ts +2 -6
  126. package/dist/elevator/floorplan-affordances.d.ts.map +1 -1
  127. package/dist/elevator/floorplan-affordances.js +13 -12
  128. package/dist/fence/actions/move-endpoint.d.ts.map +1 -1
  129. package/dist/fence/actions/move-endpoint.js +29 -1
  130. package/dist/fence/definition.d.ts.map +1 -1
  131. package/dist/fence/definition.js +74 -14
  132. package/dist/fence/floorplan-affordances.d.ts.map +1 -1
  133. package/dist/fence/floorplan-affordances.js +48 -11
  134. package/dist/fence/floorplan-move.d.ts.map +1 -1
  135. package/dist/fence/floorplan-move.js +38 -3
  136. package/dist/fence/geometry.d.ts.map +1 -1
  137. package/dist/fence/geometry.js +15 -1
  138. package/dist/fence/lift.d.ts +38 -0
  139. package/dist/fence/lift.d.ts.map +1 -0
  140. package/dist/fence/lift.js +51 -0
  141. package/dist/fence/move-tool.d.ts.map +1 -1
  142. package/dist/fence/move-tool.js +25 -5
  143. package/dist/fence/system.d.ts +3 -0
  144. package/dist/fence/system.d.ts.map +1 -0
  145. package/dist/fence/system.js +46 -0
  146. package/dist/fence/tool.d.ts.map +1 -1
  147. package/dist/fence/tool.js +103 -32
  148. package/dist/hvac-equipment/tool.d.ts.map +1 -1
  149. package/dist/hvac-equipment/tool.js +8 -3
  150. package/dist/index.d.ts +2 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/index.js +6 -0
  153. package/dist/item/definition.d.ts.map +1 -1
  154. package/dist/item/definition.js +7 -1
  155. package/dist/item/floorplan-move.d.ts.map +1 -1
  156. package/dist/item/floorplan-move.js +50 -43
  157. package/dist/item/floorplan.d.ts +1 -0
  158. package/dist/item/floorplan.d.ts.map +1 -1
  159. package/dist/item/floorplan.js +49 -0
  160. package/dist/item/renderer.d.ts +3 -0
  161. package/dist/item/renderer.d.ts.map +1 -1
  162. package/dist/item/renderer.js +96 -8
  163. package/dist/level/definition.d.ts.map +1 -1
  164. package/dist/level/definition.js +7 -3
  165. package/dist/lineset/tool.d.ts.map +1 -1
  166. package/dist/lineset/tool.js +5 -1
  167. package/dist/liquid-line/tool.d.ts.map +1 -1
  168. package/dist/liquid-line/tool.js +7 -1
  169. package/dist/measurement/definition.d.ts.map +1 -1
  170. package/dist/measurement/definition.js +5 -0
  171. package/dist/measurement/floorplan.d.ts.map +1 -1
  172. package/dist/measurement/floorplan.js +11 -10
  173. package/dist/measurement/index.d.ts +1 -1
  174. package/dist/measurement/index.d.ts.map +1 -1
  175. package/dist/measurement/index.js +1 -1
  176. package/dist/measurement/resolve.d.ts +5 -0
  177. package/dist/measurement/resolve.d.ts.map +1 -1
  178. package/dist/measurement/resolve.js +2 -2
  179. package/dist/measurement/surface-query.js +5 -5
  180. package/dist/measurement/tool.d.ts +6 -1
  181. package/dist/measurement/tool.d.ts.map +1 -1
  182. package/dist/measurement/tool.js +7 -11
  183. package/dist/pipe-segment/tool.d.ts.map +1 -1
  184. package/dist/pipe-segment/tool.js +22 -19
  185. package/dist/roof-segment/floorplan-affordances.d.ts.map +1 -1
  186. package/dist/roof-segment/floorplan-affordances.js +14 -8
  187. package/dist/shared/construction-dimension-standards.d.ts +34 -0
  188. package/dist/shared/construction-dimension-standards.d.ts.map +1 -0
  189. package/dist/shared/construction-dimension-standards.js +18 -0
  190. package/dist/shared/construction-length.d.ts +10 -0
  191. package/dist/shared/construction-length.d.ts.map +1 -0
  192. package/dist/shared/construction-length.js +57 -0
  193. package/dist/shared/dimension-string.d.ts +20 -0
  194. package/dist/shared/dimension-string.d.ts.map +1 -0
  195. package/dist/shared/dimension-string.js +19 -0
  196. package/dist/shared/draft-axis-guides.d.ts +36 -0
  197. package/dist/shared/draft-axis-guides.d.ts.map +1 -0
  198. package/dist/shared/draft-axis-guides.js +107 -0
  199. package/dist/shared/move-roof-tool.d.ts.map +1 -1
  200. package/dist/shared/move-roof-tool.js +17 -3
  201. package/dist/shared/opening-documentation-fields.d.ts +16 -0
  202. package/dist/shared/opening-documentation-fields.d.ts.map +1 -0
  203. package/dist/shared/opening-documentation-fields.js +54 -0
  204. package/dist/shared/opening-documentation.d.ts +47 -0
  205. package/dist/shared/opening-documentation.d.ts.map +1 -0
  206. package/dist/shared/opening-documentation.js +300 -0
  207. package/dist/shared/opening-guides-runtime.d.ts.map +1 -1
  208. package/dist/shared/opening-guides-runtime.js +3 -2
  209. package/dist/shared/opening-placement-dimensions.d.ts.map +1 -1
  210. package/dist/shared/opening-placement-dimensions.js +14 -5
  211. package/dist/shared/wall-opening-ceiling.d.ts +30 -0
  212. package/dist/shared/wall-opening-ceiling.d.ts.map +1 -0
  213. package/dist/shared/wall-opening-ceiling.js +29 -0
  214. package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
  215. package/dist/shelf/floorplan-affordances.js +10 -5
  216. package/dist/shelf/floorplan-move.d.ts +2 -16
  217. package/dist/shelf/floorplan-move.d.ts.map +1 -1
  218. package/dist/shelf/floorplan-move.js +19 -40
  219. package/dist/shelf/tool.d.ts.map +1 -1
  220. package/dist/shelf/tool.js +8 -3
  221. package/dist/site/recessed-slab-ground-holes.d.ts.map +1 -1
  222. package/dist/site/recessed-slab-ground-holes.js +1 -4
  223. package/dist/site/renderer.d.ts.map +1 -1
  224. package/dist/site/renderer.js +118 -28
  225. package/dist/site/terrain-drape.d.ts +91 -0
  226. package/dist/site/terrain-drape.d.ts.map +1 -0
  227. package/dist/site/terrain-drape.js +205 -0
  228. package/dist/site/terrain-geometry.d.ts +131 -0
  229. package/dist/site/terrain-geometry.d.ts.map +1 -0
  230. package/dist/site/terrain-geometry.js +259 -0
  231. package/dist/site/terrain-mesh.d.ts +49 -0
  232. package/dist/site/terrain-mesh.d.ts.map +1 -0
  233. package/dist/site/terrain-mesh.js +122 -0
  234. package/dist/site/terrain-renderer.d.ts +28 -0
  235. package/dist/site/terrain-renderer.d.ts.map +1 -0
  236. package/dist/site/terrain-renderer.js +69 -0
  237. package/dist/skylight/definition.d.ts.map +1 -1
  238. package/dist/skylight/definition.js +1 -0
  239. package/dist/skylight/tool.d.ts.map +1 -1
  240. package/dist/skylight/tool.js +16 -1
  241. package/dist/slab/definition.d.ts.map +1 -1
  242. package/dist/slab/definition.js +87 -10
  243. package/dist/slab/elevation-limit.d.ts +43 -0
  244. package/dist/slab/elevation-limit.d.ts.map +1 -0
  245. package/dist/slab/elevation-limit.js +124 -0
  246. package/dist/slab/floorplan-affordances.d.ts +16 -0
  247. package/dist/slab/floorplan-affordances.d.ts.map +1 -1
  248. package/dist/slab/geometry.d.ts.map +1 -1
  249. package/dist/slab/geometry.js +98 -5
  250. package/dist/slab/panel.d.ts.map +1 -1
  251. package/dist/slab/panel.js +82 -4
  252. package/dist/slab/parametrics.d.ts +1 -1
  253. package/dist/slab/parametrics.d.ts.map +1 -1
  254. package/dist/slab/parametrics.js +13 -1
  255. package/dist/slab/placement-ownership.d.ts +3 -0
  256. package/dist/slab/placement-ownership.d.ts.map +1 -0
  257. package/dist/slab/placement-ownership.js +3 -0
  258. package/dist/slab/quick-measurement.d.ts.map +1 -1
  259. package/dist/slab/quick-measurement.js +3 -2
  260. package/dist/slab/system.d.ts.map +1 -1
  261. package/dist/slab/system.js +14 -4
  262. package/dist/slab/tool.d.ts.map +1 -1
  263. package/dist/slab/tool.js +81 -23
  264. package/dist/solar-panel/definition.d.ts.map +1 -1
  265. package/dist/solar-panel/definition.js +1 -0
  266. package/dist/solar-panel/tool.d.ts.map +1 -1
  267. package/dist/solar-panel/tool.js +17 -1
  268. package/dist/spawn/definition.d.ts.map +1 -1
  269. package/dist/spawn/definition.js +1 -0
  270. package/dist/spawn/floorplan-affordances.d.ts.map +1 -1
  271. package/dist/spawn/floorplan-affordances.js +7 -4
  272. package/dist/spawn/floorplan-move.d.ts.map +1 -1
  273. package/dist/spawn/floorplan-move.js +8 -6
  274. package/dist/spawn/renderer.d.ts +4 -0
  275. package/dist/spawn/renderer.d.ts.map +1 -1
  276. package/dist/spawn/renderer.js +32 -18
  277. package/dist/spawn/tool.d.ts +2 -2
  278. package/dist/spawn/tool.d.ts.map +1 -1
  279. package/dist/spawn/tool.js +78 -11
  280. package/dist/stair/definition.d.ts.map +1 -1
  281. package/dist/stair/definition.js +15 -7
  282. package/dist/stair/destination.d.ts +16 -0
  283. package/dist/stair/destination.d.ts.map +1 -0
  284. package/dist/stair/destination.js +24 -0
  285. package/dist/stair/documentation.d.ts +8 -0
  286. package/dist/stair/documentation.d.ts.map +1 -0
  287. package/dist/stair/documentation.js +245 -0
  288. package/dist/stair/floor-stack.d.ts.map +1 -1
  289. package/dist/stair/floor-stack.js +38 -0
  290. package/dist/stair/floorplan-affordances.d.ts.map +1 -1
  291. package/dist/stair/floorplan-affordances.js +28 -9
  292. package/dist/stair/floorplan-move.d.ts +3 -5
  293. package/dist/stair/floorplan-move.d.ts.map +1 -1
  294. package/dist/stair/floorplan-move.js +14 -16
  295. package/dist/stair/floorplan.d.ts.map +1 -1
  296. package/dist/stair/floorplan.js +27 -14
  297. package/dist/stair/panel.d.ts.map +1 -1
  298. package/dist/stair/panel.js +35 -2
  299. package/dist/stair/renderer.d.ts.map +1 -1
  300. package/dist/stair/renderer.js +4 -3
  301. package/dist/structural-grid/coordination.d.ts +16 -0
  302. package/dist/structural-grid/coordination.d.ts.map +1 -0
  303. package/dist/structural-grid/coordination.js +105 -0
  304. package/dist/structural-grid/definition.d.ts +4 -0
  305. package/dist/structural-grid/definition.d.ts.map +1 -0
  306. package/dist/structural-grid/definition.js +51 -0
  307. package/dist/structural-grid/floorplan-tool.d.ts +16 -0
  308. package/dist/structural-grid/floorplan-tool.d.ts.map +1 -0
  309. package/dist/structural-grid/floorplan-tool.js +183 -0
  310. package/dist/structural-grid/floorplan.d.ts +3 -0
  311. package/dist/structural-grid/floorplan.d.ts.map +1 -0
  312. package/dist/structural-grid/floorplan.js +61 -0
  313. package/dist/structural-grid/index.d.ts +2 -0
  314. package/dist/structural-grid/index.d.ts.map +1 -0
  315. package/dist/structural-grid/index.js +1 -0
  316. package/dist/structural-grid/schema.d.ts +2 -0
  317. package/dist/structural-grid/schema.d.ts.map +1 -0
  318. package/dist/structural-grid/schema.js +1 -0
  319. package/dist/wall/construction-dimensions.d.ts +32 -0
  320. package/dist/wall/construction-dimensions.d.ts.map +1 -0
  321. package/dist/wall/construction-dimensions.js +1031 -0
  322. package/dist/wall/contextual-dimensions.d.ts +13 -0
  323. package/dist/wall/contextual-dimensions.d.ts.map +1 -0
  324. package/dist/wall/contextual-dimensions.js +221 -0
  325. package/dist/wall/definition.d.ts.map +1 -1
  326. package/dist/wall/definition.js +20 -1
  327. package/dist/wall/floorplan-affordances.d.ts.map +1 -1
  328. package/dist/wall/floorplan-affordances.js +23 -8
  329. package/dist/wall/floorplan-overrides.d.ts +9 -6
  330. package/dist/wall/floorplan-overrides.d.ts.map +1 -1
  331. package/dist/wall/floorplan-overrides.js +20 -11
  332. package/dist/wall/floorplan.d.ts +12 -3
  333. package/dist/wall/floorplan.d.ts.map +1 -1
  334. package/dist/wall/floorplan.js +121 -79
  335. package/dist/wall/measurement.d.ts.map +1 -1
  336. package/dist/wall/measurement.js +17 -4
  337. package/dist/wall/move-endpoint-tool.d.ts.map +1 -1
  338. package/dist/wall/move-endpoint-tool.js +74 -27
  339. package/dist/wall/move-shared.d.ts.map +1 -1
  340. package/dist/wall/move-shared.js +3 -2
  341. package/dist/wall/move-tool.d.ts.map +1 -1
  342. package/dist/wall/move-tool.js +15 -3
  343. package/dist/wall/paint.d.ts.map +1 -1
  344. package/dist/wall/paint.js +10 -6
  345. package/dist/wall/panel.d.ts.map +1 -1
  346. package/dist/wall/panel.js +32 -5
  347. package/dist/wall/parametrics.d.ts.map +1 -1
  348. package/dist/wall/parametrics.js +2 -0
  349. package/dist/wall/preview.d.ts +8 -0
  350. package/dist/wall/preview.d.ts.map +1 -0
  351. package/dist/wall/preview.js +39 -0
  352. package/dist/wall/quick-measurement.d.ts.map +1 -1
  353. package/dist/wall/quick-measurement.js +3 -2
  354. package/dist/wall/renderer.d.ts.map +1 -1
  355. package/dist/wall/renderer.js +7 -5
  356. package/dist/wall/system.d.ts +1 -1
  357. package/dist/wall/system.d.ts.map +1 -1
  358. package/dist/wall/system.js +37 -2
  359. package/dist/wall/tool.d.ts.map +1 -1
  360. package/dist/wall/tool.js +148 -96
  361. package/dist/wall/treatment-level-data.d.ts +14 -0
  362. package/dist/wall/treatment-level-data.d.ts.map +1 -0
  363. package/dist/wall/treatment-level-data.js +31 -0
  364. package/dist/wall/treatments.d.ts +8 -2
  365. package/dist/wall/treatments.d.ts.map +1 -1
  366. package/dist/wall/treatments.js +58 -15
  367. package/dist/window/contextual-dimensions.d.ts +3 -0
  368. package/dist/window/contextual-dimensions.d.ts.map +1 -0
  369. package/dist/window/contextual-dimensions.js +7 -0
  370. package/dist/window/definition.d.ts.map +1 -1
  371. package/dist/window/definition.js +15 -10
  372. package/dist/window/floorplan-affordances.d.ts +2 -5
  373. package/dist/window/floorplan-affordances.d.ts.map +1 -1
  374. package/dist/window/floorplan-affordances.js +9 -15
  375. package/dist/window/floorplan-move.d.ts.map +1 -1
  376. package/dist/window/floorplan-move.js +1 -1
  377. package/dist/window/floorplan.d.ts.map +1 -1
  378. package/dist/window/floorplan.js +7 -1
  379. package/dist/window/move-tool.d.ts.map +1 -1
  380. package/dist/window/move-tool.js +3 -3
  381. package/dist/window/panel.d.ts.map +1 -1
  382. package/dist/window/panel.js +4 -1
  383. package/dist/window/schema.d.ts +1 -1
  384. package/dist/window/schema.d.ts.map +1 -1
  385. package/dist/window/schema.js +1 -1
  386. package/dist/window/tool.d.ts.map +1 -1
  387. package/dist/window/tool.js +64 -7
  388. package/dist/window/window-math.d.ts +7 -3
  389. package/dist/window/window-math.d.ts.map +1 -1
  390. package/dist/window/window-math.js +8 -3
  391. package/dist/zone/contextual-dimensions.d.ts +3 -0
  392. package/dist/zone/contextual-dimensions.d.ts.map +1 -0
  393. package/dist/zone/contextual-dimensions.js +43 -0
  394. package/dist/zone/definition.d.ts.map +1 -1
  395. package/dist/zone/definition.js +9 -1
  396. package/dist/zone/floorplan-affordances.d.ts +36 -0
  397. package/dist/zone/floorplan-affordances.d.ts.map +1 -1
  398. package/dist/zone/floorplan.d.ts.map +1 -1
  399. package/dist/zone/floorplan.js +59 -3
  400. package/dist/zone/quantities-panel.d.ts.map +1 -1
  401. package/dist/zone/quantities-panel.js +48 -4
  402. package/dist/zone/room-clear-dimensions.d.ts +3 -0
  403. package/dist/zone/room-clear-dimensions.d.ts.map +1 -0
  404. package/dist/zone/room-clear-dimensions.js +406 -0
  405. package/dist/zone/room-documentation.d.ts +11 -0
  406. package/dist/zone/room-documentation.d.ts.map +1 -0
  407. package/dist/zone/room-documentation.js +97 -0
  408. package/package.json +7 -7
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { collectAlignmentAnchors, emitter, ShelfNode, useScene, } from '@pascal-app/core';
3
+ import { collectAlignmentAnchors, emitter, resolveSupportSlabPatch, ShelfNode, useScene, } from '@pascal-app/core';
4
4
  import { getFloorStackPreviewPosition, isAlignmentGuideActive, isGridSnapActive, isMagneticSnapActive, movementSfxStepKey, triggerSFX, useAlignmentGuides, useEditor, } from '@pascal-app/editor';
5
5
  import { useViewer } from '@pascal-app/viewer';
6
6
  import { useEffect, useMemo, useRef, useState } from 'react';
@@ -92,9 +92,14 @@ const ShelfTool = () => {
92
92
  name: 'Shelf',
93
93
  position,
94
94
  rotation: [0, 0, 0],
95
+ parentId: activeLevelId,
95
96
  });
96
- useScene.getState().createNode(shelf, activeLevelId);
97
- useViewer.getState().setSelection({ selectedIds: [shelf.id] });
97
+ const committedShelf = ShelfNode.parse({
98
+ ...shelf,
99
+ ...resolveSupportSlabPatch(shelf, useScene.getState().nodes),
100
+ });
101
+ useScene.getState().createNode(committedShelf, activeLevelId);
102
+ useViewer.getState().setSelection({ selectedIds: [committedShelf.id] });
98
103
  triggerSFX('sfx:item-place');
99
104
  useAlignmentGuides.getState().clear();
100
105
  if (useEditor.getState().getContinuation('point') === 'repeat') {
@@ -1 +1 @@
1
- {"version":3,"file":"recessed-slab-ground-holes.d.ts","sourceRoot":"","sources":["../../src/site/recessed-slab-ground-holes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CA8ChC"}
1
+ {"version":3,"file":"recessed-slab-ground-holes.d.ts","sourceRoot":"","sources":["../../src/site/recessed-slab-ground-holes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CA2ChC"}
@@ -25,10 +25,7 @@ export function getRecessedSlabGroundHoles(nodes) {
25
25
  pushByLevel(slabsByLevel, levelId, node);
26
26
  }
27
27
  return nodeList
28
- .filter((node) => node.type === 'slab' &&
29
- node.visible &&
30
- node.polygon.length >= 3 &&
31
- (node.elevation ?? 0.05) < 0)
28
+ .filter((node) => node.type === 'slab' && node.visible && node.polygon.length >= 3 && node.recessed === true)
32
29
  .filter((slab) => {
33
30
  if (!Number.isFinite(lowestLevelIndex))
34
31
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/site/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AAkGzB,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,uCAoMxD,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/site/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,QAAQ,EAOd,MAAM,kBAAkB,CAAA;AAoHzB,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,uCAmRxD,CAAA;AAED,eAAe,YAAY,CAAA"}
@@ -1,38 +1,58 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useLiveNodeOverrides, useRegistry, useScene, } from '@pascal-app/core';
3
+ import { terrainFieldOf, useLiveNodeOverrides, useLiveTerrain, useRegistry, useScene, } from '@pascal-app/core';
4
4
  import { backdropGradient, deepSkyColor, getSceneTheme, horizonHazeColor, NodeRenderer, unionPolygons, useNodeEvents, useViewer, } from '@pascal-app/viewer';
5
5
  import { useEffect, useMemo, useRef } from 'react';
6
- import { BufferGeometry, Float32BufferAttribute, Path, Shape, ShapeGeometry, } from 'three';
6
+ import { BufferAttribute, BufferGeometry, Path, Shape, ShapeGeometry } from 'three';
7
7
  import { cameraPosition, color, float, mix, positionWorld, smoothstep, vec2 } from 'three/tsl';
8
8
  import { MeshLambertNodeMaterial } from 'three/webgpu';
9
9
  import { getRecessedSlabGroundHoles } from './recessed-slab-ground-holes';
10
+ import { buildDrapedPolyline, terrainGridKey, updateDrapedHeights, } from './terrain-drape';
11
+ import { HORIZON_PLANE_Y, terrainFootprint } from './terrain-geometry';
12
+ import { TerrainRenderer } from './terrain-renderer';
10
13
  const Y_OFFSET = 0.01;
11
14
  // The horizon disc is presentation-only — clicks must fall through to the
12
15
  // real site polygon / grid, so its raycast is a no-op.
13
16
  const noopRaycast = () => { };
14
17
  /**
15
- * Creates simple line geometry for site boundary
16
- * Single horizontal line at ground level
18
+ * The site boundary line, laid on the ground rather than across it.
19
+ *
20
+ * On flat ground this is the ring it always was: the polygon's own vertices at
21
+ * `Y_OFFSET`, closed. On sculpted ground `buildDrapedPolyline` subdivides each
22
+ * edge at the mesh's creases so the line lies *on* the rendered surface — the lot
23
+ * line is on screen in every frame, and a flat ring slicing through a hill is the
24
+ * most conspicuous way sculpted ground reads as broken.
25
+ *
26
+ * UVs keep their `(u, 0)` layout, now measured as normalized XZ arc length rather
27
+ * than vertex index, so a dashed or gradient material spaces evenly along the
28
+ * perimeter instead of bunching on short edges.
17
29
  */
18
- const createBoundaryLineGeometry = (points) => {
30
+ const createBoundaryLineGeometry = (points, field) => {
19
31
  const geometry = new BufferGeometry();
20
- if (points.length < 2)
21
- return geometry;
22
- const positions = [];
23
- const uvs = [];
24
- // Create a simple line loop at ground level
25
- points.forEach(([x, z], index) => {
26
- positions.push(x ?? 0, Y_OFFSET, z ?? 0);
27
- uvs.push(points.length > 1 ? index / (points.length - 1) : 0, 0);
28
- });
29
- // Close the loop
30
- positions.push(points[0]?.[0] ?? 0, Y_OFFSET, points[0]?.[1] ?? 0);
31
- uvs.push(1, 0);
32
- geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
33
- geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
34
- return geometry;
32
+ const ring = buildDrapedPolyline({ points, field, lift: Y_OFFSET, closed: true });
33
+ const uvs = new Float32Array(ring.uvs.length * 2);
34
+ for (let index = 0; index < ring.uvs.length; index++) {
35
+ uvs[index * 2] = ring.uvs[index] ?? 0;
36
+ }
37
+ // `BufferAttribute`, not `Float32BufferAttribute`: the latter copies its input
38
+ // into a fresh array, which would leave `ring.positions` a detached CPU copy and
39
+ // silently break the in-place dab rewrite below. This shares the buffer.
40
+ geometry.setAttribute('position', new BufferAttribute(ring.positions, 3));
41
+ geometry.setAttribute('uv', new BufferAttribute(uvs, 2));
42
+ return { geometry, ring };
35
43
  };
44
+ /**
45
+ * Re-upload a draped ring's positions after an in-place height rewrite.
46
+ *
47
+ * No `addUpdateRange`: a dab can move any subset of the ring's vertices (the brush
48
+ * is a disc, the ring is a loop, and the two intersect in up to four arcs), so a
49
+ * range would either be the whole buffer or a list. The whole buffer is a few KB.
50
+ */
51
+ function markPositionsDirty(geometry) {
52
+ const attribute = geometry.getAttribute('position');
53
+ attribute.needsUpdate = true;
54
+ geometry.computeBoundingSphere();
55
+ }
36
56
  function polygonsMatch(a, b) {
37
57
  return (a.length === b.length &&
38
58
  a.every((polygon, polygonIndex) => polygon.length === b[polygonIndex]?.length &&
@@ -66,6 +86,18 @@ export const SiteRenderer = ({ node }) => {
66
86
  const maxLightIntensity = useViewer((state) => Math.max(1, ...getSceneTheme(state.sceneTheme).lights.map((light) => light.intensity)));
67
87
  const livePolygon = useLiveNodeOverrides((state) => state.overrides.get(node.id)?.polygon ?? null);
68
88
  const polygonPoints = livePolygon?.points ?? node.polygon?.points;
89
+ // Persisted terrain only. A stroke is applied imperatively further down, for the
90
+ // same reason `TerrainRenderer` does it: a dab must not re-render this subtree.
91
+ const persistedField = useMemo(() => terrainFieldOf({ id: node.id, terrain: node.terrain }), [node.id, node.terrain]);
92
+ // The terrain *grid* — mounting, resizing, or re-origining it. `hasTerrain`
93
+ // catches the first dab on a site that had no terrain at all, which is the one
94
+ // case where a stroke legitimately changes the grid and everything keyed on it
95
+ // (the horizon punch, the boundary subdivision) has to rebuild.
96
+ const hasTerrain = useLiveTerrain((state) => state.strokes.has(node.id));
97
+ const terrainGrid = hasTerrain
98
+ ? (useLiveTerrain.getState().fieldOf(node.id) ?? persistedField)
99
+ : persistedField;
100
+ const terrainKey = terrainGridKey(terrainGrid);
69
101
  // Centroid + radius of the lot polygon, for the presentation fade below.
70
102
  const fadeBounds = useMemo(() => {
71
103
  if (!polygonPoints || polygonPoints.length < 3)
@@ -158,6 +190,10 @@ export const SiteRenderer = ({ node }) => {
158
190
  addSlabHoles(shape, slabPolygons);
159
191
  return shape;
160
192
  }, [polygonPoints, slabPolygons]);
193
+ // The terrain footprint is punched out alongside the recessed slabs, and for the
194
+ // same reason: the disc must not cap ground that is modelled below it.
195
+ //
196
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `terrainKey` is the grid signature the footprint is a function of; depending on the field itself would rebuild an 800 m disc every dab.
161
197
  const horizonGeometry = useMemo(() => {
162
198
  if (!fadeBounds)
163
199
  return null;
@@ -170,17 +206,65 @@ export const SiteRenderer = ({ node }) => {
170
206
  shape.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
171
207
  }
172
208
  shape.closePath();
173
- addSlabHoles(shape, slabPolygons, fadeBounds.cx, fadeBounds.cz);
209
+ const holes = terrainGrid ? [...slabPolygons, terrainFootprint(terrainGrid)] : slabPolygons;
210
+ addSlabHoles(shape, holes, fadeBounds.cx, fadeBounds.cz);
174
211
  return new ShapeGeometry(shape);
175
- }, [fadeBounds, slabPolygons]);
212
+ }, [fadeBounds, slabPolygons, terrainKey]);
176
213
  useEffect(() => () => horizonGeometry?.dispose(), [horizonGeometry]);
177
- // Create boundary line geometry
178
- const lineGeometry = useMemo(() => {
214
+ // Boundary line geometry, subdivided against the terrain grid when there is one.
215
+ //
216
+ // Keyed on `terrainKey`, not on the field: the crease set depends on the grid
217
+ // (origin/spacing/extent) and not on the heights, so a sculpt stroke — which
218
+ // produces a new field object every dab — must not rebuild this. Heights are
219
+ // rewritten in place by the subscription below.
220
+ //
221
+ // biome-ignore lint/correctness/useExhaustiveDependencies: keyed on the grid, not the field — see above.
222
+ const boundary = useMemo(() => {
179
223
  if (!polygonPoints || polygonPoints.length < 2)
180
224
  return null;
181
- return createBoundaryLineGeometry(polygonPoints);
182
- }, [polygonPoints]);
183
- useEffect(() => () => lineGeometry?.dispose(), [lineGeometry]);
225
+ // The live field, not the persisted one: mounting mid-stroke (the very first
226
+ // dab on a terrain-free site remounts this subtree) must drape against the
227
+ // ground actually on screen.
228
+ const field = useLiveTerrain.getState().fieldOf(node.id) ?? persistedField;
229
+ return createBoundaryLineGeometry(polygonPoints, field);
230
+ }, [polygonPoints, terrainKey, node.id]);
231
+ useEffect(() => () => boundary?.geometry.dispose(), [boundary]);
232
+ const lineGeometry = boundary?.geometry ?? null;
233
+ // Per-dab height rewrite, imperative for the same reason `TerrainRenderer`'s
234
+ // upload is: a stroke pushes dozens of patches a second, and routing each through
235
+ // a React render would rebuild the ring's buffers at pointer rate. The XZ of every
236
+ // vertex is a function of the grid alone, so a dab only ever moves Y.
237
+ //
238
+ // A ref, not the memo value in a dependency, so the subscription outlives a
239
+ // polygon edit without resubscribing.
240
+ const boundaryRef = useRef(null);
241
+ boundaryRef.current = boundary;
242
+ useEffect(() => {
243
+ let lastPatch = useLiveTerrain.getState().strokeOf(node.id)?.lastPatch ?? null;
244
+ return useLiveTerrain.subscribe((state) => {
245
+ const current = boundaryRef.current;
246
+ if (!current)
247
+ return;
248
+ const stroke = state.strokes.get(node.id);
249
+ if (!stroke) {
250
+ // The stroke ended. Its heights are now the node's, and the commit
251
+ // re-rendered this subtree with them — but a *cancelled* stroke (Escape)
252
+ // commits nothing, so the ring has to be put back explicitly.
253
+ if (lastPatch) {
254
+ lastPatch = null;
255
+ const field = terrainFieldOf({ id: node.id, terrain: node.terrain });
256
+ updateDrapedHeights(current.ring, field, Y_OFFSET);
257
+ markPositionsDirty(current.geometry);
258
+ }
259
+ return;
260
+ }
261
+ if (!stroke.lastPatch || stroke.lastPatch === lastPatch)
262
+ return;
263
+ lastPatch = stroke.lastPatch;
264
+ updateDrapedHeights(current.ring, stroke.field, Y_OFFSET);
265
+ markPositionsDirty(current.geometry);
266
+ });
267
+ }, [node.id, node.terrain]);
184
268
  const groundGeometry = useMemo(() => {
185
269
  if (!groundShape)
186
270
  return null;
@@ -188,9 +272,15 @@ export const SiteRenderer = ({ node }) => {
188
272
  }, [groundShape]);
189
273
  useEffect(() => () => groundGeometry?.dispose(), [groundGeometry]);
190
274
  const handlers = useNodeEvents(node, 'site');
275
+ // Terrain replaces the flat ground fill rather than sitting on top of it: two
276
+ // coplanar ground surfaces z-fight, and the flat one would poke through any
277
+ // excavation. `hasTerrain` (above) is subscribed at the site level rather than
278
+ // inside TerrainRenderer so a stroke that starts on a site with no persisted
279
+ // terrain still mounts the mesh.
280
+ const showTerrain = terrainGrid !== null;
191
281
  if (!(node && lineGeometry)) {
192
282
  return null;
193
283
  }
194
- return (_jsxs("group", { ref: ref, ...handlers, children: [(node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))), groundGeometry && (_jsx("mesh", { geometry: groundGeometry, material: groundMaterial, position: [0, -0.05, 0], receiveShadow: true, rotation: [-Math.PI / 2, 0, 0] })), horizonGeometry && horizonMaterial && fadeBounds && (_jsx("mesh", { geometry: horizonGeometry, material: horizonMaterial, position: [fadeBounds.cx, -0.07, fadeBounds.cz], raycast: noopRaycast, receiveShadow: true, rotation: [-Math.PI / 2, 0, 0] })), _jsx("line", { frustumCulled: false, geometry: lineGeometry, renderOrder: 9, children: _jsx("lineBasicMaterial", { color: "#f59e0b", linewidth: 2, opacity: 0.6, transparent: true }) })] }));
284
+ return (_jsxs("group", { ref: ref, ...handlers, children: [(node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))), showTerrain && _jsx(TerrainRenderer, { material: groundMaterial, site: node }), groundGeometry && !showTerrain && (_jsx("mesh", { geometry: groundGeometry, material: groundMaterial, position: [0, -0.05, 0], receiveShadow: true, rotation: [-Math.PI / 2, 0, 0] })), horizonGeometry && horizonMaterial && fadeBounds && (_jsx("mesh", { geometry: horizonGeometry, material: horizonMaterial, position: [fadeBounds.cx, HORIZON_PLANE_Y, fadeBounds.cz], raycast: noopRaycast, receiveShadow: true, rotation: [-Math.PI / 2, 0, 0], userData: { pascalExport: 'strip' } })), _jsx("line", { frustumCulled: false, geometry: lineGeometry, renderOrder: 9, children: _jsx("lineBasicMaterial", { color: "#f59e0b", linewidth: 2, opacity: 0.6, transparent: true }) })] }));
195
285
  };
196
286
  export default SiteRenderer;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Laying a polyline *on* the terrain surface, exactly.
3
+ *
4
+ * The site boundary is the first consumer: a ring of four flat vertices at
5
+ * `y = 0.01` slices straight through any hill it crosses and floats over any
6
+ * excavation, which is the single most obvious way sculpted ground looks broken —
7
+ * the lot line is the one element always on screen.
8
+ *
9
+ * The approach is exactness rather than density. The rendered surface is
10
+ * piecewise-**planar**: `buildTerrainMesh` emits two triangles per cell, so the
11
+ * only places it creases are the grid lines and each cell's diagonal. Subdivide a
12
+ * segment at exactly those crossings and every resulting sub-segment lies inside
13
+ * one triangle, where the surface is a plane and a straight line on it is on it
14
+ * everywhere. Uniform oversampling can only approach that, and needs an arbitrary
15
+ * density to argue about.
16
+ *
17
+ * In field index space (`u = (x - originX) / spacing`, `v = (z - originZ) /
18
+ * spacing`) the three crease families are just `u ∈ ℤ`, `v ∈ ℤ`, and `u + v ∈ ℤ` —
19
+ * the last being the diagonals, since a cell's diagonal runs from `(c+1, r)` to
20
+ * `(c, r+1)`. All three are affine in the segment parameter, so each crossing is
21
+ * one division.
22
+ *
23
+ * Heights come from `surfaceHeightAt`, the explicit rendered-surface alias for
24
+ * the shared `heightAt` triangle-plane authority.
25
+ *
26
+ * Nothing here touches Three.js: it is array math over a field, so the guarantee
27
+ * that a sub-segment lies inside one triangle is asserted in unit tests rather
28
+ * than eyeballed on a canvas.
29
+ */
30
+ import { type TerrainField } from '@pascal-app/core';
31
+ /** A draped polyline: xyz per vertex, plus normalized arc length per vertex. */
32
+ export type DrapedPolyline = {
33
+ /** xyz per vertex, length `count * 3`. */
34
+ positions: Float32Array;
35
+ /** Normalized distance along the line per vertex, length `count`. */
36
+ uvs: Float32Array;
37
+ };
38
+ /**
39
+ * Parameters in `(0, 1)` where the segment crosses a crease of the rendered
40
+ * surface, sorted and deduplicated.
41
+ *
42
+ * Exported for tests: the crease convention is the load-bearing part, and
43
+ * asserting it on the parameter list is far sharper than inferring it from
44
+ * vertex positions.
45
+ */
46
+ export declare function creaseCrossings(field: TerrainField, ax: number, az: number, bx: number, bz: number): number[];
47
+ /**
48
+ * Build a draped polyline through `points`.
49
+ *
50
+ * `field` of `null` means flat ground at the datum: the result is the input
51
+ * points at `y = lift`, no subdivision. Keeping that case *here* rather than at
52
+ * every call site is what lets a consumer hold one geometry and one code path
53
+ * whether or not the site has terrain — and it means a scene that never touched
54
+ * terrain builds byte-identical buffers to the ones it built before draping
55
+ * existed.
56
+ *
57
+ * `closed` repeats the first point at the end, so a `LineStrip` closes the ring.
58
+ */
59
+ export declare function buildDrapedPolyline({ points, field, lift, closed, }: {
60
+ points: ReadonlyArray<readonly [number, number]>;
61
+ field: TerrainField | null;
62
+ lift: number;
63
+ closed: boolean;
64
+ }): DrapedPolyline;
65
+ /**
66
+ * Rewrite only the heights of an existing draped polyline, in place.
67
+ *
68
+ * This is the sculpt-stroke path. The crease set is a function of the field's
69
+ * *grid* — origin, spacing, sample counts — and not of its heights, so every dab
70
+ * of a stroke leaves the XZ of every vertex exactly where it was. Rebuilding the
71
+ * ring per dab would reallocate two buffers and a `BufferGeometry` at pointer
72
+ * rate for a result that differs only in Y.
73
+ *
74
+ * Reads each vertex's own XZ back out of the buffer rather than taking the source
75
+ * polygon again: there is then no second copy of the subdivision to keep in step.
76
+ *
77
+ * Arc lengths are deliberately *not* refreshed — see `writeArcLengths`.
78
+ */
79
+ export declare function updateDrapedHeights(ring: DrapedPolyline, field: TerrainField | null, lift: number): void;
80
+ /**
81
+ * Everything about a field that changes the crease set, and nothing that doesn't.
82
+ *
83
+ * A React consumer needs to rebuild a draped ring when the *grid* moves and only
84
+ * rewrite heights when the *heights* move. Keying a memo on the field object would
85
+ * rebuild every dab (each `applyHeightPatch` returns a new field); keying it on
86
+ * this rebuilds only when a resize or re-origin actually invalidates the
87
+ * subdivision, which in practice means import. `null` for no terrain, so the
88
+ * flat-ground ring is one more value in the same key space.
89
+ */
90
+ export declare function terrainGridKey(field: TerrainField | null): string | null;
91
+ //# sourceMappingURL=terrain-drape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-drape.d.ts","sourceRoot":"","sources":["../../src/site/terrain-drape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAErE,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG;IAC3B,0CAA0C;IAC1C,SAAS,EAAE,YAAY,CAAA;IACvB,qEAAqE;IACrE,GAAG,EAAE,YAAY,CAAA;CAClB,CAAA;AAcD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EACnB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT,MAAM,EAAE,CAuBV;AA+BD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,KAAK,EACL,IAAI,EACJ,MAAM,GACP,EAAE;IACD,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAChD,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB,GAAG,cAAc,CA+BjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,YAAY,GAAG,IAAI,EAC1B,IAAI,EAAE,MAAM,GACX,IAAI,CAEN;AAmCD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAGxE"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Laying a polyline *on* the terrain surface, exactly.
3
+ *
4
+ * The site boundary is the first consumer: a ring of four flat vertices at
5
+ * `y = 0.01` slices straight through any hill it crosses and floats over any
6
+ * excavation, which is the single most obvious way sculpted ground looks broken —
7
+ * the lot line is the one element always on screen.
8
+ *
9
+ * The approach is exactness rather than density. The rendered surface is
10
+ * piecewise-**planar**: `buildTerrainMesh` emits two triangles per cell, so the
11
+ * only places it creases are the grid lines and each cell's diagonal. Subdivide a
12
+ * segment at exactly those crossings and every resulting sub-segment lies inside
13
+ * one triangle, where the surface is a plane and a straight line on it is on it
14
+ * everywhere. Uniform oversampling can only approach that, and needs an arbitrary
15
+ * density to argue about.
16
+ *
17
+ * In field index space (`u = (x - originX) / spacing`, `v = (z - originZ) /
18
+ * spacing`) the three crease families are just `u ∈ ℤ`, `v ∈ ℤ`, and `u + v ∈ ℤ` —
19
+ * the last being the diagonals, since a cell's diagonal runs from `(c+1, r)` to
20
+ * `(c, r+1)`. All three are affine in the segment parameter, so each crossing is
21
+ * one division.
22
+ *
23
+ * Heights come from `surfaceHeightAt`, the explicit rendered-surface alias for
24
+ * the shared `heightAt` triangle-plane authority.
25
+ *
26
+ * Nothing here touches Three.js: it is array math over a field, so the guarantee
27
+ * that a sub-segment lies inside one triangle is asserted in unit tests rather
28
+ * than eyeballed on a canvas.
29
+ */
30
+ import { surfaceHeightAt } from '@pascal-app/core';
31
+ /**
32
+ * Crossings per segment, as a backstop only.
33
+ *
34
+ * A real segment produces at most ~3 × the field's sample count along it — a few
35
+ * hundred at 257². The cap exists so a caller that passes a segment spanning a
36
+ * degenerate field (tiny `spacing`, huge extent) cannot allocate unboundedly.
37
+ */
38
+ const MAX_CROSSINGS_PER_SEGMENT = 4096;
39
+ /** Two crossings closer than this (in segment parameter) are the same vertex. */
40
+ const PARAM_EPSILON = 1e-9;
41
+ /**
42
+ * Parameters in `(0, 1)` where the segment crosses a crease of the rendered
43
+ * surface, sorted and deduplicated.
44
+ *
45
+ * Exported for tests: the crease convention is the load-bearing part, and
46
+ * asserting it on the parameter list is far sharper than inferring it from
47
+ * vertex positions.
48
+ */
49
+ export function creaseCrossings(field, ax, az, bx, bz) {
50
+ const u0 = (ax - field.origin[0]) / field.spacing;
51
+ const v0 = (az - field.origin[1]) / field.spacing;
52
+ const u1 = (bx - field.origin[0]) / field.spacing;
53
+ const v1 = (bz - field.origin[1]) / field.spacing;
54
+ const crossings = [];
55
+ // Families are clamped to the index range the surface actually creases over.
56
+ // Outside the field the surface is the border cell's plane extruded outward, so
57
+ // crossings out there would add vertices to a stretch that is already flat —
58
+ // harmless geometrically, but an edge far from a fine grid would emit thousands.
59
+ addFamily(crossings, u0, u1, 0, field.cols - 1);
60
+ addFamily(crossings, v0, v1, 0, field.rows - 1);
61
+ addFamily(crossings, u0 + v0, u1 + v1, 0, field.cols - 1 + (field.rows - 1));
62
+ crossings.sort((a, b) => a - b);
63
+ const unique = [];
64
+ for (const t of crossings) {
65
+ const previous = unique.at(-1);
66
+ if (previous !== undefined && t - previous <= PARAM_EPSILON)
67
+ continue;
68
+ unique.push(t);
69
+ }
70
+ return unique;
71
+ }
72
+ /**
73
+ * Push the parameters where an affine coordinate `from → to` passes an integer in
74
+ * `[loBound, hiBound]`.
75
+ */
76
+ function addFamily(out, from, to, loBound, hiBound) {
77
+ const delta = to - from;
78
+ // Parallel to this family: no crossing, whether or not it lies *on* a crease.
79
+ // A segment running along a grid line is already inside the planes on both
80
+ // sides, so it needs no extra vertex.
81
+ if (Math.abs(delta) < 1e-12)
82
+ return;
83
+ const lo = Math.max(loBound, Math.ceil(Math.min(from, to)));
84
+ const hi = Math.min(hiBound, Math.floor(Math.max(from, to)));
85
+ let emitted = 0;
86
+ for (let k = lo; k <= hi; k++) {
87
+ const t = (k - from) / delta;
88
+ if (t <= PARAM_EPSILON || t >= 1 - PARAM_EPSILON)
89
+ continue;
90
+ out.push(t);
91
+ emitted += 1;
92
+ if (emitted >= MAX_CROSSINGS_PER_SEGMENT)
93
+ return;
94
+ }
95
+ }
96
+ /**
97
+ * Build a draped polyline through `points`.
98
+ *
99
+ * `field` of `null` means flat ground at the datum: the result is the input
100
+ * points at `y = lift`, no subdivision. Keeping that case *here* rather than at
101
+ * every call site is what lets a consumer hold one geometry and one code path
102
+ * whether or not the site has terrain — and it means a scene that never touched
103
+ * terrain builds byte-identical buffers to the ones it built before draping
104
+ * existed.
105
+ *
106
+ * `closed` repeats the first point at the end, so a `LineStrip` closes the ring.
107
+ */
108
+ export function buildDrapedPolyline({ points, field, lift, closed, }) {
109
+ const xz = [];
110
+ const segments = closed ? points.length : points.length - 1;
111
+ for (let index = 0; index < segments; index++) {
112
+ const from = points[index];
113
+ const to = points[(index + 1) % points.length];
114
+ if (!(from && to))
115
+ continue;
116
+ const [ax, az] = from;
117
+ const [bx, bz] = to;
118
+ xz.push([ax, az]);
119
+ if (!field)
120
+ continue;
121
+ for (const t of creaseCrossings(field, ax, az, bx, bz)) {
122
+ xz.push([ax + (bx - ax) * t, az + (bz - az) * t]);
123
+ }
124
+ }
125
+ // The final vertex: the ring's start again when closed, otherwise the last
126
+ // point, which the segment loop above never emits.
127
+ const last = closed ? points[0] : points.at(-1);
128
+ if (last)
129
+ xz.push([last[0], last[1]]);
130
+ const positions = new Float32Array(xz.length * 3);
131
+ for (let index = 0; index < xz.length; index++) {
132
+ const [x, z] = xz[index];
133
+ positions[index * 3] = x;
134
+ positions[index * 3 + 2] = z;
135
+ }
136
+ const ring = { positions, uvs: new Float32Array(xz.length) };
137
+ writeDrapedHeights(ring, field, lift);
138
+ writeArcLengths(ring);
139
+ return ring;
140
+ }
141
+ /**
142
+ * Rewrite only the heights of an existing draped polyline, in place.
143
+ *
144
+ * This is the sculpt-stroke path. The crease set is a function of the field's
145
+ * *grid* — origin, spacing, sample counts — and not of its heights, so every dab
146
+ * of a stroke leaves the XZ of every vertex exactly where it was. Rebuilding the
147
+ * ring per dab would reallocate two buffers and a `BufferGeometry` at pointer
148
+ * rate for a result that differs only in Y.
149
+ *
150
+ * Reads each vertex's own XZ back out of the buffer rather than taking the source
151
+ * polygon again: there is then no second copy of the subdivision to keep in step.
152
+ *
153
+ * Arc lengths are deliberately *not* refreshed — see `writeArcLengths`.
154
+ */
155
+ export function updateDrapedHeights(ring, field, lift) {
156
+ writeDrapedHeights(ring, field, lift);
157
+ }
158
+ function writeDrapedHeights(ring, field, lift) {
159
+ const count = ring.uvs.length;
160
+ for (let index = 0; index < count; index++) {
161
+ const x = ring.positions[index * 3] ?? 0;
162
+ const z = ring.positions[index * 3 + 2] ?? 0;
163
+ ring.positions[index * 3 + 1] = field ? surfaceHeightAt(field, x, z) + lift : lift;
164
+ }
165
+ }
166
+ /**
167
+ * Normalized cumulative distance along the line, measured in **XZ**.
168
+ *
169
+ * Horizontal, not 3D: the value has to be stable while the ground moves under a
170
+ * stroke, and a 3D arc length is not — it would stretch as a hill rises, sliding
171
+ * any dash pattern along the ring dab by dab. XZ length is what a plan-view
172
+ * reading of the lot line uses anyway, so the 2D and 3D views agree on it.
173
+ */
174
+ function writeArcLengths(ring) {
175
+ const count = ring.uvs.length;
176
+ if (count === 0)
177
+ return;
178
+ let total = 0;
179
+ for (let index = 1; index < count; index++) {
180
+ const dx = (ring.positions[index * 3] ?? 0) - (ring.positions[(index - 1) * 3] ?? 0);
181
+ const dz = (ring.positions[index * 3 + 2] ?? 0) - (ring.positions[(index - 1) * 3 + 2] ?? 0);
182
+ total += Math.hypot(dx, dz);
183
+ ring.uvs[index] = total;
184
+ }
185
+ if (total <= 0)
186
+ return;
187
+ for (let index = 1; index < count; index++) {
188
+ ring.uvs[index] = (ring.uvs[index] ?? 0) / total;
189
+ }
190
+ }
191
+ /**
192
+ * Everything about a field that changes the crease set, and nothing that doesn't.
193
+ *
194
+ * A React consumer needs to rebuild a draped ring when the *grid* moves and only
195
+ * rewrite heights when the *heights* move. Keying a memo on the field object would
196
+ * rebuild every dab (each `applyHeightPatch` returns a new field); keying it on
197
+ * this rebuilds only when a resize or re-origin actually invalidates the
198
+ * subdivision, which in practice means import. `null` for no terrain, so the
199
+ * flat-ground ring is one more value in the same key space.
200
+ */
201
+ export function terrainGridKey(field) {
202
+ if (!field)
203
+ return null;
204
+ return `${field.origin[0]}:${field.origin[1]}:${field.spacing}:${field.cols}:${field.rows}`;
205
+ }