Files
editor/packages/viewer/src/systems/ceiling/ceiling-system.tsx
T

112 lines
3.1 KiB
TypeScript

import { useFrame } from '@react-three/fiber'
import { type AnyNodeId, type CeilingNode, sceneRegistry, useScene } from '@pascal-app/core'
import * as THREE from 'three'
function ensureUv2Attribute(geometry: THREE.BufferGeometry) {
const uv = geometry.getAttribute('uv')
if (!uv) return
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2))
}
// ============================================================================
// CEILING SYSTEM
// ============================================================================
export const CeilingSystem = () => {
const dirtyNodes = useScene((state) => state.dirtyNodes)
const clearDirty = useScene((state) => state.clearDirty)
useFrame(() => {
if (dirtyNodes.size === 0) return
const nodes = useScene.getState().nodes
// Process dirty ceilings
dirtyNodes.forEach((id) => {
const node = nodes[id]
if (!node || node.type !== 'ceiling') return
const mesh = sceneRegistry.nodes.get(id) as THREE.Mesh
if (mesh) {
updateCeilingGeometry(node as CeilingNode, mesh)
clearDirty(id as AnyNodeId)
}
// If mesh not found, keep it dirty for next frame
})
})
return null
}
/**
* Updates the geometry for a single ceiling
*/
function updateCeilingGeometry(node: CeilingNode, mesh: THREE.Mesh) {
const newGeo = generateCeilingGeometry(node)
mesh.geometry.dispose()
mesh.geometry = newGeo
const gridMesh = mesh.getObjectByName('ceiling-grid') as THREE.Mesh
if (gridMesh) {
gridMesh.geometry.dispose()
gridMesh.geometry = newGeo
}
// Position at the ceiling height
mesh.position.y = (node.height ?? 2.5) - 0.01 // Slight offset to avoid z-fighting with upper-level slabs
}
/**
* Generates flat ceiling geometry from polygon (no extrusion)
*/
export function generateCeilingGeometry(ceilingNode: CeilingNode): THREE.BufferGeometry {
const polygon = ceilingNode.polygon
if (polygon.length < 3) {
return new THREE.BufferGeometry()
}
// Create shape from polygon
// Shape is in X-Y plane, we'll rotate to X-Z plane
const shape = new THREE.Shape()
const firstPt = polygon[0]!
// Negate Y (which becomes Z) to get correct orientation after rotation
shape.moveTo(firstPt[0], -firstPt[1])
for (let i = 1; i < polygon.length; i++) {
const pt = polygon[i]!
shape.lineTo(pt[0], -pt[1])
}
shape.closePath()
// Add holes to the shape
const holes = ceilingNode.holes || []
for (const holePolygon of holes) {
if (holePolygon.length < 3) continue
const holePath = new THREE.Path()
const holeFirstPt = holePolygon[0]!
holePath.moveTo(holeFirstPt[0], -holeFirstPt[1])
for (let i = 1; i < holePolygon.length; i++) {
const pt = holePolygon[i]!
holePath.lineTo(pt[0], -pt[1])
}
holePath.closePath()
shape.holes.push(holePath)
}
// Create flat shape geometry (no extrusion)
const geometry = new THREE.ShapeGeometry(shape)
// Rotate so the shape lies flat in X-Z plane
geometry.rotateX(-Math.PI / 2)
geometry.computeVertexNormals()
ensureUv2Attribute(geometry)
return geometry
}