Depth-first session: drive registered kinds through Stage B (pure
def.geometry, drop system re-export) and Stage C (def.floorplan,
short-circuit legacy inline rendering in floorplan-panel.tsx).
spawn → C
- buildSpawnFloorplan wired on definition (was written but deferred
to avoid double-render).
- floorplan-panel.tsx's floorplanSpawnEntries useMemo short-circuits
to [] when nodeRegistry.has('spawn').
fence → B
- generateFenceGeometry exported from viewer; buildFenceGeometry
wraps it in a Group+Mesh with DEFAULT_STAIR_MATERIAL.
- def.geometry set; renderer + system fields dropped.
- Deleted nodes/src/fence/{renderer.tsx,system.tsx}.
fence → C
- buildFenceFloorplan: polyline along centerline (sampled for curved
fences via sampleWallCenterline from core). Stroke width = node.thickness.
- floorplan-panel.tsx's floorplanFenceEntries short-circuits.
slab → B
- generateSlabGeometry exported from viewer; buildSlabGeometry wraps
it in a Group+Mesh + cached material (preset / custom / default
pattern preserved from legacy renderer).
- def.geometry set; renderer + system fields dropped.
- Deleted nodes/src/slab/{renderer.tsx,system.tsx}.
slab → C
- buildSlabFloorplan: SVG path with outer polygon + hole subpaths
(uses getRenderableSlabPolygon from core for wall-clipping parity).
- floorplan-panel.tsx's slabPolygons short-circuits.
ceiling → B INTENTIONALLY SKIPPED
- Ceiling renderer renders React children (hosted items) + uses TSL
shader materials + named meshes that other systems poke
(getObjectByName('ceiling-grid')). Pure def.geometry can't preserve
that. Ceiling keeps def.renderer (the custom escape hatch) — same
pattern item uses. Documented in ceiling/definition.ts.
ceiling → C
- buildCeilingFloorplan: dashed-outline path with hole subpaths
(visually distinct from slab since ceilings are above).
- floorplan-panel.tsx's ceilingPolygons short-circuits.
Per-kind progress after this session:
- shelf: B ✅ C ✅ (Stage E since brand-new)
- spawn: A ✅ C ✅
- wall: A ✅ (B blocked on ctx.levelData design)
- fence: A ✅ B ✅ C ✅
- slab: A ✅ B ✅ C ✅
- ceiling: A ✅ C ✅ (B intentionally not applicable)
- door / window / item: A ✅ (B+C pending in future sessions)
Known test issue: `bun test` in packages/nodes fails to load
`three-bvh-csg` through the viewer's transitive imports (UMD/ESM
mismatch in Bun's test runner). The Next.js editor build works fine
because it bundles differently. Fix requires either dynamic imports
(breaks sync def.geometry contract) or test env config — deferred.
Other tests (schema, geometry, parity) pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import {
|
|
type FloorplanGeometry,
|
|
type FloorplanPoint,
|
|
getRenderableSlabPolygon,
|
|
type SlabNode,
|
|
} from '@pascal-app/core'
|
|
|
|
/**
|
|
* Stage C floor-plan builder for slab. Renders the slab polygon as a
|
|
* filled path with holes cut out.
|
|
*
|
|
* Uses `getRenderableSlabPolygon` (the same helper the legacy
|
|
* floorplan-panel.tsx uses) to compute the visual polygon — accounts
|
|
* for wall-clipping when a slab is auto-generated from walls.
|
|
*/
|
|
export function buildSlabFloorplan(node: SlabNode): FloorplanGeometry | null {
|
|
const polygon = node.polygon
|
|
if (!polygon || polygon.length < 3) return null
|
|
|
|
const visualPolygon = getRenderableSlabPolygon(node)
|
|
if (!visualPolygon || visualPolygon.length < 3) return null
|
|
|
|
const outer: FloorplanPoint[] = visualPolygon.map(([x, z]) => [x, z] as FloorplanPoint)
|
|
|
|
// SVG path with outer ring + hole subpaths. Each subpath uses M/L
|
|
// commands + Z to close. Holes follow the outer ring; FloorplanGeometry
|
|
// 'path' kind supports this natively (renderer passes the `d` string
|
|
// straight to the SVG <path>).
|
|
const segments: string[] = []
|
|
const ring = (points: FloorplanPoint[]) => {
|
|
const [first, ...rest] = points
|
|
if (!first) return ''
|
|
return [`M ${first[0]} ${first[1]}`, ...rest.map(([x, y]) => `L ${x} ${y}`), 'Z'].join(' ')
|
|
}
|
|
segments.push(ring(outer))
|
|
|
|
const holes = node.holes ?? []
|
|
for (const hole of holes) {
|
|
if (hole.length < 3) continue
|
|
const holePts: FloorplanPoint[] = hole.map(([x, z]) => [x, z] as FloorplanPoint)
|
|
segments.push(ring(holePts))
|
|
}
|
|
|
|
return {
|
|
kind: 'path',
|
|
d: segments.join(' '),
|
|
fill: '#cbd5e1',
|
|
stroke: '#475569',
|
|
strokeWidth: 0.03,
|
|
opacity: 0.85,
|
|
}
|
|
}
|