fix(3d): decals snap to their preferred_wall (ported WowPlatter snapToWallAndOrient)
The store renderer placed every decal at raw pos_x/y/z with raw rotation, ignoring preferred_wall/snap/ object_type — so wall art (pos with one in-plane axis, perpendicular=0, rotation 0) floated in mid-room on the wrong axis. Ported WowPlatter public/js/virtual/decals.js snapToWallAndOrient: a space decal (rotation all-zero or snap=wall) keeps its in-plane axis, the perpendicular axis is pushed flat to its named wall (north z=-D/2 yaw0 · south z=D/2 yawpi · east x=W/2 yaw-pi/2 · west x=-W/2 yaw+pi/2 · floor/ceiling lie flat), yawed to face into the room; non-zero rotation = free manual placement, respected. Wall conventions already matched the room's walls. Fixes booth (31 decals) + entry-hall (6) automatically. Verified the snap math against real data (all land on-axis, in-bounds, facing in). Still TODO: crate_type(5)/rack(4) logo decals (backend only fetches object_type=space; need the face-snap port) + hiphop room has no decals authored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d489e8807d
commit
44517d4930
@ -262,6 +262,43 @@ function buildScene(data) {
|
||||
});
|
||||
|
||||
// decals — wall art / signage as textured planes (image) or canvas text. mesh decals skipped.
|
||||
// Placement is wall-RELATIVE (ported from WowPlatter public/js/virtual/decals.js snapToWallAndOrient):
|
||||
// a space decal keeps its in-plane axis, the perpendicular axis is pushed flat to its preferred_wall,
|
||||
// and the plane is yawed to face into the room (floor/ceiling lie flat). Data has pos with one axis 0
|
||||
// + all-zero rotation = "snap me to the wall"; a non-zero rotation or snap≠wall = free manual placement.
|
||||
const wallOf = p => { p = String(p || '').trim().toLowerCase();
|
||||
if (p === 'north' || p === 'front') return 'north';
|
||||
if (p === 'south' || p === 'back') return 'south';
|
||||
if (p === 'east' || p === 'right') return 'east';
|
||||
if (p === 'west' || p === 'left') return 'west';
|
||||
if (p === 'floor' || p === 'ceiling') return p;
|
||||
return null; };
|
||||
function placeDecal(m, d) {
|
||||
const rx = num(d.rotation_x), ry = num(d.rotation_y), rz = num(d.rotation_z);
|
||||
const isSpace = String(d.object_type || 'space').toLowerCase() === 'space';
|
||||
const snapMode = String(d.snap || '').toLowerCase();
|
||||
if (!(isSpace && (snapMode === 'wall' || (rx === 0 && ry === 0 && rz === 0)))) {
|
||||
m.position.set(num(d.pos_x), num(d.pos_y, 1.5), num(d.pos_z));
|
||||
m.rotation.set(rad(rx), rad(ry), rad(rz)); return;
|
||||
}
|
||||
const eps = 0.001;
|
||||
let x = num(d.pos_x), y = num(d.pos_y, H * 0.5), z = num(d.pos_z);
|
||||
y = Math.max(Math.min(y, H - 0.05), 0.05);
|
||||
let wall = wallOf(d.preferred_wall || d.wall);
|
||||
if (!wall) { // no wall set → nearest one
|
||||
const a = [['north', Math.abs(z + D / 2)], ['south', Math.abs(z - D / 2)],
|
||||
['east', Math.abs(x - W / 2)], ['west', Math.abs(x + W / 2)]];
|
||||
a.sort((p, q) => p[1] - q[1]); wall = a[0][0];
|
||||
}
|
||||
let yaw = 0, pitch = 0;
|
||||
if (wall === 'north') { z = -D / 2 + eps; yaw = 0; }
|
||||
else if (wall === 'south') { z = D / 2 - eps; yaw = Math.PI; }
|
||||
else if (wall === 'east') { x = W / 2 - eps; yaw = -Math.PI / 2; }
|
||||
else if (wall === 'west') { x = -W / 2 + eps; yaw = Math.PI / 2; }
|
||||
else if (wall === 'floor') { y = 0.02; pitch = -Math.PI / 2; }
|
||||
else if (wall === 'ceiling') { y = H - eps; pitch = Math.PI / 2; }
|
||||
m.position.set(x, y, z); m.rotation.set(pitch, yaw, 0);
|
||||
}
|
||||
(data.decals || []).forEach(d => {
|
||||
const w = num(d.width, 1), h = num(d.height, 1), op = num(d.opacity, 1);
|
||||
let mat;
|
||||
@ -273,8 +310,7 @@ function buildScene(data) {
|
||||
texLoader.load(d.image_url || d.asset_url, tx => { tx.colorSpace = THREE.SRGBColorSpace; mat.map = tx; mat.needsUpdate = true; }, undefined, () => {});
|
||||
} else { return; } // mesh / empty — skipped (ponytail: 17MB logo .glb left out; webp art covers the look)
|
||||
const m = new THREE.Mesh(new THREE.PlaneGeometry(w, h), mat);
|
||||
m.position.set(num(d.pos_x), num(d.pos_y, 1.5), num(d.pos_z));
|
||||
m.rotation.set(rad(num(d.rotation_x)), rad(num(d.rotation_y)), rad(num(d.rotation_z)));
|
||||
placeDecal(m, d);
|
||||
roomGroup.add(m);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user