feat(editor): live floor-stacking, unified handle system, slab-hole editing + interaction polish (#375)
- Live slab-stacking Y previews for all floor-placed kinds (item/shelf/spawn/column/stair) during placement + both move pathways, via a shared core resolver; canonical positions unchanged. - Unified 3D handle system (one drag pipeline + one visual primitive) with forgiving invisible hit-areas on every handle, kept on EDITOR_LAYER so they don't poison the MRT scene pass. - Hover + click-to-edit slab holes in 3D (manual hole -> hole editor; stair/elevator hole -> select owner); generic cross-arrow polygon-move grip; normalized handle interaction colors. - NaN-safe node mutations + non-finite shadow-light bounds guard. - Built on #373 (level-scoped alignment / registry slab tool); #373 owns X/Z alignment, this owns Y floor-stacking.
This commit is contained in:
@@ -346,8 +346,10 @@ export type TranslateHandle<N = any> = {
|
||||
* `[alongX, alongOther]` — `alongOther` is Z for the 'horizontal' plane and
|
||||
* Y for 'node-normal'. Used to align the node's edges to the grid (rotation-
|
||||
* aware: swap the pair at 90°). Omit / return null for free movement.
|
||||
* `sceneApi` is supplied for composite nodes whose footprint depends on
|
||||
* children, such as straight stairs.
|
||||
*/
|
||||
snapExtents?: (node: N) => readonly [number, number] | null
|
||||
snapExtents?: (node: N, sceneApi: SceneApi) => readonly [number, number] | null
|
||||
portal?: HandlePortal
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ export type {
|
||||
CuttableConfig,
|
||||
DragAction,
|
||||
EditorCtx,
|
||||
FloorPlacedConfig,
|
||||
FloorPlacedFootprint,
|
||||
FloorPlacedFootprintContext,
|
||||
FloorPlacedFootprintResolver,
|
||||
FloorPlacedFootprintsResolver,
|
||||
FloorplanAffordance,
|
||||
FloorplanAffordanceModifiers,
|
||||
FloorplanAffordancePoint,
|
||||
|
||||
@@ -1235,20 +1235,40 @@ export type SelectableConfig = {
|
||||
override?: (ctx: CapabilityCtx) => SelectableConfig | null
|
||||
}
|
||||
|
||||
export type FloorPlacedFootprint = {
|
||||
dimensions: [number, number, number]
|
||||
rotation: [number, number, number]
|
||||
position?: [number, number, number]
|
||||
}
|
||||
|
||||
export type FloorPlacedFootprintContext = {
|
||||
nodes: Readonly<Record<AnyNodeId, AnyNode>>
|
||||
}
|
||||
|
||||
export type FloorPlacedFootprintResolver = (
|
||||
node: AnyNode,
|
||||
ctx?: FloorPlacedFootprintContext,
|
||||
) => FloorPlacedFootprint
|
||||
|
||||
export type FloorPlacedFootprintsResolver = (
|
||||
node: AnyNode,
|
||||
ctx?: FloorPlacedFootprintContext,
|
||||
) => readonly FloorPlacedFootprint[]
|
||||
|
||||
/**
|
||||
* Floor-placed kinds rest directly on a level and need their Y lifted by
|
||||
* any slab the footprint overlaps. The generic `<FloorElevationSystem>`
|
||||
* computes `slabElevation + node.position[1]` and writes it onto the
|
||||
* registered mesh on every dirty mark. `footprint` returns the world-space
|
||||
* footprint the spatial-grid manager uses to find overlapping slabs;
|
||||
* registered mesh on every dirty mark. `footprint` returns the default
|
||||
* world-space footprint the spatial-grid manager uses to find overlapping
|
||||
* slabs; `footprints` lets composite kinds expose multiple footprint
|
||||
* segments, with the canonical resolver taking the max slab elevation;
|
||||
* `applies` is an optional predicate to skip nodes that share a kind but
|
||||
* are mounted off-floor (items attached to a wall / ceiling).
|
||||
*/
|
||||
export type FloorPlacedConfig = {
|
||||
footprint: (node: AnyNode) => {
|
||||
dimensions: [number, number, number]
|
||||
rotation: [number, number, number]
|
||||
}
|
||||
footprint?: FloorPlacedFootprintResolver
|
||||
footprints?: FloorPlacedFootprintsResolver
|
||||
applies?: (node: AnyNode) => boolean
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user