[object Object]

← 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

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">&#9638;</button>
         <button id="btn-music" class="icon-btn" title="Listen to Music">&#9835;</button>
         <button id="btn-lighting" class="icon-btn" title="Lighting Mode">&#9788;</button>
         <button id="btn-fullscreen" class="icon-btn" title="Fullscreen">&#x26F6;</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 →