@pascal-app/editor 0.7.0 → 0.9.1

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 (244) hide show
  1. package/package.json +12 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
  3. package/src/components/editor/custom-camera-controls.tsx +755 -25
  4. package/src/components/editor/editor-layout-mobile.tsx +3 -3
  5. package/src/components/editor/editor-layout-v2.tsx +62 -54
  6. package/src/components/editor/export-manager.tsx +32 -3
  7. package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
  8. package/src/components/editor/first-person/build-collider-world.ts +203 -87
  9. package/src/components/editor/first-person/bvh-ecctrl.tsx +127 -62
  10. package/src/components/editor/first-person-controls.tsx +836 -31
  11. package/src/components/editor/floating-action-menu.tsx +287 -196
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floorplan-panel.tsx +5837 -12006
  14. package/src/components/editor/grid.tsx +51 -17
  15. package/src/components/editor/group-move-handle.tsx +316 -0
  16. package/src/components/editor/group-rotate-handle.tsx +382 -0
  17. package/src/components/editor/group-transform-shared.test.ts +205 -0
  18. package/src/components/editor/group-transform-shared.ts +256 -0
  19. package/src/components/editor/handles/handle-arrow.tsx +523 -0
  20. package/src/components/editor/handles/use-handle-drag.ts +213 -0
  21. package/src/components/editor/index.tsx +234 -190
  22. package/src/components/editor/measurement-pill.tsx +75 -0
  23. package/src/components/editor/node-arrow-handles.tsx +1453 -0
  24. package/src/components/editor/selection-manager.tsx +435 -158
  25. package/src/components/editor/site-edge-labels.tsx +44 -20
  26. package/src/components/editor/slab-hole-highlights.tsx +466 -0
  27. package/src/components/editor/snapshot-capture-overlay.tsx +539 -0
  28. package/src/components/editor/thumbnail-generator.tsx +27 -64
  29. package/src/components/editor/use-floorplan-background-placement.ts +122 -35
  30. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  31. package/src/components/editor/use-floorplan-scene-data.ts +14 -2
  32. package/src/components/editor/wall-measurement-label.tsx +11 -6
  33. package/src/components/editor/wall-move-side-handles.tsx +855 -0
  34. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  35. package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
  36. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
  37. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  38. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  39. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
  40. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
  41. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  42. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
  43. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  44. package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
  45. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  46. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -61
  47. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
  48. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
  49. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +29 -14
  50. package/src/components/editor-2d/svg-paths.ts +10 -2
  51. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +30 -9
  52. package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
  53. package/src/components/systems/roof/roof-edit-system.tsx +86 -12
  54. package/src/components/systems/stair/stair-edit-system.tsx +2 -2
  55. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
  56. package/src/components/systems/zone/zone-system.tsx +0 -0
  57. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  58. package/src/components/tools/elevator/elevator-tool.tsx +349 -0
  59. package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
  60. package/src/components/tools/fence/fence-drafting.ts +64 -22
  61. package/src/components/tools/item/move-tool.tsx +40 -107
  62. package/src/components/tools/item/placement-math.test.ts +10 -0
  63. package/src/components/tools/item/placement-math.ts +33 -9
  64. package/src/components/tools/item/placement-strategies.ts +201 -25
  65. package/src/components/tools/item/placement-types.ts +10 -1
  66. package/src/components/tools/item/use-draft-node.ts +20 -2
  67. package/src/components/tools/item/use-placement-coordinator.tsx +700 -299
  68. package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
  69. package/src/components/tools/roof/roof-tool.tsx +108 -12
  70. package/src/components/tools/select/box-select-state.ts +77 -0
  71. package/src/components/tools/select/box-select-tool.tsx +324 -543
  72. package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
  73. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  74. package/src/components/tools/select/select-candidates.test.ts +109 -0
  75. package/src/components/tools/select/select-candidates.ts +85 -0
  76. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  77. package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
  78. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  79. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  80. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  81. package/src/components/tools/shared/placement-box.tsx +123 -0
  82. package/src/components/tools/shared/polygon-editor.tsx +617 -167
  83. package/src/components/tools/shared/segment-angle.ts +57 -1
  84. package/src/components/tools/site/site-boundary-editor.tsx +464 -12
  85. package/src/components/tools/site/site-flag-model.ts +18 -0
  86. package/src/components/tools/stair/stair-defaults.ts +1 -1
  87. package/src/components/tools/stair/stair-tool.tsx +269 -35
  88. package/src/components/tools/tool-manager.tsx +172 -56
  89. package/src/components/tools/wall/wall-drafting.ts +98 -98
  90. package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
  91. package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
  92. package/src/components/tools/zone/zone-tool.tsx +36 -11
  93. package/src/components/ui/action-menu/action-button.tsx +21 -1
  94. package/src/components/ui/action-menu/camera-actions.tsx +0 -0
  95. package/src/components/ui/action-menu/control-modes.tsx +5 -76
  96. package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
  97. package/src/components/ui/action-menu/index.tsx +33 -196
  98. package/src/components/ui/action-menu/structure-tools.tsx +13 -88
  99. package/src/components/ui/action-menu/view-toggles.tsx +262 -37
  100. package/src/components/ui/command-palette/editor-commands.tsx +24 -17
  101. package/src/components/ui/command-palette/index.tsx +6 -256
  102. package/src/components/ui/controls/material-paint-panel.tsx +83 -0
  103. package/src/components/ui/controls/material-picker.tsx +17 -35
  104. package/src/components/ui/controls/metric-control.tsx +33 -11
  105. package/src/components/ui/floating-level-selector.tsx +48 -11
  106. package/src/components/ui/helpers/helper-manager.tsx +21 -17
  107. package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
  108. package/src/components/ui/item-catalog/catalog-items.tsx +1743 -315
  109. package/src/components/ui/item-catalog/item-catalog.tsx +91 -46
  110. package/src/components/ui/level-duplicate-dialog.tsx +5 -6
  111. package/src/components/ui/panels/node-display.ts +1 -0
  112. package/src/components/ui/panels/paint-panel.tsx +66 -41
  113. package/src/components/ui/panels/panel-manager.tsx +33 -74
  114. package/src/components/ui/panels/panel-wrapper.tsx +217 -12
  115. package/src/components/ui/panels/parametric-inspector.tsx +469 -0
  116. package/src/components/ui/panels/reference-panel.tsx +28 -13
  117. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  118. package/src/components/ui/primitives/number-input.tsx +1 -1
  119. package/src/components/ui/primitives/sidebar.tsx +0 -0
  120. package/src/components/ui/scene-loader.tsx +1 -3
  121. package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
  122. package/src/components/ui/sidebar/icon-rail.tsx +0 -0
  123. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  124. package/src/components/ui/sidebar/panels/items-panel/index.tsx +404 -0
  125. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
  126. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
  127. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
  128. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  129. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
  130. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
  131. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
  132. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  133. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
  134. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
  135. package/src/components/ui/sidebar/panels/site-panel/index.tsx +8 -12
  136. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  137. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
  138. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
  139. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
  140. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
  141. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
  142. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -34
  144. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
  145. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +52 -3
  146. package/src/components/ui/sidebar/tab-bar.tsx +61 -1
  147. package/src/components/ui/slider.tsx +1 -1
  148. package/src/components/viewer-overlay.tsx +169 -52
  149. package/src/components/viewer-zone-system.tsx +1 -1
  150. package/src/hooks/use-auto-save.ts +24 -3
  151. package/src/hooks/use-ceiling-events.ts +175 -0
  152. package/src/hooks/use-drag-action.ts +124 -0
  153. package/src/hooks/use-keyboard.ts +125 -62
  154. package/src/hooks/use-selection.ts +64 -0
  155. package/src/index.tsx +243 -4
  156. package/src/lib/constants.ts +6 -2
  157. package/src/lib/editor-api.ts +62 -0
  158. package/src/lib/elevator-support.ts +96 -0
  159. package/src/lib/floorplan/apply-alignment.ts +111 -0
  160. package/src/lib/floorplan/index.ts +7 -0
  161. package/src/lib/floorplan/selection-tool.ts +40 -0
  162. package/src/lib/floorplan/stairs.ts +12 -1
  163. package/src/lib/fresh-planar-placement.test.ts +102 -0
  164. package/src/lib/fresh-planar-placement.ts +63 -0
  165. package/src/lib/history.ts +2 -1
  166. package/src/lib/level-duplication.test.ts +0 -2
  167. package/src/lib/level-duplication.ts +55 -43
  168. package/src/lib/level-name.ts +11 -0
  169. package/src/lib/material-paint.ts +212 -20
  170. package/src/lib/placement-metadata.ts +29 -0
  171. package/src/lib/planar-cursor-placement.test.ts +43 -0
  172. package/src/lib/planar-cursor-placement.ts +42 -0
  173. package/src/lib/roof-duplication.ts +2 -2
  174. package/src/lib/scene-bounds.ts +1 -1
  175. package/src/lib/scene-clipboard.ts +267 -0
  176. package/src/lib/scene.ts +13 -4
  177. package/src/lib/sfx-bus.ts +25 -1
  178. package/src/lib/sfx-player.ts +75 -27
  179. package/src/lib/site-boundary.ts +1 -0
  180. package/src/lib/stair-duplication.ts +2 -2
  181. package/src/lib/stair-levels.test.ts +146 -0
  182. package/src/lib/stair-levels.ts +177 -0
  183. package/src/lib/world-grid-snap.ts +199 -0
  184. package/src/lib/zone-content.ts +130 -0
  185. package/src/store/use-alignment-guides.ts +21 -0
  186. package/src/store/use-editor.tsx +342 -91
  187. package/src/store/use-placement-preview.ts +31 -0
  188. package/src/store/use-wall-move-ghosts.ts +36 -0
  189. package/src/store/use-wall-snap-indicator.ts +32 -0
  190. package/tsconfig.json +2 -1
  191. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  192. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  193. package/src/components/feedback-dialog.tsx +0 -265
  194. package/src/components/pascal-radio.tsx +0 -280
  195. package/src/components/preview-button.tsx +0 -16
  196. package/src/components/tools/building/move-building-tool.tsx +0 -157
  197. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  198. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  199. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  200. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -257
  201. package/src/components/tools/column/column-tool.tsx +0 -97
  202. package/src/components/tools/column/move-column-tool.tsx +0 -105
  203. package/src/components/tools/door/door-math.ts +0 -110
  204. package/src/components/tools/door/door-tool.tsx +0 -324
  205. package/src/components/tools/door/move-door-tool.tsx +0 -412
  206. package/src/components/tools/fence/curve-fence-tool.tsx +0 -179
  207. package/src/components/tools/fence/fence-tool.tsx +0 -346
  208. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -438
  209. package/src/components/tools/fence/move-fence-tool.tsx +0 -298
  210. package/src/components/tools/item/item-tool.tsx +0 -26
  211. package/src/components/tools/roof/move-roof-tool.tsx +0 -359
  212. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  213. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  214. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  215. package/src/components/tools/slab/slab-tool.tsx +0 -322
  216. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  217. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  218. package/src/components/tools/wall/curve-wall-tool.tsx +0 -176
  219. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -423
  220. package/src/components/tools/wall/move-wall-tool.tsx +0 -356
  221. package/src/components/tools/wall/wall-tool.tsx +0 -332
  222. package/src/components/tools/window/move-window-tool.tsx +0 -447
  223. package/src/components/tools/window/window-math.ts +0 -117
  224. package/src/components/tools/window/window-tool.tsx +0 -332
  225. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  226. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  227. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  228. package/src/components/ui/panels/ceiling-panel.tsx +0 -250
  229. package/src/components/ui/panels/column-panel.tsx +0 -715
  230. package/src/components/ui/panels/door-panel.tsx +0 -1292
  231. package/src/components/ui/panels/fence-panel.tsx +0 -227
  232. package/src/components/ui/panels/item-panel.tsx +0 -306
  233. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  234. package/src/components/ui/panels/roof-panel.tsx +0 -230
  235. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  236. package/src/components/ui/panels/slab-panel.tsx +0 -250
  237. package/src/components/ui/panels/spawn-panel.tsx +0 -155
  238. package/src/components/ui/panels/stair-panel.tsx +0 -525
  239. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  240. package/src/components/ui/panels/wall-panel.tsx +0 -177
  241. package/src/components/ui/panels/window-panel.tsx +0 -960
  242. package/src/components/ui/viewer-toolbar.tsx +0 -436
  243. package/src/contexts/presets-context.tsx +0 -121
  244. package/src/hooks/use-contextual-tools.ts +0 -61
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/editor",
3
- "version": "0.7.0",
3
+ "version": "0.9.1",
4
4
  "description": "Pascal building editor component",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,8 +11,8 @@
11
11
  "check-types": "tsc --noEmit"
12
12
  },
13
13
  "peerDependencies": {
14
- "@pascal-app/core": "^0.7.0",
15
- "@pascal-app/viewer": "^0.7.0",
14
+ "@pascal-app/core": "^0.9.1",
15
+ "@pascal-app/viewer": "^0.9.1",
16
16
  "@react-three/drei": "^10",
17
17
  "@react-three/fiber": "^9",
18
18
  "next": ">=15",
@@ -25,7 +25,7 @@
25
25
  "@dnd-kit/sortable": "^10.0.0",
26
26
  "@dnd-kit/utilities": "^3.2.2",
27
27
  "@iconify/react": "^6.0.2",
28
- "@number-flow/react": "^0.5.14",
28
+ "@number-flow/react": "^0.6.0",
29
29
  "@radix-ui/react-alert-dialog": "^1.1.15",
30
30
  "@radix-ui/react-context-menu": "^2.2.16",
31
31
  "@radix-ui/react-dialog": "^1.1.15",
@@ -38,30 +38,29 @@
38
38
  "@radix-ui/react-switch": "^1.2.6",
39
39
  "@radix-ui/react-tabs": "^1.1.13",
40
40
  "@radix-ui/react-tooltip": "^1.2.8",
41
- "@react-three/uikit-lucide": "^1.0.62",
42
- "@visual-json/react": "latest",
41
+ "@visual-json/react": "^0.4.0",
43
42
  "class-variance-authority": "^0.7.1",
44
43
  "clsx": "^2.1.1",
45
44
  "cmdk": "^1.1.1",
46
45
  "howler": "^2.2.4",
47
- "lucide-react": "^0.562.0",
46
+ "lucide-react": "^1.7.0",
48
47
  "mitt": "^3.0.1",
49
48
  "motion": "^12.34.3",
50
49
  "nanoid": "^5.1.6",
51
50
  "tailwind-merge": "^3.5.0",
52
- "three-mesh-bvh": "^0.9.8",
53
51
  "zod": "^4.3.6",
54
- "zustand": "^5.0.11"
52
+ "zustand": "^5.0.11",
53
+ "three-mesh-bvh": "~0.9.8"
55
54
  },
56
55
  "devDependencies": {
57
- "@pascal-app/core": "^0.7.0",
58
- "@pascal-app/viewer": "^0.7.0",
56
+ "@pascal-app/core": "^0.9.1",
57
+ "@pascal-app/viewer": "^0.9.1",
59
58
  "@pascal/typescript-config": "*",
59
+ "@types/bun": "^1.3.0",
60
60
  "@types/howler": "^2.2.12",
61
- "@types/node": "^22.19.12",
62
61
  "@types/react": "19.2.2",
63
62
  "@types/react-dom": "19.2.2",
64
63
  "@types/three": "^0.184.0",
65
- "typescript": "5.9.3"
64
+ "typescript": "6.0.3"
66
65
  }
67
66
  }
@@ -0,0 +1,160 @@
1
+ 'use client'
2
+
3
+ import { type AlignmentGuide, sceneRegistry } from '@pascal-app/core'
4
+ import { useAlignmentGuides } from '@pascal-app/editor'
5
+ import { useViewer } from '@pascal-app/viewer'
6
+ import { Html } from '@react-three/drei'
7
+ import { useFrame } from '@react-three/fiber'
8
+ import { memo, useMemo, useRef } from 'react'
9
+ import { BoxGeometry, CircleGeometry, type Group, Vector3 } from 'three'
10
+ import { MeshBasicNodeMaterial } from 'three/webgpu'
11
+ import { EDITOR_LAYER } from '../../lib/constants'
12
+ import { formatMeasurement } from './measurement-pill'
13
+
14
+ /**
15
+ * Figma-style alignment guides for the 3D editor — the spatial twin of
16
+ * `FloorplanAlignmentGuideLayer`. Subscribes to the shared
17
+ * `useAlignmentGuides` store (published by the move / placement / wall tools
18
+ * during a drag) and draws each guide as a dashed ribbon on the floor with a
19
+ * flat circular marker at each endpoint and a distance pill.
20
+ *
21
+ * The dashes + dots lie flat on the floor plane (XZ) — a ground ribbon, like
22
+ * the design reference — so they're real 3D geometry, not screen billboards.
23
+ * Only the distance pill is screen-space (`<Html>`).
24
+ *
25
+ * Guide coordinates are XZ meters in the WORLD frame — alignment now resolves
26
+ * on the world axes (via `resolveAlignmentForActiveBuilding`) so the guides
27
+ * stay parallel to the visible world grid even when the active building is
28
+ * rotated. This layer is mounted OUTSIDE the building-local ToolManager group
29
+ * for the same reason. The whole ribbon is lifted to the active level's WORLD
30
+ * Y each frame so it lies on the floor being edited — not the building base —
31
+ * when floors are stacked.
32
+ */
33
+
34
+ const LINE_COLOR = 0x81_8c_f8 // indigo-400 — matches the editor's selection accent (box-select / wall highlights)
35
+ const PILL_COLOR = '#6366f1' // indigo-500 — same hue, darker for white-text contrast
36
+ const GUIDE_Y = 0.03 // small lift so guides read above the floor grid
37
+ const DASH_LEN = 0.18 // world-meter dash length
38
+ const DASH_GAP = 0.12 // world-meter gap between dashes
39
+ const LINE_WIDTH = 0.06 // world-meter ribbon thickness
40
+ const DOT_RADIUS = 0.11 // world-meter radius of the endpoint markers
41
+ const MAX_DASHES = 80 // cap so a very long guide can't spawn thousands of quads
42
+
43
+ // Shared resources — one violet material + unit geometries scaled per
44
+ // instance, so guide churn during a drag doesn't rebuild GPU buffers.
45
+ const guideMaterial = new MeshBasicNodeMaterial({
46
+ color: LINE_COLOR,
47
+ depthTest: false,
48
+ depthWrite: false,
49
+ toneMapped: false,
50
+ transparent: true,
51
+ })
52
+ const DASH_GEOMETRY = new BoxGeometry(1, 1, 1)
53
+ const DOT_GEOMETRY = new CircleGeometry(1, 24)
54
+
55
+ type Vec3 = [number, number, number]
56
+
57
+ export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
58
+ const guides = useAlignmentGuides((s) => s.guides)
59
+ const levelId = useViewer((s) => s.selection.levelId)
60
+ const unit = useViewer((s) => s.unit)
61
+ const groupRef = useRef<Group>(null)
62
+
63
+ // Guides carry only XZ in WORLD coords; their Y has to track the active
64
+ // level's world Y so the ground ribbon lies on the floor being edited,
65
+ // not the building base. `getWorldPosition` walks the level mesh's
66
+ // parents (building / site) so it stays correct even if the building has
67
+ // a Y offset.
68
+ const worldYWork = useMemo(() => new Vector3(), [])
69
+ useFrame(() => {
70
+ const group = groupRef.current
71
+ if (!group) return
72
+ const levelMesh = levelId ? sceneRegistry.nodes.get(levelId) : null
73
+ group.position.y = levelMesh ? levelMesh.getWorldPosition(worldYWork).y : 0
74
+ })
75
+
76
+ if (guides.length === 0) return null
77
+ return (
78
+ <group ref={groupRef}>
79
+ {guides.map((guide, i) => (
80
+ <GuideLine guide={guide} key={i} unit={unit} />
81
+ ))}
82
+ </group>
83
+ )
84
+ })
85
+
86
+ function GuideLine({ guide, unit }: { guide: AlignmentGuide; unit: 'metric' | 'imperial' }) {
87
+ const { x: fx, z: fz } = guide.from
88
+ const { x: tx, z: tz } = guide.to
89
+ const distLabel = formatMeasurement(guide.distance, unit)
90
+
91
+ // Lay out the dash centres along the from→to direction. The ribbon
92
+ // stretches the dash period up if the line is long enough to exceed the
93
+ // dash cap, so it always reads as a continuous dashed line.
94
+ const { dashes, angleY } = useMemo(() => {
95
+ const dx = tx - fx
96
+ const dz = tz - fz
97
+ const length = Math.hypot(dx, dz)
98
+ const angle = -Math.atan2(dz, dx)
99
+ if (length < 1e-4) return { dashes: [] as Vec3[], angleY: angle }
100
+ const ux = dx / length
101
+ const uz = dz / length
102
+ const period = Math.max(DASH_LEN + DASH_GAP, length / MAX_DASHES)
103
+ const centres: Vec3[] = []
104
+ for (let d = period / 2; d - DASH_LEN / 2 <= length; d += period) {
105
+ centres.push([fx + ux * d, GUIDE_Y, fz + uz * d])
106
+ }
107
+ return { dashes: centres, angleY: angle }
108
+ }, [fx, fz, tx, tz])
109
+
110
+ const mid: Vec3 = [(fx + tx) / 2, GUIDE_Y, (fz + tz) / 2]
111
+
112
+ return (
113
+ <>
114
+ {dashes.map((centre, i) => (
115
+ <mesh
116
+ geometry={DASH_GEOMETRY}
117
+ key={i}
118
+ layers={EDITOR_LAYER}
119
+ material={guideMaterial}
120
+ position={centre}
121
+ renderOrder={1000}
122
+ rotation={[0, angleY, 0]}
123
+ scale={[DASH_LEN, 0.002, LINE_WIDTH]}
124
+ />
125
+ ))}
126
+ <Dot position={[fx, GUIDE_Y + 0.001, fz]} />
127
+ <Dot position={[tx, GUIDE_Y + 0.001, tz]} />
128
+ {guide.distance > 1e-4 && (
129
+ <Html
130
+ center
131
+ position={mid}
132
+ style={{ pointerEvents: 'none', userSelect: 'none' }}
133
+ zIndexRange={[20, 0]}
134
+ >
135
+ <div
136
+ className="whitespace-nowrap rounded-[3px] px-[5px] py-[2px] font-medium font-sans text-[11px] text-white"
137
+ style={{ backgroundColor: PILL_COLOR }}
138
+ >
139
+ {distLabel}
140
+ </div>
141
+ </Html>
142
+ )}
143
+ </>
144
+ )
145
+ }
146
+
147
+ /** Flat circular marker lying on the floor plane at a guide endpoint. */
148
+ function Dot({ position }: { position: Vec3 }) {
149
+ return (
150
+ <mesh
151
+ geometry={DOT_GEOMETRY}
152
+ layers={EDITOR_LAYER}
153
+ material={guideMaterial}
154
+ position={position}
155
+ renderOrder={1001}
156
+ rotation={[-Math.PI / 2, 0, 0]}
157
+ scale={[DOT_RADIUS, DOT_RADIUS, DOT_RADIUS]}
158
+ />
159
+ )
160
+ }