@pascal-app/editor 0.6.0 → 0.8.0
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.
- package/package.json +13 -9
- package/src/components/editor/bottom-sheet.tsx +149 -0
- package/src/components/editor/custom-camera-controls.tsx +74 -5
- package/src/components/editor/editor-layout-mobile.tsx +264 -0
- package/src/components/editor/editor-layout-v2.tsx +24 -3
- package/src/components/editor/first-person/build-collider-world.ts +363 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +860 -0
- package/src/components/editor/first-person-controls.tsx +496 -143
- package/src/components/editor/floating-action-menu.tsx +32 -55
- package/src/components/editor/floorplan-background-selection.ts +113 -0
- package/src/components/editor/floorplan-panel.tsx +9861 -3297
- package/src/components/editor/index.tsx +295 -32
- package/src/components/editor/selection-manager.tsx +575 -13
- package/src/components/editor/snapshot-capture-overlay.tsx +465 -0
- package/src/components/editor/thumbnail-generator.tsx +56 -68
- package/src/components/editor/use-floorplan-background-placement.ts +257 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +171 -0
- package/src/components/editor/use-floorplan-scene-data.ts +189 -0
- package/src/components/editor/wall-measurement-label.tsx +267 -36
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +95 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +160 -0
- package/src/components/editor-2d/floorplan-hotkey-handlers.tsx +92 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +124 -0
- package/src/components/editor-2d/renderers/floorplan-marquee-layer.tsx +58 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +202 -0
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +113 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +474 -0
- package/src/components/editor-2d/svg-paths.ts +119 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +10 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1 -1
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
- package/src/components/systems/zone/zone-system.tsx +0 -0
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +1 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +1 -0
- package/src/components/tools/ceiling/ceiling-tool.tsx +5 -5
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +9 -2
- package/src/components/tools/column/column-tool.tsx +97 -0
- package/src/components/tools/column/move-column-tool.tsx +105 -0
- package/src/components/tools/door/door-tool.tsx +7 -0
- package/src/components/tools/door/move-door-tool.tsx +28 -8
- package/src/components/tools/fence/curve-fence-tool.tsx +4 -5
- package/src/components/tools/fence/fence-drafting.ts +10 -3
- package/src/components/tools/fence/fence-tool.tsx +160 -4
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +139 -25
- package/src/components/tools/fence/move-fence-tool.tsx +111 -40
- package/src/components/tools/item/move-tool.tsx +7 -1
- package/src/components/tools/item/placement-math.ts +32 -5
- package/src/components/tools/item/placement-strategies.ts +110 -31
- package/src/components/tools/item/placement-types.ts +7 -0
- package/src/components/tools/item/use-draft-node.ts +1 -0
- package/src/components/tools/item/use-placement-coordinator.tsx +558 -52
- package/src/components/tools/roof/move-roof-tool.tsx +29 -17
- package/src/components/tools/select/box-select-tool.tsx +12 -17
- package/src/components/tools/shared/polygon-editor.tsx +153 -28
- package/src/components/tools/shared/segment-angle.ts +156 -0
- package/src/components/tools/slab/slab-boundary-editor.tsx +1 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +1 -0
- package/src/components/tools/spawn/move-spawn-tool.tsx +101 -0
- package/src/components/tools/spawn/spawn-tool.tsx +130 -0
- package/src/components/tools/tool-manager.tsx +20 -5
- package/src/components/tools/wall/curve-wall-tool.tsx +8 -6
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +131 -27
- package/src/components/tools/wall/move-wall-tool.tsx +6 -4
- package/src/components/tools/wall/wall-drafting.ts +18 -9
- package/src/components/tools/wall/wall-tool.tsx +136 -4
- package/src/components/tools/window/move-window-tool.tsx +18 -0
- package/src/components/tools/window/window-tool.tsx +5 -0
- package/src/components/tools/zone/zone-tool.tsx +20 -5
- package/src/components/ui/action-menu/camera-actions.tsx +37 -33
- package/src/components/ui/action-menu/control-modes.tsx +34 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +98 -59
- package/src/components/ui/action-menu/structure-tools.tsx +2 -0
- package/src/components/ui/action-menu/view-toggles.tsx +418 -41
- package/src/components/ui/command-palette/editor-commands.tsx +24 -5
- package/src/components/ui/command-palette/index.tsx +4 -255
- package/src/components/ui/controls/material-picker.tsx +154 -164
- package/src/components/ui/controls/slider-control.tsx +66 -18
- package/src/components/ui/floating-level-selector.tsx +286 -55
- package/src/components/ui/helpers/helper-manager.tsx +10 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +2563 -1239
- package/src/components/ui/item-catalog/item-catalog.tsx +96 -187
- package/src/components/ui/level-duplicate-dialog.tsx +113 -0
- package/src/components/ui/panels/ceiling-panel.tsx +3 -28
- package/src/components/ui/panels/column-panel.tsx +759 -0
- package/src/components/ui/panels/door-panel.tsx +989 -290
- package/src/components/ui/panels/fence-panel.tsx +2 -49
- package/src/components/ui/panels/mobile-panel-sheet.tsx +108 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +100 -0
- package/src/components/ui/panels/node-display.ts +39 -0
- package/src/components/ui/panels/paint-panel.tsx +163 -0
- package/src/components/ui/panels/panel-manager.tsx +208 -28
- package/src/components/ui/panels/panel-wrapper.tsx +48 -39
- package/src/components/ui/panels/reference-panel.tsx +253 -5
- package/src/components/ui/panels/roof-panel.tsx +13 -64
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -25
- package/src/components/ui/panels/slab-panel.tsx +4 -30
- package/src/components/ui/panels/spawn-panel.tsx +161 -0
- package/src/components/ui/panels/stair-panel.tsx +20 -74
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -25
- package/src/components/ui/panels/wall-panel.tsx +10 -8
- package/src/components/ui/panels/window-panel.tsx +668 -139
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
- package/src/components/ui/sidebar/icon-rail.tsx +0 -0
- package/src/components/ui/sidebar/mobile-tab-bar.tsx +46 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +330 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +0 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +77 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +105 -22
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +76 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +11 -3
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +10 -5
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +1 -1
- package/src/components/ui/sidebar/tab-bar.tsx +3 -0
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +0 -0
- package/src/components/viewer-zone-system.tsx +0 -0
- package/src/hooks/use-auto-frame.ts +45 -0
- package/src/hooks/use-auto-save.ts +14 -0
- package/src/hooks/use-keyboard.ts +74 -7
- package/src/hooks/use-mobile.ts +12 -12
- package/src/index.tsx +8 -1
- package/src/lib/door-interaction.ts +88 -0
- package/src/lib/floorplan/geometry.ts +263 -0
- package/src/lib/floorplan/index.ts +38 -0
- package/src/lib/floorplan/items.ts +179 -0
- package/src/lib/floorplan/selection-tool.ts +231 -0
- package/src/lib/floorplan/stairs.ts +478 -0
- package/src/lib/floorplan/types.ts +57 -0
- package/src/lib/floorplan/walls.ts +23 -0
- package/src/lib/guide-events.ts +10 -0
- package/src/lib/level-duplication.test.ts +70 -0
- package/src/lib/level-duplication.ts +153 -0
- package/src/lib/local-guide-image.ts +42 -0
- package/src/lib/material-paint.ts +284 -0
- package/src/lib/roof-duplication.ts +214 -0
- package/src/lib/scene-bounds.test.ts +183 -0
- package/src/lib/scene-bounds.ts +169 -0
- package/src/lib/scene.ts +0 -0
- package/src/lib/sfx-bus.ts +2 -0
- package/src/lib/sfx-player.ts +5 -5
- package/src/lib/stair-duplication.ts +126 -0
- package/src/lib/window-interaction.ts +86 -0
- package/src/store/use-editor.tsx +186 -62
- package/tsconfig.json +2 -1
- package/src/components/feedback-dialog.tsx +0 -265
- package/src/components/pascal-radio.tsx +0 -280
- package/src/components/preview-button.tsx +0 -16
- package/src/components/ui/viewer-toolbar.tsx +0 -395
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
type WallNode,
|
|
17
17
|
} from '@pascal-app/core'
|
|
18
18
|
import { useViewer } from '@pascal-app/viewer'
|
|
19
|
+
import { Html } from '@react-three/drei'
|
|
19
20
|
import { useFrame } from '@react-three/fiber'
|
|
20
|
-
import { useEffect, useRef } from 'react'
|
|
21
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
21
22
|
import {
|
|
22
|
-
|
|
23
|
-
EdgesGeometry,
|
|
23
|
+
BufferGeometry,
|
|
24
24
|
Euler,
|
|
25
|
+
Float32BufferAttribute,
|
|
25
26
|
type Group,
|
|
26
27
|
type LineSegments,
|
|
27
28
|
type Mesh,
|
|
@@ -33,7 +34,8 @@ import { distance, smoothstep, uv, vec2 } from 'three/tsl'
|
|
|
33
34
|
import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
|
|
34
35
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
35
36
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
36
|
-
import
|
|
37
|
+
import useEditor from '../../../store/use-editor'
|
|
38
|
+
import { getGridAlignedDimensions, snapToGrid, snapUpToGridStep } from './placement-math'
|
|
37
39
|
import {
|
|
38
40
|
ceilingStrategy,
|
|
39
41
|
checkCanPlace,
|
|
@@ -46,6 +48,194 @@ import type { DraftNodeHandle } from './use-draft-node'
|
|
|
46
48
|
|
|
47
49
|
const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
|
|
48
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
|
+
return `${Number.parseFloat(value.toFixed(2))}m`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type PreviewBounds = {
|
|
63
|
+
min: [number, number, number]
|
|
64
|
+
max: [number, number, number]
|
|
65
|
+
dimensions: [number, number, number]
|
|
66
|
+
center: [number, number, number]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Expand `bounds` outward so each axis is rounded up to the active grid step.
|
|
71
|
+
* The wireframe stays centered on the original bounds centre on each axis we
|
|
72
|
+
* expand, so an off-centre mesh bbox stays off-centre. Wall-side items keep
|
|
73
|
+
* `max.z = 0` (flush with the wall plane); the bottom (`min.y`) is preserved
|
|
74
|
+
* so the box still sits on the floor / attachment plane.
|
|
75
|
+
*
|
|
76
|
+
* Floor / ceiling / item-surface: X and Z expand; Y stays exact.
|
|
77
|
+
* Wall / wall-side: X and Y expand; Z stays exact.
|
|
78
|
+
*/
|
|
79
|
+
function expandBoundsToGrid(
|
|
80
|
+
bounds: PreviewBounds,
|
|
81
|
+
attachTo: AssetInput['attachTo'] | null | undefined,
|
|
82
|
+
step: number,
|
|
83
|
+
): PreviewBounds {
|
|
84
|
+
const [w, h, d] = bounds.dimensions
|
|
85
|
+
const [cx, , cz] = bounds.center
|
|
86
|
+
const onWall = attachTo === 'wall' || attachTo === 'wall-side'
|
|
87
|
+
const expandedW = snapUpToGridStep(w, step)
|
|
88
|
+
const expandedH = onWall ? snapUpToGridStep(h, step) : h
|
|
89
|
+
const expandedD = onWall ? d : snapUpToGridStep(d, step)
|
|
90
|
+
|
|
91
|
+
const minX = cx - expandedW / 2
|
|
92
|
+
const maxX = cx + expandedW / 2
|
|
93
|
+
const minY = bounds.min[1]
|
|
94
|
+
const maxY = minY + expandedH
|
|
95
|
+
|
|
96
|
+
let minZ: number
|
|
97
|
+
let maxZ: number
|
|
98
|
+
let newCz: number
|
|
99
|
+
if (attachTo === 'wall-side') {
|
|
100
|
+
maxZ = 0
|
|
101
|
+
minZ = -expandedD
|
|
102
|
+
newCz = -expandedD / 2
|
|
103
|
+
} else {
|
|
104
|
+
minZ = cz - expandedD / 2
|
|
105
|
+
maxZ = cz + expandedD / 2
|
|
106
|
+
newCz = cz
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
min: [minX, minY, minZ],
|
|
111
|
+
max: [maxX, maxY, maxZ],
|
|
112
|
+
dimensions: [expandedW, expandedH, expandedD],
|
|
113
|
+
center: [cx, (minY + maxY) / 2, newCz],
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getFallbackPreviewBounds(
|
|
118
|
+
item: import('@pascal-app/core').ItemNode | null,
|
|
119
|
+
asset: AssetInput,
|
|
120
|
+
attachTo: AssetInput['attachTo'],
|
|
121
|
+
): PreviewBounds {
|
|
122
|
+
const dims = item ? getScaledDimensions(item) : (asset.dimensions ?? DEFAULT_DIMENSIONS)
|
|
123
|
+
return {
|
|
124
|
+
min: [-dims[0] / 2, 0, attachTo === 'wall-side' ? -dims[2] : -dims[2] / 2],
|
|
125
|
+
max: [dims[0] / 2, dims[1], attachTo === 'wall-side' ? 0 : dims[2] / 2],
|
|
126
|
+
dimensions: dims,
|
|
127
|
+
center: [0, dims[1] / 2, attachTo === 'wall-side' ? -dims[2] / 2 : 0],
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createLineGeometry(points: number[] = [0, 0, 0, 0, 0, 0]): BufferGeometry {
|
|
132
|
+
const geometry = new BufferGeometry()
|
|
133
|
+
geometry.setAttribute('position', new Float32BufferAttribute(points, 3))
|
|
134
|
+
return geometry
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getBoxEdgePoints(bounds: PreviewBounds): number[] {
|
|
138
|
+
const [width, height, depth] = bounds.dimensions
|
|
139
|
+
const [centerX, centerY, centerZ] = bounds.center
|
|
140
|
+
const minX = centerX - width / 2
|
|
141
|
+
const maxX = centerX + width / 2
|
|
142
|
+
const minY = centerY - height / 2
|
|
143
|
+
const maxY = centerY + height / 2
|
|
144
|
+
const minZ = centerZ - depth / 2
|
|
145
|
+
const maxZ = centerZ + depth / 2
|
|
146
|
+
|
|
147
|
+
return [
|
|
148
|
+
minX,
|
|
149
|
+
minY,
|
|
150
|
+
minZ,
|
|
151
|
+
maxX,
|
|
152
|
+
minY,
|
|
153
|
+
minZ,
|
|
154
|
+
maxX,
|
|
155
|
+
minY,
|
|
156
|
+
minZ,
|
|
157
|
+
maxX,
|
|
158
|
+
minY,
|
|
159
|
+
maxZ,
|
|
160
|
+
maxX,
|
|
161
|
+
minY,
|
|
162
|
+
maxZ,
|
|
163
|
+
minX,
|
|
164
|
+
minY,
|
|
165
|
+
maxZ,
|
|
166
|
+
minX,
|
|
167
|
+
minY,
|
|
168
|
+
maxZ,
|
|
169
|
+
minX,
|
|
170
|
+
minY,
|
|
171
|
+
minZ,
|
|
172
|
+
|
|
173
|
+
minX,
|
|
174
|
+
maxY,
|
|
175
|
+
minZ,
|
|
176
|
+
maxX,
|
|
177
|
+
maxY,
|
|
178
|
+
minZ,
|
|
179
|
+
maxX,
|
|
180
|
+
maxY,
|
|
181
|
+
minZ,
|
|
182
|
+
maxX,
|
|
183
|
+
maxY,
|
|
184
|
+
maxZ,
|
|
185
|
+
maxX,
|
|
186
|
+
maxY,
|
|
187
|
+
maxZ,
|
|
188
|
+
minX,
|
|
189
|
+
maxY,
|
|
190
|
+
maxZ,
|
|
191
|
+
minX,
|
|
192
|
+
maxY,
|
|
193
|
+
maxZ,
|
|
194
|
+
minX,
|
|
195
|
+
maxY,
|
|
196
|
+
minZ,
|
|
197
|
+
|
|
198
|
+
minX,
|
|
199
|
+
minY,
|
|
200
|
+
minZ,
|
|
201
|
+
minX,
|
|
202
|
+
maxY,
|
|
203
|
+
minZ,
|
|
204
|
+
maxX,
|
|
205
|
+
minY,
|
|
206
|
+
minZ,
|
|
207
|
+
maxX,
|
|
208
|
+
maxY,
|
|
209
|
+
minZ,
|
|
210
|
+
maxX,
|
|
211
|
+
minY,
|
|
212
|
+
maxZ,
|
|
213
|
+
maxX,
|
|
214
|
+
maxY,
|
|
215
|
+
maxZ,
|
|
216
|
+
minX,
|
|
217
|
+
minY,
|
|
218
|
+
maxZ,
|
|
219
|
+
minX,
|
|
220
|
+
maxY,
|
|
221
|
+
maxZ,
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function updateLineGeometry(ref: React.RefObject<LineSegments>, points: number[]) {
|
|
226
|
+
const geometry = ref.current?.geometry
|
|
227
|
+
if (!geometry) return
|
|
228
|
+
|
|
229
|
+
const attribute = geometry.getAttribute('position') as Float32BufferAttribute | undefined
|
|
230
|
+
if (!attribute || attribute.array.length !== points.length) {
|
|
231
|
+
geometry.setAttribute('position', new Float32BufferAttribute(points, 3))
|
|
232
|
+
} else {
|
|
233
|
+
attribute.set(points)
|
|
234
|
+
attribute.needsUpdate = true
|
|
235
|
+
}
|
|
236
|
+
geometry.computeBoundingSphere()
|
|
237
|
+
}
|
|
238
|
+
|
|
49
239
|
// Shared materials for placement cursor - we just change colors, not swap materials
|
|
50
240
|
// Note: EdgesGeometry doesn't work with dashed lines, so using solid lines
|
|
51
241
|
const edgeMaterial = new LineBasicNodeMaterial({
|
|
@@ -55,6 +245,13 @@ const edgeMaterial = new LineBasicNodeMaterial({
|
|
|
55
245
|
depthWrite: false,
|
|
56
246
|
})
|
|
57
247
|
|
|
248
|
+
const measurementMaterial = new LineBasicNodeMaterial({
|
|
249
|
+
color: 0x0f_17_2a,
|
|
250
|
+
linewidth: 2,
|
|
251
|
+
depthTest: false,
|
|
252
|
+
depthWrite: false,
|
|
253
|
+
})
|
|
254
|
+
|
|
58
255
|
const basePlaneMaterial = new MeshBasicNodeMaterial({
|
|
59
256
|
color: 0xef_44_44, // red-500 (invalid)
|
|
60
257
|
transparent: true,
|
|
@@ -82,6 +279,9 @@ export interface PlacementCoordinatorConfig {
|
|
|
82
279
|
export function usePlacementCoordinator(config: PlacementCoordinatorConfig): React.ReactNode {
|
|
83
280
|
const cursorGroupRef = useRef<Group>(null!)
|
|
84
281
|
const edgesRef = useRef<LineSegments>(null!)
|
|
282
|
+
const measurementWidthRef = useRef<LineSegments>(null!)
|
|
283
|
+
const measurementDepthRef = useRef<LineSegments>(null!)
|
|
284
|
+
const measurementHeightRef = useRef<LineSegments>(null!)
|
|
85
285
|
const basePlaneRef = useRef<Mesh>(null!)
|
|
86
286
|
const gridPosition = useRef(new Vector3(0, 0, 0))
|
|
87
287
|
const lastRawPos = useRef(new Vector3(0, 0, 0))
|
|
@@ -89,6 +289,8 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
89
289
|
config.initialState ?? { surface: 'floor', wallId: null, ceilingId: null, surfaceItemId: null },
|
|
90
290
|
)
|
|
91
291
|
const shiftFreeRef = useRef(false)
|
|
292
|
+
const previewBoundsSignatureRef = useRef<string | null>(null)
|
|
293
|
+
const [dimensionBounds, setDimensionBounds] = useState<PreviewBounds | null>(null)
|
|
92
294
|
|
|
93
295
|
// Store config callbacks in refs to avoid re-running effect when they change
|
|
94
296
|
const configRef = useRef(config)
|
|
@@ -96,6 +298,130 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
96
298
|
|
|
97
299
|
const { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling } = useSpatialQuery()
|
|
98
300
|
const { asset, draftNode } = config
|
|
301
|
+
const unit = useViewer((state) => state.unit)
|
|
302
|
+
const gridSnapStep = useEditor((s) => s.gridSnapStep)
|
|
303
|
+
const updatePreviewGeometry = (bounds: PreviewBounds) => {
|
|
304
|
+
const [width, height, depth] = bounds.dimensions
|
|
305
|
+
const [centerX, centerY, centerZ] = bounds.center
|
|
306
|
+
const signature = `${width.toFixed(4)}:${height.toFixed(4)}:${depth.toFixed(4)}:${centerX.toFixed(4)}:${centerY.toFixed(4)}:${centerZ.toFixed(4)}`
|
|
307
|
+
|
|
308
|
+
if (previewBoundsSignatureRef.current === signature) return
|
|
309
|
+
previewBoundsSignatureRef.current = signature
|
|
310
|
+
|
|
311
|
+
const nextBasePlaneGeometry = new PlaneGeometry(width, depth)
|
|
312
|
+
nextBasePlaneGeometry.rotateX(-Math.PI / 2)
|
|
313
|
+
nextBasePlaneGeometry.translate(centerX, 0.01, centerZ)
|
|
314
|
+
|
|
315
|
+
updateLineGeometry(edgesRef, getBoxEdgePoints(bounds))
|
|
316
|
+
|
|
317
|
+
const oldBasePlaneGeometry = basePlaneRef.current.geometry
|
|
318
|
+
basePlaneRef.current.geometry = nextBasePlaneGeometry
|
|
319
|
+
oldBasePlaneGeometry.dispose()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const updateDimensionGuides = (bounds: PreviewBounds) => {
|
|
323
|
+
setDimensionBounds((current) => {
|
|
324
|
+
if (
|
|
325
|
+
current &&
|
|
326
|
+
current.dimensions[0] === bounds.dimensions[0] &&
|
|
327
|
+
current.dimensions[1] === bounds.dimensions[1] &&
|
|
328
|
+
current.dimensions[2] === bounds.dimensions[2] &&
|
|
329
|
+
current.center[0] === bounds.center[0] &&
|
|
330
|
+
current.center[1] === bounds.center[1] &&
|
|
331
|
+
current.center[2] === bounds.center[2]
|
|
332
|
+
) {
|
|
333
|
+
return current
|
|
334
|
+
}
|
|
335
|
+
return bounds
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const [width, , depth] = bounds.dimensions
|
|
339
|
+
const [centerX, , centerZ] = bounds.center
|
|
340
|
+
const minX = centerX - width / 2
|
|
341
|
+
const maxX = centerX + width / 2
|
|
342
|
+
const minZ = centerZ - depth / 2
|
|
343
|
+
const maxZ = centerZ + depth / 2
|
|
344
|
+
const guideOffset = 0.18
|
|
345
|
+
const tick = 0.08
|
|
346
|
+
const y = 0.02
|
|
347
|
+
|
|
348
|
+
const widthPoints = [
|
|
349
|
+
minX,
|
|
350
|
+
y,
|
|
351
|
+
maxZ + guideOffset,
|
|
352
|
+
maxX,
|
|
353
|
+
y,
|
|
354
|
+
maxZ + guideOffset,
|
|
355
|
+
|
|
356
|
+
minX,
|
|
357
|
+
y,
|
|
358
|
+
maxZ + guideOffset - tick,
|
|
359
|
+
minX,
|
|
360
|
+
y,
|
|
361
|
+
maxZ + guideOffset + tick,
|
|
362
|
+
|
|
363
|
+
maxX,
|
|
364
|
+
y,
|
|
365
|
+
maxZ + guideOffset - tick,
|
|
366
|
+
maxX,
|
|
367
|
+
y,
|
|
368
|
+
maxZ + guideOffset + tick,
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
const depthPoints = [
|
|
372
|
+
maxX + guideOffset,
|
|
373
|
+
y,
|
|
374
|
+
minZ,
|
|
375
|
+
maxX + guideOffset,
|
|
376
|
+
y,
|
|
377
|
+
maxZ,
|
|
378
|
+
|
|
379
|
+
maxX + guideOffset - tick,
|
|
380
|
+
y,
|
|
381
|
+
minZ,
|
|
382
|
+
maxX + guideOffset + tick,
|
|
383
|
+
y,
|
|
384
|
+
minZ,
|
|
385
|
+
|
|
386
|
+
maxX + guideOffset - tick,
|
|
387
|
+
y,
|
|
388
|
+
maxZ,
|
|
389
|
+
maxX + guideOffset + tick,
|
|
390
|
+
y,
|
|
391
|
+
maxZ,
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
const heightPoints = [
|
|
395
|
+
minX - guideOffset,
|
|
396
|
+
0,
|
|
397
|
+
minZ,
|
|
398
|
+
minX - guideOffset,
|
|
399
|
+
bounds.dimensions[1],
|
|
400
|
+
minZ,
|
|
401
|
+
|
|
402
|
+
minX - guideOffset - tick,
|
|
403
|
+
0,
|
|
404
|
+
minZ,
|
|
405
|
+
minX - guideOffset + tick,
|
|
406
|
+
0,
|
|
407
|
+
minZ,
|
|
408
|
+
|
|
409
|
+
minX - guideOffset - tick,
|
|
410
|
+
bounds.dimensions[1],
|
|
411
|
+
minZ,
|
|
412
|
+
minX - guideOffset + tick,
|
|
413
|
+
bounds.dimensions[1],
|
|
414
|
+
minZ,
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
const applyPoints = (ref: React.RefObject<LineSegments>, points: number[]) => {
|
|
418
|
+
updateLineGeometry(ref, points)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
applyPoints(measurementWidthRef, widthPoints)
|
|
422
|
+
applyPoints(measurementDepthRef, depthPoints)
|
|
423
|
+
applyPoints(measurementHeightRef, heightPoints)
|
|
424
|
+
}
|
|
99
425
|
|
|
100
426
|
useEffect(() => {
|
|
101
427
|
if (!asset) return
|
|
@@ -110,6 +436,10 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
110
436
|
ceilingId: null,
|
|
111
437
|
surfaceItemId: null,
|
|
112
438
|
}
|
|
439
|
+
if (!asset.attachTo && placementState.current.surface === 'floor') {
|
|
440
|
+
gridPosition.current.y = 0
|
|
441
|
+
cursorGroupRef.current.position.y = 0
|
|
442
|
+
}
|
|
113
443
|
|
|
114
444
|
// ---- Helpers ----
|
|
115
445
|
|
|
@@ -119,6 +449,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
119
449
|
draftItem: draftNode.current,
|
|
120
450
|
gridPosition: gridPosition.current,
|
|
121
451
|
state: { ...placementState.current },
|
|
452
|
+
currentCursorRotationY: cursorGroupRef.current.rotation.y,
|
|
122
453
|
})
|
|
123
454
|
|
|
124
455
|
const getActiveValidators = () =>
|
|
@@ -236,9 +567,11 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
236
567
|
|
|
237
568
|
previousGridPos = [...result.gridPosition]
|
|
238
569
|
gridPosition.current.set(...result.gridPosition)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
570
|
+
cursorGroupRef.current.position.set(
|
|
571
|
+
result.cursorPosition[0],
|
|
572
|
+
result.cursorPosition[1],
|
|
573
|
+
result.cursorPosition[2],
|
|
574
|
+
)
|
|
242
575
|
|
|
243
576
|
const draft = draftNode.current
|
|
244
577
|
if (draft) draft.position = result.gridPosition
|
|
@@ -463,6 +796,32 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
463
796
|
|
|
464
797
|
// ---- Item Surface Handlers ----
|
|
465
798
|
|
|
799
|
+
const detachItemSurfaceToFloor = (event: ItemEvent) => {
|
|
800
|
+
const buildingLocalPoint = worldToBuildingLocal(
|
|
801
|
+
event.position[0],
|
|
802
|
+
event.position[1],
|
|
803
|
+
event.position[2],
|
|
804
|
+
)
|
|
805
|
+
const wx = Math.round(buildingLocalPoint.x * 2) / 2
|
|
806
|
+
const wz = Math.round(buildingLocalPoint.z * 2) / 2
|
|
807
|
+
const floorPos: [number, number, number] = [wx, 0, wz]
|
|
808
|
+
|
|
809
|
+
Object.assign(placementState.current, { surface: 'floor', surfaceItemId: null })
|
|
810
|
+
gridPosition.current.set(wx, 0, wz)
|
|
811
|
+
cursorGroupRef.current.position.set(wx, 0, wz)
|
|
812
|
+
|
|
813
|
+
const draft = draftNode.current
|
|
814
|
+
if (draft) {
|
|
815
|
+
draft.position = floorPos
|
|
816
|
+
useScene.getState().updateNode(draft.id, {
|
|
817
|
+
parentId: useViewer.getState().selection.levelId as string,
|
|
818
|
+
position: floorPos,
|
|
819
|
+
})
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
revalidate()
|
|
823
|
+
}
|
|
824
|
+
|
|
466
825
|
const onItemEnter = (event: ItemEvent) => {
|
|
467
826
|
if (event.node.id === draftNode.current?.id) return
|
|
468
827
|
const result = itemSurfaceStrategy.enter(getContext(), event)
|
|
@@ -496,6 +855,24 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
496
855
|
return
|
|
497
856
|
}
|
|
498
857
|
|
|
858
|
+
if (ctx.state.surface === 'item-surface' && event.node.id !== ctx.state.surfaceItemId) {
|
|
859
|
+
const enterResult = itemSurfaceStrategy.enter(
|
|
860
|
+
{ ...ctx, state: { ...ctx.state, surface: 'floor', surfaceItemId: null } },
|
|
861
|
+
event,
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
event.stopPropagation()
|
|
865
|
+
if (enterResult) {
|
|
866
|
+
applyTransition(enterResult)
|
|
867
|
+
if (draftNode.current && enterResult.nodeUpdate.parentId) {
|
|
868
|
+
useScene.getState().updateNode(draftNode.current.id, enterResult.nodeUpdate)
|
|
869
|
+
}
|
|
870
|
+
} else {
|
|
871
|
+
detachItemSurfaceToFloor(event)
|
|
872
|
+
}
|
|
873
|
+
return
|
|
874
|
+
}
|
|
875
|
+
|
|
499
876
|
if (!draftNode.current) {
|
|
500
877
|
const enterResult = itemSurfaceStrategy.enter(getContext(), event)
|
|
501
878
|
if (!enterResult) return
|
|
@@ -537,26 +914,12 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
537
914
|
|
|
538
915
|
event.stopPropagation()
|
|
539
916
|
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
gridPosition.current.set(wx, 0, wz)
|
|
547
|
-
cursorGroupRef.current.position.x = wx
|
|
548
|
-
cursorGroupRef.current.position.z = wz
|
|
549
|
-
|
|
550
|
-
const draft = draftNode.current
|
|
551
|
-
if (draft) {
|
|
552
|
-
draft.position = floorPos
|
|
553
|
-
useScene.getState().updateNode(draft.id, {
|
|
554
|
-
parentId: useViewer.getState().selection.levelId as string,
|
|
555
|
-
position: floorPos,
|
|
556
|
-
})
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
revalidate()
|
|
917
|
+
// `event.localPosition` from useNodeEvents is in the LEAVING item's
|
|
918
|
+
// local space (the sofa/table the draft is detaching from), not
|
|
919
|
+
// building-local. Convert from world via worldToBuildingLocal instead,
|
|
920
|
+
// otherwise the wireframe jumps to a surface-local-coordinate ghost
|
|
921
|
+
// position until the next mouse move.
|
|
922
|
+
detachItemSurfaceToFloor(event)
|
|
560
923
|
}
|
|
561
924
|
|
|
562
925
|
const onItemClick = (event: ItemEvent) => {
|
|
@@ -614,7 +977,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
614
977
|
return
|
|
615
978
|
}
|
|
616
979
|
|
|
617
|
-
lastRawPos.current.set(event.
|
|
980
|
+
lastRawPos.current.set(event.localPosition[0], event.localPosition[1], event.localPosition[2])
|
|
618
981
|
const result = ceilingStrategy.move(getContext(), event)
|
|
619
982
|
if (!result) return
|
|
620
983
|
|
|
@@ -823,14 +1186,19 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
823
1186
|
window.addEventListener('contextmenu', onContextMenu)
|
|
824
1187
|
|
|
825
1188
|
// ---- Bounding box geometry ----
|
|
1189
|
+
// Always derive the wireframe from `asset.dimensions × scale` rather than
|
|
1190
|
+
// the rendered mesh bounds. Asset dimensions describe the item's footprint
|
|
1191
|
+
// (e.g. only the trunk for a palm tree), while the mesh bbox would include
|
|
1192
|
+
// foliage or other visual overhang the snap logic intentionally ignores.
|
|
826
1193
|
|
|
827
1194
|
const draft = draftNode.current
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1195
|
+
const previewBounds = expandBoundsToGrid(
|
|
1196
|
+
getFallbackPreviewBounds(draft, asset, asset.attachTo),
|
|
1197
|
+
asset.attachTo,
|
|
1198
|
+
gridSnapStep,
|
|
1199
|
+
)
|
|
1200
|
+
updatePreviewGeometry(previewBounds)
|
|
1201
|
+
updateDimensionGuides(previewBounds)
|
|
834
1202
|
|
|
835
1203
|
// ---- Undo protection ----
|
|
836
1204
|
// Undo replaces the entire `nodes` object with a previous snapshot, which doesn't
|
|
@@ -902,7 +1270,19 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
902
1270
|
}
|
|
903
1271
|
}, [asset, canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling, draftNode])
|
|
904
1272
|
|
|
905
|
-
//
|
|
1273
|
+
// Refresh wireframe when the grid step changes mid-placement so the green/red
|
|
1274
|
+
// box snaps to the new cell size right away.
|
|
1275
|
+
useEffect(() => {
|
|
1276
|
+
if (!asset) return
|
|
1277
|
+
const draft = draftNode.current
|
|
1278
|
+
const previewBounds = expandBoundsToGrid(
|
|
1279
|
+
getFallbackPreviewBounds(draft, asset, asset.attachTo),
|
|
1280
|
+
asset.attachTo,
|
|
1281
|
+
gridSnapStep,
|
|
1282
|
+
)
|
|
1283
|
+
updatePreviewGeometry(previewBounds)
|
|
1284
|
+
updateDimensionGuides(previewBounds)
|
|
1285
|
+
}, [gridSnapStep, asset, draftNode])
|
|
906
1286
|
// Wall/ceiling items are managed by their own surface entry events (ensureDraft / reparent).
|
|
907
1287
|
const viewerLevelId = useViewer((s) => s.selection.levelId)
|
|
908
1288
|
useEffect(() => {
|
|
@@ -942,36 +1322,162 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|
|
942
1322
|
const slabElevation = spatialGridManager.getSlabElevationForItem(
|
|
943
1323
|
levelId,
|
|
944
1324
|
[gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
|
|
945
|
-
|
|
1325
|
+
getGridAlignedDimensions(
|
|
1326
|
+
getScaledDimensions(draftNode.current),
|
|
1327
|
+
draftNode.current.asset.attachTo,
|
|
1328
|
+
),
|
|
946
1329
|
draftNode.current.rotation,
|
|
947
1330
|
)
|
|
948
1331
|
mesh.position.y = slabElevation
|
|
949
|
-
// Cursor group is at the world root (not inside a level group), so add the
|
|
950
|
-
// level group's current world Y to convert from level-local to world space.
|
|
951
|
-
const levelGroup = sceneRegistry.nodes.get(levelId as AnyNodeId)
|
|
952
|
-
cursorGroupRef.current.position.y = slabElevation + (levelGroup?.position.y ?? 0)
|
|
953
1332
|
}
|
|
954
1333
|
}
|
|
955
1334
|
})
|
|
956
1335
|
|
|
957
1336
|
const initialDraft = draftNode.current
|
|
958
|
-
const
|
|
1337
|
+
const initialAttachTo = config.asset?.attachTo
|
|
1338
|
+
const rawDims = initialDraft
|
|
959
1339
|
? getScaledDimensions(initialDraft)
|
|
960
1340
|
: (config.asset?.dimensions ?? DEFAULT_DIMENSIONS)
|
|
961
|
-
const
|
|
962
|
-
const wallSideZOffset =
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1341
|
+
const dims = getGridAlignedDimensions(rawDims, initialAttachTo, gridSnapStep)
|
|
1342
|
+
const wallSideZOffset = initialAttachTo === 'wall-side' ? -dims[2] / 2 : 0
|
|
1343
|
+
const initialDimensionBounds = expandBoundsToGrid(
|
|
1344
|
+
getFallbackPreviewBounds(initialDraft, config.asset!, initialAttachTo),
|
|
1345
|
+
initialAttachTo,
|
|
1346
|
+
gridSnapStep,
|
|
1347
|
+
)
|
|
1348
|
+
const initialEdgeGeometry = useMemo(
|
|
1349
|
+
() => createLineGeometry(getBoxEdgePoints(initialDimensionBounds)),
|
|
1350
|
+
[
|
|
1351
|
+
initialDimensionBounds.center[0],
|
|
1352
|
+
initialDimensionBounds.center[1],
|
|
1353
|
+
initialDimensionBounds.center[2],
|
|
1354
|
+
initialDimensionBounds.dimensions[0],
|
|
1355
|
+
initialDimensionBounds.dimensions[1],
|
|
1356
|
+
initialDimensionBounds.dimensions[2],
|
|
1357
|
+
],
|
|
1358
|
+
)
|
|
1359
|
+
const basePlaneGeometry = useMemo(() => {
|
|
1360
|
+
const geometry = new PlaneGeometry(dims[0], dims[2])
|
|
1361
|
+
geometry.rotateX(-Math.PI / 2)
|
|
1362
|
+
geometry.translate(0, 0.01, wallSideZOffset)
|
|
1363
|
+
return geometry
|
|
1364
|
+
}, [dims[0], dims[2], wallSideZOffset])
|
|
1365
|
+
const initialWidthGuideGeometry = useMemo(() => createLineGeometry(), [])
|
|
1366
|
+
const initialDepthGuideGeometry = useMemo(() => createLineGeometry(), [])
|
|
1367
|
+
const initialHeightGuideGeometry = useMemo(() => createLineGeometry(), [])
|
|
1368
|
+
const currentDimensionBounds = dimensionBounds ?? initialDimensionBounds
|
|
1369
|
+
const widthLabel = formatMeasurement(currentDimensionBounds.dimensions[0], unit)
|
|
1370
|
+
const depthLabel = formatMeasurement(currentDimensionBounds.dimensions[2], unit)
|
|
1371
|
+
const heightLabel = formatMeasurement(currentDimensionBounds.dimensions[1], unit)
|
|
1372
|
+
const widthLabelPosition: [number, number, number] = [
|
|
1373
|
+
currentDimensionBounds.center[0],
|
|
1374
|
+
0.04,
|
|
1375
|
+
currentDimensionBounds.center[2] + currentDimensionBounds.dimensions[2] / 2 + 0.24,
|
|
1376
|
+
]
|
|
1377
|
+
const depthLabelPosition: [number, number, number] = [
|
|
1378
|
+
currentDimensionBounds.center[0] + currentDimensionBounds.dimensions[0] / 2 + 0.24,
|
|
1379
|
+
0.04,
|
|
1380
|
+
currentDimensionBounds.center[2],
|
|
1381
|
+
]
|
|
1382
|
+
const heightLabelPosition: [number, number, number] = [
|
|
1383
|
+
currentDimensionBounds.center[0] - currentDimensionBounds.dimensions[0] / 2 - 0.24,
|
|
1384
|
+
currentDimensionBounds.dimensions[1] / 2,
|
|
1385
|
+
currentDimensionBounds.center[2] - currentDimensionBounds.dimensions[2] / 2,
|
|
1386
|
+
]
|
|
1387
|
+
|
|
1388
|
+
const measurementContent = (
|
|
1389
|
+
<>
|
|
1390
|
+
<lineSegments
|
|
1391
|
+
geometry={initialWidthGuideGeometry}
|
|
1392
|
+
layers={EDITOR_LAYER}
|
|
1393
|
+
material={measurementMaterial}
|
|
1394
|
+
ref={measurementWidthRef}
|
|
1395
|
+
renderOrder={998}
|
|
1396
|
+
/>
|
|
1397
|
+
<lineSegments
|
|
1398
|
+
geometry={initialDepthGuideGeometry}
|
|
1399
|
+
layers={EDITOR_LAYER}
|
|
1400
|
+
material={measurementMaterial}
|
|
1401
|
+
ref={measurementDepthRef}
|
|
1402
|
+
renderOrder={998}
|
|
1403
|
+
/>
|
|
1404
|
+
<lineSegments
|
|
1405
|
+
geometry={initialHeightGuideGeometry}
|
|
1406
|
+
layers={EDITOR_LAYER}
|
|
1407
|
+
material={measurementMaterial}
|
|
1408
|
+
ref={measurementHeightRef}
|
|
1409
|
+
renderOrder={998}
|
|
1410
|
+
/>
|
|
1411
|
+
<Html center position={widthLabelPosition} style={{ pointerEvents: 'none' }}>
|
|
1412
|
+
<div
|
|
1413
|
+
style={{
|
|
1414
|
+
background: 'rgba(15, 23, 42, 0.86)',
|
|
1415
|
+
border: '1px solid rgba(15, 23, 42, 0.65)',
|
|
1416
|
+
borderRadius: '999px',
|
|
1417
|
+
color: '#f8fafc',
|
|
1418
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
1419
|
+
fontSize: '11px',
|
|
1420
|
+
fontWeight: 600,
|
|
1421
|
+
lineHeight: 1,
|
|
1422
|
+
padding: '4px 8px',
|
|
1423
|
+
pointerEvents: 'none',
|
|
1424
|
+
whiteSpace: 'nowrap',
|
|
1425
|
+
}}
|
|
1426
|
+
>
|
|
1427
|
+
{widthLabel}
|
|
1428
|
+
</div>
|
|
1429
|
+
</Html>
|
|
1430
|
+
<Html center position={depthLabelPosition} style={{ pointerEvents: 'none' }}>
|
|
1431
|
+
<div
|
|
1432
|
+
style={{
|
|
1433
|
+
background: 'rgba(15, 23, 42, 0.86)',
|
|
1434
|
+
border: '1px solid rgba(15, 23, 42, 0.65)',
|
|
1435
|
+
borderRadius: '999px',
|
|
1436
|
+
color: '#f8fafc',
|
|
1437
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
1438
|
+
fontSize: '11px',
|
|
1439
|
+
fontWeight: 600,
|
|
1440
|
+
lineHeight: 1,
|
|
1441
|
+
padding: '4px 8px',
|
|
1442
|
+
pointerEvents: 'none',
|
|
1443
|
+
whiteSpace: 'nowrap',
|
|
1444
|
+
}}
|
|
1445
|
+
>
|
|
1446
|
+
{depthLabel}
|
|
1447
|
+
</div>
|
|
1448
|
+
</Html>
|
|
1449
|
+
<Html center position={heightLabelPosition} style={{ pointerEvents: 'none' }}>
|
|
1450
|
+
<div
|
|
1451
|
+
style={{
|
|
1452
|
+
background: 'rgba(15, 23, 42, 0.86)',
|
|
1453
|
+
border: '1px solid rgba(15, 23, 42, 0.65)',
|
|
1454
|
+
borderRadius: '999px',
|
|
1455
|
+
color: '#f8fafc',
|
|
1456
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
1457
|
+
fontSize: '11px',
|
|
1458
|
+
fontWeight: 600,
|
|
1459
|
+
lineHeight: 1,
|
|
1460
|
+
padding: '4px 8px',
|
|
1461
|
+
pointerEvents: 'none',
|
|
1462
|
+
whiteSpace: 'nowrap',
|
|
1463
|
+
}}
|
|
1464
|
+
>
|
|
1465
|
+
{heightLabel}
|
|
1466
|
+
</div>
|
|
1467
|
+
</Html>
|
|
1468
|
+
</>
|
|
1469
|
+
)
|
|
969
1470
|
|
|
970
1471
|
return (
|
|
971
1472
|
<group ref={cursorGroupRef}>
|
|
972
|
-
<lineSegments
|
|
973
|
-
|
|
974
|
-
|
|
1473
|
+
<lineSegments
|
|
1474
|
+
geometry={initialEdgeGeometry}
|
|
1475
|
+
layers={EDITOR_LAYER}
|
|
1476
|
+
material={edgeMaterial}
|
|
1477
|
+
ref={edgesRef}
|
|
1478
|
+
renderOrder={999}
|
|
1479
|
+
/>
|
|
1480
|
+
{measurementContent}
|
|
975
1481
|
<mesh
|
|
976
1482
|
geometry={basePlaneGeometry}
|
|
977
1483
|
layers={EDITOR_LAYER}
|