feat(editor): mode-driven shelf/column/spawn placement + cross-kind floor collision
Migrate the remaining floor-placed kinds onto the unified snapping/modifier
model and generalize floor collision so any solid floor kind blocks any other.
- shelf/column/spawn declare `snapProfile: 'item'` → contextual snapping chip,
Shift=cycle, Ctrl=grid step during placement; their tools read the active
mode (grid/lines/off) instead of legacy Shift/Alt bypass; spawn fresh
placement now respects alignment ("lines") like its move.
- Resize/radial handles claim the handle-drag scope (new RESIZE_HANDLE_DRAG_LABEL)
so the HUD shows no select-mode shortcuts mid-resize.
- Column move migrated to the generic MoveRegistryNodeTool (declare `movable`,
drop the bespoke move-tool) — gains mode-driven snapping, alignment, R/T,
slab lift, grid SFX, and the collision box for free. 2D move still routes
through `floorplanMoveTarget`.
- Cross-kind floor collision: new declarative `FloorPlacedConfig.collides`
(item/shelf/column opt in; spawn/MEP/stair stay off). `canPlaceOnFloor` now
treats every colliding floor kind as an obstacle (was item-only), reading the
declarative footprint; the generic move tool's red/green placement box gates
on `collides`. Column footprint uses the visible `columnFootprintHalf` extent
so the box/slab-lift/collision track the real (round/square) column size.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
04f1b0d59e
commit
8a57105eec
@@ -298,20 +298,23 @@ function columnHandles(node: ColumnNodeType): HandleDescriptor<ColumnNodeType>[]
|
||||
/**
|
||||
* Column — Stage A registration. Wrap-export of the legacy
|
||||
* `ColumnRenderer` (no system — column geometry is computed inline in
|
||||
* the renderer). Inspector / move / floorplan still go through legacy
|
||||
* paths via panel-manager.tsx / item-move-tool.tsx / floorplan-panel.tsx
|
||||
* (their hardcoded `case 'column':` entries fire before the registry
|
||||
* fallback).
|
||||
* the renderer). Inspector / floorplan still go through legacy paths via
|
||||
* panel-manager.tsx / floorplan-panel.tsx (their hardcoded `case 'column':`
|
||||
* entries fire before the registry fallback).
|
||||
*
|
||||
* Capabilities: column doesn't declare `movable` because its move is
|
||||
* bespoke (legacy MoveColumnTool snaps to slab + free placement on
|
||||
* the X/Z plane with rotation).
|
||||
* Capabilities: column declares the generic `movable` (translate on XZ
|
||||
* with grid snap), so its 3D move runs through the shared
|
||||
* `MoveRegistryNodeTool` — which gives it grid/line/off snapping, alignment,
|
||||
* R/T rotation, slab-elevation lift, and the `collides` red/green placement
|
||||
* box for free. (2D move still routes through `floorplanMoveTarget`, which
|
||||
* wins the 2D dispatch.)
|
||||
*
|
||||
* Defaults computed via stub-parse so we leverage every zod
|
||||
* `.default()` annotation on the schema (~60 fields).
|
||||
*/
|
||||
export const columnDefinition: NodeDefinition<typeof ColumnNode> = {
|
||||
kind: 'column',
|
||||
snapProfile: 'item',
|
||||
schemaVersion: 1,
|
||||
schema: ColumnNode,
|
||||
category: 'structure',
|
||||
@@ -327,19 +330,29 @@ export const columnDefinition: NodeDefinition<typeof ColumnNode> = {
|
||||
selectable: { hitVolume: 'bbox' },
|
||||
duplicable: true,
|
||||
deletable: true,
|
||||
// Generic 3D translate-on-XZ via `MoveRegistryNodeTool` (grid snap + the
|
||||
// mode-driven snapping the overhaul standardised). 2D move keeps using
|
||||
// `floorplanMoveTarget`, which wins the 2D move dispatch.
|
||||
movable: { axes: ['x', 'z'], gridSnap: true },
|
||||
slots: (node) => columnSlots(node as ColumnNodeType),
|
||||
paint: columnPaint,
|
||||
// Slab elevation lift via the generic `<FloorElevationSystem>`.
|
||||
// Slab elevation lift via the generic `<FloorElevationSystem>` + the
|
||||
// placement/collision box. Use the VISIBLE footprint (round → radius,
|
||||
// square → width, rectangular → width/depth, plus brace spread) so the
|
||||
// box, slab-overlap, and collision all track the real column size rather
|
||||
// than the raw width/depth (stale for a round column resized by radius).
|
||||
floorPlaced: {
|
||||
footprint: (node) => {
|
||||
const column = node as ColumnNodeType
|
||||
const { halfX, halfZ } = columnFootprintHalf(column)
|
||||
return {
|
||||
dimensions: [column.width, column.height, column.depth] as [number, number, number],
|
||||
dimensions: [halfX * 2, column.height, halfZ * 2] as [number, number, number],
|
||||
// Column stores Y rotation as a scalar; the slab-overlap query
|
||||
// expects the full Euler tuple.
|
||||
rotation: [0, column.rotation, 0] as [number, number, number],
|
||||
}
|
||||
},
|
||||
collides: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -350,12 +363,6 @@ export const columnDefinition: NodeDefinition<typeof ColumnNode> = {
|
||||
kind: 'parametric',
|
||||
module: () => import('./renderer'),
|
||||
},
|
||||
// Stage D — 3D move-tool (registry-driven). Replaces the legacy
|
||||
// `MoveColumnTool` in editor's dispatcher. Same 0.5m grid snap +
|
||||
// live-transform preview the legacy used.
|
||||
affordanceTools: {
|
||||
move: () => import('./move-tool'),
|
||||
},
|
||||
// Registry-driven placement tool — renders a translucent `ColumnPreview`
|
||||
// ghost at the cursor (mirroring the shelf build tool) instead of the
|
||||
// bare sphere the legacy editor-side `ColumnTool` showed. `ToolManager`'s
|
||||
|
||||
Reference in New Issue
Block a user