diff --git a/webstore/index.html b/webstore/index.html index 3aca9b9..8b50003 100644 --- a/webstore/index.html +++ b/webstore/index.html @@ -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); });