From 389fb3a592832092835da0c2cc1608a57ba5f629 Mon Sep 17 00:00:00 2001 From: LalleSX Date: Sat, 24 Oct 2026 14:18:00 +0200 Subject: Add tests for math rendering and interactive scenes; update navigation and viewport handling - Updated `lab.spec.ts` to navigate directly to the field lesson. - Created `math.spec.ts` with tests for math rendering, error checking, and responsive design. - Added `test4.spec.ts` for interactive scenes, ensuring all questions are functional and accessible. - Modified `vite.config.ts` to keep original question/answer links navigable in production. --- src/scene/lab.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'src/scene/lab.ts') diff --git a/src/scene/lab.ts b/src/scene/lab.ts index 5d2cadf..782f300 100644 --- a/src/scene/lab.ts +++ b/src/scene/lab.ts @@ -1,5 +1,6 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js'; import type { ExperimentId, Parameters } from '../experiments'; import { buildExperiment, type Animate } from './experiments'; import { disposeGroup } from './primitives'; @@ -14,12 +15,15 @@ export function createLab(viewport: HTMLElement) { error.setAttribute('role', 'alert'); error.textContent = 'WebGL could not start. Enable browser hardware acceleration and reload to see the 3D scenes. Lessons, controls and measurements still work.'; viewport.append(error); - return { update: () => {}, resetCamera: () => {}, setPaused: () => {}, dispose: () => {} }; + return { update: () => {}, resetCamera: (_id?: ExperimentId) => {}, setPaused: () => {}, dispose: () => {} }; } renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.domElement.setAttribute('aria-label', '3D experiment: drag to orbit, scroll to zoom, right-drag to pan'); renderer.domElement.setAttribute('tabindex', '0'); viewport.append(renderer.domElement); + const labels = new CSS2DRenderer(); + labels.domElement.className = 'scene-labels'; + viewport.append(labels.domElement); const scene = new THREE.Scene(); scene.background = new THREE.Color('#101822'); scene.add(new THREE.HemisphereLight(0xe9f7ff, 0x263149, 3)); @@ -32,8 +36,15 @@ export function createLab(viewport: HTMLElement) { controls.minDistance = 3; controls.maxDistance = 32; controls.listenToKeyEvents(renderer.domElement); - function resetCamera() { - camera.position.set(8, 7.5, 11); + let currentId: ExperimentId | undefined; + let framingScale = 1; + const fitScale = (aspect: number) => currentId?.startsWith('test-') ? Math.max(1, Math.min(2, 1.1 / aspect)) : 1; + function resetCamera(id = currentId) { + currentId = id; + const { width, height } = viewport.getBoundingClientRect(); + framingScale = fitScale(width / Math.max(1, height)); + if (currentId?.startsWith('test-')) camera.position.set(3.7, 5, 14.5).multiplyScalar(framingScale); + else camera.position.set(8, 7.5, 11); controls.target.set(0, 0, 0); controls.update(); } @@ -52,8 +63,13 @@ export function createLab(viewport: HTMLElement) { const { width, height } = viewport.getBoundingClientRect(); if (width <= 0 || height <= 0) return; renderer.setSize(width, height); + labels.setSize(width, height); camera.aspect = width / height; camera.updateProjectionMatrix(); + const nextScale = fitScale(camera.aspect); + camera.position.sub(controls.target).multiplyScalar(nextScale / framingScale).add(controls.target); + framingScale = nextScale; + controls.update(); }); observer.observe(viewport); renderer.domElement.addEventListener('webglcontextlost', event => { @@ -80,6 +96,7 @@ export function createLab(viewport: HTMLElement) { if (!paused && !document.hidden) animate(delta); controls.update(); renderer.render(scene, camera); + labels.render(scene, camera); }); return { update, @@ -92,6 +109,7 @@ export function createLab(viewport: HTMLElement) { disposeGroup(group); renderer.dispose(); renderer.domElement.remove(); + labels.domElement.remove(); }, }; } -- cgit v1.2.3