@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,182 +0,0 @@
1
- 'use client'
2
-
3
- import {
4
- type AnyNodeId,
5
- emitter,
6
- type FenceNode,
7
- type GridEvent,
8
- type LevelNode,
9
- type SlabNode,
10
- useScene,
11
- type WallNode,
12
- } from '@pascal-app/core'
13
- import { useViewer } from '@pascal-app/viewer'
14
- import { useCallback, useEffect, useRef, useState } from 'react'
15
- import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
16
- import { sfxEmitter } from '../../../lib/sfx-bus'
17
- import useEditor from '../../../store/use-editor'
18
- import { snapFenceDraftPoint } from '../fence/fence-drafting'
19
- import { CursorSphere } from '../shared/cursor-sphere'
20
-
21
- function translatePolygon(
22
- polygon: Array<[number, number]>,
23
- deltaX: number,
24
- deltaZ: number,
25
- ): Array<[number, number]> {
26
- return polygon.map(([x, z]) => [x + deltaX, z + deltaZ] as [number, number])
27
- }
28
-
29
- function getPolygonCenter(polygon: Array<[number, number]>): [number, number] {
30
- if (polygon.length === 0) return [0, 0]
31
- let sumX = 0
32
- let sumZ = 0
33
- for (const [x, z] of polygon) {
34
- sumX += x
35
- sumZ += z
36
- }
37
- return [sumX / polygon.length, sumZ / polygon.length]
38
- }
39
-
40
- export const MoveSlabTool: React.FC<{ node: SlabNode }> = ({ node }) => {
41
- const activatedAtRef = useRef<number>(Date.now())
42
- const originalPolygonRef = useRef(node.polygon.map(([x, z]) => [x, z] as [number, number]))
43
- const originalHolesRef = useRef(
44
- (node.holes ?? []).map((hole) => hole.map(([x, z]) => [x, z] as [number, number])),
45
- )
46
- const dragAnchorRef = useRef<[number, number] | null>(null)
47
- const previousGridPosRef = useRef<[number, number] | null>(null)
48
- const previewRef = useRef<{
49
- polygon: Array<[number, number]>
50
- holes: Array<Array<[number, number]>>
51
- } | null>(null)
52
-
53
- const [cursorLocalPos, setCursorLocalPos] = useState<[number, number, number]>(() => {
54
- const center = getPolygonCenter(node.polygon)
55
- return [center[0], 0, center[1]]
56
- })
57
-
58
- const exitMoveMode = useCallback(() => {
59
- useEditor.getState().setMovingNode(null)
60
- }, [])
61
-
62
- useEffect(() => {
63
- const originalPolygon = originalPolygonRef.current
64
- const originalHoles = originalHolesRef.current
65
- const levelNode =
66
- node.parentId && useScene.getState().nodes[node.parentId as AnyNodeId]?.type === 'level'
67
- ? (useScene.getState().nodes[node.parentId as AnyNodeId] as LevelNode)
68
- : null
69
- const levelChildren = levelNode?.children ?? []
70
- const levelWalls = levelChildren
71
- .map((childId) => useScene.getState().nodes[childId as AnyNodeId])
72
- .filter((child): child is WallNode => child?.type === 'wall')
73
- const levelFences = levelChildren
74
- .map((childId) => useScene.getState().nodes[childId as AnyNodeId])
75
- .filter((child): child is FenceNode => child?.type === 'fence')
76
-
77
- useScene.temporal.getState().pause()
78
- let wasCommitted = false
79
-
80
- const applyPreview = (
81
- polygon: Array<[number, number]>,
82
- holes: Array<Array<[number, number]>>,
83
- ) => {
84
- previewRef.current = { polygon, holes }
85
- const center = getPolygonCenter(polygon)
86
- setCursorLocalPos([center[0], 0, center[1]])
87
- useScene.getState().updateNode(node.id, { polygon, holes })
88
- useScene.getState().markDirty(node.id as AnyNodeId)
89
- }
90
-
91
- const restoreOriginal = () => {
92
- useScene.getState().updateNode(node.id, {
93
- holes: originalHoles,
94
- polygon: originalPolygon,
95
- })
96
- useScene.getState().markDirty(node.id as AnyNodeId)
97
- }
98
-
99
- const onGridMove = (event: GridEvent) => {
100
- const [localX, localZ] = snapFenceDraftPoint({
101
- point: [event.localPosition[0], event.localPosition[2]],
102
- walls: levelWalls,
103
- fences: levelFences,
104
- })
105
-
106
- if (
107
- previousGridPosRef.current &&
108
- (localX !== previousGridPosRef.current[0] || localZ !== previousGridPosRef.current[1])
109
- ) {
110
- sfxEmitter.emit('sfx:grid-snap')
111
- }
112
- previousGridPosRef.current = [localX, localZ]
113
-
114
- const anchor = dragAnchorRef.current ?? [localX, localZ]
115
- dragAnchorRef.current = anchor
116
-
117
- const deltaX = localX - anchor[0]
118
- const deltaZ = localZ - anchor[1]
119
-
120
- applyPreview(
121
- translatePolygon(originalPolygon, deltaX, deltaZ),
122
- originalHoles.map((hole) => translatePolygon(hole, deltaX, deltaZ)),
123
- )
124
- }
125
-
126
- const onGridClick = (event: GridEvent) => {
127
- if (Date.now() - activatedAtRef.current < 150) {
128
- event.nativeEvent?.stopPropagation?.()
129
- return
130
- }
131
-
132
- const preview = previewRef.current ?? { polygon: originalPolygon, holes: originalHoles }
133
-
134
- wasCommitted = true
135
-
136
- // Restore original baseline while paused so the next resume+update
137
- // registers as a single tracked change (undo reverts to original).
138
- useScene.getState().updateNode(node.id, {
139
- polygon: originalPolygon,
140
- holes: originalHoles,
141
- })
142
-
143
- useScene.temporal.getState().resume()
144
- useScene.getState().updateNode(node.id, preview)
145
- useScene.getState().markDirty(node.id as AnyNodeId)
146
- useScene.temporal.getState().pause()
147
-
148
- sfxEmitter.emit('sfx:item-place')
149
- useViewer.getState().setSelection({ selectedIds: [node.id] })
150
- exitMoveMode()
151
- event.nativeEvent?.stopPropagation?.()
152
- }
153
-
154
- const onCancel = () => {
155
- restoreOriginal()
156
- useViewer.getState().setSelection({ selectedIds: [node.id] })
157
- useScene.temporal.getState().resume()
158
- markToolCancelConsumed()
159
- exitMoveMode()
160
- }
161
-
162
- emitter.on('grid:move', onGridMove)
163
- emitter.on('grid:click', onGridClick)
164
- emitter.on('tool:cancel', onCancel)
165
-
166
- return () => {
167
- if (!wasCommitted) {
168
- restoreOriginal()
169
- }
170
- useScene.temporal.getState().resume()
171
- emitter.off('grid:move', onGridMove)
172
- emitter.off('grid:click', onGridClick)
173
- emitter.off('tool:cancel', onCancel)
174
- }
175
- }, [exitMoveMode, node.id])
176
-
177
- return (
178
- <group>
179
- <CursorSphere position={cursorLocalPos} showTooltip={false} />
180
- </group>
181
- )
182
- }
@@ -1,43 +0,0 @@
1
- import { resolveLevelId, type SlabNode, useScene } from '@pascal-app/core'
2
- import { useViewer } from '@pascal-app/viewer'
3
- import { useCallback } from 'react'
4
- import { PolygonEditor } from '../shared/polygon-editor'
5
-
6
- interface SlabBoundaryEditorProps {
7
- slabId: SlabNode['id']
8
- }
9
-
10
- /**
11
- * Slab boundary editor - allows editing slab polygon vertices for a specific slab
12
- * Uses the generic PolygonEditor component
13
- */
14
- export const SlabBoundaryEditor: React.FC<SlabBoundaryEditorProps> = ({ slabId }) => {
15
- const slabNode = useScene((state) => state.nodes[slabId])
16
- const updateNode = useScene((state) => state.updateNode)
17
- const setSelection = useViewer((state) => state.setSelection)
18
-
19
- const slab = slabNode?.type === 'slab' ? (slabNode as SlabNode) : null
20
-
21
- const handlePolygonChange = useCallback(
22
- (newPolygon: Array<[number, number]>) => {
23
- updateNode(slabId, { polygon: newPolygon })
24
- // Re-assert selection so the slab stays selected after the edit
25
- setSelection({ selectedIds: [slabId] })
26
- },
27
- [slabId, updateNode, setSelection],
28
- )
29
-
30
- if (!slab?.polygon || slab.polygon.length < 3) return null
31
-
32
- return (
33
- <PolygonEditor
34
- allowEdgeMove
35
- color="#a3a3a3"
36
- levelId={resolveLevelId(slab, useScene.getState().nodes)}
37
- minVertices={3}
38
- onPolygonChange={handlePolygonChange}
39
- polygon={slab.polygon}
40
- surfaceHeight={slab.elevation ?? 0.05}
41
- />
42
- )
43
- }
@@ -1,49 +0,0 @@
1
- import { resolveLevelId, type SlabNode, useScene } from '@pascal-app/core'
2
- import { useViewer } from '@pascal-app/viewer'
3
- import { useCallback } from 'react'
4
- import { PolygonEditor } from '../shared/polygon-editor'
5
-
6
- interface SlabHoleEditorProps {
7
- slabId: SlabNode['id']
8
- holeIndex: number
9
- }
10
-
11
- /**
12
- * Slab hole editor - allows editing a specific hole polygon within a slab
13
- * Uses the generic PolygonEditor component
14
- */
15
- export const SlabHoleEditor: React.FC<SlabHoleEditorProps> = ({ slabId, holeIndex }) => {
16
- const slabNode = useScene((state) => state.nodes[slabId])
17
- const updateNode = useScene((state) => state.updateNode)
18
- const setSelection = useViewer((state) => state.setSelection)
19
-
20
- const slab = slabNode?.type === 'slab' ? (slabNode as SlabNode) : null
21
- const holes = slab?.holes || []
22
- const hole = holes[holeIndex]
23
-
24
- const handlePolygonChange = useCallback(
25
- (newPolygon: Array<[number, number]>) => {
26
- const updatedHoles = [...holes]
27
- updatedHoles[holeIndex] = newPolygon
28
- updateNode(slabId, { holes: updatedHoles })
29
- // Re-assert selection so the slab stays selected after the edit
30
- setSelection({ selectedIds: [slabId] })
31
- },
32
- [slabId, holeIndex, holes, updateNode, setSelection],
33
- )
34
-
35
- if (!(slab && hole) || hole.length < 3) return null
36
-
37
- return (
38
- <PolygonEditor
39
- allowEdgeMove
40
- allowPolygonMove
41
- color="#ef4444"
42
- levelId={resolveLevelId(slab, useScene.getState().nodes)} // red for holes
43
- minVertices={3}
44
- onPolygonChange={handlePolygonChange}
45
- polygon={hole}
46
- surfaceHeight={slab.elevation ?? 0.05}
47
- />
48
- )
49
- }
@@ -1,322 +0,0 @@
1
- import { emitter, type GridEvent, type LevelNode, SlabNode, useScene } from '@pascal-app/core'
2
- import { useViewer } from '@pascal-app/viewer'
3
- import { useEffect, useMemo, useRef, useState } from 'react'
4
- import { BufferGeometry, DoubleSide, type Group, type Line, Shape, Vector3 } from 'three'
5
- import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
6
- import { EDITOR_LAYER } from '../../../lib/constants'
7
- import { sfxEmitter } from '../../../lib/sfx-bus'
8
- import { CursorSphere } from '../shared/cursor-sphere'
9
-
10
- const Y_OFFSET = 0.02
11
-
12
- /**
13
- * Snaps a point to the nearest axis-aligned or 45-degree diagonal from the last point
14
- */
15
- const calculateSnapPoint = (
16
- lastPoint: [number, number],
17
- currentPoint: [number, number],
18
- ): [number, number] => {
19
- const [x1, y1] = lastPoint
20
- const [x, y] = currentPoint
21
-
22
- const dx = x - x1
23
- const dy = y - y1
24
- const absDx = Math.abs(dx)
25
- const absDy = Math.abs(dy)
26
-
27
- // Calculate distances to horizontal, vertical, and diagonal lines
28
- const horizontalDist = absDy
29
- const verticalDist = absDx
30
- const diagonalDist = Math.abs(absDx - absDy)
31
-
32
- // Find the minimum distance to determine which axis to snap to
33
- const minDist = Math.min(horizontalDist, verticalDist, diagonalDist)
34
-
35
- if (minDist === diagonalDist) {
36
- // Snap to 45° diagonal
37
- const diagonalLength = Math.min(absDx, absDy)
38
- return [x1 + Math.sign(dx) * diagonalLength, y1 + Math.sign(dy) * diagonalLength]
39
- }
40
- if (minDist === horizontalDist) {
41
- // Snap to horizontal
42
- return [x, y1]
43
- }
44
- // Snap to vertical
45
- return [x1, y]
46
- }
47
-
48
- /**
49
- * Creates a slab with the given polygon points and returns its ID
50
- */
51
- const commitSlabDrawing = (levelId: LevelNode['id'], points: Array<[number, number]>): string => {
52
- const { createNode, nodes } = useScene.getState()
53
-
54
- // Count existing slabs for naming
55
- const slabCount = Object.values(nodes).filter((n) => n.type === 'slab').length
56
- const name = `Slab ${slabCount + 1}`
57
-
58
- const slab = SlabNode.parse({
59
- name,
60
- polygon: points,
61
- })
62
-
63
- createNode(slab, levelId)
64
- sfxEmitter.emit('sfx:structure-build')
65
- return slab.id
66
- }
67
-
68
- export const SlabTool: React.FC = () => {
69
- const cursorRef = useRef<Group>(null)
70
- const mainLineRef = useRef<Line>(null!)
71
- const closingLineRef = useRef<Line>(null!)
72
- const currentLevelId = useViewer((state) => state.selection.levelId)
73
- const setSelection = useViewer((state) => state.setSelection)
74
-
75
- const [points, setPoints] = useState<Array<[number, number]>>([])
76
- const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0])
77
- const [snappedCursorPosition, setSnappedCursorPosition] = useState<[number, number]>([0, 0])
78
- const [levelY, setLevelY] = useState(0)
79
- const previousSnappedPointRef = useRef<[number, number] | null>(null)
80
- const shiftPressed = useRef(false)
81
-
82
- // Update cursor position and lines on grid move
83
- useEffect(() => {
84
- if (!currentLevelId) return
85
-
86
- const onGridMove = (event: GridEvent) => {
87
- if (!cursorRef.current) return
88
-
89
- const gridX = Math.round(event.localPosition[0] * 2) / 2
90
- const gridZ = Math.round(event.localPosition[2] * 2) / 2
91
- const gridPosition: [number, number] = [gridX, gridZ]
92
-
93
- setCursorPosition(gridPosition)
94
- setLevelY(event.localPosition[1])
95
-
96
- // Calculate snapped display position (bypass snap when Shift is held)
97
- const lastPoint = points[points.length - 1]
98
- const displayPoint =
99
- shiftPressed.current || !lastPoint
100
- ? gridPosition
101
- : calculateSnapPoint(lastPoint, gridPosition)
102
- setSnappedCursorPosition(displayPoint)
103
-
104
- // Play snap sound when the snapped position actually changes (only when drawing)
105
- if (
106
- points.length > 0 &&
107
- previousSnappedPointRef.current &&
108
- (displayPoint[0] !== previousSnappedPointRef.current[0] ||
109
- displayPoint[1] !== previousSnappedPointRef.current[1])
110
- ) {
111
- sfxEmitter.emit('sfx:grid-snap')
112
- }
113
-
114
- previousSnappedPointRef.current = displayPoint
115
- cursorRef.current.position.set(displayPoint[0], event.localPosition[1], displayPoint[1])
116
- }
117
-
118
- const onGridClick = (_event: GridEvent) => {
119
- if (!currentLevelId) return
120
-
121
- // Use the last displayed snapped position (respects Shift state from onGridMove)
122
- const clickPoint = previousSnappedPointRef.current ?? cursorPosition
123
-
124
- // Check if clicking on the first point to close the shape
125
- const firstPoint = points[0]
126
- if (
127
- points.length >= 3 &&
128
- firstPoint &&
129
- Math.abs(clickPoint[0] - firstPoint[0]) < 0.25 &&
130
- Math.abs(clickPoint[1] - firstPoint[1]) < 0.25
131
- ) {
132
- // Create the slab and select it
133
- const slabId = commitSlabDrawing(currentLevelId, points)
134
- setSelection({ selectedIds: [slabId] })
135
- setPoints([])
136
- } else {
137
- // Add point to polygon
138
- setPoints([...points, clickPoint])
139
- }
140
- }
141
-
142
- const onGridDoubleClick = (_event: GridEvent) => {
143
- if (!currentLevelId) return
144
-
145
- // Need at least 3 points to form a polygon
146
- if (points.length >= 3) {
147
- const slabId = commitSlabDrawing(currentLevelId, points)
148
- setSelection({ selectedIds: [slabId] })
149
- setPoints([])
150
- }
151
- }
152
-
153
- const onCancel = () => {
154
- if (points.length > 0) markToolCancelConsumed()
155
- setPoints([])
156
- }
157
-
158
- const onKeyDown = (e: KeyboardEvent) => {
159
- if (e.key === 'Shift') shiftPressed.current = true
160
- }
161
- const onKeyUp = (e: KeyboardEvent) => {
162
- if (e.key === 'Shift') shiftPressed.current = false
163
- }
164
- document.addEventListener('keydown', onKeyDown)
165
- document.addEventListener('keyup', onKeyUp)
166
-
167
- emitter.on('grid:move', onGridMove)
168
- emitter.on('grid:click', onGridClick)
169
- emitter.on('grid:double-click', onGridDoubleClick)
170
- emitter.on('tool:cancel', onCancel)
171
-
172
- return () => {
173
- document.removeEventListener('keydown', onKeyDown)
174
- document.removeEventListener('keyup', onKeyUp)
175
- emitter.off('grid:move', onGridMove)
176
- emitter.off('grid:click', onGridClick)
177
- emitter.off('grid:double-click', onGridDoubleClick)
178
- emitter.off('tool:cancel', onCancel)
179
- }
180
- }, [currentLevelId, points, cursorPosition, setSelection])
181
-
182
- // Update line geometries when points change
183
- useEffect(() => {
184
- if (!(mainLineRef.current && closingLineRef.current)) return
185
-
186
- if (points.length === 0) {
187
- mainLineRef.current.visible = false
188
- closingLineRef.current.visible = false
189
- return
190
- }
191
-
192
- const y = levelY + Y_OFFSET
193
- const snappedCursor = snappedCursorPosition
194
-
195
- // Build main line points
196
- const linePoints: Vector3[] = points.map(([x, z]) => new Vector3(x, y, z))
197
- linePoints.push(new Vector3(snappedCursor[0], y, snappedCursor[1]))
198
-
199
- // Update main line
200
- if (linePoints.length >= 2) {
201
- mainLineRef.current.geometry.dispose()
202
- mainLineRef.current.geometry = new BufferGeometry().setFromPoints(linePoints)
203
- mainLineRef.current.visible = true
204
- } else {
205
- mainLineRef.current.visible = false
206
- }
207
-
208
- // Update closing line (from cursor back to first point)
209
- const firstPoint = points[0]
210
- if (points.length >= 2 && firstPoint) {
211
- const closingPoints = [
212
- new Vector3(snappedCursor[0], y, snappedCursor[1]),
213
- new Vector3(firstPoint[0], y, firstPoint[1]),
214
- ]
215
- closingLineRef.current.geometry.dispose()
216
- closingLineRef.current.geometry = new BufferGeometry().setFromPoints(closingPoints)
217
- closingLineRef.current.visible = true
218
- } else {
219
- closingLineRef.current.visible = false
220
- }
221
- }, [points, snappedCursorPosition, levelY])
222
-
223
- // Create preview shape when we have 3+ points
224
- const previewShape = useMemo(() => {
225
- if (points.length < 3) return null
226
-
227
- const snappedCursor = snappedCursorPosition
228
-
229
- const allPoints = [...points, snappedCursor]
230
-
231
- // THREE.Shape is in X-Y plane. After rotation of -PI/2 around X:
232
- // - Shape X -> World X
233
- // - Shape Y -> World -Z (so we negate Z to get correct orientation)
234
- const firstPt = allPoints[0]
235
- if (!firstPt) return null
236
-
237
- const shape = new Shape()
238
- shape.moveTo(firstPt[0], -firstPt[1])
239
-
240
- for (let i = 1; i < allPoints.length; i++) {
241
- const pt = allPoints[i]
242
- if (pt) {
243
- shape.lineTo(pt[0], -pt[1])
244
- }
245
- }
246
- shape.closePath()
247
-
248
- return shape
249
- }, [points, snappedCursorPosition])
250
-
251
- return (
252
- <group>
253
- {/* Cursor */}
254
- <CursorSphere ref={cursorRef} />
255
-
256
- {/* Preview fill */}
257
- {previewShape && (
258
- <mesh
259
- frustumCulled={false}
260
- layers={EDITOR_LAYER}
261
- position={[0, levelY + Y_OFFSET, 0]}
262
- rotation={[-Math.PI / 2, 0, 0]}
263
- >
264
- <shapeGeometry args={[previewShape]} />
265
- <meshBasicMaterial
266
- color="#818cf8"
267
- depthTest={false}
268
- opacity={0.15}
269
- side={DoubleSide}
270
- transparent
271
- />
272
- </mesh>
273
- )}
274
-
275
- {/* Main line */}
276
- {/* @ts-ignore */}
277
- <line
278
- frustumCulled={false}
279
- layers={EDITOR_LAYER}
280
- // @ts-expect-error
281
- ref={mainLineRef}
282
- renderOrder={1}
283
- visible={false}
284
- >
285
- <bufferGeometry />
286
- <lineBasicNodeMaterial color="#818cf8" depthTest={false} depthWrite={false} linewidth={3} />
287
- </line>
288
-
289
- {/* Closing line */}
290
- {/* @ts-ignore */}
291
- <line
292
- frustumCulled={false}
293
- layers={EDITOR_LAYER}
294
- // @ts-expect-error
295
- ref={closingLineRef}
296
- renderOrder={1}
297
- visible={false}
298
- >
299
- <bufferGeometry />
300
- <lineBasicNodeMaterial
301
- color="#818cf8"
302
- depthTest={false}
303
- depthWrite={false}
304
- linewidth={2}
305
- opacity={0.5}
306
- transparent
307
- />
308
- </line>
309
-
310
- {/* Point markers */}
311
- {points.map(([x, z], index) => (
312
- <CursorSphere
313
- color="#818cf8"
314
- height={0}
315
- key={index}
316
- position={[x, levelY + Y_OFFSET + 0.01, z]}
317
- showTooltip={false}
318
- />
319
- ))}
320
- </group>
321
- )
322
- }
@@ -1,101 +0,0 @@
1
- import '../../../three-types'
2
-
3
- import {
4
- emitter,
5
- type GridEvent,
6
- type SpawnNode,
7
- sceneRegistry,
8
- useLiveTransforms,
9
- useScene,
10
- } from '@pascal-app/core'
11
- import { useCallback, useEffect, useState } from 'react'
12
- import { Vector3 } from 'three'
13
- import { sfxEmitter } from '../../../lib/sfx-bus'
14
- import useEditor from '../../../store/use-editor'
15
- import { CursorSphere } from '../shared/cursor-sphere'
16
-
17
- const roundToHalf = (value: number) => Math.round(value * 2) / 2
18
- const worldVector = new Vector3()
19
-
20
- function getLevelLocalSpawnPosition(node: SpawnNode, event: GridEvent): [number, number, number] {
21
- const levelObject = node.parentId ? sceneRegistry.nodes.get(node.parentId) : null
22
- if (!levelObject) {
23
- return [
24
- roundToHalf(event.localPosition[0]),
25
- event.localPosition[1],
26
- roundToHalf(event.localPosition[2]),
27
- ]
28
- }
29
-
30
- worldVector.set(event.position[0], event.position[1], event.position[2])
31
- levelObject.updateWorldMatrix(true, false)
32
- levelObject.worldToLocal(worldVector)
33
-
34
- return [roundToHalf(worldVector.x), worldVector.y, roundToHalf(worldVector.z)]
35
- }
36
-
37
- export const MoveSpawnTool: React.FC<{
38
- node: SpawnNode
39
- onCommitted?: (nodeId: SpawnNode['id']) => void
40
- }> = ({ node, onCommitted }) => {
41
- const [previewPosition, setPreviewPosition] = useState<[number, number, number]>(node.position)
42
-
43
- const exitMoveMode = useCallback(() => {
44
- useEditor.getState().setMovingNode(null)
45
- }, [])
46
-
47
- useEffect(() => {
48
- useScene.temporal.getState().pause()
49
-
50
- let committed = false
51
-
52
- const onGridMove = (event: GridEvent) => {
53
- const nextPosition: [number, number, number] = [
54
- roundToHalf(event.localPosition[0]),
55
- event.localPosition[1],
56
- roundToHalf(event.localPosition[2]),
57
- ]
58
- setPreviewPosition(nextPosition)
59
- useLiveTransforms.getState().set(node.id, {
60
- position: [...nextPosition],
61
- rotation: node.rotation,
62
- })
63
- }
64
-
65
- const onGridClick = (event: GridEvent) => {
66
- const nextPosition = getLevelLocalSpawnPosition(node, event)
67
-
68
- committed = true
69
- useScene.temporal.getState().resume()
70
- useScene.getState().updateNode(node.id, { position: nextPosition })
71
- onCommitted?.(node.id)
72
- useLiveTransforms.getState().clear(node.id)
73
- sfxEmitter.emit('sfx:item-place')
74
- exitMoveMode()
75
- }
76
-
77
- const onCancel = () => {
78
- useLiveTransforms.getState().clear(node.id)
79
- useScene.temporal.getState().resume()
80
- exitMoveMode()
81
- }
82
-
83
- emitter.on('grid:move', onGridMove)
84
- emitter.on('grid:click', onGridClick)
85
- emitter.on('tool:cancel', onCancel)
86
-
87
- return () => {
88
- emitter.off('grid:move', onGridMove)
89
- emitter.off('grid:click', onGridClick)
90
- emitter.off('tool:cancel', onCancel)
91
- useLiveTransforms.getState().clear(node.id)
92
- if (!committed) {
93
- useScene.temporal.getState().resume()
94
- }
95
- }
96
- }, [exitMoveMode, node, onCommitted])
97
-
98
- return (
99
- <CursorSphere color="#60a5fa" height={2.2} position={previewPosition} showTooltip={false} />
100
- )
101
- }