feat(paint-slots): per-part paint for windows + doors, chrome/brass, world-scale UVs
Builds on the explicit per-mesh slot tagging (currentDoorSlot/currentWindowSlot): - Per-part painting: door = panel/frame/glass/hardware, window = frame/glass, each independently paintable. The recessed door/window body sits behind the wall, so the proud invisible cutout wins the scene raycast over the wall and the shared resolveSlotByReRaycast() re-raycasts the kind's own subtree to pick the exact part under the cursor (panel↔frame↔glass↔hardware). Hover tracks the cursor via a re-eval (idempotent, no flicker). - Door frame is its own slot (separate frameMaterial); hardware = new flat 'metal-chrome'. - Library defaults (generic): panel/frame -> library:preset-softwhite, glass -> library:preset-glass (flipped preset-glass to FrontSide — DoubleSide poisons the WebGPU MRT pass; it's the only glass we use). - Catalog: add flat (non-PBR) 'metal-chrome' + 'metal-brass'; drop metal metalness 1 -> 0.6 so metals are lit by existing lights (no env needed). - World-scale UVs (1 unit = 1m) on door/window box meshes via shared box-uv.ts, so finishes tile at real-world scale instead of stretching. - PaintResolveArgs gains an optional for subtree re-raycasting. 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
c3bd065f2f
commit
8aa179e9cb
@@ -0,0 +1,40 @@
|
||||
import type { BoxGeometry } from 'three'
|
||||
|
||||
/**
|
||||
* Rewrite a default `BoxGeometry`'s UVs to world scale — 1 UV unit = 1 metre —
|
||||
* so tiled finishes (with `repeat` in tiles-per-metre) render at a consistent
|
||||
* real-world scale instead of stretching to fit each face. Matches the
|
||||
* world-scale UV convention used by the procedural slab/wall geometry.
|
||||
*
|
||||
* three.js builds box faces in the fixed order [+X, -X, +Y, -Y, +Z, -Z], four
|
||||
* verts each, with UVs spanning 0→1 across the face. Each face's two in-plane
|
||||
* dimensions differ, so we scale U/V per face by that face's size in metres.
|
||||
*/
|
||||
export function applyWorldScaleBoxUVs(
|
||||
geometry: BoxGeometry,
|
||||
w: number,
|
||||
h: number,
|
||||
d: number,
|
||||
): void {
|
||||
const uv = geometry.getAttribute('uv')
|
||||
if (!uv || uv.count < 24) return // non-default segmentation — leave as-is
|
||||
|
||||
// [uScaleMetres, vScaleMetres] per face, in three's face order.
|
||||
const faceScale: Array<[number, number]> = [
|
||||
[d, h], // +X
|
||||
[d, h], // -X
|
||||
[w, d], // +Y
|
||||
[w, d], // -Y
|
||||
[w, h], // +Z
|
||||
[w, h], // -Z
|
||||
]
|
||||
|
||||
for (let face = 0; face < 6; face += 1) {
|
||||
const [us, vs] = faceScale[face]!
|
||||
for (let v = 0; v < 4; v += 1) {
|
||||
const i = face * 4 + v
|
||||
uv.setXY(i, uv.getX(i) * us, uv.getY(i) * vs)
|
||||
}
|
||||
}
|
||||
uv.needsUpdate = true
|
||||
}
|
||||
Reference in New Issue
Block a user