70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
import { type AnyNodeId, type RoofNode, sceneRegistry, useScene } from '@pascal-app/core'
|
|
import { useViewer } from '@pascal-app/viewer'
|
|
import { useEffect, useRef } from 'react'
|
|
|
|
/**
|
|
* Imperatively toggles the Three.js visibility of roof objects based on the
|
|
* editor selection — without causing React re-renders in RoofRenderer.
|
|
*
|
|
* When a roof-segment is selected:
|
|
* - merged-roof mesh is hidden
|
|
* - segments-wrapper group is shown (individual segments visible for editing)
|
|
* - all children are marked dirty so RoofSystem rebuilds their geometry
|
|
*
|
|
* When deselected:
|
|
* - merged-roof mesh is shown
|
|
* - segments-wrapper group is hidden
|
|
*/
|
|
export const RoofEditSystem = () => {
|
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
const prevActiveRoofIds = useRef(new Set<string>())
|
|
|
|
useEffect(() => {
|
|
const nodes = useScene.getState().nodes
|
|
|
|
// Collect which roof nodes should be in "edit mode".
|
|
// Selecting the roof itself should keep the merged visual intact so
|
|
// material appearance does not jump between merged and per-segment meshes.
|
|
const activeRoofIds = new Set<string>()
|
|
for (const id of selectedIds) {
|
|
const node = nodes[id as AnyNodeId]
|
|
if (!node) continue
|
|
if (node.type === 'roof-segment' && node.parentId) {
|
|
activeRoofIds.add(node.parentId)
|
|
}
|
|
}
|
|
|
|
// Update all roofs that are currently active OR were previously active
|
|
const roofIdsToUpdate = new Set([...activeRoofIds, ...prevActiveRoofIds.current])
|
|
|
|
for (const roofId of roofIdsToUpdate) {
|
|
const group = sceneRegistry.nodes.get(roofId)
|
|
if (!group) continue
|
|
|
|
const mergedMesh = group.getObjectByName('merged-roof')
|
|
const segmentsWrapper = group.getObjectByName('segments-wrapper')
|
|
const isActive = activeRoofIds.has(roofId)
|
|
|
|
if (mergedMesh) mergedMesh.visible = !isActive
|
|
if (segmentsWrapper) segmentsWrapper.visible = isActive
|
|
|
|
const roofNode = nodes[roofId as AnyNodeId] as RoofNode | undefined
|
|
if (roofNode?.children?.length) {
|
|
const wasActive = prevActiveRoofIds.current.has(roofId)
|
|
if (isActive !== wasActive) {
|
|
// Entering edit mode: rebuild individual segment geometries
|
|
// Exiting edit mode: sync transforms + rebuild merged mesh
|
|
const { markDirty } = useScene.getState()
|
|
for (const childId of roofNode.children) {
|
|
markDirty(childId as AnyNodeId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
prevActiveRoofIds.current = activeRoofIds
|
|
}, [selectedIds])
|
|
|
|
return null
|
|
}
|