shelf: v2 — cubby default, withBottom, item hosting, paintable surface
Schema v2 adds style/rows/columns/withBack/withSides/withBottom/bracketStyle and a `children: ItemNode[]` field for item hosting. Schema-level defaults preserve the v1 wall-shelf visual so existing scenes load unchanged; the placement tool spreads `shelfDefinition.defaults()` for fresh shelves (cubby 3x2 at 1m × 0.5m × 1.8m, thickness 0.05m, back/sides/bottom on). Four style geometries (wall-shelf / bookshelf / open-rack / cubby) share the dimensional schema. `shelfRowSurfaceYs` exposes one host surface per row, plus the bottom-board top when `withBottom` is on for cubby / bookshelf. Material is a single paintable surface (same shape walls / slabs / stairs use); `DEFAULT_SHELF_MATERIAL` aligned with `DEFAULT_WALL_MATERIAL` so unpainted shelves read as the canonical off-white. Preview clones each cached material before mutating `transparent / opacity` on the ghost — without the clone the mutation leaked into the cached `getShelfMaterial` instance every committed shelf was using, rendering them all see-through after the first placement preview rendered. Store hardening: `migrateNodes` patches missing `children: []` on v1 shelves, and `updateNodesAction` reparenting tolerates a missing children array on the new parent. `MaterialTarget` enum adds `'shelf'` so paint mode picks up the kind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
56e022a093
commit
924567293a
@@ -2,20 +2,19 @@ import type { FloorplanGeometry } from '@pascal-app/core'
|
||||
import type { ShelfNode } from './schema'
|
||||
|
||||
/**
|
||||
* 2D floor-plan representation of a shelf. The top board (the largest
|
||||
* visible surface from above) projects to a rectangle of `width × depth`
|
||||
* centered on `(position.x, position.z)`, rotated by the shelf's Y angle.
|
||||
* 2D floor-plan representation of a shelf. The unit's outer footprint
|
||||
* projects to a rectangle of `width × depth` centered on the shelf's
|
||||
* position, rotated by its Y angle. For `bookshelf` / `cubby` with
|
||||
* columns > 1, vertical column dividers project as thin lines so the
|
||||
* grid is legible from above.
|
||||
*
|
||||
* Brackets are intentionally omitted — they're hidden under the top
|
||||
* board from a top-down view, and adding them as separate rects clutters
|
||||
* the plan without conveying useful information at typical zoom levels.
|
||||
* Brackets / posts / individual boards are intentionally omitted — they
|
||||
* stack vertically under the topmost board from a top-down view and
|
||||
* adding them clutters the plan without conveying useful information.
|
||||
*
|
||||
* Coordinates are level-local meters; the floor-plan panel applies the
|
||||
* world→SVG transform via its viewBox. Rotation is radians (three.js
|
||||
* convention); the renderer converts to SVG degrees.
|
||||
*
|
||||
* Pairs with `buildShelfGeometry(node)` — the 3D builder. Same shape,
|
||||
* different output projection.
|
||||
*/
|
||||
export function buildShelfFloorplan(node: ShelfNode): FloorplanGeometry {
|
||||
const [px, , pz] = node.position
|
||||
@@ -23,21 +22,48 @@ export function buildShelfFloorplan(node: ShelfNode): FloorplanGeometry {
|
||||
const halfW = node.width / 2
|
||||
const halfD = node.depth / 2
|
||||
|
||||
// Floor-plan fill: a single neutral fill regardless of `material`.
|
||||
// 2D doesn't render the actual paint material — surfaces in plan view
|
||||
// read as outline + tone, not photoreal texture. Using a fixed light
|
||||
// gray keeps the plan visually consistent with the other furniture
|
||||
// kinds (item / column / etc.) which also render as neutral fills.
|
||||
const children: FloorplanGeometry[] = [
|
||||
{
|
||||
kind: 'rect',
|
||||
x: -halfW,
|
||||
y: -halfD,
|
||||
width: node.width,
|
||||
height: node.depth,
|
||||
fill: '#d6d3d1',
|
||||
stroke: '#1f2937',
|
||||
strokeWidth: 0.015,
|
||||
opacity: 0.9,
|
||||
},
|
||||
]
|
||||
|
||||
// Show column dividers for grid-style shelves so the cubby / bookshelf
|
||||
// grid is visible from above.
|
||||
if ((node.style === 'bookshelf' || node.style === 'cubby') && node.columns > 1) {
|
||||
const innerWidth = node.width - 2 * node.thickness
|
||||
const colStep = innerWidth / node.columns
|
||||
for (let c = 1; c < node.columns; c++) {
|
||||
const x = -innerWidth / 2 + c * colStep
|
||||
children.push({
|
||||
kind: 'line',
|
||||
x1: x,
|
||||
y1: -halfD + node.thickness,
|
||||
x2: x,
|
||||
y2: halfD - node.thickness,
|
||||
stroke: '#1f2937',
|
||||
strokeWidth: 0.012,
|
||||
opacity: 0.7,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'group',
|
||||
transform: { translate: [px, pz], rotate: ry },
|
||||
children: [
|
||||
{
|
||||
kind: 'rect',
|
||||
x: -halfW,
|
||||
y: -halfD,
|
||||
width: node.width,
|
||||
height: node.depth,
|
||||
fill: node.color,
|
||||
stroke: '#1f2937',
|
||||
strokeWidth: 0.015,
|
||||
opacity: 0.9,
|
||||
},
|
||||
],
|
||||
children,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user