@pascal-app/editor 0.9.2 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -7
- package/src/components/editor/bake-exporter.tsx +3 -1
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +307 -212
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +66 -2
- package/src/components/editor/first-person/build-collider-world.ts +20 -0
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +295 -132
- package/src/components/editor/floating-action-menu.tsx +59 -65
- package/src/components/editor/floorplan-background-selection.test.ts +16 -2
- package/src/components/editor/floorplan-background-selection.ts +4 -18
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +931 -861
- package/src/components/editor/group-actions.ts +155 -15
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-rotate-handle.tsx +1 -1
- package/src/components/editor/group-selection-box-3d.tsx +1 -1
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +17 -7
- package/src/components/editor/index.tsx +42 -9
- package/src/components/editor/measurement-pill.tsx +6 -1
- package/src/components/editor/node-arrow-handles.tsx +51 -15
- package/src/components/editor/quick-measurement-card.tsx +9 -3
- package/src/components/editor/quick-measurement-hud.tsx +7 -1
- package/src/components/editor/selection-manager.tsx +3 -1
- package/src/components/editor/site-edge-labels.tsx +24 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -1
- package/src/components/editor/thumbnail-generator.tsx +66 -294
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-mesh-settle-epoch.ts +1 -0
- package/src/components/editor/wall-measurement-label.tsx +8 -16
- package/src/components/editor/wall-move-side-handles.tsx +193 -18
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +6 -4
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +39 -8
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +47 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +21 -5
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +93 -59
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +27 -0
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +75 -1
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +92 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +93 -28
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +279 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +621 -281
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +12 -7
- package/src/components/tools/fence/fence-drafting.ts +27 -0
- package/src/components/tools/item/use-draft-node.ts +131 -72
- package/src/components/tools/item/use-placement-coordinator.tsx +52 -4
- package/src/components/tools/registry/move-registry-node-tool.tsx +75 -4
- package/src/components/tools/roof/roof-tool.tsx +16 -0
- package/src/components/tools/shared/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/site/site-boundary-editor.tsx +13 -6
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-tool.tsx +98 -47
- package/src/components/tools/tool-manager.tsx +67 -9
- package/src/components/tools/wall/wall-drafting.test.ts +118 -1
- package/src/components/tools/wall/wall-drafting.ts +128 -9
- package/src/components/tools/wall/wall-snap-geometry.test.ts +8 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +70 -3
- package/src/components/tools/zone/zone-tool.tsx +16 -0
- package/src/components/ui/action-menu/control-modes.tsx +4 -1
- package/src/components/ui/action-menu/measurement-control.tsx +107 -28
- package/src/components/ui/command-palette/editor-commands.tsx +14 -1
- package/src/components/ui/controls/material-paint-panel.tsx +7 -1
- package/src/components/ui/controls/material-picker.tsx +86 -7
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +73 -19
- package/src/components/ui/helpers/helper-manager.tsx +46 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/panel-manager.tsx +14 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -2
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +14 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +16 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -0
- package/src/components/viewer/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +20 -655
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +53 -30
- package/src/index.tsx +137 -4
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +8 -1
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/direct-manipulation.test.ts +83 -0
- package/src/lib/direct-manipulation.ts +9 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +265 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +745 -131
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +2 -3
- package/src/lib/fresh-planar-placement.test.ts +44 -1
- package/src/lib/fresh-planar-placement.ts +21 -4
- package/src/lib/glb-export.test.ts +116 -1
- package/src/lib/glb-export.ts +204 -7
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +22 -0
- package/src/lib/interaction/overlay-policy.test.ts +2 -1
- package/src/lib/interaction/scope.ts +40 -8
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/measurements.test.ts +5 -0
- package/src/lib/measurements.ts +12 -1
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/scene-clipboard.test.ts +130 -1
- package/src/lib/scene-clipboard.ts +237 -13
- package/src/lib/selection-routing.test.ts +16 -1
- package/src/lib/selection-routing.ts +5 -0
- package/src/lib/sfx-bus.ts +33 -2
- package/src/lib/sfx-player.test.ts +148 -6
- package/src/lib/sfx-player.ts +156 -8
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- package/src/lib/snapping-mode.test.ts +29 -2
- package/src/lib/snapping-mode.ts +15 -3
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +57 -85
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +184 -15
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-fence-curve-draft.ts +38 -8
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +54 -4
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +23 -3
- package/src/store/use-interaction-scope.ts +8 -0
- package/src/store/use-measurement-draft.test.ts +29 -0
- package/src/store/use-measurement-draft.ts +33 -6
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-stair-build-preview.ts +12 -2
- package/src/lib/floorplan/selection-tool.ts +0 -271
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import { cn } from '../lib/utils'
|
|
5
|
+
import type { WalkthroughInteract } from '../store/use-first-person-hud'
|
|
6
|
+
|
|
7
|
+
export type { WalkthroughInteract } from '../store/use-first-person-hud'
|
|
8
|
+
|
|
9
|
+
export type WalkthroughHudProps = {
|
|
10
|
+
floorLabel?: string | null
|
|
11
|
+
zoneLabel?: string | null
|
|
12
|
+
interact?: WalkthroughInteract
|
|
13
|
+
/** Pointer lock temporarily released (OS screenshot) — the pill flips to
|
|
14
|
+
* "Click to resume" and lets clicks fall through to the canvas. */
|
|
15
|
+
suspended?: boolean
|
|
16
|
+
onExit?: () => void
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function WalkthroughHud({
|
|
21
|
+
floorLabel,
|
|
22
|
+
zoneLabel,
|
|
23
|
+
interact = null,
|
|
24
|
+
suspended = false,
|
|
25
|
+
onExit,
|
|
26
|
+
children,
|
|
27
|
+
}: WalkthroughHudProps) {
|
|
28
|
+
const kbdClass = 'rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]'
|
|
29
|
+
const pillClass =
|
|
30
|
+
'flex items-center gap-1.5 rounded-full border border-border/40 bg-background/70 px-3 py-1 text-muted-foreground text-xs backdrop-blur-xl'
|
|
31
|
+
const exitContent = (
|
|
32
|
+
<>
|
|
33
|
+
<kbd className={kbdClass}>Esc</kbd>
|
|
34
|
+
to exit
|
|
35
|
+
</>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="dark pointer-events-none absolute inset-0 z-30 text-foreground">
|
|
40
|
+
<div className="absolute top-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-1.5">
|
|
41
|
+
{floorLabel && (
|
|
42
|
+
<div className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
43
|
+
{floorLabel}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
{zoneLabel && (
|
|
47
|
+
<div className="corner-smooth rounded-full border border-border/40 bg-background/80 px-3 py-1 font-medium text-sm shadow-elevation-3 backdrop-blur-xl">
|
|
48
|
+
{zoneLabel}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
55
|
+
<div
|
|
56
|
+
className={cn(
|
|
57
|
+
'rounded-full transition-all duration-150',
|
|
58
|
+
interact
|
|
59
|
+
? 'h-4 w-4 border-2 border-emerald-400 bg-emerald-400/10'
|
|
60
|
+
: 'h-1.5 w-1.5 bg-white/80 shadow-[0_0_2px_rgba(0,0,0,0.6)]',
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="absolute bottom-6 left-1/2 flex -translate-x-1/2 items-center gap-2">
|
|
66
|
+
{suspended ? (
|
|
67
|
+
<div className={pillClass}>
|
|
68
|
+
<span className="font-medium text-foreground">Click</span>
|
|
69
|
+
<span>or</span>
|
|
70
|
+
<kbd className={kbdClass}>P</kbd>
|
|
71
|
+
<span>to resume</span>
|
|
72
|
+
<span className="text-muted-foreground/60">·</span>
|
|
73
|
+
{exitContent}
|
|
74
|
+
</div>
|
|
75
|
+
) : (
|
|
76
|
+
<>
|
|
77
|
+
<div className={pillClass}>
|
|
78
|
+
<kbd className={kbdClass}>P</kbd>
|
|
79
|
+
free cursor
|
|
80
|
+
</div>
|
|
81
|
+
{onExit ? (
|
|
82
|
+
<button
|
|
83
|
+
className={cn(pillClass, 'pointer-events-auto')}
|
|
84
|
+
onClick={onExit}
|
|
85
|
+
type="button"
|
|
86
|
+
>
|
|
87
|
+
{exitContent}
|
|
88
|
+
</button>
|
|
89
|
+
) : (
|
|
90
|
+
<div className={pillClass}>{exitContent}</div>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{interact && (
|
|
97
|
+
<div className="absolute top-1/2 left-1/2 mt-7 -translate-x-1/2 whitespace-nowrap">
|
|
98
|
+
<div className="corner-smooth flex items-center gap-1.5 rounded-full border border-border/40 bg-background/80 px-3 py-1 text-xs shadow-elevation-3 backdrop-blur-xl">
|
|
99
|
+
<kbd className="rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]">
|
|
100
|
+
E
|
|
101
|
+
</kbd>
|
|
102
|
+
<span className="text-muted-foreground">or click to</span>
|
|
103
|
+
<span className="font-medium">
|
|
104
|
+
{interact.verb} {interact.label}
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -9,6 +9,8 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
9
9
|
import { useThree } from '@react-three/fiber'
|
|
10
10
|
import { useEffect, useRef } from 'react'
|
|
11
11
|
import { Plane, Raycaster, Vector2, Vector3 } from 'three'
|
|
12
|
+
import { getPlacementSurface } from '../lib/active-placement-surface'
|
|
13
|
+
import { resolveTerrainGroundHit } from '../lib/ground-surface'
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Custom grid events hook that uses manual raycasting instead of mesh events.
|
|
@@ -38,6 +40,28 @@ export function useGridEvents(gridY: number) {
|
|
|
38
40
|
// Update raycaster
|
|
39
41
|
raycaster.current.setFromCamera(pointer.current, camera)
|
|
40
42
|
|
|
43
|
+
// Sculpted ground wins over the plane, but only while the plane IS the
|
|
44
|
+
// ground (see `isSiteGroundPlane`): a plane riding a storey base or a slab
|
|
45
|
+
// top is a real flat surface and must stay planar. The march is what removes
|
|
46
|
+
// the perspective skew — a ray intersected at the datum instead of at the
|
|
47
|
+
// hillside reports an XZ that slides along the ray, so every tool consuming
|
|
48
|
+
// `position`/`localPosition` was placing short of the cursor on a slope.
|
|
49
|
+
//
|
|
50
|
+
// The plane's own height is the query height, not the `gridY` argument: this
|
|
51
|
+
// effect is keyed on `[camera, gl]` (re-attaching listeners mid-drag would
|
|
52
|
+
// drop the gesture), so the argument in this closure is the value from mount.
|
|
53
|
+
// `constant = -gridY` by construction in the effect above.
|
|
54
|
+
const { origin, direction } = raycaster.current.ray
|
|
55
|
+
const fixedConstructionPlane = getPlacementSurface()?.projection === 'fixed-plane'
|
|
56
|
+
const hit = fixedConstructionPlane
|
|
57
|
+
? null
|
|
58
|
+
: resolveTerrainGroundHit(
|
|
59
|
+
[origin.x, origin.y, origin.z],
|
|
60
|
+
[direction.x, direction.y, direction.z],
|
|
61
|
+
-groundPlane.current.constant,
|
|
62
|
+
)
|
|
63
|
+
if (hit) return intersectionPoint.current.set(hit.x, hit.y, hit.z).clone()
|
|
64
|
+
|
|
41
65
|
// Intersect with ground plane
|
|
42
66
|
if (raycaster.current.ray.intersectPlane(groundPlane.current, intersectionPoint.current)) {
|
|
43
67
|
return intersectionPoint.current.clone()
|
|
@@ -10,6 +10,11 @@ import {
|
|
|
10
10
|
import { useViewer } from '@pascal-app/viewer'
|
|
11
11
|
import { useEffect } from 'react'
|
|
12
12
|
import { Vector3 } from 'three'
|
|
13
|
+
import {
|
|
14
|
+
cutSelectionToEditorClipboard,
|
|
15
|
+
deleteSelection,
|
|
16
|
+
pasteSelectionAndPickUp,
|
|
17
|
+
} from '../components/editor/group-actions'
|
|
13
18
|
import {
|
|
14
19
|
classifyParticipant,
|
|
15
20
|
collectParticipants,
|
|
@@ -24,12 +29,11 @@ import { toggleDoorOpenState } from '../lib/door-interaction'
|
|
|
24
29
|
import { guideEmitter } from '../lib/guide-events'
|
|
25
30
|
import { runRedo, runUndo } from '../lib/history'
|
|
26
31
|
import { isActive } from '../lib/interaction/scope'
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} from '../lib/scene-clipboard'
|
|
31
|
-
import { emitDeleteSFX, sfxEmitter } from '../lib/sfx-bus'
|
|
32
|
+
import { copySelectedNodesToEditorClipboard } from '../lib/scene-clipboard'
|
|
33
|
+
import { sfxEmitter } from '../lib/sfx-bus'
|
|
34
|
+
import { activeSiteNode, clampBrushRadius } from '../lib/terrain-sculpt'
|
|
32
35
|
import { toggleWindowOpenState } from '../lib/window-interaction'
|
|
36
|
+
import useDeleteConfirmation from '../store/use-delete-confirmation'
|
|
33
37
|
import useEditor, { getActiveContinuationContext, getActiveSnapContext } from '../store/use-editor'
|
|
34
38
|
import useInteractionScope, { getMovingNode } from '../store/use-interaction-scope'
|
|
35
39
|
|
|
@@ -210,6 +214,10 @@ export const useKeyboard = ({
|
|
|
210
214
|
return
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
if (useDeleteConfirmation.getState().request) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
213
221
|
if (e.key === 'Shift' && !e.repeat && useEditor.getState().mode === 'material-paint') {
|
|
214
222
|
// In paint mode Shift cycles the application scope (this surface →
|
|
215
223
|
// whole item / all matching / room) — the paint-mode analogue of the
|
|
@@ -220,6 +228,34 @@ export const useKeyboard = ({
|
|
|
220
228
|
return
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
// Brush size, on the keys every sculpting tool in the industry uses. Gated
|
|
232
|
+
// on sculpt mode so `[`/`]` stay free everywhere else. Key-repeat is
|
|
233
|
+
// allowed (unlike the cycles above) because holding to resize is the
|
|
234
|
+
// expected feel, and the step is multiplicative so one press is a
|
|
235
|
+
// proportional change at both the floor and 20 m rather than 40× coarser at
|
|
236
|
+
// the bottom of the range. The range comes from `brushRadiusRange` so this
|
|
237
|
+
// and the panel's slider cannot disagree about it — and so the low end
|
|
238
|
+
// tracks the field's sample spacing, below which a dab paints nothing.
|
|
239
|
+
if (
|
|
240
|
+
(e.key === '[' || e.key === ']') &&
|
|
241
|
+
!e.metaKey &&
|
|
242
|
+
!e.ctrlKey &&
|
|
243
|
+
useEditor.getState().mode === 'terrain-sculpt'
|
|
244
|
+
) {
|
|
245
|
+
e.preventDefault()
|
|
246
|
+
const { terrainBrush, setTerrainBrush } = useEditor.getState()
|
|
247
|
+
const factor = e.key === ']' ? 1.25 : 1 / 1.25
|
|
248
|
+
const radius = clampBrushRadius(
|
|
249
|
+
activeSiteNode(),
|
|
250
|
+
Math.round(terrainBrush.radius * factor * 10) / 10,
|
|
251
|
+
)
|
|
252
|
+
if (radius !== terrainBrush.radius) {
|
|
253
|
+
setTerrainBrush({ radius })
|
|
254
|
+
sfxEmitter.emit('sfx:grid-snap')
|
|
255
|
+
}
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
223
259
|
if (e.key === 'Shift' && !e.repeat && isSnappingCycleContext()) {
|
|
224
260
|
// Cycle the global snapping mode (grid → lines → angles → off).
|
|
225
261
|
// `'off'` is the snap bypass now, so Shift no longer holds-to-bypass.
|
|
@@ -357,17 +393,24 @@ export const useKeyboard = ({
|
|
|
357
393
|
useEditor.getState().setPhase('structure')
|
|
358
394
|
useEditor.getState().setStructureLayer('elements')
|
|
359
395
|
useEditor.getState().setMode('material-paint')
|
|
396
|
+
} else if (e.key === 'g' && !e.metaKey && !e.ctrlKey) {
|
|
397
|
+
if (isVersionPreviewMode) return
|
|
398
|
+
e.preventDefault()
|
|
399
|
+
// G for ground. No `setPhase` — `setMode` moves to the site phase itself,
|
|
400
|
+
// and doing it here would set the phase twice with a mode reset between.
|
|
401
|
+
useEditor.getState().setMode('terrain-sculpt')
|
|
360
402
|
} else if (e.key === 'c' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
|
361
403
|
if (isVersionPreviewMode) return
|
|
362
404
|
e.preventDefault()
|
|
363
405
|
copySelectedNodesToEditorClipboard()
|
|
406
|
+
} else if (e.key === 'x' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
|
407
|
+
if (isVersionPreviewMode) return
|
|
408
|
+
e.preventDefault()
|
|
409
|
+
cutSelectionToEditorClipboard()
|
|
364
410
|
} else if (e.key === 'v' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
|
365
411
|
if (isVersionPreviewMode) return
|
|
366
412
|
e.preventDefault()
|
|
367
|
-
|
|
368
|
-
if (result?.pastedIds.length) {
|
|
369
|
-
sfxEmitter.emit('sfx:item-place')
|
|
370
|
-
}
|
|
413
|
+
void pasteSelectionAndPickUp()
|
|
371
414
|
} else if (e.key.toLowerCase() === 'z' && e.shiftKey && (e.metaKey || e.ctrlKey)) {
|
|
372
415
|
if (isVersionPreviewMode) return
|
|
373
416
|
e.preventDefault()
|
|
@@ -621,27 +664,7 @@ export const useKeyboard = ({
|
|
|
621
664
|
}
|
|
622
665
|
}
|
|
623
666
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (selectedNodeIds.length > 0) {
|
|
627
|
-
// Guard against accidental bulk deletion (e.g. box-select all + Delete)
|
|
628
|
-
const BULK_DELETE_THRESHOLD = 10
|
|
629
|
-
if (selectedNodeIds.length >= BULK_DELETE_THRESHOLD) {
|
|
630
|
-
const confirmed = window.confirm(
|
|
631
|
-
`Delete ${selectedNodeIds.length} selected elements? This cannot be undone if the undo history is exhausted.`,
|
|
632
|
-
)
|
|
633
|
-
if (!confirmed) return
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Play appropriate SFX based on what's being deleted
|
|
637
|
-
if (selectedNodeIds.length === 1) {
|
|
638
|
-
const node = useScene.getState().nodes[selectedNodeIds[0]!]
|
|
639
|
-
emitDeleteSFX(node?.type)
|
|
640
|
-
} else {
|
|
641
|
-
sfxEmitter.emit('sfx:structure-delete')
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
useScene.getState().deleteNodes(selectedNodeIds)
|
|
667
|
+
if (deleteSelection()) {
|
|
645
668
|
return
|
|
646
669
|
}
|
|
647
670
|
|
package/src/index.tsx
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
// own shells on top of `@pascal-app/editor` (community-app, embedders)
|
|
3
3
|
// don't have to learn three separate package imports. The canonical
|
|
4
4
|
// definitions still live in `@pascal-app/core` / `@pascal-app/viewer`.
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
type ApplySceneSnapshotOptions,
|
|
7
|
+
acquireSceneReadOnlyLease,
|
|
8
|
+
applyScenePatch,
|
|
9
|
+
applySceneSnapshot,
|
|
10
|
+
type SceneCommit,
|
|
11
|
+
type SceneCommitListener,
|
|
12
|
+
type SceneCommitOrigin,
|
|
13
|
+
type SceneMaterialPatch,
|
|
14
|
+
type SceneNodePatch,
|
|
15
|
+
type ScenePatch,
|
|
16
|
+
type SceneSnapshot,
|
|
17
|
+
subscribeSceneCommits,
|
|
18
|
+
useScene,
|
|
19
|
+
} from '@pascal-app/core'
|
|
6
20
|
export { useViewer } from '@pascal-app/viewer'
|
|
7
21
|
export type { EditorProps } from './components/editor'
|
|
8
22
|
export { default as Editor } from './components/editor'
|
|
@@ -12,6 +26,8 @@ export { default as Editor } from './components/editor'
|
|
|
12
26
|
// surface uses the shorter, shell-friendly names from the unified
|
|
13
27
|
// preset-system spec.
|
|
14
28
|
export { BakeExporter } from './components/editor/bake-exporter'
|
|
29
|
+
export { BakeThumbnail } from './components/editor/bake-thumbnail'
|
|
30
|
+
export { FirstPersonControls } from './components/editor/first-person-controls'
|
|
15
31
|
export { FloatingActionMenu as FloatingMenu } from './components/editor/floating-action-menu'
|
|
16
32
|
// Embed surface — the editor's real in-canvas affordances, so a host can mount
|
|
17
33
|
// authentic selection handles, interactive build tools, and the mover on top
|
|
@@ -66,6 +82,13 @@ export {
|
|
|
66
82
|
type SnapshotCameraData,
|
|
67
83
|
ThumbnailGenerator,
|
|
68
84
|
} from './components/editor/thumbnail-generator'
|
|
85
|
+
export { useFloorplanRender } from './components/editor-2d/floorplan-render-context'
|
|
86
|
+
export { FloorplanDimensionRenderer } from './components/editor-2d/renderers/floorplan-dimension-renderer'
|
|
87
|
+
export { FloorplanGeometryRenderer } from './components/editor-2d/renderers/floorplan-geometry-renderer'
|
|
88
|
+
export {
|
|
89
|
+
FloorplanNodePreview,
|
|
90
|
+
type FloorplanNodePreviewProps,
|
|
91
|
+
} from './components/editor-2d/renderers/floorplan-placement-preview-layer'
|
|
69
92
|
// SVG path builders for arc / annular-sector / arrow-head shapes —
|
|
70
93
|
// inlined into `kind: 'path'` / `kind: 'polygon'` primitives by curved
|
|
71
94
|
// stair rendering in `nodes/src/stair/floorplan.ts`.
|
|
@@ -113,7 +136,21 @@ export { CursorSphere } from './components/tools/shared/cursor-sphere'
|
|
|
113
136
|
export { DragBoundingBox } from './components/tools/shared/drag-bounding-box'
|
|
114
137
|
export { getFloorStackPreviewPosition } from './components/tools/shared/floor-stack-preview'
|
|
115
138
|
export { useFreshPlacementVisibility } from './components/tools/shared/fresh-placement-visibility'
|
|
139
|
+
export {
|
|
140
|
+
type HorizontalConstructionPlane,
|
|
141
|
+
publishHorizontalConstructionPlane,
|
|
142
|
+
resampleTerrainConstructionPlane,
|
|
143
|
+
resolveEventConstructionPlane,
|
|
144
|
+
resolveLevelConstructionPlane,
|
|
145
|
+
} from './components/tools/shared/horizontal-construction-plane'
|
|
116
146
|
export { PlacementBox } from './components/tools/shared/placement-box'
|
|
147
|
+
// Pointer-decided support surface (deck top vs floor underneath) — the
|
|
148
|
+
// draw tools (wall / fence) ride their grid plane and commit cap on it.
|
|
149
|
+
export {
|
|
150
|
+
type PointerSupportSurface,
|
|
151
|
+
resolvePointerSupportElevation,
|
|
152
|
+
resolvePointerSupportSurface,
|
|
153
|
+
} from './components/tools/shared/pointer-support-cap'
|
|
117
154
|
// Phase 5 Stage D — PolygonEditor for slab/ceiling boundary + hole editors.
|
|
118
155
|
export {
|
|
119
156
|
PolygonEditor,
|
|
@@ -148,7 +185,7 @@ export {
|
|
|
148
185
|
DEFAULT_STAIR_TYPE,
|
|
149
186
|
DEFAULT_STAIR_WIDTH,
|
|
150
187
|
} from './components/tools/stair/stair-defaults'
|
|
151
|
-
export { ToolManager } from './components/tools/tool-manager'
|
|
188
|
+
export { preloadRegistryToolModules, ToolManager } from './components/tools/tool-manager'
|
|
152
189
|
export {
|
|
153
190
|
chainEndJoinsExistingWall,
|
|
154
191
|
createWallOnCurrentLevel,
|
|
@@ -182,12 +219,20 @@ export {
|
|
|
182
219
|
} from './components/ui/action-menu/view-toggles'
|
|
183
220
|
export { useCommandPalette } from './components/ui/command-palette'
|
|
184
221
|
export { ActionButton, ActionGroup } from './components/ui/controls/action-button'
|
|
185
|
-
export {
|
|
186
|
-
|
|
222
|
+
export {
|
|
223
|
+
MaterialPaintPanel,
|
|
224
|
+
type MaterialPaintPanelProps,
|
|
225
|
+
} from './components/ui/controls/material-paint-panel'
|
|
226
|
+
export {
|
|
227
|
+
MaterialPicker,
|
|
228
|
+
type MaterialPickerProps,
|
|
229
|
+
type MaterialSourceFilter,
|
|
230
|
+
} from './components/ui/controls/material-picker'
|
|
187
231
|
export { MetricControl } from './components/ui/controls/metric-control'
|
|
188
232
|
export { PanelSection } from './components/ui/controls/panel-section'
|
|
189
233
|
export { SegmentedControl } from './components/ui/controls/segmented-control'
|
|
190
234
|
export { SliderControl } from './components/ui/controls/slider-control'
|
|
235
|
+
export { TerrainSculptPanel } from './components/ui/controls/terrain-sculpt-panel'
|
|
191
236
|
export { ToggleControl } from './components/ui/controls/toggle-control'
|
|
192
237
|
export { FloatingLevelSelector } from './components/ui/floating-level-selector'
|
|
193
238
|
export { CATALOG_ITEMS } from './components/ui/item-catalog/catalog-items'
|
|
@@ -229,6 +274,19 @@ export {
|
|
|
229
274
|
SnapTargetBadge,
|
|
230
275
|
SnapTargetIcon,
|
|
231
276
|
} from './components/ui/snap-target-badge'
|
|
277
|
+
export {
|
|
278
|
+
ViewerControlsBar,
|
|
279
|
+
type ViewerControlsBarProps,
|
|
280
|
+
} from './components/viewer/viewer-controls-bar'
|
|
281
|
+
export {
|
|
282
|
+
ViewerSceneHeader,
|
|
283
|
+
type ViewerSceneHeaderProps,
|
|
284
|
+
} from './components/viewer/viewer-scene-header'
|
|
285
|
+
export {
|
|
286
|
+
WalkthroughHud,
|
|
287
|
+
type WalkthroughHudProps,
|
|
288
|
+
type WalkthroughInteract,
|
|
289
|
+
} from './components/walkthrough-hud'
|
|
232
290
|
export type { SaveStatus } from './hooks/use-auto-save'
|
|
233
291
|
// useDragAction is the React-side glue for the registry's DragAction
|
|
234
292
|
// primitive. Public so registry-driven kinds (Phase 5+ Stage D ports)
|
|
@@ -236,6 +294,7 @@ export type { SaveStatus } from './hooks/use-auto-save'
|
|
|
236
294
|
export { type UseDragActionArgs, useDragAction } from './hooks/use-drag-action'
|
|
237
295
|
// Phase 5 Stage D — extras for kind-owned placement tools (FenceTool etc.).
|
|
238
296
|
export { markToolCancelConsumed } from './hooks/use-keyboard'
|
|
297
|
+
export { useReducedMotion } from './hooks/use-reduced-motion'
|
|
239
298
|
export { type Selection, useSelection } from './hooks/use-selection'
|
|
240
299
|
export {
|
|
241
300
|
clearPlacementSurface,
|
|
@@ -259,6 +318,17 @@ export {
|
|
|
259
318
|
continuationContextOf,
|
|
260
319
|
nextContinuation,
|
|
261
320
|
} from './lib/continuation'
|
|
321
|
+
export {
|
|
322
|
+
clearStructuralElevationGuide,
|
|
323
|
+
collectElevationSnapTargets,
|
|
324
|
+
ELEVATION_ALIGNMENT_THRESHOLD_M,
|
|
325
|
+
type ElevationGuideSource,
|
|
326
|
+
type ElevationSnapMatch,
|
|
327
|
+
type ElevationSnapTarget,
|
|
328
|
+
publishStructuralElevationGuide,
|
|
329
|
+
resolveElevationSnapMatch,
|
|
330
|
+
resolveStructuralElevationSnap,
|
|
331
|
+
} from './lib/elevation-guides'
|
|
262
332
|
export {
|
|
263
333
|
resolveCurrentBuildingId,
|
|
264
334
|
resolveElevatorNodeSupportY,
|
|
@@ -285,14 +355,50 @@ export {
|
|
|
285
355
|
type FloorplanStairSegmentEntry,
|
|
286
356
|
getFloorplanWallThickness,
|
|
287
357
|
} from './lib/floorplan'
|
|
358
|
+
export type {
|
|
359
|
+
FloorplanAnnotationCategory,
|
|
360
|
+
FloorplanAnnotationVisibility,
|
|
361
|
+
} from './lib/floorplan/annotation-visibility'
|
|
362
|
+
export {
|
|
363
|
+
createFloorplanContextExtensions,
|
|
364
|
+
FLOORPLAN_CONTEXT_EXTENSION_KEY,
|
|
365
|
+
FLOORPLAN_GEOMETRY_METADATA_KEY,
|
|
366
|
+
FLOORPLAN_NODE_EXTENSION_KEY,
|
|
367
|
+
type FloorplanAnnotationRole,
|
|
368
|
+
type FloorplanMetricNotation,
|
|
369
|
+
type FloorplanNodeExtension,
|
|
370
|
+
type FloorplanRenderPurpose,
|
|
371
|
+
type FloorplanSchedule,
|
|
372
|
+
type FloorplanToolContext,
|
|
373
|
+
type FloorplanToolMode,
|
|
374
|
+
floorplanGeometryMetadata,
|
|
375
|
+
getFloorplanNodeExtension,
|
|
376
|
+
readFloorplanContext,
|
|
377
|
+
readFloorplanGeometryMetadata,
|
|
378
|
+
readFloorplanMetricNotationOverride,
|
|
379
|
+
withFloorplanGeometryMetadata,
|
|
380
|
+
} from './lib/floorplan/floorplan-extension'
|
|
381
|
+
export {
|
|
382
|
+
DEFAULT_FLOORPLAN_MODE,
|
|
383
|
+
FLOORPLAN_MODES,
|
|
384
|
+
type FloorplanMode,
|
|
385
|
+
isFloorplanToolAvailableInMode,
|
|
386
|
+
} from './lib/floorplan/floorplan-mode'
|
|
288
387
|
export { commitFreshPlacementSubtree } from './lib/fresh-planar-placement'
|
|
289
388
|
export { exportSceneToGlb } from './lib/glb-export'
|
|
389
|
+
export {
|
|
390
|
+
type HistoryCommandDelegate,
|
|
391
|
+
installHistoryCommandDelegate,
|
|
392
|
+
runRedo,
|
|
393
|
+
runUndo,
|
|
394
|
+
} from './lib/history'
|
|
290
395
|
export {
|
|
291
396
|
boundaryReshapeScope,
|
|
292
397
|
curveReshapeScope,
|
|
293
398
|
endpointReshapeScope,
|
|
294
399
|
holeEditScope,
|
|
295
400
|
movingNodeOf,
|
|
401
|
+
scopeNodeId,
|
|
296
402
|
} from './lib/interaction/scope'
|
|
297
403
|
export {
|
|
298
404
|
buildResetSurfaceMaterialUpdates,
|
|
@@ -366,6 +472,7 @@ export type { SceneGraph } from './lib/scene'
|
|
|
366
472
|
export { applySceneGraphToEditor } from './lib/scene'
|
|
367
473
|
export { movementSfxStepKey } from './lib/sfx/movement-tick'
|
|
368
474
|
export { triggerSFX } from './lib/sfx-bus'
|
|
475
|
+
export { playSFX, type SFXName, type SFXPlaybackOptions } from './lib/sfx-player'
|
|
369
476
|
export {
|
|
370
477
|
clearSlabSnapFeedback,
|
|
371
478
|
resolveSlabEdgeBandSnap,
|
|
@@ -392,6 +499,13 @@ export {
|
|
|
392
499
|
type SurfacePlanSnapInput,
|
|
393
500
|
type SurfacePlanSnapResult,
|
|
394
501
|
} from './lib/surface-plan-snap'
|
|
502
|
+
export {
|
|
503
|
+
fieldExtentForSite,
|
|
504
|
+
flattenSite,
|
|
505
|
+
resetSiteTerrain,
|
|
506
|
+
resolveFlattenTarget,
|
|
507
|
+
sculptFieldForSite,
|
|
508
|
+
} from './lib/terrain-sculpt'
|
|
395
509
|
// `cn` (twMerge + clsx) — used by kind-owned panels in `@pascal-app/
|
|
396
510
|
// nodes` so they don't need their own copy / their own tailwind-merge
|
|
397
511
|
// dependency.
|
|
@@ -407,6 +521,10 @@ export {
|
|
|
407
521
|
export { default as useAlignmentGuides } from './store/use-alignment-guides'
|
|
408
522
|
export { default as useAudio } from './store/use-audio'
|
|
409
523
|
export { type CommandAction, useCommandRegistry } from './store/use-command-registry'
|
|
524
|
+
export {
|
|
525
|
+
DRAWING_TYPE_OPTIONS,
|
|
526
|
+
default as useDrawingView,
|
|
527
|
+
} from './store/use-drawing-view'
|
|
410
528
|
export type {
|
|
411
529
|
CaptureMode,
|
|
412
530
|
FloorplanSelectionTool,
|
|
@@ -429,6 +547,10 @@ export {
|
|
|
429
547
|
} from './store/use-editor'
|
|
430
548
|
export { default as useFacingPose, type FacingPose } from './store/use-facing-pose'
|
|
431
549
|
export { default as useFenceCurveDraft } from './store/use-fence-curve-draft'
|
|
550
|
+
export { type FirstPersonHudState, useFirstPersonHud } from './store/use-first-person-hud'
|
|
551
|
+
export { default as useFloorplanAnnotationVisibility } from './store/use-floorplan-annotation-visibility'
|
|
552
|
+
export { useFloorplanDraftPreview } from './store/use-floorplan-draft-preview'
|
|
553
|
+
export { default as useFloorplanMode } from './store/use-floorplan-mode'
|
|
432
554
|
export {
|
|
433
555
|
default as useInteractionScope,
|
|
434
556
|
getEditingHole,
|
|
@@ -466,6 +588,13 @@ export {
|
|
|
466
588
|
type PaletteViewProps,
|
|
467
589
|
usePaletteViewRegistry,
|
|
468
590
|
} from './store/use-palette-view-registry'
|
|
591
|
+
export {
|
|
592
|
+
type PathDraftKind,
|
|
593
|
+
type PathDraftParameter,
|
|
594
|
+
type PathDraftParameters,
|
|
595
|
+
type PathDraftPoint,
|
|
596
|
+
usePathDraftPreview,
|
|
597
|
+
} from './store/use-path-draft-preview'
|
|
469
598
|
export { default as usePlacementPreview } from './store/use-placement-preview'
|
|
470
599
|
export {
|
|
471
600
|
activateQuickMeasurementHudSource,
|
|
@@ -477,6 +606,10 @@ export {
|
|
|
477
606
|
useQuickMeasurementHud,
|
|
478
607
|
} from './store/use-quick-measurement-hud'
|
|
479
608
|
export { default as useSegmentDraftChain } from './store/use-segment-draft-chain'
|
|
609
|
+
export {
|
|
610
|
+
type StairPreviewPoint,
|
|
611
|
+
useStairBuildPreview,
|
|
612
|
+
} from './store/use-stair-build-preview'
|
|
480
613
|
export { useUploadStore } from './store/use-upload'
|
|
481
614
|
export { useWallMoveGhosts, type WallMoveGhostBridge } from './store/use-wall-move-ghosts'
|
|
482
615
|
export {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { Vector3 } from 'three'
|
|
3
|
+
import {
|
|
4
|
+
clearPlacementSurface,
|
|
5
|
+
getPlacementSurface,
|
|
6
|
+
publishPlacementSurface,
|
|
7
|
+
} from './active-placement-surface'
|
|
8
|
+
|
|
9
|
+
describe('active placement surface', () => {
|
|
10
|
+
afterEach(() => clearPlacementSurface())
|
|
11
|
+
|
|
12
|
+
test('marks a frozen construction plane so terrain raycasting can bypass it', () => {
|
|
13
|
+
publishPlacementSurface(new Vector3(1, 0, 2), new Vector3(0, 1, 0), 'fixed-plane')
|
|
14
|
+
|
|
15
|
+
expect(getPlacementSurface()?.projection).toBe('fixed-plane')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('ordinary surface publishes reset the projection mode', () => {
|
|
19
|
+
publishPlacementSurface(new Vector3(), new Vector3(0, 1, 0), 'fixed-plane')
|
|
20
|
+
publishPlacementSurface(new Vector3(), new Vector3(0, 1, 0))
|
|
21
|
+
|
|
22
|
+
expect(getPlacementSurface()?.projection).toBe('surface')
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -12,17 +12,24 @@ import { Vector3 } from 'three'
|
|
|
12
12
|
export type PlacementSurface = {
|
|
13
13
|
point: Vector3
|
|
14
14
|
normal: Vector3
|
|
15
|
+
projection: 'surface' | 'fixed-plane'
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const surface: PlacementSurface = {
|
|
18
19
|
point: new Vector3(),
|
|
19
20
|
normal: new Vector3(0, 1, 0),
|
|
21
|
+
projection: 'surface',
|
|
20
22
|
}
|
|
21
23
|
let active = false
|
|
22
24
|
|
|
23
|
-
export function publishPlacementSurface(
|
|
25
|
+
export function publishPlacementSurface(
|
|
26
|
+
point: Vector3,
|
|
27
|
+
normal: Vector3,
|
|
28
|
+
projection: PlacementSurface['projection'] = 'surface',
|
|
29
|
+
): void {
|
|
24
30
|
surface.point.copy(point)
|
|
25
31
|
surface.normal.copy(normal)
|
|
32
|
+
surface.projection = projection
|
|
26
33
|
active = true
|
|
27
34
|
}
|
|
28
35
|
|