feat: sync surface polygon editing
This commit is contained in:
@@ -1,19 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
collectAlignmentAnchors,
|
||||
emitter,
|
||||
type GridEvent,
|
||||
type LevelNode,
|
||||
resolveAlignment,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { emitter, type GridEvent, type LevelNode, useScene } from '@pascal-app/core'
|
||||
import {
|
||||
CursorSphere,
|
||||
clearSlabSnapFeedback,
|
||||
EDITOR_LAYER,
|
||||
markToolCancelConsumed,
|
||||
resolveSlabPlanPointSnap,
|
||||
triggerSFX,
|
||||
useAlignmentGuides,
|
||||
useEditor,
|
||||
} from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
@@ -34,8 +28,6 @@ import { SlabNode } from './schema'
|
||||
*/
|
||||
|
||||
const Y_OFFSET = 0.02
|
||||
/** Figma-style alignment-snap threshold (meters), matching the move tools. */
|
||||
const ALIGNMENT_THRESHOLD_M = 0.08
|
||||
|
||||
function calculateSnapPoint(
|
||||
lastPoint: [number, number],
|
||||
@@ -90,52 +82,11 @@ export const SlabTool: React.FC = () => {
|
||||
// isn't built with a stale preset's parameters. Unmount-only.
|
||||
useEffect(() => () => useEditor.getState().setToolDefaults('slab', null), [])
|
||||
|
||||
// Clear alignment guides on unmount ONLY. The main drawing effect re-runs
|
||||
// on every cursor move (cursorPosition is in its deps), so clearing guides
|
||||
// in its cleanup would wipe the guide the instant after each move sets it.
|
||||
useEffect(() => () => useAlignmentGuides.getState().clear(), [])
|
||||
useEffect(() => () => clearSlabSnapFeedback(), [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentLevelId) return
|
||||
|
||||
// Alignment candidates — anchors of every OTHER alignable object. The
|
||||
// slab's own in-progress vertices are intentionally excluded (no
|
||||
// self-alignment while drawing).
|
||||
const alignmentCandidates = collectAlignmentAnchors(useScene.getState().nodes, '')
|
||||
// Snap the drafted vertex onto another object's nearest real anchor and
|
||||
// publish the guide. The probe is the RAW cursor, NOT the 0.5m-grid-snapped
|
||||
// point: resolving against the grid point would only ever catch anchors
|
||||
// that happen to sit on a grid line, so off-grid items (furniture, angled
|
||||
// walls) would never surface a guide. The matched axis locks exactly to the
|
||||
// candidate's coordinate; the other axis keeps its grid/ortho snap. Alt
|
||||
// bypasses.
|
||||
const alignPoint = (
|
||||
fallback: [number, number],
|
||||
raw: [number, number],
|
||||
bypass: boolean,
|
||||
): [number, number] => {
|
||||
if (bypass || alignmentCandidates.length === 0) {
|
||||
useAlignmentGuides.getState().clear()
|
||||
return fallback
|
||||
}
|
||||
const ar = resolveAlignment({
|
||||
moving: [{ nodeId: '__slab-draft__', kind: 'corner', x: raw[0], z: raw[1] }],
|
||||
candidates: alignmentCandidates,
|
||||
threshold: ALIGNMENT_THRESHOLD_M,
|
||||
})
|
||||
if (ar.guides.length === 0) {
|
||||
useAlignmentGuides.getState().clear()
|
||||
return fallback
|
||||
}
|
||||
useAlignmentGuides.getState().set(ar.guides)
|
||||
let [x, z] = fallback
|
||||
for (const guide of ar.guides) {
|
||||
if (guide.axis === 'x') x = guide.coord
|
||||
else z = guide.coord
|
||||
}
|
||||
return [x, z]
|
||||
}
|
||||
|
||||
const onGridMove = (event: GridEvent) => {
|
||||
if (!cursorRef.current) return
|
||||
const rawPoint: [number, number] = [event.localPosition[0], event.localPosition[2]]
|
||||
@@ -149,7 +100,12 @@ export const SlabTool: React.FC = () => {
|
||||
shiftPressed.current || !lastPoint
|
||||
? gridPosition
|
||||
: calculateSnapPoint(lastPoint, gridPosition)
|
||||
const displayPoint = alignPoint(orthoPoint, rawPoint, event.nativeEvent?.altKey === true)
|
||||
const displayPoint = resolveSlabPlanPointSnap({
|
||||
rawPoint,
|
||||
fallbackPoint: orthoPoint,
|
||||
levelId: currentLevelId,
|
||||
altKey: event.nativeEvent?.altKey === true,
|
||||
}).point
|
||||
setSnappedCursorPosition(displayPoint)
|
||||
if (
|
||||
points.length > 0 &&
|
||||
@@ -176,7 +132,7 @@ export const SlabTool: React.FC = () => {
|
||||
const slabId = commitSlabDrawing(currentLevelId, points)
|
||||
setSelection({ selectedIds: [slabId] })
|
||||
setPoints([])
|
||||
useAlignmentGuides.getState().clear()
|
||||
clearSlabSnapFeedback()
|
||||
} else {
|
||||
// Every non-closing vertex is a "start" tick; the closing click above
|
||||
// fires the structure-build (end) cue.
|
||||
@@ -191,14 +147,14 @@ export const SlabTool: React.FC = () => {
|
||||
const slabId = commitSlabDrawing(currentLevelId, points)
|
||||
setSelection({ selectedIds: [slabId] })
|
||||
setPoints([])
|
||||
useAlignmentGuides.getState().clear()
|
||||
clearSlabSnapFeedback()
|
||||
}
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
if (points.length > 0) markToolCancelConsumed()
|
||||
setPoints([])
|
||||
useAlignmentGuides.getState().clear()
|
||||
clearSlabSnapFeedback()
|
||||
}
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user