diff --git a/ui/src/ShadingSandbox.vue b/ui/src/ShadingSandbox.vue index db705b9..f591af1 100644 --- a/ui/src/ShadingSandbox.vue +++ b/ui/src/ShadingSandbox.vue @@ -192,6 +192,9 @@ let animationId = null; // Geometry cache for height exaggeration let geometryCache = null; +// Resize observer +let resizeObserver = null; + // Initialize Three.js const initThreeJS = () => { if (!canvasRef.value) return; @@ -213,9 +216,8 @@ const initThreeJS = () => { }); const width = canvasRef.value.clientWidth || 800; - const height = canvasRef.value.clientHeight || 800; - const size = Math.min(width, height); - renderer.setSize(size,size); + const height = canvasRef.value.clientHeight || 600; + renderer.setSize(width, height); renderer.setPixelRatio(window.devicePixelRatio); // Lights @@ -237,6 +239,25 @@ const initThreeJS = () => { animate(); }; +// Handle canvas resize +const handleResize = () => { + if (!canvasRef.value || !renderer || !camera) return; + + const width = canvasRef.value.clientWidth || 800; + const height = canvasRef.value.clientHeight || 600; + + renderer.setSize(width, height); + renderer.setPixelRatio(window.devicePixelRatio); + + // Always maintain square frustum regardless of canvas aspect ratio + const viewSize = 6; + camera.left = -viewSize; + camera.right = viewSize; + camera.top = viewSize; + camera.bottom = -viewSize; + camera.updateProjectionMatrix(); +}; + // Load tile data const loadTileData = (tileData) => { if (!scene) { @@ -317,12 +338,10 @@ const loadTileData = (tileData) => { mesh = new THREE.Mesh(geometry, material); scene.add(mesh); - // Configure camera frustum for orthographic view - // We want to see the 10-unit box with some padding - const aspect = canvasRef.value.clientWidth / canvasRef.value.clientHeight; + // Configure camera frustum for orthographic view - ALWAYS SQUARE const viewSize = 6; // 10-unit terrain + padding - camera.left = -viewSize * aspect; - camera.right = viewSize * aspect; + camera.left = -viewSize; + camera.right = viewSize; camera.top = viewSize; camera.bottom = -viewSize; @@ -508,11 +527,10 @@ const renderTileWithSettings = async (tileData, renderSettings, resolution = 102 renderer.setSize(resolution, resolution); renderer.setPixelRatio(1); - // Update camera for square aspect - const aspect = 1; + // Update camera for square aspect (always square) const viewSize = 6; - camera.left = -viewSize * aspect; - camera.right = viewSize * aspect; + camera.left = -viewSize; + camera.right = viewSize; camera.top = viewSize; camera.bottom = -viewSize; camera.updateProjectionMatrix(); @@ -527,10 +545,9 @@ const renderTileWithSettings = async (tileData, renderSettings, resolution = 102 renderer.setSize(originalWidth / originalPixelRatio, originalHeight / originalPixelRatio); renderer.setPixelRatio(originalPixelRatio); - // Restore camera aspect - const canvasAspect = originalWidth / originalHeight; - camera.left = -viewSize * canvasAspect; - camera.right = viewSize * canvasAspect; + // Restore camera (always square frustum) + camera.left = -viewSize; + camera.right = viewSize; camera.top = viewSize; camera.bottom = -viewSize; camera.updateProjectionMatrix(); @@ -571,6 +588,10 @@ const cleanup = () => { if (animationId) { cancelAnimationFrame(animationId); } + if (resizeObserver) { + resizeObserver.disconnect(); + resizeObserver = null; + } if (renderer) { renderer.dispose(); } @@ -585,6 +606,14 @@ onMounted(() => { if (props.visible) { nextTick(() => { initThreeJS(); + + // Set up resize observer + if (canvasRef.value) { + resizeObserver = new ResizeObserver(() => { + handleResize(); + }); + resizeObserver.observe(canvasRef.value); + } }); } }); @@ -594,6 +623,14 @@ watch(() => props.visible, (newVal) => { if (newVal && !renderer) { nextTick(() => { initThreeJS(); + + // Set up resize observer + if (canvasRef.value && !resizeObserver) { + resizeObserver = new ResizeObserver(() => { + handleResize(); + }); + resizeObserver.observe(canvasRef.value); + } }); } });