'use client' import { type AnyNode, type AnyNodeId, type SlabNode, useScene, type WallNode, } from '@pascal-app/core' import { useEffect } from 'react' /** * Slab dependency tracker. The renderable slab polygon derives from level * context — wall centerlines/thickness (exterior flush offsets) and sibling * slab polygons (interior centerline seams) — none of which lives on the slab * node itself. Store updates only dirty the node that changed, so a wall * thickness edit or a neighbour slab add/remove/reshape would leave stale * slab meshes. Watch a per-level signature of those inputs and dirty every * slab on a level whose signature moved; `GeometrySystem` then rebuilds * them through `def.geometry` as usual. */ function levelSlabContextSignatures(nodes: Record): Map { const partsByLevel = new Map() const push = (levelId: string, part: string) => { const parts = partsByLevel.get(levelId) if (parts) parts.push(part) else partsByLevel.set(levelId, [part]) } for (const node of Object.values(nodes)) { const levelId = node.parentId if (!levelId) continue if (node.type === 'wall') { const wall = node as WallNode push( levelId, `w|${wall.id}|${wall.start[0]},${wall.start[1]}|${wall.end[0]},${wall.end[1]}|${wall.thickness ?? ''}|${wall.curveOffset ?? ''}`, ) } else if (node.type === 'slab') { const slab = node as SlabNode // Elevation is a seam input: an unequal-elevation seam projects to // the lower side's wall face, so a height change reshapes siblings. push( levelId, `s|${slab.id}|${slab.elevation ?? ''}|${slab.polygon.map(([x, z]) => `${x},${z}`).join(';')}`, ) } } const signatures = new Map() for (const [levelId, parts] of partsByLevel.entries()) { signatures.set(levelId, parts.sort().join('||')) } return signatures } const SlabSystems = () => { useEffect(() => { let previous = levelSlabContextSignatures(useScene.getState().nodes) return useScene.subscribe((state) => { const current = levelSlabContextSignatures(state.nodes) for (const [levelId, signature] of current.entries()) { if (previous.get(levelId) === signature) continue for (const node of Object.values(state.nodes)) { if (node.type === 'slab' && node.parentId === levelId) { state.markDirty(node.id as AnyNodeId) } } } previous = current }) }, []) return null } export default SlabSystems