diff options
Diffstat (limited to 'src/scene/lab.ts')
| -rw-r--r-- | src/scene/lab.ts | 24 |
1 files changed, 21 insertions, 3 deletions
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(); }, }; } |