Phase 5 Stage C continued: wall, door, window now in registry floor plan

Three remaining kinds at Stage C this session:

wall → C
 - buildWallFloorplan: uses ctx.siblings to gather other walls in the
   level, runs calculateLevelMiters, computes plan footprint via
   getWallPlanFootprint. Same visual output as legacy.
 - getFloorplanWall thickness exaggeration inlined (~25 lines from
   editor/lib/floorplan/walls.ts) to keep nodes/wall self-contained.
 - floorplan-panel.tsx's wallPolygons short-circuits to [] when wall
   is registered.
 - Performance note: recomputes level miter data per wall (O(N²) for N
   walls in a level). Acceptable for typical scenes; ctx.levelData?.
   miters optimization deferred to Stage B's wall design pass.

door → C
 - buildDoorFloorplan: inlines getOpeningFootprint math from
   floorplan-panel.tsx (40 lines, pure math). Uses ctx.parent as the
   wall to compute direction + perpendicular for the cutout footprint.
 - Returns null when parent isn't a wall (orphaned doors during
   placement).

window → C
 - buildWindowFloorplan: same shape as door, glass-blue tint to
   distinguish visually.

Both share the legacy openingsPolygons gating:
 - floorplan-panel.tsx's openingsPolygons useMemo filters per kind so
   a partial migration still works (e.g., if only door registers, only
   doors get skipped). When both registered, returns [] entirely.

Item C intentionally deferred — needs parent-chain transform helpers
(buildFloorplanItemEntry / getItemFloorplanTransform from editor/lib/
floorplan/items.ts) exposed publicly or moved into core. A focused
session is the right place to design that boundary.

Stage B for door / window / wall still pending — each is a focused
session per kind (large geometry math extractions, wall needs ctx.
levelData design).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-15 16:43:53 -04:00
co-authored by Claude Opus 4.7
parent 969b154b08
commit 9bcb25d0aa
7 changed files with 274 additions and 124 deletions
+12 -10
View File
@@ -1,4 +1,5 @@
import type { NodeDefinition } from '@pascal-app/core'
import { buildDoorFloorplan } from './floorplan'
import { doorParametrics } from './parametrics'
import { DoorNode } from './schema'
@@ -12,16 +13,14 @@ import { DoorNode } from './schema'
* keeps legacy `MoveDoorTool`.
* - `selectable`, `duplicable`, `deletable` standard.
*
* Relations:
* - `parentId` references a wall — re-anchors on wall move (handled by
* `DoorSystem`'s cascade to parent wall).
* - `cascadeDelete: 'children'` — door has no children in v1.
*
* Renderer + system: wrap-export legacy `DoorRenderer` + bundle
* `DoorSystem` + `DoorAnimationSystem`.
*
* Tool field absent: door placement / move tools wired through editor
* state, not registry dispatch. Legacy DoorTool / MoveDoorTool continue.
* Stages:
* - A: registered.
* - B: deferred — door geometry (frame / leaf / glass / hardware /
* segments) is ~800 lines in DoorSystem; extraction is a focused
* session. `def.renderer` (wrap-export of legacy DoorRenderer) +
* `def.system` (DoorSystem + DoorAnimationSystem bundle) hold parity.
* - C: `def.floorplan` polygon sits in parent wall's cutout. Legacy
* `openingPolygons` short-circuits door entries when registered.
*/
export const doorDefinition: NodeDefinition<typeof DoorNode> = {
kind: 'door',
@@ -56,6 +55,9 @@ export const doorDefinition: NodeDefinition<typeof DoorNode> = {
// before wall mitering at 4).
priority: 3,
},
// Stage C: floor-plan polygon. Needs ctx.parent (the wall) to compute
// direction + perpendicular for the cutout footprint.
floorplan: buildDoorFloorplan,
toolHints: [
{ key: 'Left click', label: 'Place door on wall' },
+60
View File
@@ -0,0 +1,60 @@
import type {
DoorNode,
FloorplanGeometry,
FloorplanPoint,
GeometryContext,
WallNode,
} from '@pascal-app/core'
/**
* Stage C floor-plan builder for door. Doors render as a small polygon
* sitting in the wall's cutout — width = door.width along the wall
* direction, depth = wall.thickness perpendicular.
*
* Requires `ctx.parent` to be a wall (door.parentId is the wall it's
* mounted on). Returns null when the parent isn't a wall (orphaned
* doors during placement etc.).
*
* Inlined from the legacy `getOpeningFootprint` helper in
* floorplan-panel.tsx. Window's builder is structurally identical.
*/
export function buildDoorFloorplan(node: DoorNode, ctx: GeometryContext): FloorplanGeometry | null {
const wall = ctx.parent as WallNode | null
if (!wall || wall.type !== 'wall') return null
const [x1, z1] = wall.start
const [x2, z2] = wall.end
const dx = x2 - x1
const dz = z2 - z1
const length = Math.sqrt(dx * dx + dz * dz)
if (length < 1e-9) return null
const dirX = dx / length
const dirZ = dz / length
const perpX = -dirZ
const perpZ = dirX
const distance = node.position[0] // door's local X = distance along wall
const width = node.width
const depth = wall.thickness ?? 0.1
const cx = x1 + dirX * distance
const cz = z1 + dirZ * distance
const halfWidth = width / 2
const halfDepth = depth / 2
const points: readonly FloorplanPoint[] = [
[cx - dirX * halfWidth + perpX * halfDepth, cz - dirZ * halfWidth + perpZ * halfDepth],
[cx + dirX * halfWidth + perpX * halfDepth, cz + dirZ * halfWidth + perpZ * halfDepth],
[cx + dirX * halfWidth - perpX * halfDepth, cz + dirZ * halfWidth - perpZ * halfDepth],
[cx - dirX * halfWidth - perpX * halfDepth, cz - dirZ * halfWidth - perpZ * halfDepth],
]
return {
kind: 'polygon',
points,
fill: '#f8fafc',
stroke: '#374151',
strokeWidth: 0.015,
opacity: 0.95,
}
}