@pascal-app/editor 0.8.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 (223) hide show
  1. package/package.json +8 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
  3. package/src/components/editor/custom-camera-controls.tsx +754 -25
  4. package/src/components/editor/editor-layout-mobile.tsx +3 -3
  5. package/src/components/editor/editor-layout-v2.tsx +58 -51
  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 +201 -83
  9. package/src/components/editor/first-person/bvh-ecctrl.tsx +8 -8
  10. package/src/components/editor/first-person-controls.tsx +825 -20
  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 +5829 -12005
  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 +178 -149
  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 +433 -156
  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 +136 -62
  28. package/src/components/editor/thumbnail-generator.tsx +9 -3
  29. package/src/components/editor/use-floorplan-background-placement.ts +119 -32
  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-geometry-renderer.tsx +266 -0
  45. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  46. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
  47. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
  48. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  49. package/src/components/editor-2d/svg-paths.ts +10 -2
  50. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +26 -3
  51. package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
  52. package/src/components/systems/roof/roof-edit-system.tsx +86 -12
  53. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  54. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  55. package/src/components/tools/elevator/elevator-tool.tsx +349 -0
  56. package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
  57. package/src/components/tools/fence/fence-drafting.ts +64 -22
  58. package/src/components/tools/item/move-tool.tsx +41 -105
  59. package/src/components/tools/item/placement-math.test.ts +10 -0
  60. package/src/components/tools/item/placement-math.ts +31 -5
  61. package/src/components/tools/item/placement-strategies.ts +190 -15
  62. package/src/components/tools/item/placement-types.ts +10 -1
  63. package/src/components/tools/item/use-draft-node.ts +20 -1
  64. package/src/components/tools/item/use-placement-coordinator.tsx +694 -191
  65. package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
  66. package/src/components/tools/roof/roof-tool.tsx +108 -12
  67. package/src/components/tools/select/box-select-state.ts +77 -0
  68. package/src/components/tools/select/box-select-tool.tsx +324 -538
  69. package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
  70. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  71. package/src/components/tools/select/select-candidates.test.ts +109 -0
  72. package/src/components/tools/select/select-candidates.ts +85 -0
  73. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  74. package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
  75. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  76. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  77. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  78. package/src/components/tools/shared/placement-box.tsx +123 -0
  79. package/src/components/tools/shared/polygon-editor.tsx +617 -167
  80. package/src/components/tools/shared/segment-angle.ts +56 -0
  81. package/src/components/tools/site/site-boundary-editor.tsx +464 -12
  82. package/src/components/tools/site/site-flag-model.ts +18 -0
  83. package/src/components/tools/stair/stair-defaults.ts +1 -1
  84. package/src/components/tools/stair/stair-tool.tsx +269 -35
  85. package/src/components/tools/tool-manager.tsx +172 -56
  86. package/src/components/tools/wall/wall-drafting.ts +98 -98
  87. package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
  88. package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
  89. package/src/components/tools/zone/zone-tool.tsx +16 -6
  90. package/src/components/ui/action-menu/action-button.tsx +21 -1
  91. package/src/components/ui/action-menu/control-modes.tsx +5 -82
  92. package/src/components/ui/action-menu/index.tsx +1 -113
  93. package/src/components/ui/action-menu/structure-tools.tsx +13 -88
  94. package/src/components/ui/action-menu/view-toggles.tsx +243 -6
  95. package/src/components/ui/command-palette/editor-commands.tsx +18 -13
  96. package/src/components/ui/command-palette/index.tsx +2 -1
  97. package/src/components/ui/controls/material-paint-panel.tsx +83 -0
  98. package/src/components/ui/controls/material-picker.tsx +11 -32
  99. package/src/components/ui/controls/metric-control.tsx +33 -11
  100. package/src/components/ui/floating-level-selector.tsx +47 -10
  101. package/src/components/ui/helpers/helper-manager.tsx +16 -17
  102. package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
  103. package/src/components/ui/item-catalog/catalog-items.tsx +1 -0
  104. package/src/components/ui/item-catalog/item-catalog.tsx +3 -0
  105. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  106. package/src/components/ui/panels/node-display.ts +1 -0
  107. package/src/components/ui/panels/panel-manager.tsx +35 -47
  108. package/src/components/ui/panels/panel-wrapper.tsx +217 -12
  109. package/src/components/ui/panels/parametric-inspector.tsx +469 -0
  110. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  111. package/src/components/ui/scene-loader.tsx +1 -3
  112. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  113. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  114. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
  115. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
  116. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
  117. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  118. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
  119. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
  120. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
  121. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  122. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
  123. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
  124. package/src/components/ui/sidebar/panels/site-panel/index.tsx +4 -6
  125. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  126. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
  127. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
  128. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
  129. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
  130. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
  131. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  132. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -36
  133. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  134. package/src/components/ui/sidebar/tab-bar.tsx +61 -1
  135. package/src/components/viewer-overlay.tsx +169 -52
  136. package/src/components/viewer-zone-system.tsx +1 -1
  137. package/src/hooks/use-auto-save.ts +10 -3
  138. package/src/hooks/use-ceiling-events.ts +175 -0
  139. package/src/hooks/use-drag-action.ts +124 -0
  140. package/src/hooks/use-keyboard.ts +115 -62
  141. package/src/hooks/use-selection.ts +64 -0
  142. package/src/index.tsx +237 -5
  143. package/src/lib/constants.ts +6 -2
  144. package/src/lib/editor-api.ts +62 -0
  145. package/src/lib/elevator-support.ts +96 -0
  146. package/src/lib/floorplan/apply-alignment.ts +111 -0
  147. package/src/lib/floorplan/index.ts +7 -0
  148. package/src/lib/floorplan/selection-tool.ts +40 -0
  149. package/src/lib/floorplan/stairs.ts +12 -1
  150. package/src/lib/fresh-planar-placement.test.ts +102 -0
  151. package/src/lib/fresh-planar-placement.ts +63 -0
  152. package/src/lib/history.ts +2 -1
  153. package/src/lib/level-duplication.ts +54 -42
  154. package/src/lib/level-name.ts +11 -0
  155. package/src/lib/material-paint.ts +211 -19
  156. package/src/lib/placement-metadata.ts +29 -0
  157. package/src/lib/planar-cursor-placement.test.ts +43 -0
  158. package/src/lib/planar-cursor-placement.ts +42 -0
  159. package/src/lib/roof-duplication.ts +1 -1
  160. package/src/lib/scene-clipboard.ts +267 -0
  161. package/src/lib/scene.ts +13 -4
  162. package/src/lib/sfx-bus.ts +23 -1
  163. package/src/lib/sfx-player.ts +70 -22
  164. package/src/lib/site-boundary.ts +1 -0
  165. package/src/lib/stair-levels.test.ts +146 -0
  166. package/src/lib/stair-levels.ts +177 -0
  167. package/src/lib/world-grid-snap.ts +199 -0
  168. package/src/lib/zone-content.ts +130 -0
  169. package/src/store/use-alignment-guides.ts +21 -0
  170. package/src/store/use-editor.tsx +320 -37
  171. package/src/store/use-placement-preview.ts +31 -0
  172. package/src/store/use-wall-move-ghosts.ts +36 -0
  173. package/src/store/use-wall-snap-indicator.ts +32 -0
  174. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  175. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  176. package/src/components/tools/building/move-building-tool.tsx +0 -157
  177. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  178. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  179. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  180. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  181. package/src/components/tools/column/column-tool.tsx +0 -97
  182. package/src/components/tools/column/move-column-tool.tsx +0 -105
  183. package/src/components/tools/door/door-math.ts +0 -110
  184. package/src/components/tools/door/door-tool.tsx +0 -324
  185. package/src/components/tools/door/move-door-tool.tsx +0 -412
  186. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  187. package/src/components/tools/fence/fence-tool.tsx +0 -346
  188. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  189. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  190. package/src/components/tools/item/item-tool.tsx +0 -26
  191. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  192. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  193. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  194. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  195. package/src/components/tools/slab/slab-tool.tsx +0 -322
  196. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  197. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  198. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  199. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  200. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  201. package/src/components/tools/wall/wall-tool.tsx +0 -332
  202. package/src/components/tools/window/move-window-tool.tsx +0 -447
  203. package/src/components/tools/window/window-math.ts +0 -117
  204. package/src/components/tools/window/window-tool.tsx +0 -332
  205. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  206. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  207. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  208. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  209. package/src/components/ui/panels/column-panel.tsx +0 -759
  210. package/src/components/ui/panels/door-panel.tsx +0 -1299
  211. package/src/components/ui/panels/fence-panel.tsx +0 -222
  212. package/src/components/ui/panels/item-panel.tsx +0 -306
  213. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  214. package/src/components/ui/panels/roof-panel.tsx +0 -280
  215. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  216. package/src/components/ui/panels/slab-panel.tsx +0 -250
  217. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  218. package/src/components/ui/panels/stair-panel.tsx +0 -577
  219. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  220. package/src/components/ui/panels/wall-panel.tsx +0 -273
  221. package/src/components/ui/panels/window-panel.tsx +0 -968
  222. package/src/contexts/presets-context.tsx +0 -121
  223. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -1,346 +0,0 @@
1
- import {
2
- emitter,
3
- type FenceNode,
4
- type GridEvent,
5
- type LevelNode,
6
- useScene,
7
- type WallNode,
8
- } from '@pascal-app/core'
9
- import { useViewer } from '@pascal-app/viewer'
10
- import { Html } from '@react-three/drei'
11
- import { useEffect, useRef, useState } from 'react'
12
- import { DoubleSide, type Group, type Mesh, Shape, ShapeGeometry, Vector3 } from 'three'
13
- import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
14
- import { EDITOR_LAYER } from '../../../lib/constants'
15
- import { sfxEmitter } from '../../../lib/sfx-bus'
16
- import { CursorSphere } from '../shared/cursor-sphere'
17
- import {
18
- formatAngleRadians,
19
- getAngleToSegmentReference,
20
- getSegmentAngleReferenceAtPoint,
21
- } from '../shared/segment-angle'
22
- import {
23
- createFenceOnCurrentLevel,
24
- type FencePlanPoint,
25
- snapFenceDraftPoint,
26
- } from './fence-drafting'
27
-
28
- const FENCE_PREVIEW_HEIGHT = 1.8
29
- const DRAFT_LABEL_Y = FENCE_PREVIEW_HEIGHT + 0.22
30
- const DRAFT_ANGLE_LABEL_Y = 0.28
31
-
32
- type DraftAngleLabel = {
33
- id: string
34
- label: string
35
- position: [number, number, number]
36
- }
37
-
38
- type DraftMeasurementState = {
39
- lengthLabel: string
40
- lengthPosition: [number, number, number]
41
- angleLabels: DraftAngleLabel[]
42
- } | null
43
-
44
- type SegmentLike = {
45
- id: string
46
- start: FencePlanPoint
47
- end: FencePlanPoint
48
- curveOffset?: number
49
- }
50
-
51
- function formatMeasurement(value: number, unit: 'metric' | 'imperial') {
52
- if (unit === 'imperial') {
53
- const feet = value * 3.280_84
54
- const wholeFeet = Math.floor(feet)
55
- const inches = Math.round((feet - wholeFeet) * 12)
56
- if (inches === 12) return `${wholeFeet + 1}'0"`
57
- return `${wholeFeet}'${inches}"`
58
- }
59
-
60
- return `${Number.parseFloat(value.toFixed(2))}m`
61
- }
62
-
63
- function getDraftAngleLabels(
64
- start: FencePlanPoint,
65
- end: FencePlanPoint,
66
- segments: SegmentLike[],
67
- ): DraftAngleLabel[] {
68
- const draftFromStart: FencePlanPoint = [end[0] - start[0], end[1] - start[1]]
69
- const draftFromEnd: FencePlanPoint = [start[0] - end[0], start[1] - end[1]]
70
- const endpoints = [
71
- { id: 'start', point: start, draftVector: draftFromStart },
72
- { id: 'end', point: end, draftVector: draftFromEnd },
73
- ]
74
- const labels: DraftAngleLabel[] = []
75
-
76
- for (const endpoint of endpoints) {
77
- const connectedSegment = segments.find((segment) =>
78
- Boolean(getSegmentAngleReferenceAtPoint(endpoint.point, segment)),
79
- )
80
- if (!connectedSegment) continue
81
-
82
- const connectedReference = getSegmentAngleReferenceAtPoint(endpoint.point, connectedSegment)
83
- if (!connectedReference) continue
84
-
85
- const angle = getAngleToSegmentReference(endpoint.draftVector, connectedReference)
86
- if (angle === null) continue
87
-
88
- labels.push({
89
- id: endpoint.id,
90
- label: formatAngleRadians(angle),
91
- position: [endpoint.point[0], DRAFT_ANGLE_LABEL_Y, endpoint.point[1]],
92
- })
93
- }
94
-
95
- return labels
96
- }
97
-
98
- function getDraftMeasurementState(
99
- start: FencePlanPoint,
100
- end: FencePlanPoint,
101
- segments: SegmentLike[],
102
- unit: 'metric' | 'imperial',
103
- ): DraftMeasurementState {
104
- const dx = end[0] - start[0]
105
- const dz = end[1] - start[1]
106
- const length = Math.hypot(dx, dz)
107
-
108
- if (length < 0.01) return null
109
-
110
- return {
111
- lengthLabel: formatMeasurement(length, unit),
112
- lengthPosition: [(start[0] + end[0]) / 2, DRAFT_LABEL_Y, (start[1] + end[1]) / 2],
113
- angleLabels: getDraftAngleLabels(start, end, segments),
114
- }
115
- }
116
-
117
- function getReferenceSegments(walls: WallNode[], fences: FenceNode[]): SegmentLike[] {
118
- return [
119
- ...walls.map((wall) => ({
120
- id: wall.id,
121
- start: wall.start,
122
- end: wall.end,
123
- curveOffset: wall.curveOffset,
124
- })),
125
- ...fences.map((fence) => ({
126
- id: fence.id,
127
- start: fence.start,
128
- end: fence.end,
129
- curveOffset: fence.curveOffset,
130
- })),
131
- ]
132
- }
133
-
134
- const updateFencePreview = (mesh: Mesh, start: Vector3, end: Vector3) => {
135
- const direction = new Vector3(end.x - start.x, 0, end.z - start.z)
136
- const length = direction.length()
137
-
138
- if (length < 0.01) {
139
- mesh.visible = false
140
- return
141
- }
142
-
143
- mesh.visible = true
144
- direction.normalize()
145
-
146
- const shape = new Shape()
147
- shape.moveTo(0, 0)
148
- shape.lineTo(length, 0)
149
- shape.lineTo(length, FENCE_PREVIEW_HEIGHT)
150
- shape.lineTo(0, FENCE_PREVIEW_HEIGHT)
151
- shape.closePath()
152
-
153
- const geometry = new ShapeGeometry(shape)
154
- const angle = -Math.atan2(direction.z, direction.x)
155
-
156
- mesh.position.set(start.x, start.y, start.z)
157
- mesh.rotation.y = angle
158
-
159
- if (mesh.geometry) {
160
- mesh.geometry.dispose()
161
- }
162
- mesh.geometry = geometry
163
- }
164
-
165
- const getCurrentLevelElements = (): { walls: WallNode[]; fences: FenceNode[] } => {
166
- const currentLevelId = useViewer.getState().selection.levelId
167
- const { nodes } = useScene.getState()
168
-
169
- if (!currentLevelId) return { walls: [], fences: [] }
170
-
171
- const levelNode = nodes[currentLevelId]
172
- if (!levelNode || levelNode.type !== 'level') return { walls: [], fences: [] }
173
-
174
- const children = (levelNode as LevelNode).children.map((childId) => nodes[childId])
175
-
176
- return {
177
- walls: children.filter((node): node is WallNode => node?.type === 'wall'),
178
- fences: children.filter((node): node is FenceNode => node?.type === 'fence'),
179
- }
180
- }
181
-
182
- export const FenceTool: React.FC = () => {
183
- const unit = useViewer((state) => state.unit)
184
- const cursorRef = useRef<Group>(null)
185
- const previewRef = useRef<Mesh>(null!)
186
- const startingPoint = useRef(new Vector3(0, 0, 0))
187
- const endingPoint = useRef(new Vector3(0, 0, 0))
188
- const buildingState = useRef(0)
189
- const shiftPressed = useRef(false)
190
- const [draftMeasurement, setDraftMeasurement] = useState<DraftMeasurementState>(null)
191
-
192
- useEffect(() => {
193
- let previousFenceEnd: [number, number] | null = null
194
-
195
- const onGridMove = (event: GridEvent) => {
196
- if (!(cursorRef.current && previewRef.current)) return
197
-
198
- const { walls, fences } = getCurrentLevelElements()
199
- const localPoint: FencePlanPoint = [event.localPosition[0], event.localPosition[2]]
200
-
201
- if (buildingState.current === 1) {
202
- const snappedLocal = snapFenceDraftPoint({
203
- point: localPoint,
204
- walls,
205
- fences,
206
- start: [startingPoint.current.x, startingPoint.current.z],
207
- angleSnap: !shiftPressed.current,
208
- })
209
- endingPoint.current.set(snappedLocal[0], event.localPosition[1], snappedLocal[1])
210
- cursorRef.current.position.copy(endingPoint.current)
211
-
212
- const currentFenceEnd: [number, number] = [snappedLocal[0], snappedLocal[1]]
213
- if (
214
- previousFenceEnd &&
215
- (currentFenceEnd[0] !== previousFenceEnd[0] || currentFenceEnd[1] !== previousFenceEnd[1])
216
- ) {
217
- sfxEmitter.emit('sfx:grid-snap')
218
- }
219
- previousFenceEnd = currentFenceEnd
220
-
221
- updateFencePreview(previewRef.current, startingPoint.current, endingPoint.current)
222
- setDraftMeasurement(
223
- getDraftMeasurementState(
224
- [startingPoint.current.x, startingPoint.current.z],
225
- snappedLocal,
226
- getReferenceSegments(walls, fences),
227
- unit,
228
- ),
229
- )
230
- } else {
231
- const snappedPoint = snapFenceDraftPoint({ point: localPoint, walls, fences })
232
- cursorRef.current.position.set(snappedPoint[0], event.localPosition[1], snappedPoint[1])
233
- setDraftMeasurement(null)
234
- }
235
- }
236
-
237
- const onGridClick = (event: GridEvent) => {
238
- const { walls, fences } = getCurrentLevelElements()
239
- const localClick: FencePlanPoint = [event.localPosition[0], event.localPosition[2]]
240
-
241
- if (buildingState.current === 0) {
242
- const snappedStart = snapFenceDraftPoint({ point: localClick, walls, fences })
243
- startingPoint.current.set(snappedStart[0], event.localPosition[1], snappedStart[1])
244
- endingPoint.current.copy(startingPoint.current)
245
- buildingState.current = 1
246
- previewRef.current.visible = true
247
- setDraftMeasurement(null)
248
- } else {
249
- const snappedEnd = snapFenceDraftPoint({
250
- point: localClick,
251
- walls,
252
- fences,
253
- start: [startingPoint.current.x, startingPoint.current.z],
254
- angleSnap: !shiftPressed.current,
255
- })
256
- const dx = snappedEnd[0] - startingPoint.current.x
257
- const dz = snappedEnd[1] - startingPoint.current.z
258
- if (dx * dx + dz * dz < 0.01 * 0.01) return
259
- createFenceOnCurrentLevel([startingPoint.current.x, startingPoint.current.z], snappedEnd)
260
- previewRef.current.visible = false
261
- buildingState.current = 0
262
- setDraftMeasurement(null)
263
- }
264
- }
265
-
266
- const onKeyDown = (e: KeyboardEvent) => {
267
- if (e.key === 'Shift') shiftPressed.current = true
268
- }
269
-
270
- const onKeyUp = (e: KeyboardEvent) => {
271
- if (e.key === 'Shift') shiftPressed.current = false
272
- }
273
-
274
- const onCancel = () => {
275
- if (buildingState.current === 1) {
276
- markToolCancelConsumed()
277
- buildingState.current = 0
278
- previewRef.current.visible = false
279
- setDraftMeasurement(null)
280
- }
281
- }
282
-
283
- emitter.on('grid:move', onGridMove)
284
- emitter.on('grid:click', onGridClick)
285
- emitter.on('tool:cancel', onCancel)
286
- window.addEventListener('keydown', onKeyDown)
287
- window.addEventListener('keyup', onKeyUp)
288
-
289
- return () => {
290
- emitter.off('grid:move', onGridMove)
291
- emitter.off('grid:click', onGridClick)
292
- emitter.off('tool:cancel', onCancel)
293
- window.removeEventListener('keydown', onKeyDown)
294
- window.removeEventListener('keyup', onKeyUp)
295
- }
296
- }, [unit])
297
-
298
- return (
299
- <group>
300
- <CursorSphere height={FENCE_PREVIEW_HEIGHT} ref={cursorRef} />
301
- <mesh layers={EDITOR_LAYER} ref={previewRef} renderOrder={1} visible={false}>
302
- <shapeGeometry />
303
- <meshBasicMaterial
304
- color="#ffffff"
305
- depthTest={false}
306
- depthWrite={false}
307
- opacity={0.45}
308
- side={DoubleSide}
309
- transparent
310
- />
311
- </mesh>
312
-
313
- {draftMeasurement && (
314
- <>
315
- <DraftMeasurementLabel
316
- label={draftMeasurement.lengthLabel}
317
- position={draftMeasurement.lengthPosition}
318
- />
319
- {draftMeasurement.angleLabels.map((angleLabel) => (
320
- <DraftMeasurementLabel
321
- key={angleLabel.id}
322
- label={angleLabel.label}
323
- position={angleLabel.position}
324
- />
325
- ))}
326
- </>
327
- )}
328
- </group>
329
- )
330
- }
331
-
332
- function DraftMeasurementLabel({
333
- label,
334
- position,
335
- }: {
336
- label: string
337
- position: [number, number, number]
338
- }) {
339
- return (
340
- <Html center position={position} style={{ pointerEvents: 'none' }} zIndexRange={[100, 0]}>
341
- <div className="whitespace-nowrap rounded-full border border-border bg-background/95 px-2 py-1 font-mono font-semibold text-[11px] text-foreground shadow-lg backdrop-blur-md">
342
- {label}
343
- </div>
344
- </Html>
345
- )
346
- }