@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.
- package/package.json +8 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
- package/src/components/editor/custom-camera-controls.tsx +754 -25
- package/src/components/editor/editor-layout-mobile.tsx +3 -3
- package/src/components/editor/editor-layout-v2.tsx +58 -51
- package/src/components/editor/export-manager.tsx +32 -3
- package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
- package/src/components/editor/first-person/build-collider-world.ts +201 -83
- package/src/components/editor/first-person/bvh-ecctrl.tsx +8 -8
- package/src/components/editor/first-person-controls.tsx +825 -20
- package/src/components/editor/floating-action-menu.tsx +287 -196
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.tsx +5829 -12005
- package/src/components/editor/grid.tsx +51 -17
- package/src/components/editor/group-move-handle.tsx +316 -0
- package/src/components/editor/group-rotate-handle.tsx +382 -0
- package/src/components/editor/group-transform-shared.test.ts +205 -0
- package/src/components/editor/group-transform-shared.ts +256 -0
- package/src/components/editor/handles/handle-arrow.tsx +523 -0
- package/src/components/editor/handles/use-handle-drag.ts +213 -0
- package/src/components/editor/index.tsx +178 -149
- package/src/components/editor/measurement-pill.tsx +75 -0
- package/src/components/editor/node-arrow-handles.tsx +1453 -0
- package/src/components/editor/selection-manager.tsx +433 -156
- package/src/components/editor/site-edge-labels.tsx +44 -20
- package/src/components/editor/slab-hole-highlights.tsx +466 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +136 -62
- package/src/components/editor/thumbnail-generator.tsx +9 -3
- package/src/components/editor/use-floorplan-background-placement.ts +119 -32
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +14 -2
- package/src/components/editor/wall-measurement-label.tsx +11 -6
- package/src/components/editor/wall-move-side-handles.tsx +855 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +26 -3
- package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
- package/src/components/systems/roof/roof-edit-system.tsx +86 -12
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +349 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
- package/src/components/tools/fence/fence-drafting.ts +64 -22
- package/src/components/tools/item/move-tool.tsx +41 -105
- package/src/components/tools/item/placement-math.test.ts +10 -0
- package/src/components/tools/item/placement-math.ts +31 -5
- package/src/components/tools/item/placement-strategies.ts +190 -15
- package/src/components/tools/item/placement-types.ts +10 -1
- package/src/components/tools/item/use-draft-node.ts +20 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +694 -191
- package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
- package/src/components/tools/roof/roof-tool.tsx +108 -12
- package/src/components/tools/select/box-select-state.ts +77 -0
- package/src/components/tools/select/box-select-tool.tsx +324 -538
- package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +123 -0
- package/src/components/tools/shared/polygon-editor.tsx +617 -167
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +464 -12
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -1
- package/src/components/tools/stair/stair-tool.tsx +269 -35
- package/src/components/tools/tool-manager.tsx +172 -56
- package/src/components/tools/wall/wall-drafting.ts +98 -98
- package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
- package/src/components/tools/zone/zone-tool.tsx +16 -6
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/control-modes.tsx +5 -82
- package/src/components/ui/action-menu/index.tsx +1 -113
- package/src/components/ui/action-menu/structure-tools.tsx +13 -88
- package/src/components/ui/action-menu/view-toggles.tsx +243 -6
- package/src/components/ui/command-palette/editor-commands.tsx +18 -13
- package/src/components/ui/command-palette/index.tsx +2 -1
- package/src/components/ui/controls/material-paint-panel.tsx +83 -0
- package/src/components/ui/controls/material-picker.tsx +11 -32
- package/src/components/ui/controls/metric-control.tsx +33 -11
- package/src/components/ui/floating-level-selector.tsx +47 -10
- package/src/components/ui/helpers/helper-manager.tsx +16 -17
- package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1 -0
- package/src/components/ui/item-catalog/item-catalog.tsx +3 -0
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +1 -0
- package/src/components/ui/panels/panel-manager.tsx +35 -47
- package/src/components/ui/panels/panel-wrapper.tsx +217 -12
- package/src/components/ui/panels/parametric-inspector.tsx +469 -0
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +4 -6
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -36
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +61 -1
- package/src/components/viewer-overlay.tsx +169 -52
- package/src/components/viewer-zone-system.tsx +1 -1
- package/src/hooks/use-auto-save.ts +10 -3
- package/src/hooks/use-ceiling-events.ts +175 -0
- package/src/hooks/use-drag-action.ts +124 -0
- package/src/hooks/use-keyboard.ts +115 -62
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +237 -5
- package/src/lib/constants.ts +6 -2
- package/src/lib/editor-api.ts +62 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.ts +111 -0
- package/src/lib/floorplan/index.ts +7 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +102 -0
- package/src/lib/fresh-planar-placement.ts +63 -0
- package/src/lib/history.ts +2 -1
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-name.ts +11 -0
- package/src/lib/material-paint.ts +211 -19
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +43 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/roof-duplication.ts +1 -1
- package/src/lib/scene-clipboard.ts +267 -0
- package/src/lib/scene.ts +13 -4
- package/src/lib/sfx-bus.ts +23 -1
- package/src/lib/sfx-player.ts +70 -22
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/world-grid-snap.ts +199 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-editor.tsx +320 -37
- package/src/store/use-placement-preview.ts +31 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +32 -0
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- 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.
|
|
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.
|
|
15
|
-
"@pascal-app/viewer": "^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.
|
|
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,8 +38,7 @@
|
|
|
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
|
-
"@
|
|
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",
|
|
@@ -54,14 +53,14 @@
|
|
|
54
53
|
"three-mesh-bvh": "~0.9.8"
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|
|
57
|
-
"@pascal-app/core": "^0.
|
|
58
|
-
"@pascal-app/viewer": "^0.
|
|
56
|
+
"@pascal-app/core": "^0.9.1",
|
|
57
|
+
"@pascal-app/viewer": "^0.9.1",
|
|
59
58
|
"@pascal/typescript-config": "*",
|
|
60
59
|
"@types/bun": "^1.3.0",
|
|
61
60
|
"@types/howler": "^2.2.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": "6.0.
|
|
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
|
+
}
|