Move render systems out of core
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user