@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
@@ -0,0 +1,131 @@
1
+ import { type HeightPatch, type TerrainField } from '@pascal-app/core';
2
+ /**
3
+ * Builds the terrain mesh buffers from a `TerrainField`.
4
+ *
5
+ * Split out of the renderer as pure array math so the vertex layout is
6
+ * unit-testable without a canvas, and so the partial-upload path has one
7
+ * definition. The renderer owns the `BufferGeometry`; this owns what goes in it.
8
+ *
9
+ * The triangulation this file emits is the convention `surfaceHeightAt`
10
+ * (`core/lib/terrain-field.ts`) reads back: two triangles per cell split along the
11
+ * `(c+1,r)`–`(c,r+1)` diagonal. Anything drawn *on* the ground samples that
12
+ * function, so changing the winding below without changing it there would make
13
+ * every draped line sink into half of every cell.
14
+ *
15
+ * Two rules that are easy to get wrong and expensive to debug:
16
+ *
17
+ * - **Normals are analytic, never `computeVertexNormals()`.** Beyond the cost
18
+ * (~1.5 ms at 257²), `computeVertexNormals()` rewrites the *whole* normal
19
+ * attribute, which silently defeats the dirty-rect partial upload that makes
20
+ * sculpting cheap. `normalAt` central-differences the field instead, so a
21
+ * patch touches only the rows it changed.
22
+ * - **Vertices are one-per-sample and shared between triangles**, so a partial
23
+ * upload is a contiguous row range. Duplicating verts per-triangle for flat
24
+ * shading would triple memory and scatter the dirty range.
25
+ */
26
+ /**
27
+ * Y of the site's presentation horizon disc (`site/renderer.tsx`).
28
+ *
29
+ * It lives here, next to the skirt, because the skirt is the thing that has to
30
+ * bracket it: its top edge never sits below this plane and its base always clears
31
+ * it, so no sightline can pass between the sculpted ground and the surrounding
32
+ * one. Two readers, one number.
33
+ */
34
+ export declare const HORIZON_PLANE_Y = -0.07;
35
+ /** One vertex per sample, row-major — index `r * cols + c` matches the field. */
36
+ export type TerrainMeshBuffers = {
37
+ /** xyz per sample, length `cols * rows * 3`. */
38
+ positions: Float32Array;
39
+ /** xyz per sample, length `cols * rows * 3`. */
40
+ normals: Float32Array;
41
+ /** uv per sample, length `cols * rows * 2`. */
42
+ uvs: Float32Array;
43
+ /** Triangle indices, two per grid cell. */
44
+ indices: Uint32Array;
45
+ };
46
+ export declare function buildTerrainMesh(field: TerrainField): TerrainMeshBuffers;
47
+ /**
48
+ * A vertical curtain around the field boundary, closing the terrain against the
49
+ * site's horizon disc.
50
+ *
51
+ * The disc is a flat plate at `HORIZON_PLANE_Y` standing in for ground beyond the
52
+ * field, and the field's extent is punched out of it so an excavation is not simply
53
+ * hidden under it. That punch leaves an open rim, and sculpted ground turns it into
54
+ * a visible fault: a raised edge shows daylight under the terrain shell, a lowered
55
+ * one shows the disc's cut edge hanging in the air over the pit.
56
+ *
57
+ * The curtain spans `[min(h, H) - SKIRT_DROP, max(h, H)]` at every boundary
58
+ * sample, so it *brackets* both the terrain edge and the horizon plane no matter
59
+ * which is higher. That is the whole correctness argument: a sightline through the
60
+ * rim has to cross the curtain, because the curtain covers the rim's entire Y
61
+ * range at the rim's exact XZ.
62
+ *
63
+ * One vertex pair (top, bottom) per boundary sample, in a ring whose first sample
64
+ * is repeated at the end so the strip closes.
65
+ */
66
+ export type TerrainSkirtBuffers = {
67
+ /** xyz, `(perimeter + 1) * 2` vertices — top then bottom per ring position. */
68
+ positions: Float32Array;
69
+ normals: Float32Array;
70
+ indices: Uint32Array;
71
+ };
72
+ /**
73
+ * The field's own footprint, as a polygon to punch out of the horizon disc.
74
+ *
75
+ * The punch is the only reason an excavation is visible at all: without it the disc
76
+ * — a flat plate at `HORIZON_PLANE_Y` standing in for ground beyond the field —
77
+ * roofs over every pit, so digging a basement reads as no change whatsoever. On
78
+ * raised or flat ground it costs nothing visually, because the terrain shell and its
79
+ * skirt cover the hole from both sides.
80
+ *
81
+ * Inset by half a cell, so the disc and the terrain edge *overlap* rather than meet:
82
+ * two coplanar edges at exactly the same XZ z-fight along the whole perimeter. The
83
+ * inset is what the skirt's bracketing span is covering, which is why this lives
84
+ * beside `buildTerrainSkirt` — the two have to agree about where the rim is, and a
85
+ * footprint that punched wider than the skirt spans would open a gap the skirt never
86
+ * closes.
87
+ *
88
+ * Kept here rather than in the renderer so it is reachable without Three.js: it
89
+ * decides whether a pit is visible, and that is a property to assert, not to eyeball.
90
+ */
91
+ export declare function terrainFootprint(field: TerrainField): Array<[number, number]>;
92
+ export declare function buildTerrainSkirt(field: TerrainField): TerrainSkirtBuffers;
93
+ /**
94
+ * Rewrite the whole skirt in place.
95
+ *
96
+ * Whole, not dirty-ranged, unlike the surface: the perimeter is `2(cols + rows)`
97
+ * vertices — 1 028 at 257², under 3% of the surface's vertex count — so the
98
+ * bookkeeping to find which boundary samples a rectangular patch touched costs
99
+ * more than re-uploading all of them. The surface's dirty range earns its
100
+ * complexity because it saves 400 KB a dab; here there is nothing to save.
101
+ */
102
+ export declare function updateTerrainSkirt(field: TerrainField, buffers: TerrainSkirtBuffers): void;
103
+ /**
104
+ * The contiguous vertex range a patch dirties, in **array elements** — the unit
105
+ * `BufferAttribute.addUpdateRange(start, count)` expects.
106
+ *
107
+ * A patch is rectangular, so the touched vertices are not contiguous unless the
108
+ * patch spans full rows. Rather than issue one range per row, this returns the
109
+ * single span from the patch's first vertex to its last: over-reporting a few
110
+ * clean vertices is far cheaper than many small GPU uploads, and it keeps the
111
+ * caller to one `addUpdateRange` call.
112
+ *
113
+ * The range is widened by one row on each side because a height change moves the
114
+ * *normals* of its neighbours too (central differences reach one sample out).
115
+ * Getting this wrong produces the classic symptom: correct silhouette, stale
116
+ * shading along the edit boundary.
117
+ */
118
+ export declare function patchUpdateRange(field: TerrainField, patch: HeightPatch, itemSize: number): {
119
+ start: number;
120
+ count: number;
121
+ } | null;
122
+ /**
123
+ * Rewrites only the vertices a patch touched, in place.
124
+ *
125
+ * Takes the *already-patched* field (the one `applyHeightPatch` returned) and
126
+ * the patch that produced it, so the caller cannot accidentally rebuild from
127
+ * stale heights. Mutates the buffers rather than reallocating — that is the
128
+ * whole point of the dirty-rect path.
129
+ */
130
+ export declare function updateTerrainMesh(field: TerrainField, buffers: TerrainMeshBuffers, patch: HeightPatch): void;
131
+ //# sourceMappingURL=terrain-geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-geometry.d.ts","sourceRoot":"","sources":["../../src/site/terrain-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAA4B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEhG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAA;AAWpC,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gDAAgD;IAChD,SAAS,EAAE,YAAY,CAAA;IACvB,gDAAgD;IAChD,OAAO,EAAE,YAAY,CAAA;IACrB,+CAA+C;IAC/C,GAAG,EAAE,YAAY,CAAA;IACjB,2CAA2C;IAC3C,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAOD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,kBAAkB,CAkCxE;AA8BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,+EAA+E;IAC/E,SAAS,EAAE,YAAY,CAAA;IACvB,OAAO,EAAE,YAAY,CAAA;IACrB,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAY7E;AAwBD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,mBAAmB,CAsB1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAyB1F;AAmBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,WAAW,GACjB,IAAI,CASN"}
@@ -0,0 +1,259 @@
1
+ import { heightAtSample, normalAt } from '@pascal-app/core';
2
+ /**
3
+ * Builds the terrain mesh buffers from a `TerrainField`.
4
+ *
5
+ * Split out of the renderer as pure array math so the vertex layout is
6
+ * unit-testable without a canvas, and so the partial-upload path has one
7
+ * definition. The renderer owns the `BufferGeometry`; this owns what goes in it.
8
+ *
9
+ * The triangulation this file emits is the convention `surfaceHeightAt`
10
+ * (`core/lib/terrain-field.ts`) reads back: two triangles per cell split along the
11
+ * `(c+1,r)`–`(c,r+1)` diagonal. Anything drawn *on* the ground samples that
12
+ * function, so changing the winding below without changing it there would make
13
+ * every draped line sink into half of every cell.
14
+ *
15
+ * Two rules that are easy to get wrong and expensive to debug:
16
+ *
17
+ * - **Normals are analytic, never `computeVertexNormals()`.** Beyond the cost
18
+ * (~1.5 ms at 257²), `computeVertexNormals()` rewrites the *whole* normal
19
+ * attribute, which silently defeats the dirty-rect partial upload that makes
20
+ * sculpting cheap. `normalAt` central-differences the field instead, so a
21
+ * patch touches only the rows it changed.
22
+ * - **Vertices are one-per-sample and shared between triangles**, so a partial
23
+ * upload is a contiguous row range. Duplicating verts per-triangle for flat
24
+ * shading would triple memory and scatter the dirty range.
25
+ */
26
+ /**
27
+ * Y of the site's presentation horizon disc (`site/renderer.tsx`).
28
+ *
29
+ * It lives here, next to the skirt, because the skirt is the thing that has to
30
+ * bracket it: its top edge never sits below this plane and its base always clears
31
+ * it, so no sightline can pass between the sculpted ground and the surrounding
32
+ * one. Two readers, one number.
33
+ */
34
+ export const HORIZON_PLANE_Y = -0.07;
35
+ /**
36
+ * How far the skirt's base hangs below the lowest thing it must close over.
37
+ *
38
+ * Only the top edge is visually load-bearing; the base just has to be out of sight
39
+ * under the horizon disc. A metre reads as earth thickness from a grazing camera
40
+ * without being tall enough to poke out of a neighbouring excavation.
41
+ */
42
+ const SKIRT_DROP = 1;
43
+ /** Vertex index of sample [c,r]. */
44
+ function vertexIndex(field, col, row) {
45
+ return row * field.cols + col;
46
+ }
47
+ export function buildTerrainMesh(field) {
48
+ const count = field.cols * field.rows;
49
+ const positions = new Float32Array(count * 3);
50
+ const normals = new Float32Array(count * 3);
51
+ const uvs = new Float32Array(count * 2);
52
+ for (let r = 0; r < field.rows; r++) {
53
+ for (let c = 0; c < field.cols; c++) {
54
+ const i = r * field.cols + c;
55
+ writeVertex(field, positions, normals, uvs, c, r, i);
56
+ }
57
+ }
58
+ // Two triangles per cell. Winding is counter-clockwise seen from +Y so the
59
+ // surface faces up under the default FrontSide material.
60
+ const cells = (field.cols - 1) * (field.rows - 1);
61
+ const indices = new Uint32Array(Math.max(0, cells) * 6);
62
+ let n = 0;
63
+ for (let r = 0; r < field.rows - 1; r++) {
64
+ for (let c = 0; c < field.cols - 1; c++) {
65
+ const a = vertexIndex(field, c, r);
66
+ const b = vertexIndex(field, c + 1, r);
67
+ const d = vertexIndex(field, c, r + 1);
68
+ const e = vertexIndex(field, c + 1, r + 1);
69
+ indices[n++] = a;
70
+ indices[n++] = d;
71
+ indices[n++] = b;
72
+ indices[n++] = b;
73
+ indices[n++] = d;
74
+ indices[n++] = e;
75
+ }
76
+ }
77
+ return { positions, normals, uvs, indices };
78
+ }
79
+ function writeVertex(field, positions, normals, uvs, col, row, index) {
80
+ const x = field.origin[0] + col * field.spacing;
81
+ const z = field.origin[1] + row * field.spacing;
82
+ const y = heightAtSample(field, col, row);
83
+ positions[index * 3] = x;
84
+ positions[index * 3 + 1] = y;
85
+ positions[index * 3 + 2] = z;
86
+ const [nx, ny, nz] = normalAt(field, x, z);
87
+ normals[index * 3] = nx;
88
+ normals[index * 3 + 1] = ny;
89
+ normals[index * 3 + 2] = nz;
90
+ // UVs span the field 0..1 so a tiled ground material scales with the site
91
+ // rather than with sample count.
92
+ uvs[index * 2] = field.cols > 1 ? col / (field.cols - 1) : 0;
93
+ uvs[index * 2 + 1] = field.rows > 1 ? row / (field.rows - 1) : 0;
94
+ }
95
+ /**
96
+ * The field's own footprint, as a polygon to punch out of the horizon disc.
97
+ *
98
+ * The punch is the only reason an excavation is visible at all: without it the disc
99
+ * — a flat plate at `HORIZON_PLANE_Y` standing in for ground beyond the field —
100
+ * roofs over every pit, so digging a basement reads as no change whatsoever. On
101
+ * raised or flat ground it costs nothing visually, because the terrain shell and its
102
+ * skirt cover the hole from both sides.
103
+ *
104
+ * Inset by half a cell, so the disc and the terrain edge *overlap* rather than meet:
105
+ * two coplanar edges at exactly the same XZ z-fight along the whole perimeter. The
106
+ * inset is what the skirt's bracketing span is covering, which is why this lives
107
+ * beside `buildTerrainSkirt` — the two have to agree about where the rim is, and a
108
+ * footprint that punched wider than the skirt spans would open a gap the skirt never
109
+ * closes.
110
+ *
111
+ * Kept here rather than in the renderer so it is reachable without Three.js: it
112
+ * decides whether a pit is visible, and that is a property to assert, not to eyeball.
113
+ */
114
+ export function terrainFootprint(field) {
115
+ const inset = field.spacing / 2;
116
+ const minX = field.origin[0] + inset;
117
+ const minZ = field.origin[1] + inset;
118
+ const maxX = field.origin[0] + (field.cols - 1) * field.spacing - inset;
119
+ const maxZ = field.origin[1] + (field.rows - 1) * field.spacing - inset;
120
+ return [
121
+ [minX, minZ],
122
+ [maxX, minZ],
123
+ [maxX, maxZ],
124
+ [minX, maxZ],
125
+ ];
126
+ }
127
+ /** Boundary sample count — a function of the grid shape alone. */
128
+ function perimeterCount(field) {
129
+ if (field.cols < 2 || field.rows < 2)
130
+ return 0;
131
+ return 2 * (field.cols - 1) + 2 * (field.rows - 1);
132
+ }
133
+ /**
134
+ * The `i`th boundary sample, walking top → right → bottom → left.
135
+ *
136
+ * The direction matters: traversed this way the outward horizontal is the tangent
137
+ * rotated as `(tx, tz) → (tz, -tx)` on every edge, which is what makes one winding
138
+ * rule and one normal formula cover all four sides instead of four special cases.
139
+ */
140
+ function boundarySample(field, i) {
141
+ const lastCol = field.cols - 1;
142
+ const lastRow = field.rows - 1;
143
+ if (i < lastCol)
144
+ return { col: i, row: 0 };
145
+ if (i < lastCol + lastRow)
146
+ return { col: lastCol, row: i - lastCol };
147
+ if (i < 2 * lastCol + lastRow)
148
+ return { col: 2 * lastCol + lastRow - i, row: lastRow };
149
+ return { col: 0, row: 2 * (lastCol + lastRow) - i };
150
+ }
151
+ export function buildTerrainSkirt(field) {
152
+ const count = perimeterCount(field);
153
+ const vertices = count === 0 ? 0 : (count + 1) * 2;
154
+ const buffers = {
155
+ positions: new Float32Array(vertices * 3),
156
+ normals: new Float32Array(vertices * 3),
157
+ indices: new Uint32Array(count * 6),
158
+ };
159
+ for (let i = 0; i < count; i++) {
160
+ const base = i * 6;
161
+ const top = i * 2;
162
+ const nextTop = (i + 1) * 2;
163
+ // Outward faces front: see the winding note on `buildTerrainMesh`.
164
+ buffers.indices[base] = top;
165
+ buffers.indices[base + 1] = nextTop;
166
+ buffers.indices[base + 2] = top + 1;
167
+ buffers.indices[base + 3] = top + 1;
168
+ buffers.indices[base + 4] = nextTop;
169
+ buffers.indices[base + 5] = nextTop + 1;
170
+ }
171
+ updateTerrainSkirt(field, buffers);
172
+ return buffers;
173
+ }
174
+ /**
175
+ * Rewrite the whole skirt in place.
176
+ *
177
+ * Whole, not dirty-ranged, unlike the surface: the perimeter is `2(cols + rows)`
178
+ * vertices — 1 028 at 257², under 3% of the surface's vertex count — so the
179
+ * bookkeeping to find which boundary samples a rectangular patch touched costs
180
+ * more than re-uploading all of them. The surface's dirty range earns its
181
+ * complexity because it saves 400 KB a dab; here there is nothing to save.
182
+ */
183
+ export function updateTerrainSkirt(field, buffers) {
184
+ const count = perimeterCount(field);
185
+ if (count === 0)
186
+ return;
187
+ for (let i = 0; i <= count; i++) {
188
+ const { col, row } = boundarySample(field, i % count);
189
+ const x = field.origin[0] + col * field.spacing;
190
+ const z = field.origin[1] + row * field.spacing;
191
+ const h = heightAtSample(field, col, row);
192
+ const previous = boundarySample(field, (i - 1 + count) % count);
193
+ const next = boundarySample(field, (i + 1) % count);
194
+ // Tangent across the neighbours, so a corner gets the bevelled average of its
195
+ // two edges rather than one of them arbitrarily.
196
+ const tx = next.col - previous.col;
197
+ const tz = next.row - previous.row;
198
+ const tLength = Math.hypot(tx, tz) || 1;
199
+ const nx = tz / tLength;
200
+ const nz = -tx / tLength;
201
+ const topIndex = i * 2;
202
+ const bottomIndex = topIndex + 1;
203
+ writeSkirtVertex(buffers, topIndex, x, Math.max(h, HORIZON_PLANE_Y), z, nx, nz);
204
+ writeSkirtVertex(buffers, bottomIndex, x, Math.min(h, HORIZON_PLANE_Y) - SKIRT_DROP, z, nx, nz);
205
+ }
206
+ }
207
+ function writeSkirtVertex(buffers, index, x, y, z, nx, nz) {
208
+ buffers.positions[index * 3] = x;
209
+ buffers.positions[index * 3 + 1] = y;
210
+ buffers.positions[index * 3 + 2] = z;
211
+ buffers.normals[index * 3] = nx;
212
+ buffers.normals[index * 3 + 1] = 0;
213
+ buffers.normals[index * 3 + 2] = nz;
214
+ }
215
+ /**
216
+ * The contiguous vertex range a patch dirties, in **array elements** — the unit
217
+ * `BufferAttribute.addUpdateRange(start, count)` expects.
218
+ *
219
+ * A patch is rectangular, so the touched vertices are not contiguous unless the
220
+ * patch spans full rows. Rather than issue one range per row, this returns the
221
+ * single span from the patch's first vertex to its last: over-reporting a few
222
+ * clean vertices is far cheaper than many small GPU uploads, and it keeps the
223
+ * caller to one `addUpdateRange` call.
224
+ *
225
+ * The range is widened by one row on each side because a height change moves the
226
+ * *normals* of its neighbours too (central differences reach one sample out).
227
+ * Getting this wrong produces the classic symptom: correct silhouette, stale
228
+ * shading along the edit boundary.
229
+ */
230
+ export function patchUpdateRange(field, patch, itemSize) {
231
+ const firstRow = Math.max(0, patch.row0 - 1);
232
+ const lastRow = Math.min(field.rows - 1, patch.row0 + patch.rows);
233
+ if (lastRow < firstRow)
234
+ return null;
235
+ const startVertex = firstRow * field.cols;
236
+ const endVertex = lastRow * field.cols + (field.cols - 1);
237
+ return {
238
+ start: startVertex * itemSize,
239
+ count: (endVertex - startVertex + 1) * itemSize,
240
+ };
241
+ }
242
+ /**
243
+ * Rewrites only the vertices a patch touched, in place.
244
+ *
245
+ * Takes the *already-patched* field (the one `applyHeightPatch` returned) and
246
+ * the patch that produced it, so the caller cannot accidentally rebuild from
247
+ * stale heights. Mutates the buffers rather than reallocating — that is the
248
+ * whole point of the dirty-rect path.
249
+ */
250
+ export function updateTerrainMesh(field, buffers, patch) {
251
+ const firstRow = Math.max(0, patch.row0 - 1);
252
+ const lastRow = Math.min(field.rows - 1, patch.row0 + patch.rows);
253
+ for (let r = firstRow; r <= lastRow; r++) {
254
+ for (let c = 0; c < field.cols; c++) {
255
+ const i = r * field.cols + c;
256
+ writeVertex(field, buffers.positions, buffers.normals, buffers.uvs, c, r, i);
257
+ }
258
+ }
259
+ }
@@ -0,0 +1,49 @@
1
+ import type { HeightPatch, TerrainField } from '@pascal-app/core';
2
+ import { BufferGeometry } from 'three';
3
+ import { type TerrainMeshBuffers, type TerrainSkirtBuffers } from './terrain-geometry';
4
+ /**
5
+ * The Three.js side of the terrain heightfield: owns the `BufferGeometry` and the
6
+ * dirty-rect upload.
7
+ *
8
+ * Kept beside the pure builder rather than in `viewer` because the dependency
9
+ * runs `nodes -> viewer`, not the other way, and this is a per-kind concern
10
+ * anyway. `terrain-geometry.ts` computes *what* goes in the buffers (testable
11
+ * without a canvas); this owns the GPU resource. The only thing that cannot live
12
+ * on the pure side is `addUpdateRange`/`needsUpdate`, which is why this file is
13
+ * thin.
14
+ *
15
+ * Why patch instead of rebuild: a 129² field is 16 641 vertices, so a full
16
+ * re-upload is ~400 KB per dab. There is no render-on-demand to hide behind —
17
+ * `FrameLimiter` runs a perpetual rAF at 50 FPS — so a brush dragged across a
18
+ * slope would re-upload the whole field dozens of times a second.
19
+ */
20
+ /** A terrain geometry plus the CPU buffers backing it. */
21
+ export type TerrainGeometry = {
22
+ readonly geometry: BufferGeometry;
23
+ readonly buffers: TerrainMeshBuffers;
24
+ /** The edge curtain that closes the field against the horizon disc. */
25
+ readonly skirt: {
26
+ readonly geometry: BufferGeometry;
27
+ readonly buffers: TerrainSkirtBuffers;
28
+ };
29
+ };
30
+ export declare function createTerrainGeometry(field: TerrainField): TerrainGeometry;
31
+ /**
32
+ * Push one patch to the GPU, touching only the affected rows.
33
+ *
34
+ * Both `position` and `normal` are updated: a height change moves its
35
+ * neighbours' normals too, and `patchUpdateRange` already widens the span by one
36
+ * row on each side to cover that. UVs are never touched — they are a function of
37
+ * grid indices, not heights.
38
+ */
39
+ export declare function applyTerrainPatch(target: TerrainGeometry, field: TerrainField, patch: HeightPatch): void;
40
+ /**
41
+ * True when a geometry was built for a differently-shaped field.
42
+ *
43
+ * A resized field cannot be patched — the vertex count changed — so the caller
44
+ * must rebuild. Checking here keeps that decision in one place instead of every
45
+ * caller comparing `cols`/`rows` by hand.
46
+ */
47
+ export declare function needsRebuild(target: TerrainGeometry, field: TerrainField): boolean;
48
+ export declare function disposeTerrainGeometry(target: TerrainGeometry): void;
49
+ //# sourceMappingURL=terrain-mesh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-mesh.d.ts","sourceRoot":"","sources":["../../src/site/terrain-mesh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAmB,cAAc,EAAmB,MAAM,OAAO,CAAA;AACxE,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAGzB,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;;;;;;;;GAeG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;IACpC,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAC7F,CAAA;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAoB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,WAAW,GACjB,IAAI,CA0BN;AA8DD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAElF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAGpE"}
@@ -0,0 +1,122 @@
1
+ import { BufferAttribute, BufferGeometry, Sphere, Vector3 } from 'three';
2
+ import { buildTerrainMesh, buildTerrainSkirt, HORIZON_PLANE_Y, patchUpdateRange, updateTerrainMesh, updateTerrainSkirt, } from './terrain-geometry';
3
+ export function createTerrainGeometry(field) {
4
+ const buffers = buildTerrainMesh(field);
5
+ const geometry = new BufferGeometry();
6
+ geometry.setAttribute('position', new BufferAttribute(buffers.positions, 3));
7
+ geometry.setAttribute('normal', new BufferAttribute(buffers.normals, 3));
8
+ geometry.setAttribute('uv', new BufferAttribute(buffers.uvs, 2));
9
+ geometry.setIndex(new BufferAttribute(buffers.indices, 1));
10
+ setTerrainBounds(geometry, field);
11
+ // A separate geometry, not extra vertices on the surface: the surface's dirty
12
+ // range is a row span over a `cols * rows` layout, and appending a perimeter ring
13
+ // to it would break that indexing for a saving of one draw call.
14
+ const skirtBuffers = buildTerrainSkirt(field);
15
+ const skirtGeometry = new BufferGeometry();
16
+ skirtGeometry.setAttribute('position', new BufferAttribute(skirtBuffers.positions, 3));
17
+ skirtGeometry.setAttribute('normal', new BufferAttribute(skirtBuffers.normals, 3));
18
+ skirtGeometry.setIndex(new BufferAttribute(skirtBuffers.indices, 1));
19
+ setSkirtBounds(skirtGeometry, field);
20
+ return { geometry, buffers, skirt: { geometry: skirtGeometry, buffers: skirtBuffers } };
21
+ }
22
+ /**
23
+ * Push one patch to the GPU, touching only the affected rows.
24
+ *
25
+ * Both `position` and `normal` are updated: a height change moves its
26
+ * neighbours' normals too, and `patchUpdateRange` already widens the span by one
27
+ * row on each side to cover that. UVs are never touched — they are a function of
28
+ * grid indices, not heights.
29
+ */
30
+ export function applyTerrainPatch(target, field, patch) {
31
+ updateTerrainMesh(field, target.buffers, patch);
32
+ // The skirt goes up unconditionally, before the range bail: a patch that leaves
33
+ // the surface's dirty span empty can still have moved a boundary sample, and a
34
+ // stale skirt is a hole at the property line.
35
+ updateTerrainSkirt(field, target.skirt.buffers);
36
+ for (const name of ['position', 'normal']) {
37
+ const attribute = target.skirt.geometry.getAttribute(name);
38
+ attribute.needsUpdate = true;
39
+ }
40
+ setSkirtBounds(target.skirt.geometry, field);
41
+ const range = patchUpdateRange(field, patch, 3);
42
+ if (!range)
43
+ return;
44
+ for (const name of ['position', 'normal']) {
45
+ const attribute = target.geometry.getAttribute(name);
46
+ // `clearUpdateRanges` first: ranges accumulate, so a stroke dabbing 60 times
47
+ // a second would otherwise grow an unbounded list that all gets re-uploaded.
48
+ attribute.clearUpdateRanges();
49
+ attribute.addUpdateRange(range.start, range.count);
50
+ attribute.needsUpdate = true;
51
+ }
52
+ setTerrainBounds(target.geometry, field);
53
+ }
54
+ /**
55
+ * Set bounds analytically instead of calling `computeBoundingSphere()`.
56
+ *
57
+ * The computed version walks every vertex twice and allocates; the field's
58
+ * horizontal extent is known in O(1) from `origin`/`spacing`/`cols`/`rows`, and
59
+ * only the height range needs a scan. Skipping bounds entirely is not an option —
60
+ * a stale bounding sphere gets a raised hill frustum-culled while it is still on
61
+ * screen, which reads as terrain flickering out at certain camera angles.
62
+ */
63
+ function setTerrainBounds(geometry, field) {
64
+ const { minY, maxY } = heightSpan(field);
65
+ const minX = field.origin[0];
66
+ const minZ = field.origin[1];
67
+ const maxX = minX + (field.cols - 1) * field.spacing;
68
+ const maxZ = minZ + (field.rows - 1) * field.spacing;
69
+ const center = new Vector3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
70
+ const radius = Math.hypot((maxX - minX) / 2, (maxY - minY) / 2, (maxZ - minZ) / 2);
71
+ geometry.boundingSphere = new Sphere(center, radius);
72
+ if (geometry.boundingBox) {
73
+ geometry.boundingBox.set(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));
74
+ }
75
+ }
76
+ /**
77
+ * Bounds for the skirt, which hangs below the field's own low point.
78
+ *
79
+ * Reusing `setTerrainBounds` would cull the curtain the moment its top edge left
80
+ * the surface's sphere — the exact symptom the surface's analytic bounds exist to
81
+ * avoid, one geometry over. The vertical span brackets the horizon plane for the
82
+ * same reason the geometry does.
83
+ */
84
+ function setSkirtBounds(geometry, field) {
85
+ const { minY, maxY } = heightSpan(field);
86
+ const low = Math.min(minY, HORIZON_PLANE_Y) - 1;
87
+ const high = Math.max(maxY, HORIZON_PLANE_Y);
88
+ const minX = field.origin[0];
89
+ const minZ = field.origin[1];
90
+ const maxX = minX + (field.cols - 1) * field.spacing;
91
+ const maxZ = minZ + (field.rows - 1) * field.spacing;
92
+ const center = new Vector3((minX + maxX) / 2, (low + high) / 2, (minZ + maxZ) / 2);
93
+ const radius = Math.hypot((maxX - minX) / 2, (high - low) / 2, (maxZ - minZ) / 2);
94
+ geometry.boundingSphere = new Sphere(center, radius);
95
+ }
96
+ /** Height span in metres, always including the datum so flat ground has a box. */
97
+ function heightSpan(field) {
98
+ let minH = 0;
99
+ let maxH = 0;
100
+ for (let i = 0; i < field.heights.length; i++) {
101
+ const h = field.heights[i] ?? 0;
102
+ if (h < minH)
103
+ minH = h;
104
+ if (h > maxH)
105
+ maxH = h;
106
+ }
107
+ return { minY: minH * field.step, maxY: maxH * field.step };
108
+ }
109
+ /**
110
+ * True when a geometry was built for a differently-shaped field.
111
+ *
112
+ * A resized field cannot be patched — the vertex count changed — so the caller
113
+ * must rebuild. Checking here keeps that decision in one place instead of every
114
+ * caller comparing `cols`/`rows` by hand.
115
+ */
116
+ export function needsRebuild(target, field) {
117
+ return target.buffers.positions.length !== field.cols * field.rows * 3;
118
+ }
119
+ export function disposeTerrainGeometry(target) {
120
+ target.geometry.dispose();
121
+ target.skirt.geometry.dispose();
122
+ }
@@ -0,0 +1,28 @@
1
+ import { type SiteNode } from '@pascal-app/core';
2
+ import type { Material } from 'three';
3
+ /**
4
+ * Renders the site's sculpted ground.
5
+ *
6
+ * Mounted by `SiteRenderer` only when the site has terrain (or a stroke is in
7
+ * flight), so a scene that never touched terrain mounts nothing at all and pays
8
+ * nothing.
9
+ *
10
+ * Two things here are load-bearing and easy to get wrong:
11
+ *
12
+ * - **The geometry is a ref, not React state.** A sculpt stroke pushes dozens of
13
+ * patches a second; routing each through a re-render would rebuild the
14
+ * `BufferGeometry` object identity every dab and throw away the partial-upload
15
+ * win entirely. The component subscribes to the *patch*, mutates the buffers in
16
+ * place, and never re-renders during a stroke.
17
+ * - **It stays on the default `SCENE_LAYER`.** This is real, visible,
18
+ * shadow-receiving geometry — not an overlay. Overlay/hit-area meshes must use
19
+ * the editor layer, but moving actual scene geometry there would exclude it from
20
+ * the MRT depth/normal pass and break the screen-space ink against it.
21
+ */
22
+ export declare const TerrainRenderer: ({ material, site, }: {
23
+ /** Owned by `SiteRenderer` so the ground material stays defined in one place. */
24
+ material: Material;
25
+ site: SiteNode;
26
+ }) => import("react").JSX.Element | null;
27
+ export default TerrainRenderer;
28
+ //# sourceMappingURL=terrain-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terrain-renderer.d.ts","sourceRoot":"","sources":["../../src/site/terrain-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAkC,MAAM,kBAAkB,CAAA;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AASrC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,GAAI,qBAG7B;IACD,iFAAiF;IACjF,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,QAAQ,CAAA;CACf,uCA6DA,CAAA;AAED,eAAe,eAAe,CAAA"}
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { terrainFieldOf, useLiveTerrain } from '@pascal-app/core';
4
+ import { useEffect, useMemo, useRef } from 'react';
5
+ import { applyTerrainPatch, createTerrainGeometry, disposeTerrainGeometry, needsRebuild, } from './terrain-mesh';
6
+ /**
7
+ * Renders the site's sculpted ground.
8
+ *
9
+ * Mounted by `SiteRenderer` only when the site has terrain (or a stroke is in
10
+ * flight), so a scene that never touched terrain mounts nothing at all and pays
11
+ * nothing.
12
+ *
13
+ * Two things here are load-bearing and easy to get wrong:
14
+ *
15
+ * - **The geometry is a ref, not React state.** A sculpt stroke pushes dozens of
16
+ * patches a second; routing each through a re-render would rebuild the
17
+ * `BufferGeometry` object identity every dab and throw away the partial-upload
18
+ * win entirely. The component subscribes to the *patch*, mutates the buffers in
19
+ * place, and never re-renders during a stroke.
20
+ * - **It stays on the default `SCENE_LAYER`.** This is real, visible,
21
+ * shadow-receiving geometry — not an overlay. Overlay/hit-area meshes must use
22
+ * the editor layer, but moving actual scene geometry there would exclude it from
23
+ * the MRT depth/normal pass and break the screen-space ink against it.
24
+ */
25
+ export const TerrainRenderer = ({ material, site, }) => {
26
+ const targetRef = useRef(null);
27
+ // Persisted terrain only — the live stroke is applied imperatively below, so a
28
+ // dab never triggers a React render.
29
+ const persistedField = useMemo(() => terrainFieldOf({ id: site.id, terrain: site.terrain }), [site.id, site.terrain]);
30
+ const target = useMemo(() => {
31
+ const field = useLiveTerrain.getState().fieldOf(site.id) ?? persistedField;
32
+ if (!field)
33
+ return null;
34
+ return createTerrainGeometry(field);
35
+ }, [persistedField, site.id]);
36
+ targetRef.current = target;
37
+ useEffect(() => () => {
38
+ if (target)
39
+ disposeTerrainGeometry(target);
40
+ }, [target]);
41
+ // Imperative subscription rather than a selector hook: the point is to react to
42
+ // a dab *without* re-rendering. Zustand's subscribe gives the previous state
43
+ // too, so a patch is only uploaded once even if an unrelated store update
44
+ // notifies.
45
+ useEffect(() => {
46
+ let lastPatch = useLiveTerrain.getState().strokeOf(site.id)?.lastPatch ?? null;
47
+ return useLiveTerrain.subscribe((state) => {
48
+ const stroke = state.strokes.get(site.id);
49
+ const current = targetRef.current;
50
+ if (!(stroke && current)) {
51
+ lastPatch = null;
52
+ return;
53
+ }
54
+ if (!stroke.lastPatch || stroke.lastPatch === lastPatch)
55
+ return;
56
+ lastPatch = stroke.lastPatch;
57
+ // A resized field cannot be patched — the vertex count changed. That only
58
+ // happens on import, never mid-stroke, so bail and let the persisted-field
59
+ // rebuild handle it rather than silently corrupting the buffers.
60
+ if (needsRebuild(current, stroke.field))
61
+ return;
62
+ applyTerrainPatch(current, stroke.field, stroke.lastPatch);
63
+ });
64
+ }, [site.id]);
65
+ if (!target)
66
+ return null;
67
+ return (_jsxs(_Fragment, { children: [_jsx("mesh", { castShadow: true, geometry: target.geometry, material: material, receiveShadow: true }), _jsx("mesh", { geometry: target.skirt.geometry, material: material, receiveShadow: true })] }));
68
+ };
69
+ export default TerrainRenderer;