fix(editor): improve guided manipulation and snap affordances
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { type AnyNodeId, DormerNode, useScene } from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useMemo } from 'react'
|
||||
import { RoofAttachmentFallbackPreview } from '../shared/roof-attachment-fallback-preview'
|
||||
import { dormerDefinition } from './definition'
|
||||
import DormerPreview from './preview'
|
||||
import { useDormerPlacement } from './use-dormer-placement'
|
||||
@@ -47,36 +48,44 @@ const DormerTool = () => {
|
||||
[],
|
||||
)
|
||||
|
||||
const { activeBuildingId, segmentXform, hitLocal, ghostRotation } = useDormerPlacement({
|
||||
onCommit: (hit, rotation) => {
|
||||
const state = useScene.getState()
|
||||
const dormer = DormerNode.parse({
|
||||
...dormerDefinition.defaults(),
|
||||
name: `Dormer ${nextDormerNumber(state.nodes)}`,
|
||||
roofSegmentId: hit.segment.id,
|
||||
parentId: hit.segment.id,
|
||||
// Anchor at the slope height so the renderer matches the ghost.
|
||||
// The CSG still carves cleanly because it inverts T(position)
|
||||
// when bringing the host into dormer-local.
|
||||
position: [hit.localX, hit.localY, hit.localZ],
|
||||
rotation,
|
||||
})
|
||||
state.createNode(dormer, hit.segment.id as AnyNodeId)
|
||||
state.dirtyNodes.add(hit.segment.id as AnyNodeId)
|
||||
setSelection({ selectedIds: [dormer.id] })
|
||||
},
|
||||
})
|
||||
|
||||
if (!activeBuildingId || !segmentXform || !hitLocal) return null
|
||||
const { activeBuildingId, clearPreview, segmentXform, hitLocal, ghostRotation } =
|
||||
useDormerPlacement({
|
||||
onCommit: (hit, rotation) => {
|
||||
const state = useScene.getState()
|
||||
const dormer = DormerNode.parse({
|
||||
...dormerDefinition.defaults(),
|
||||
name: `Dormer ${nextDormerNumber(state.nodes)}`,
|
||||
roofSegmentId: hit.segment.id,
|
||||
parentId: hit.segment.id,
|
||||
// Anchor at the slope height so the renderer matches the ghost.
|
||||
// The CSG still carves cleanly because it inverts T(position)
|
||||
// when bringing the host into dormer-local.
|
||||
position: [hit.localX, hit.localY, hit.localZ],
|
||||
rotation,
|
||||
})
|
||||
state.createNode(dormer, hit.segment.id as AnyNodeId)
|
||||
state.dirtyNodes.add(hit.segment.id as AnyNodeId)
|
||||
setSelection({ selectedIds: [dormer.id] })
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<group position={segmentXform.position} quaternion={segmentXform.quaternion}>
|
||||
<group position={hitLocal}>
|
||||
<group rotation-y={ghostRotation}>
|
||||
<DormerPreview node={previewNode} />
|
||||
<>
|
||||
<RoofAttachmentFallbackPreview
|
||||
activeBuildingId={activeBuildingId}
|
||||
onInvalidTarget={clearPreview}
|
||||
size={[1.8, 1.8, 1.4]}
|
||||
/>
|
||||
{activeBuildingId && segmentXform && hitLocal && (
|
||||
<group position={segmentXform.position} quaternion={segmentXform.quaternion}>
|
||||
<group position={hitLocal}>
|
||||
<group rotation-y={ghostRotation}>
|
||||
<DormerPreview node={previewNode} />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user