Improve 3D gallery and debug checkup workflow for image curation.
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>
This commit is contained in:
co-authored by
Cursor
parent
4c6acd5a3a
commit
019ce4e136
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user