fix: sync ceiling handles during polygon edits

This commit is contained in:
Aymeric Rabot
2026-06-09 15:58:43 -04:00
parent 265acdb2be
commit f3c1407cae
2 changed files with 57 additions and 46 deletions
+31 -6
View File
@@ -3,7 +3,7 @@
import { type CeilingNode, resolveLevelId, useLiveNodeOverrides, useScene } from '@pascal-app/core'
import { PolygonEditor, triggerSFX } from '@pascal-app/editor'
import { useViewer } from '@pascal-app/viewer'
import { useCallback, useEffect, useRef } from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
/**
* Phase 5 Stage D — ceiling boundary editor (registry-driven).
@@ -25,8 +25,17 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
const setSelection = useViewer((s) => s.setSelection)
const setHoveredId = useViewer((s) => s.setHoveredId)
const ownsCeilingHoverRef = useRef(false)
const ownsPolygonPreviewRef = useRef(false)
const liveOverride = useLiveNodeOverrides((state) => {
if (ownsPolygonPreviewRef.current) return null
return state.overrides.get(ceilingId) as Partial<CeilingNode> | undefined
})
const ceiling = ceilingNode?.type === 'ceiling' ? (ceilingNode as CeilingNode) : null
const effectiveCeiling = useMemo(
() => (ceiling && liveOverride ? ({ ...ceiling, ...liveOverride } as CeilingNode) : ceiling),
[ceiling, liveOverride],
)
const handlePolygonChange = useCallback(
(newPolygon: Array<[number, number]>) => {
@@ -39,11 +48,13 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
const handlePolygonPreview = useCallback(
(preview: ReadonlyArray<readonly [number, number]> | null) => {
if (preview) {
ownsPolygonPreviewRef.current = true
useLiveNodeOverrides.getState().set(ceilingId, {
polygon: preview.map(([x, z]) => [x, z] as [number, number]),
})
} else {
useLiveNodeOverrides.getState().clear(ceilingId)
ownsPolygonPreviewRef.current = false
}
markDirty(ceilingId)
},
@@ -74,15 +85,28 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
const handleDragStateChange = useCallback(
(isDragging: boolean) => {
if (!isDragging) {
ownsPolygonPreviewRef.current = false
}
setCeilingHandleHover(isDragging)
},
[setCeilingHandleHover],
)
const handlePolygonEditorDragStart = useCallback(() => {
ownsPolygonPreviewRef.current = true
triggerSFX('sfx:item-pick')
}, [])
const handlePolygonEditorBeforeVertexDrag = useCallback(() => {
ownsPolygonPreviewRef.current = true
}, [])
useEffect(() => {
return () => {
useLiveNodeOverrides.getState().clear(ceilingId)
useScene.getState().markDirty(ceilingId)
ownsPolygonPreviewRef.current = false
if (ownsCeilingHoverRef.current && useViewer.getState().hoveredId === ceilingId) {
useViewer.getState().setHoveredId(null)
}
@@ -90,25 +114,26 @@ export const CeilingBoundaryEditor: React.FC<{ ceilingId: CeilingNode['id'] }> =
}
}, [ceilingId])
if (!ceiling?.polygon || ceiling.polygon.length < 3) return null
if (!effectiveCeiling?.polygon || effectiveCeiling.polygon.length < 3) return null
return (
<PolygonEditor
allowEdgeMove
color="#d4d4d4"
highlightConnectedHandles
levelId={resolveLevelId(ceiling, useScene.getState().nodes)}
levelId={resolveLevelId(effectiveCeiling, useScene.getState().nodes)}
minVertices={3}
onBeforeVertexDrag={handlePolygonEditorBeforeVertexDrag}
onDragStateChange={handleDragStateChange}
onDragCommit={() => triggerSFX('sfx:item-place')}
onDragStart={() => triggerSFX('sfx:item-pick')}
onDragStart={handlePolygonEditorDragStart}
onEdgeHoverChange={handleHandleHoverChange}
onMidpointHoverChange={handleHandleHoverChange}
onPolygonChange={handlePolygonChange}
onPolygonPreview={handlePolygonPreview}
onVertexHoverChange={handleHandleHoverChange}
polygon={ceiling.polygon}
surfaceHeight={ceiling.height ?? 2.5}
polygon={effectiveCeiling.polygon}
surfaceHeight={effectiveCeiling.height ?? 2.5}
/>
)
}