floorplan/zone: name label inside polygon + polygon editor on select

`buildZoneFloorplan` now emits a centered name label at the polygon's
area-weighted centroid (Shoelace formula, with bbox-center fallback for
degenerate rings). Label uses the legacy `FloorplanZoneLabel` styling:
`fontSize: 0.2`, white fill, zone-color stroke, `paintOrder: 'stroke'`
for the "outlined text" look that stays legible above any fill.

When the zone is selected the builder also emits the polygon editor —
edge-handle per edge, midpoint-handle per midpoint, endpoint-handle per
vertex — driven by the shared `createPolygonVertexAffordance` /
`createPolygonAddVertexAffordance` / `createPolygonMoveEdgeAffordance`
factories slabs and ceilings already use. Zones have no `holes` field
so the factory's optional `holeIndex` stays undefined and the operations
target `node.polygon` directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-19 15:12:02 -04:00
co-authored by Claude Opus 4.7
parent 3419cf8587
commit 0dee7747d2
8 changed files with 506 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import { type NodeDefinition, ZoneNode as ZoneNodeSchema } from '@pascal-app/core'
import {
zoneAddVertexAffordance,
zoneMoveEdgeAffordance,
zoneMoveVertexAffordance,
} from './floorplan-affordances'
import { buildZoneFloorplan } from './floorplan'
import { zoneParametrics } from './parametrics'
import { ZoneNode } from './schema'
/**
* Zone — Stage A. Custom-behavior escape hatch: zone uses TSL shader
* materials + `<Html>` portals + per-frame uniform poking, so it
* lives via `def.renderer` + `def.system` (no `def.geometry` possible
* because zone isn't really a mesh).
*/
export const zoneDefinition: NodeDefinition<typeof ZoneNode> = {
kind: 'zone',
schemaVersion: 1,
schema: ZoneNode,
category: 'site',
defaults: () => {
const stub = ZoneNodeSchema.parse({ id: 'zone_default' as never, type: 'zone' })
const { id: _id, type: _type, ...rest } = stub
return rest
},
capabilities: {
selectable: { hitVolume: 'bbox' },
duplicable: true,
deletable: true,
},
parametrics: zoneParametrics,
renderer: {
kind: 'parametric',
module: () => import('./renderer'),
},
system: {
module: () => import('./system'),
priority: 4,
},
floorplan: buildZoneFloorplan,
// Polygon editor when selected — same three operations slabs / ceilings
// expose. The shared factories key off `node.polygon`, optional
// `node.holes` (absent on zones). See `floorplan-affordances.ts`.
floorplanAffordances: {
'move-vertex': zoneMoveVertexAffordance,
'add-vertex': zoneAddVertexAffordance,
'move-edge': zoneMoveEdgeAffordance,
},
presentation: {
label: 'Zone',
description: 'A polygonal site zone (lawn, water, paving) with a TSL gradient material.',
icon: { kind: 'url', src: '/icons/zone.png' },
paletteSection: 'site',
paletteOrder: 20,
},
mcp: {
description: 'A polygon-bounded site zone with a typed surface (grass / water / paving / ...).',
},
}