Skip to main content

Overview

The CeilingSystem is a React component that processes dirty ceiling nodes and generates 3D ceiling geometry. It supports:
  • Polygon-based geometry matching room shapes
  • Hole support for ceiling cutouts (skylights, vents)
  • Configurable height positioning
  • Grid mesh support for ceiling tile visualization
The system runs in a useFrame hook (default priority), ensuring ceilings update after core systems.

Location

~/workspace/source/packages/core/src/systems/ceiling/ceiling-system.tsx

How It Works

Processing Pipeline

1

Monitor dirty nodes

The system watches the dirtyNodes set for nodes with type === 'ceiling'.
2

Update geometry

For each dirty ceiling:
  • Generate new flat geometry from the polygon
  • Add holes to the shape
  • Dispose old geometry and replace it
  • Update optional grid mesh (for ceiling tiles)
3

Position at height

Set mesh position to ceiling.height - 0.01 (slight offset to avoid z-fighting with upper-level slabs).
4

Clear dirty flag

Once the mesh is updated, the node is removed from dirtyNodes.

Geometry Generation

The generateCeilingGeometry() function:
  1. Create THREE.Shape from polygon points
  2. Add holes from the holes array
  3. Create ShapeGeometry (flat, no extrusion)
  4. Rotate to lie flat in X-Z plane
Unlike slabs, ceilings use ShapeGeometry instead of ExtrudeGeometry because they have zero thickness.

Props

The CeilingSystem component has no props. It reads state from the Zustand store:
dirtyNodes
Set<AnyNodeId>
Set of node IDs that need geometry updates
nodes
Record<AnyNodeId, AnyNode>
All nodes in the scene graph

Dependencies

CeilingNode Schema

The system processes CeilingNode objects with these properties:
id
string
required
Unique ceiling identifier
polygon
Array<[number, number]>
required
Array of [x, z] coordinates defining the ceiling boundary in level coordinates
holes
Array<Array<[number, number]>>
default:"[]"
Array of polygons representing holes in the ceiling (for skylights, vents, etc.)
height
number
default:"2.5"
Ceiling height in meters (Y-axis position)
children
string[]
Child item IDs (ceiling-attached items like lights, fans)

External Systems

  • Scene Registry (sceneRegistry.nodes): Maps node IDs to THREE.js meshes

Usage Example

import { CeilingSystem } from '@pascal/core/systems/ceiling/ceiling-system'
import { useScene } from '@pascal/core/store/use-scene'

function createCeiling() {
  const { addNode, markDirty } = useScene.getState()
  
  const ceiling = {
    id: 'ceiling_1',
    type: 'ceiling',
    parentId: 'level_1',
    polygon: [
      [0, 0],
      [5, 0],
      [5, 5],
      [0, 5],
    ],
    height: 2.5,
    holes: [],
    children: [],
  }
  
  addNode(ceiling)
  markDirty(ceiling.id)
}

function Viewer() {
  return (
    <Canvas>
      {/* Other scene content */}
      <CeilingSystem />
    </Canvas>
  )
}

Coordinate System

Ceilings use the level coordinate system:
  • X-axis: Horizontal (left-right)
  • Y-axis: Vertical (height)
  • Z-axis: Horizontal (forward-back)
Polygon coordinates are in the [x, z] plane, and the ceiling is positioned at height on the Y-axis.

Holes

Ceilings support multiple holes for architectural features:
const ceilingWithHoles = {
  id: 'ceiling_2',
  type: 'ceiling',
  polygon: [
    [0, 0], [10, 0], [10, 10], [0, 10]
  ],
  holes: [
    // Skylight
    [
      [2, 2], [4, 2], [4, 4], [2, 4]
    ],
    // Air vent
    [
      [6, 6], [7, 6], [7, 7], [6, 7]
    ],
  ],
  height: 3.0,
}
Holes are:
  • Defined as polygons in the same coordinate system
  • Automatically converted to THREE.Path and added to the shape
  • Cut completely through the ceiling (since it’s flat)

Ceiling Grid Mesh

The system supports an optional 'ceiling-grid' mesh for visualizing ceiling tiles:
// In your ceiling mesh component
<mesh ref={meshRef} name={node.id}>
  <primitive object={geometry} />
  <meshStandardMaterial color="white" />
  
  {/* Optional grid for ceiling tiles */}
  <mesh name="ceiling-grid">
    <primitive object={geometry} />
    <meshBasicMaterial 
      color="#333" 
      wireframe 
      opacity={0.2} 
      transparent 
    />
  </mesh>
</mesh>
The system automatically updates both the main mesh and the grid mesh when geometry changes.

Height Positioning

Ceilings are positioned at:
mesh.position.y = (node.height ?? 2.5) - 0.01
The -0.01 offset prevents z-fighting with upper-level slabs that might be at the same height.

Performance Notes

  • Only updates dirty ceilings (not all ceilings every frame)
  • Disposes old geometry before creating new geometry
  • Uses flat ShapeGeometry instead of extruded geometry for efficiency
  • Updates both main mesh and grid mesh in a single pass

Integration with Other Systems

Item System

Ceiling-attached items (lights, fans) use the ceiling node to:
  • Position at correct height
  • Orient downward
  • Parent to the ceiling in the scene graph

Wall System

Walls and ceilings often share the same polygon coordinates to create enclosed rooms. The wall height typically matches the ceiling height.

Build docs developers (and LLMs) love