From 5ab1da2a1f2cf93c28cc13120b4eed590b43cf59 Mon Sep 17 00:00:00 2001 From: doomtube Date: Sun, 11 Jan 2026 17:42:26 -0500 Subject: [PATCH] Fix pyramid canvas: enable full rotation and add pixel grid - Remove maxPolarAngle limit to allow viewing base from below - Add visible grid lines between pixels (4x4 texture per pixel) - Update texture functions for new grid format Co-Authored-By: Claude Opus 4.5 --- .../components/pyramid/PyramidCanvas.svelte | 128 ++++++++++++------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/frontend/src/lib/components/pyramid/PyramidCanvas.svelte b/frontend/src/lib/components/pyramid/PyramidCanvas.svelte index 7955f26..2836d3e 100644 --- a/frontend/src/lib/components/pyramid/PyramidCanvas.svelte +++ b/frontend/src/lib/components/pyramid/PyramidCanvas.svelte @@ -96,31 +96,49 @@ function createDataTexture(faceData) { const size = FACE_SIZE; - const data = new Uint8Array(size * size * 4); + // Use larger texture for grid lines (each pixel is 4x4 with 1px grid) + const texSize = size * 4; + const data = new Uint8Array(texSize * texSize * 4); - // Fill with default color (dark gray) - for (let i = 0; i < size * size; i++) { - data[i * 4] = 34; // R - data[i * 4 + 1] = 34; // G - data[i * 4 + 2] = 34; // B - data[i * 4 + 3] = 255; // A - } + // Fill with grid pattern + for (let py = 0; py < size; py++) { + for (let px = 0; px < size; px++) { + // Get pixel color (default dark gray or from faceData) + let r = 40, g = 40, b = 40; - // Apply pixel data from the face map - if (faceData) { - faceData.forEach((color, key) => { - const [x, y] = key.split(',').map(Number); - const idx = (y * size + x) * 4; - const rgb = hexToRgb(color); - if (rgb) { - data[idx] = rgb.r; - data[idx + 1] = rgb.g; - data[idx + 2] = rgb.b; + if (faceData) { + const key = `${px},${py}`; + const color = faceData.get(key); + if (color) { + const rgb = hexToRgb(color); + if (rgb) { r = rgb.r; g = rgb.g; b = rgb.b; } + } } - }); + + // Draw 4x4 pixel block with 1px grid border + for (let dy = 0; dy < 4; dy++) { + for (let dx = 0; dx < 4; dx++) { + const tx = px * 4 + dx; + const ty = py * 4 + dy; + const idx = (ty * texSize + tx) * 4; + + // Grid lines on edges (darker) + if (dx === 0 || dy === 0) { + data[idx] = 20; + data[idx + 1] = 20; + data[idx + 2] = 20; + } else { + data[idx] = r; + data[idx + 1] = g; + data[idx + 2] = b; + } + data[idx + 3] = 255; + } + } + } } - const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat); + const texture = new THREE.DataTexture(data, texSize, texSize, THREE.RGBAFormat); texture.needsUpdate = true; texture.magFilter = THREE.NearestFilter; texture.minFilter = THREE.NearestFilter; @@ -225,7 +243,8 @@ controls.screenSpacePanning = false; controls.minDistance = 100; controls.maxDistance = 500; - controls.maxPolarAngle = Math.PI / 2 - 0.1; + // Allow full rotation to view base from below + controls.maxPolarAngle = Math.PI; controls.target.set(0, 50, 0); controls.update(); } @@ -291,26 +310,41 @@ const texture = faceTextures[faceId]; const size = FACE_SIZE; + const texSize = size * 4; - // Reset to default color - for (let i = 0; i < size * size; i++) { - texture.image.data[i * 4] = 34; - texture.image.data[i * 4 + 1] = 34; - texture.image.data[i * 4 + 2] = 34; - } - - // Apply pixel data - if (faceData) { - faceData.forEach((color, key) => { - const [x, y] = key.split(',').map(Number); - const idx = (y * size + x) * 4; - const rgb = hexToRgb(color); - if (rgb) { - texture.image.data[idx] = rgb.r; - texture.image.data[idx + 1] = rgb.g; - texture.image.data[idx + 2] = rgb.b; + // Reset with grid pattern + for (let py = 0; py < size; py++) { + for (let px = 0; px < size; px++) { + // Get pixel color + let r = 40, g = 40, b = 40; + if (faceData) { + const key = `${px},${py}`; + const color = faceData.get(key); + if (color) { + const rgb = hexToRgb(color); + if (rgb) { r = rgb.r; g = rgb.g; b = rgb.b; } + } } - }); + + // Draw 4x4 block with grid + for (let dy = 0; dy < 4; dy++) { + for (let dx = 0; dx < 4; dx++) { + const tx = px * 4 + dx; + const ty = py * 4 + dy; + const idx = (ty * texSize + tx) * 4; + + if (dx === 0 || dy === 0) { + texture.image.data[idx] = 20; + texture.image.data[idx + 1] = 20; + texture.image.data[idx + 2] = 20; + } else { + texture.image.data[idx] = r; + texture.image.data[idx + 1] = g; + texture.image.data[idx + 2] = b; + } + } + } + } } texture.needsUpdate = true; @@ -321,13 +355,21 @@ if (!faceTextures[faceId]) return; const texture = faceTextures[faceId]; - const idx = (y * FACE_SIZE + x) * 4; + const texSize = FACE_SIZE * 4; const rgb = hexToRgb(color); if (rgb) { - texture.image.data[idx] = rgb.r; - texture.image.data[idx + 1] = rgb.g; - texture.image.data[idx + 2] = rgb.b; + // Update 4x4 block (skip grid lines at dx=0, dy=0) + for (let dy = 1; dy < 4; dy++) { + for (let dx = 1; dx < 4; dx++) { + const tx = x * 4 + dx; + const ty = y * 4 + dy; + const idx = (ty * texSize + tx) * 4; + texture.image.data[idx] = rgb.r; + texture.image.data[idx + 1] = rgb.g; + texture.image.data[idx + 2] = rgb.b; + } + } texture.needsUpdate = true; } }