Add parquet floor, museum-style exit, movement-tinted walls, and gold/black frames with gallery sync after Fix it. Debug panel gets Checked and Fix it buttons; documentation and image-fetch reliability updates included. Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import * as THREE from 'three';
|
|
|
|
/** Warm oak tones with subtle variation — museum-style herringbone parquet. */
|
|
const OAK_TONES = ['#6b4a2e', '#735234', '#624428', '#7a5638', '#5c3f26', '#6e4d30'];
|
|
|
|
function shade(hex: string, amount: number): string {
|
|
const n = parseInt(hex.slice(1), 16);
|
|
const r = Math.min(255, Math.max(0, ((n >> 16) & 255) + amount));
|
|
const g = Math.min(255, Math.max(0, ((n >> 8) & 255) + amount));
|
|
const b = Math.min(255, Math.max(0, (n & 255) + amount));
|
|
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
|
|
}
|
|
|
|
function drawPlank(
|
|
ctx: CanvasRenderingContext2D,
|
|
cx: number,
|
|
cy: number,
|
|
length: number,
|
|
width: number,
|
|
angle: number,
|
|
color: string
|
|
) {
|
|
ctx.save();
|
|
ctx.translate(cx, cy);
|
|
ctx.rotate(angle);
|
|
|
|
const grad = ctx.createLinearGradient(-length / 2, 0, length / 2, 0);
|
|
grad.addColorStop(0, shade(color, -12));
|
|
grad.addColorStop(0.35, color);
|
|
grad.addColorStop(0.65, shade(color, 6));
|
|
grad.addColorStop(1, shade(color, -8));
|
|
ctx.fillStyle = grad;
|
|
ctx.fillRect(-length / 2, -width / 2, length, width);
|
|
|
|
// Fine grain lines
|
|
ctx.strokeStyle = 'rgba(0,0,0,0.12)';
|
|
ctx.lineWidth = 0.6;
|
|
for (let g = -length / 2 + 4; g < length / 2; g += 7) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(g, -width / 2 + 1);
|
|
ctx.lineTo(g + 3, width / 2 - 1);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Plank edge / grout
|
|
ctx.strokeStyle = 'rgba(18,10,4,0.55)';
|
|
ctx.lineWidth = 1.1;
|
|
ctx.strokeRect(-length / 2, -width / 2, length, width);
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function paintHerringbone(ctx: CanvasRenderingContext2D, size: number) {
|
|
ctx.fillStyle = '#2a1810';
|
|
ctx.fillRect(0, 0, size, size);
|
|
|
|
const plankLen = 56;
|
|
const plankW = 14;
|
|
const step = plankLen * 0.707; // cos(45°) spacing for herringbone unit
|
|
|
|
const cols = Math.ceil(size / step) + 3;
|
|
const rows = Math.ceil(size / step) + 3;
|
|
|
|
for (let row = -1; row < rows; row++) {
|
|
for (let col = -1; col < cols; col++) {
|
|
const px = col * step;
|
|
const py = row * step;
|
|
const tone = OAK_TONES[(row * 5 + col * 7 + size) % OAK_TONES.length];
|
|
|
|
if ((row + col) % 2 === 0) {
|
|
drawPlank(ctx, px, py, plankLen, plankW, Math.PI / 4, tone);
|
|
drawPlank(ctx, px + step * 0.5, py + step * 0.5, plankLen, plankW, Math.PI / 4, shade(tone, -4));
|
|
} else {
|
|
drawPlank(ctx, px + step * 0.25, py + step * 0.25, plankLen, plankW, -Math.PI / 4, tone);
|
|
drawPlank(ctx, px + step * 0.75, py + step * 0.75, plankLen, plankW, -Math.PI / 4, shade(tone, 5));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Soft polish sheen
|
|
const sheen = ctx.createLinearGradient(0, 0, size, size);
|
|
sheen.addColorStop(0, 'rgba(255,240,210,0.06)');
|
|
sheen.addColorStop(0.5, 'rgba(255,255,255,0)');
|
|
sheen.addColorStop(1, 'rgba(255,230,200,0.04)');
|
|
ctx.fillStyle = sheen;
|
|
ctx.fillRect(0, 0, size, size);
|
|
}
|
|
|
|
let cachedTexture: THREE.CanvasTexture | null = null;
|
|
|
|
/** Seamless herringbone parquet — shared across gallery halls. */
|
|
export function createParquetFloorTexture(): THREE.CanvasTexture {
|
|
if (cachedTexture) return cachedTexture;
|
|
|
|
const size = 512;
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) throw new Error('Could not create parquet canvas');
|
|
|
|
paintHerringbone(ctx, size);
|
|
|
|
const texture = new THREE.CanvasTexture(canvas);
|
|
texture.wrapS = THREE.RepeatWrapping;
|
|
texture.wrapT = THREE.RepeatWrapping;
|
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
texture.anisotropy = 8;
|
|
texture.needsUpdate = true;
|
|
|
|
cachedTexture = texture;
|
|
return texture;
|
|
}
|
|
|
|
/** Meters covered by one texture repeat — tune for plank scale in the hall. */
|
|
export const PARQUET_METERS_PER_TILE = 2.8;
|