← back to Quadrille Showroom
Wall-cladding: focused/perused wing renders its pattern onto all room walls at architectural scale (toggle in HUD)
98df95db63393a6cb8e7b05ec4fc9c4166b3e556 · 2026-06-26 11:44:38 -0700 · Steve
Files touched
M public/js/showroom.jsM public/showroom.html
Diff
commit 98df95db63393a6cb8e7b05ec4fc9c4166b3e556
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 11:44:38 2026 -0700
Wall-cladding: focused/perused wing renders its pattern onto all room walls at architectural scale (toggle in HUD)
---
public/js/showroom.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++----
public/showroom.html | 1 +
2 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/public/js/showroom.js b/public/js/showroom.js
index 6dd120d..c36bc4e 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -37,6 +37,9 @@ let currentBrand = 'all', currentSort = 'natural', currentWallGroup = null;
// Peruse (endless auto-tour) state
let peruseActive = false, peruseIndex = 0, peruseTimer = null;
const PERUSE_DWELL = 3000; // ms per wing
+// Wall-cladding: render the focused pattern onto the actual room walls
+let roomWalls = []; // [{ mesh, w, h, origMat, cladMat }]
+let wallsAutoClad = true; // focusing a wing clads the room walls in its pattern
// SHARED materials — key perf optimization
const MAT = {};
@@ -268,10 +271,12 @@ function buildRoom() {
const ceil = new THREE.Mesh(new THREE.PlaneGeometry(W, D), new THREE.MeshLambertMaterial({ map: ct }));
ceil.rotation.x = Math.PI/2; ceil.position.y = H; scene.add(ceil);
- // Walls
- addWall(0, H/2, -D/2, W, H, 0, MAT.wall); // Back
- addWall(-W/2, H/2, 0, D, H, Math.PI/2, MAT.brick); // Left (brick)
- addWall(W/2, H/2, 0, D, H, -Math.PI/2, MAT.wall); // Right
+ // Walls — capture the 3 main surfaces so a focused pattern can clad them
+ roomWalls = [
+ { mesh: addWall(0, H/2, -D/2, W, H, 0, MAT.wall), w: W, h: H, origMat: MAT.wall }, // Back
+ { mesh: addWall(-W/2, H/2, 0, D, H, Math.PI/2, MAT.brick), w: D, h: H, origMat: MAT.brick }, // Left (brick)
+ { mesh: addWall(W/2, H/2, 0, D, H, -Math.PI/2, MAT.wall), w: D, h: H, origMat: MAT.wall }, // Right
+ ];
// Front (with doorway)
addWall(-W/4-0.3, H/2, D/2, W/2-0.6, H, Math.PI, MAT.wall);
addWall(W/4+0.3, H/2, D/2, W/2-0.6, H, Math.PI, MAT.wall);
@@ -317,6 +322,44 @@ function buildRoom() {
function addWall(x, y, z, w, h, rotY, mat) {
const m = new THREE.Mesh(new THREE.PlaneGeometry(w, h), mat);
m.position.set(x, y, z); m.rotation.y = rotY; scene.add(m);
+ return m;
+}
+
+// ============================================================
+// WALL CLADDING — render the selected pattern onto the room walls
+// ============================================================
+const PHYS_TILE_M = 0.686; // assumed physical tile ≈ 27" so the pattern reads at room scale
+function cladWalls(imageUrl, isSeamlessTile) {
+ if (!imageUrl || !roomWalls.length) return;
+ const cached = imageTexCache[imageUrl];
+ if (cached && cached.texture && cached.texture.image) { applyWallImage(cached.texture.image, isSeamlessTile); return; }
+ const src = imageUrl.charAt(0) === '/' ? imageUrl : ('/api/proxy/image?url=' + encodeURIComponent(imageUrl));
+ const img = new Image();
+ img.onload = () => applyWallImage(img, isSeamlessTile);
+ img.src = src;
+}
+
+function applyWallImage(img, isSeamlessTile) {
+ roomWalls.forEach(w => {
+ const tex = new THREE.Texture(img);
+ tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
+ tex.minFilter = THREE.LinearFilter; tex.magFilter = THREE.LinearFilter;
+ // Seamless tiles already repeat; raw swatches get a coarser repeat so seams hide in the architecture.
+ const div = isSeamlessTile ? PHYS_TILE_M : PHYS_TILE_M * 1.6;
+ tex.repeat.set(Math.max(1, Math.round(w.w / div)), Math.max(1, Math.round(w.h / div)));
+ tex.needsUpdate = true;
+ if (!w.cladMat) w.cladMat = new THREE.MeshLambertMaterial();
+ if (w.cladMat.map) w.cladMat.map.dispose();
+ w.cladMat.map = tex; w.cladMat.needsUpdate = true;
+ w.mesh.material = w.cladMat;
+ });
+}
+
+function revertWalls() {
+ roomWalls.forEach(w => {
+ w.mesh.material = w.origMat;
+ if (w.cladMat && w.cladMat.map) { w.cladMat.map.dispose(); w.cladMat.map = null; }
+ });
}
// ============================================================
@@ -902,6 +945,8 @@ function focusOnWing(pivot) {
// Show detail panel with product info
showWingDetail(product);
+ // Clad the room walls in this pattern (immersive "see it on the wall")
+ if (wallsAutoClad) cladWalls(product.image, product.isSeamlessTile);
updateOpenBtnState(false);
document.getElementById('info-text').textContent = `Wing: ${product.pattern_name || product.name} by ${product.vendor} | Fan open to browse adjacent patterns`;
}
@@ -976,6 +1021,9 @@ function closeFanCascade(skipBtnUpdate) {
function unfocusWing(skipPanel) {
if (fanWings.length > 0) closeFanCascade();
focusedWing = null;
+ // Revert walls only on a true exit — peruse's internal wing-to-wing close passes
+ // skipPanel=true and the next cladWalls overwrites, so no neutral flicker.
+ if (!skipPanel) revertWalls();
if (!skipPanel) document.getElementById('wing-detail').classList.add('hidden');
// Unlock first so smoothCameraTo can move
@@ -1314,6 +1362,15 @@ function initHUD() {
const pb = document.getElementById('btn-peruse');
if (pb) pb.addEventListener('click', () => togglePeruse());
+ const wb = document.getElementById('btn-walls');
+ if (wb) wb.addEventListener('click', () => {
+ wallsAutoClad = !wallsAutoClad;
+ wb.classList.toggle('active', wallsAutoClad);
+ if (wallsAutoClad && focusedWing) cladWalls(focusedWing.userData.product.image, focusedWing.userData.product.isSeamlessTile);
+ else revertWalls();
+ document.getElementById('info-text').textContent = wallsAutoClad ? 'Walls: wallpapered on focus' : 'Walls: neutral';
+ });
+
const vd = document.getElementById('btn-view-dw');
if (vd) vd.addEventListener('click', () => {
const p = window._activeProduct;
diff --git a/public/showroom.html b/public/showroom.html
index fbe77b5..adf3013 100644
--- a/public/showroom.html
+++ b/public/showroom.html
@@ -69,6 +69,7 @@
<button class="nav-btn active" data-section="overview" title="Full View">Overview</button>
</div>
<div class="top-right">
+ <button id="btn-walls" class="icon-btn active" title="Wallpaper the room walls on focus">▦</button>
<button id="btn-music" class="icon-btn" title="Listen to Music">♫</button>
<button id="btn-lighting" class="icon-btn" title="Lighting Mode">☼</button>
<button id="btn-fullscreen" class="icon-btn" title="Fullscreen">⛶</button>
← b98ec59 Record DTD hosting verdict (C: keep local, defer deploy; Opt
·
back to Quadrille Showroom
·
auto-save: 2026-06-26T12:33:24 (1 files) — server.js c208bf2 →