@pascal-app/editor 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -1,8 +1,10 @@
1
1
  import {
2
+ type AnyNode,
2
3
  type AnyNodeId,
3
4
  type DoorNode,
4
5
  getGarageVisibleOpeningRatio,
5
6
  isOperationDoorType,
7
+ nodeRegistry,
6
8
  sceneRegistry,
7
9
  useInteractive,
8
10
  useScene,
@@ -10,21 +12,11 @@ import {
10
12
  import * as THREE from 'three'
11
13
  import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
12
14
  import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from 'three-mesh-bvh'
13
-
14
- const COLLIDER_NODE_TYPES = [
15
- 'wall',
16
- 'fence',
17
- 'slab',
18
- 'stair',
19
- 'stair-segment',
20
- 'roof',
21
- 'roof-segment',
22
- 'door',
23
- 'window',
24
- 'item',
25
- ] as const
15
+ import { computeSceneBoundsXZ } from '../../../lib/scene-bounds'
26
16
 
27
17
  const SKIPPED_MESH_NAMES = new Set(['cutout', 'collision-mesh'])
18
+ const COLLIDER_NODE_CATEGORIES = new Set(['structure', 'furnish'])
19
+ const DEDICATED_COLLIDER_NODE_TYPES = new Set<AnyNode['type']>(['elevator'])
28
20
  const COLLIDER_MATERIAL = new THREE.MeshBasicMaterial()
29
21
  const DOWN = new THREE.Vector3(0, -1, 0)
30
22
  const UP = new THREE.Vector3(0, 1, 0)
@@ -32,6 +24,10 @@ const SPAWN_EYE_HEIGHT = 1.65
32
24
  const RAYCAST_CLEARANCE = 25
33
25
  const DOOR_LEAF_COLLIDER_DEPTH = 0.06
34
26
  const OPERATION_DOOR_COLLIDER_OPEN_THRESHOLD = 0.85
27
+ const LEVEL_FALLBACK_FLOOR_THICKNESS = 0.08
28
+ const LEVEL_FALLBACK_FLOOR_PADDING = 2
29
+ const LEVEL_FALLBACK_FLOOR_MIN_SIZE = 30
30
+ const SITE_GROUND_COLLIDER_MIN_SIZE = 2000
35
31
 
36
32
  export const FIRST_PERSON_SPAWN_EYE_HEIGHT = SPAWN_EYE_HEIGHT
37
33
 
@@ -46,16 +42,174 @@ export type FirstPersonSpawn = {
46
42
  yaw: number
47
43
  }
48
44
 
49
- type ColliderNodeType = (typeof COLLIDER_NODE_TYPES)[number]
45
+ type LevelNode = Extract<AnyNode, { type: 'level' }>
46
+ type SiteNode = Extract<AnyNode, { type: 'site' }>
47
+ type SceneNodes = ReturnType<typeof useScene.getState>['nodes']
50
48
 
51
49
  function isMesh(object: THREE.Object3D): object is THREE.Mesh {
52
50
  return 'isMesh' in object && (object as THREE.Mesh).isMesh
53
51
  }
54
52
 
53
+ // Renderer-effective visibility: an invisible ancestor hides the whole
54
+ // subtree at render time even when the object's own flag is true. The
55
+ // collider world must match what's rendered — the roof keeps stale,
56
+ // UNCUT per-segment CSG inside its hidden `segments-wrapper` (full-edit
57
+ // exit hides the wrapper without stripping geometry), and cloning those
58
+ // meshes would block the walkthrough player at openings the visible
59
+ // merged shell has cut through.
60
+ function isEffectivelyVisible(object: THREE.Object3D) {
61
+ let current: THREE.Object3D | null = object
62
+ while (current) {
63
+ if (!current.visible) return false
64
+ current = current.parent
65
+ }
66
+ return true
67
+ }
68
+
55
69
  function isColliderMaterialVisible(material: THREE.Material | THREE.Material[]) {
56
70
  return Array.isArray(material) ? material.some((entry) => entry.visible) : material.visible
57
71
  }
58
72
 
73
+ function isGenericColliderNode(node: AnyNode) {
74
+ if (node.visible === false) return false
75
+ if (DEDICATED_COLLIDER_NODE_TYPES.has(node.type)) return false
76
+ const def = nodeRegistry.get(node.type)
77
+ // Ceilings are a transparent mount surface for fixtures (lights, fans), not a
78
+ // walkable or blocking structure — the walkthrough player must pass through
79
+ // them rather than be held up as if standing on a floor slab.
80
+ if (def?.surfaceRole === 'ceiling') return false
81
+ return COLLIDER_NODE_CATEGORIES.has(def?.category ?? '')
82
+ }
83
+
84
+ function createBoxColliderGeometry(width: number, height: number, depth: number) {
85
+ const sourceGeometry = new THREE.BoxGeometry(width, height, depth).toNonIndexed()
86
+ const geometry = new THREE.BufferGeometry()
87
+ geometry.setAttribute('position', sourceGeometry.getAttribute('position').clone())
88
+ geometry.setAttribute('normal', sourceGeometry.getAttribute('normal').clone())
89
+ sourceGeometry.dispose()
90
+ return geometry
91
+ }
92
+
93
+ function getVisibleLevelChildren(level: LevelNode, nodes: SceneNodes) {
94
+ return level.children
95
+ .map((childId) => nodes[childId as AnyNodeId])
96
+ .filter((child): child is AnyNode => Boolean(child && child.visible !== false))
97
+ }
98
+
99
+ function createLevelFallbackFloorGeometry(level: LevelNode, nodes: SceneNodes) {
100
+ if (level.visible === false) return null
101
+
102
+ const children = getVisibleLevelChildren(level, nodes)
103
+ if (children.some((child) => child.type === 'slab')) return null
104
+
105
+ const levelObject = sceneRegistry.nodes.get(level.id)
106
+ if (!levelObject?.visible) return null
107
+
108
+ const bounds = computeSceneBoundsXZ(children)
109
+ const [centerX, centerZ] = bounds?.center ?? [0, 0]
110
+ const [boundsWidth, boundsDepth] = bounds?.size ?? [0, 0]
111
+ const width = Math.max(
112
+ boundsWidth + LEVEL_FALLBACK_FLOOR_PADDING * 2,
113
+ LEVEL_FALLBACK_FLOOR_MIN_SIZE,
114
+ )
115
+ const depth = Math.max(
116
+ boundsDepth + LEVEL_FALLBACK_FLOOR_PADDING * 2,
117
+ LEVEL_FALLBACK_FLOOR_MIN_SIZE,
118
+ )
119
+
120
+ const geometry = createBoxColliderGeometry(width, LEVEL_FALLBACK_FLOOR_THICKNESS, depth)
121
+
122
+ levelObject.updateWorldMatrix(true, false)
123
+ geometry.applyMatrix4(
124
+ new THREE.Matrix4().makeTranslation(centerX, -LEVEL_FALLBACK_FLOOR_THICKNESS / 2, centerZ),
125
+ )
126
+ geometry.applyMatrix4(levelObject.matrixWorld)
127
+ return geometry
128
+ }
129
+
130
+ function collectLevelFallbackFloorGeometries(nodes: SceneNodes) {
131
+ const geometries: THREE.BufferGeometry[] = []
132
+
133
+ for (const levelId of sceneRegistry.byType.level!) {
134
+ const node = nodes[levelId as AnyNodeId]
135
+ if (node?.type !== 'level') continue
136
+
137
+ const geometry = createLevelFallbackFloorGeometry(node, nodes)
138
+ if (geometry) geometries.push(geometry)
139
+ }
140
+
141
+ return geometries
142
+ }
143
+
144
+ // The visible ground is the site node's ground mesh, but `site` is a `site`
145
+ // category node and therefore excluded from the generic collider sweep. Without
146
+ // a dedicated collider, a spawn on the bare ground (no slab, or not parented to
147
+ // a level that triggers the per-level fallback) has no floor to stand on and the
148
+ // walkthrough player falls through. Derive a thin ground slab from node data (not
149
+ // the rendered mesh) so it exists regardless of geometry-mount timing. The slab
150
+ // is effectively unbounded (not sized to the site polygon): the ground plane must
151
+ // keep holding the player up even after they step past the site boundary,
152
+ // otherwise they fall below the ground plane into the void.
153
+ function createSiteGroundColliderGeometry(site: SiteNode, nodes: SceneNodes) {
154
+ if (site.visible === false) return null
155
+
156
+ const siteObject = sceneRegistry.nodes.get(site.id)
157
+ if (!siteObject?.visible) return null
158
+
159
+ const bounds = computeSceneBoundsXZ(nodes)
160
+ const [centerX, centerZ] = bounds?.center ?? [0, 0]
161
+ const [boundsWidth, boundsDepth] = bounds?.size ?? [0, 0]
162
+ const width = Math.max(
163
+ boundsWidth + LEVEL_FALLBACK_FLOOR_PADDING * 2,
164
+ SITE_GROUND_COLLIDER_MIN_SIZE,
165
+ )
166
+ const depth = Math.max(
167
+ boundsDepth + LEVEL_FALLBACK_FLOOR_PADDING * 2,
168
+ SITE_GROUND_COLLIDER_MIN_SIZE,
169
+ )
170
+
171
+ const geometry = createBoxColliderGeometry(width, LEVEL_FALLBACK_FLOOR_THICKNESS, depth)
172
+
173
+ siteObject.updateWorldMatrix(true, false)
174
+ geometry.applyMatrix4(
175
+ new THREE.Matrix4().makeTranslation(centerX, -LEVEL_FALLBACK_FLOOR_THICKNESS / 2, centerZ),
176
+ )
177
+ geometry.applyMatrix4(siteObject.matrixWorld)
178
+ return geometry
179
+ }
180
+
181
+ function collectSiteGroundColliderGeometries(nodes: SceneNodes) {
182
+ const geometries: THREE.BufferGeometry[] = []
183
+
184
+ for (const siteId of sceneRegistry.byType.site ?? []) {
185
+ const node = nodes[siteId as AnyNodeId]
186
+ if (node?.type !== 'site') continue
187
+
188
+ const geometry = createSiteGroundColliderGeometry(node, nodes)
189
+ if (geometry) geometries.push(geometry)
190
+ }
191
+
192
+ return geometries
193
+ }
194
+
195
+ // Decode any attribute (interleaved, quantized/normalized integer, Float64…) into a
196
+ // plain, non-normalized Float32Array BufferAttribute. mergeGeometries() requires every
197
+ // merged geometry to share the same typed-array constructor for matching attributes, so
198
+ // imported item GLBs using KHR_mesh_quantization or interleaved buffers must be coerced
199
+ // to Float32 to match wall/slab geometry.
200
+ function toFloat32Attribute(source: THREE.BufferAttribute | THREE.InterleavedBufferAttribute) {
201
+ const itemSize = source.itemSize
202
+ const array = new Float32Array(source.count * itemSize)
203
+ for (let i = 0; i < source.count; i++) {
204
+ const offset = i * itemSize
205
+ array[offset] = source.getX(i)
206
+ if (itemSize > 1) array[offset + 1] = source.getY(i)
207
+ if (itemSize > 2) array[offset + 2] = source.getZ(i)
208
+ if (itemSize > 3) array[offset + 3] = source.getW(i)
209
+ }
210
+ return new THREE.BufferAttribute(array, itemSize)
211
+ }
212
+
59
213
  function cloneWorldGeometry(mesh: THREE.Mesh) {
60
214
  const sourceGeometry = mesh.geometry
61
215
  const position = sourceGeometry.getAttribute('position')
@@ -65,11 +219,14 @@ function cloneWorldGeometry(mesh: THREE.Mesh) {
65
219
  ? sourceGeometry.toNonIndexed()
66
220
  : sourceGeometry.clone()
67
221
  const cleanGeometry = new THREE.BufferGeometry()
68
- cleanGeometry.setAttribute('position', workingGeometry.getAttribute('position').clone())
222
+ cleanGeometry.setAttribute(
223
+ 'position',
224
+ toFloat32Attribute(workingGeometry.getAttribute('position')),
225
+ )
69
226
 
70
227
  const normal = workingGeometry.getAttribute('normal')
71
228
  if (normal) {
72
- cleanGeometry.setAttribute('normal', normal.clone())
229
+ cleanGeometry.setAttribute('normal', toFloat32Attribute(normal))
73
230
  } else {
74
231
  cleanGeometry.computeVertexNormals()
75
232
  }
@@ -86,16 +243,12 @@ function cloneWorldGeometry(mesh: THREE.Mesh) {
86
243
  return cleanGeometry
87
244
  }
88
245
 
89
- function shouldSkipColliderNode(nodeId: string, type: (typeof COLLIDER_NODE_TYPES)[number]) {
90
- if (type === 'window') {
91
- const node = useScene.getState().nodes[nodeId as AnyNodeId]
92
- return node?.type === 'window' && node.openingKind === 'opening'
246
+ function shouldSkipColliderNode(node: AnyNode) {
247
+ if (node.type === 'window') {
248
+ return node.openingKind === 'opening'
93
249
  }
94
250
 
95
- if (type !== 'door') return false
96
-
97
- const node = useScene.getState().nodes[nodeId as AnyNodeId]
98
- if (!node || node.type !== 'door') return false
251
+ if (node.type !== 'door') return false
99
252
 
100
253
  if (!node.segments.length) return true
101
254
 
@@ -124,15 +277,7 @@ function createDoorLeafColliderGeometry(root: THREE.Object3D, node: DoorNode) {
124
277
  const visibleHeight = leafH * (1 - openAmount)
125
278
  if (visibleHeight <= 0.12) return null
126
279
 
127
- const sourceGeometry = new THREE.BoxGeometry(
128
- leafW,
129
- visibleHeight,
130
- DOOR_LEAF_COLLIDER_DEPTH,
131
- ).toNonIndexed()
132
- const geometry = new THREE.BufferGeometry()
133
- geometry.setAttribute('position', sourceGeometry.getAttribute('position').clone())
134
- geometry.setAttribute('normal', sourceGeometry.getAttribute('normal').clone())
135
- sourceGeometry.dispose()
280
+ const geometry = createBoxColliderGeometry(leafW, visibleHeight, DOOR_LEAF_COLLIDER_DEPTH)
136
281
  const visibleCenterY = leafCenterY - leafH / 2 + visibleHeight / 2
137
282
  geometry.applyMatrix4(
138
283
  root.matrixWorld.clone().multiply(new THREE.Matrix4().makeTranslation(0, visibleCenterY, 0)),
@@ -153,15 +298,7 @@ function createDoorLeafColliderGeometry(root: THREE.Object3D, node: DoorNode) {
153
298
  const clampedSwingAngle = Math.max(0, Math.min(Math.PI / 2, swingAngle ?? 0))
154
299
  const leafSwingRotation = clampedSwingAngle * swingDirectionSign * hingeDirectionSign
155
300
 
156
- const sourceGeometry = new THREE.BoxGeometry(
157
- leafW,
158
- leafH,
159
- DOOR_LEAF_COLLIDER_DEPTH,
160
- ).toNonIndexed()
161
- const geometry = new THREE.BufferGeometry()
162
- geometry.setAttribute('position', sourceGeometry.getAttribute('position').clone())
163
- geometry.setAttribute('normal', sourceGeometry.getAttribute('normal').clone())
164
- sourceGeometry.dispose()
301
+ const geometry = createBoxColliderGeometry(leafW, leafH, DOOR_LEAF_COLLIDER_DEPTH)
165
302
  const matrix = root.matrixWorld
166
303
  .clone()
167
304
  .multiply(new THREE.Matrix4().makeTranslation(hingeX, 0, 0))
@@ -172,16 +309,17 @@ function createDoorLeafColliderGeometry(root: THREE.Object3D, node: DoorNode) {
172
309
  return geometry
173
310
  }
174
311
 
175
- function buildRegisteredNodeTypeLookup() {
176
- const nodeTypes = new Map<string, ColliderNodeType>()
312
+ function buildRegisteredColliderNodeIds(nodes: SceneNodes) {
313
+ const nodeIds = new Set<string>()
177
314
 
178
- for (const type of COLLIDER_NODE_TYPES) {
179
- for (const nodeId of sceneRegistry.byType[type]) {
180
- nodeTypes.set(nodeId, type)
181
- }
315
+ for (const nodeId of sceneRegistry.nodes.keys()) {
316
+ const node = nodes[nodeId as AnyNodeId]
317
+ if (!node || !isGenericColliderNode(node)) continue
318
+ if (shouldSkipColliderNode(node)) continue
319
+ nodeIds.add(nodeId)
182
320
  }
183
321
 
184
- return nodeTypes
322
+ return nodeIds
185
323
  }
186
324
 
187
325
  function collectColliderGeometriesFromNode(
@@ -189,7 +327,7 @@ function collectColliderGeometriesFromNode(
189
327
  rootNodeId: string,
190
328
  visitedMeshes: WeakSet<THREE.Object3D>,
191
329
  registeredObjectIds: Map<THREE.Object3D, string>,
192
- registeredNodeTypes: Map<string, ColliderNodeType>,
330
+ registeredColliderNodeIds: Set<string>,
193
331
  ): THREE.BufferGeometry[] {
194
332
  const geometries: THREE.BufferGeometry[] = []
195
333
 
@@ -197,9 +335,12 @@ function collectColliderGeometriesFromNode(
197
335
  if (visitedMeshes.has(object)) return
198
336
  visitedMeshes.add(object)
199
337
 
338
+ // Prune hidden subtrees — children of an invisible group never render,
339
+ // so they must not collide either (see isEffectivelyVisible).
340
+ if (!object.visible) return
341
+
200
342
  if (
201
343
  isMesh(object) &&
202
- object.visible &&
203
344
  isColliderMaterialVisible(object.material) &&
204
345
  !SKIPPED_MESH_NAMES.has(object.name)
205
346
  ) {
@@ -211,11 +352,8 @@ function collectColliderGeometriesFromNode(
211
352
 
212
353
  for (const child of object.children) {
213
354
  const childNodeId = registeredObjectIds.get(child)
214
- if (childNodeId && childNodeId !== rootNodeId) {
215
- const childType = registeredNodeTypes.get(childNodeId)
216
- if (childType && COLLIDER_NODE_TYPES.includes(childType)) {
217
- continue
218
- }
355
+ if (childNodeId && childNodeId !== rootNodeId && registeredColliderNodeIds.has(childNodeId)) {
356
+ continue
219
357
  }
220
358
 
221
359
  visit(child)
@@ -228,52 +366,59 @@ function collectColliderGeometriesFromNode(
228
366
  }
229
367
 
230
368
  export function buildFirstPersonColliderWorldFromRegistry(): FirstPersonColliderWorld | null {
369
+ const nodes = useScene.getState().nodes
231
370
  const geometries: THREE.BufferGeometry[] = []
232
371
  const visitedMeshes = new WeakSet<THREE.Object3D>()
233
- const registeredNodeTypes = buildRegisteredNodeTypeLookup()
372
+ const registeredColliderNodeIds = buildRegisteredColliderNodeIds(nodes)
234
373
  const registeredObjectIds = new Map<THREE.Object3D, string>()
235
374
 
236
375
  for (const [nodeId, object] of sceneRegistry.nodes) {
237
376
  registeredObjectIds.set(object, nodeId)
238
377
  }
239
378
 
240
- for (const type of COLLIDER_NODE_TYPES) {
241
- for (const nodeId of sceneRegistry.byType[type]) {
242
- if (shouldSkipColliderNode(nodeId, type)) continue
379
+ for (const nodeId of registeredColliderNodeIds) {
380
+ const node = nodes[nodeId as AnyNodeId]
381
+ if (!node) continue
243
382
 
244
- const root = sceneRegistry.nodes.get(nodeId)
245
- if (!root) continue
383
+ const root = sceneRegistry.nodes.get(nodeId)
384
+ if (!root) continue
246
385
 
247
- if (type === 'door') {
248
- const node = useScene.getState().nodes[nodeId as AnyNodeId]
249
- if (node?.type !== 'door') continue
386
+ // Registered objects can sit inside a hidden wrapper (roof segments
387
+ // under `segments-wrapper`) — the per-node traversal starts AT the
388
+ // object, so the ancestor chain must be checked here.
389
+ if (!isEffectivelyVisible(root)) continue
250
390
 
251
- const doorGeometry = createDoorLeafColliderGeometry(root, node)
252
- if (doorGeometry) {
253
- geometries.push(doorGeometry)
254
- }
255
- continue
391
+ if (node.type === 'door') {
392
+ const doorGeometry = createDoorLeafColliderGeometry(root, node)
393
+ if (doorGeometry) {
394
+ geometries.push(doorGeometry)
256
395
  }
257
-
258
- root.updateMatrixWorld(true)
259
- geometries.push(
260
- ...collectColliderGeometriesFromNode(
261
- root,
262
- nodeId,
263
- visitedMeshes,
264
- registeredObjectIds,
265
- registeredNodeTypes,
266
- ),
267
- )
396
+ continue
268
397
  }
398
+
399
+ root.updateMatrixWorld(true)
400
+ geometries.push(
401
+ ...collectColliderGeometriesFromNode(
402
+ root,
403
+ nodeId,
404
+ visitedMeshes,
405
+ registeredObjectIds,
406
+ registeredColliderNodeIds,
407
+ ),
408
+ )
269
409
  }
270
410
 
411
+ geometries.push(...collectLevelFallbackFloorGeometries(nodes))
412
+ geometries.push(...collectSiteGroundColliderGeometries(nodes))
413
+
271
414
  if (geometries.length === 0) {
272
415
  return null
273
416
  }
274
417
 
275
418
  const mergedGeometry = mergeGeometries(geometries, false)
276
- geometries.forEach((geometry) => geometry.dispose())
419
+ for (const geometry of geometries) {
420
+ geometry.dispose()
421
+ }
277
422
 
278
423
  if (!mergedGeometry || mergedGeometry.getAttribute('position') == null) {
279
424
  mergedGeometry?.dispose()
@@ -288,7 +433,7 @@ export function buildFirstPersonColliderWorldFromRegistry(): FirstPersonCollider
288
433
  ;(bvhGeometry as any).computeBoundsTree = computeBoundsTree
289
434
  ;(bvhGeometry as any).disposeBoundsTree = disposeBoundsTree
290
435
  bvhGeometry.computeBoundsTree?.({
291
- maxLeafTris: 12,
436
+ maxLeafSize: 12,
292
437
  strategy: 0,
293
438
  } as never)
294
439
  bvhGeometry.computeBoundingBox()