← back to Quadrille Showroom
BOARD WALL DROP-SHADOW: each board casts a faint soft shadow onto the back wall behind it (shared soft-box sprite, dropped down+left to match the upper-right key light, opacity 0.34, just in front of the wall surface) so boards have real depth OFF the wall — not flush decals. Cheap: pure sprite, no shadow-map/light cost. Auto-hides on focus/isolate (board swings forward off the wall) and restores on close. FPS 72, errorCount 0, specs intact (27x27, China Seas)
ce3b7f362c6851363bc959616b41f78eb5dfa7e5 · 2026-06-27 12:42:42 -0700 · Steve
Files touched
Diff
commit ce3b7f362c6851363bc959616b41f78eb5dfa7e5
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jun 27 12:42:42 2026 -0700
BOARD WALL DROP-SHADOW: each board casts a faint soft shadow onto the back wall behind it (shared soft-box sprite, dropped down+left to match the upper-right key light, opacity 0.34, just in front of the wall surface) so boards have real depth OFF the wall — not flush decals. Cheap: pure sprite, no shadow-map/light cost. Auto-hides on focus/isolate (board swings forward off the wall) and restores on close. FPS 72, errorCount 0, specs intact (27x27, China Seas)
---
public/js/showroom.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 55 insertions(+), 2 deletions(-)
diff --git a/public/js/showroom.js b/public/js/showroom.js
index da75932..d0cce7e 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -328,6 +328,31 @@ function makeBoardLightOverlay(leafW, H, yC) {
return m;
}
+// WALL DROP-SHADOW — a soft-edged rectangular dark sprite for the shadow each board
+// casts onto the wall behind it. A tall vertical soft-box: opaque-ish in the middle,
+// feathered to nothing at all four edges, so a plane behind+below each board reads as a
+// believable contact-cast shadow off the wall (gives the board depth OFF the wall) at
+// zero shadow-map / light cost. Built once, shared.
+let _wallDropTex = null;
+function wallDropShadowTexture() {
+ if (_wallDropTex) return _wallDropTex;
+ const c = document.createElement('canvas'); c.width = 128; c.height = 256;
+ const ctx = c.getContext('2d');
+ ctx.clearRect(0, 0, 128, 256);
+ // Horizontal feather (left↔right) × vertical feather (top↔bottom), multiplied, so the
+ // sprite is darkest in the centre and fades on every edge.
+ const gx = ctx.createLinearGradient(0, 0, 128, 0);
+ gx.addColorStop(0.0, 'rgba(20,17,12,0)'); gx.addColorStop(0.5, 'rgba(20,17,12,1)'); gx.addColorStop(1.0, 'rgba(20,17,12,0)');
+ ctx.fillStyle = gx; ctx.fillRect(0, 0, 128, 256);
+ ctx.globalCompositeOperation = 'destination-in';
+ const gy = ctx.createLinearGradient(0, 0, 0, 256);
+ gy.addColorStop(0.0, 'rgba(0,0,0,0)'); gy.addColorStop(0.18, 'rgba(0,0,0,1)'); gy.addColorStop(0.82, 'rgba(0,0,0,1)'); gy.addColorStop(1.0, 'rgba(0,0,0,0)');
+ ctx.fillStyle = gy; ctx.fillRect(0, 0, 128, 256);
+ _wallDropTex = new THREE.CanvasTexture(c);
+ _wallDropTex.minFilter = THREE.LinearFilter; _wallDropTex.magFilter = THREE.LinearFilter;
+ return _wallDropTex;
+}
+
// Drop a soft contact shadow on the floor. (cx,cz) = world centre; w,d = footprint;
// opacity scales the darkness. Returns the mesh so callers can group/parent it.
function addContactShadow(cx, cz, w, d, opacity) {
@@ -1051,6 +1076,24 @@ function buildWingWall() {
shade.position.set(wingX, 0.006, 0.02); // 6 mm above floor, nudged just forward of the wall
shade.renderOrder = 1;
wallGroup.add(shade);
+
+ // WALL DROP-SHADOW — a faint soft shadow of the board cast onto the wall behind it
+ // (the key light comes from upper-right, so the shadow drops slightly down + left and
+ // is a touch taller/wider than the board). A flat sprite just in front of the wall
+ // surface (local z ≈ -0.19, wall is at -0.2) so it sits ON the wall, behind the board.
+ // Pure sprite — no extra shadow-map/light cost. Skipped when the board is open/away.
+ const dropMat = new THREE.MeshBasicMaterial({
+ map: wallDropShadowTexture(), transparent: true, opacity: 0.34,
+ depthWrite: false, polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1
+ });
+ const drop = new THREE.Mesh(new THREE.PlaneGeometry(wingW * 1.12, H * 1.04), dropMat);
+ drop.position.set(wingX - 0.05, yC - 0.05, -0.19); // down+left of the board, on the wall
+ drop.renderOrder = 0; // draw before the board face
+ drop.userData.isWallDrop = true;
+ wallGroup.add(drop);
+ // Carry a handle so isolateBoard()/focus can hide it when this board swings open
+ // (a swung-open board would no longer cast onto the wall directly behind it).
+ pivot.userData.wallDrop = drop;
}
// Vendor section labels and dividers — colors from the loaded brand list
@@ -1357,8 +1400,18 @@ function focusOnWing(pivot) {
// Isolate the focused board — hide the others so the open design is presented alone,
// dead-centre. Restored on exit / window rebuild.
-function isolateBoard(pivot) { wingBoards.forEach(p => { p.visible = (p === pivot); }); }
-function restoreBoards() { wingBoards.forEach(p => { p.visible = true; }); }
+function isolateBoard(pivot) {
+ wingBoards.forEach(p => {
+ p.visible = (p === pivot);
+ // Hide every wall drop-shadow in focus/hero mode: the other boards are hidden (their
+ // shadow would float), and the focused board swings forward off the wall (so it no
+ // longer casts directly behind itself). They come back in restoreBoards().
+ if (p.userData.wallDrop) p.userData.wallDrop.visible = false;
+ });
+}
+function restoreBoards() {
+ wingBoards.forEach(p => { p.visible = true; if (p.userData.wallDrop) p.userData.wallDrop.visible = true; });
+}
// Next / Back — flip to the next/previous wingboard (pages window edges, endless).
async function gotoBoardOffset(delta) {
← 433800b PER-BOARD LIGHT GRADIENT: each 6-ft board face gets a baked
·
back to Quadrille Showroom
·
REAL PICTURE-LIGHT SHADOWS: enable castShadow on the gallery ac87a36 →