diff options
Diffstat (limited to '')
| -rw-r--r-- | src/scene/lab.ts | 124 |
1 files changed, 0 insertions, 124 deletions
diff --git a/src/scene/lab.ts b/src/scene/lab.ts deleted file mode 100644 index e00c234..0000000 --- a/src/scene/lab.ts +++ /dev/null @@ -1,124 +0,0 @@ -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 { onLanguageChange, t } from '../i18n'; -import { buildExperiment, type Animate } from './experiments'; -import { disposeGroup } from './primitives'; - -export function createLab(viewport: HTMLElement) { - let renderer: THREE.WebGLRenderer; - try { - renderer = new THREE.WebGLRenderer({ antialias: true }); - } catch { - const error = document.createElement('div'); - error.className = 'webgl-error'; - error.setAttribute('role', 'alert'); - error.textContent = t('webglStart'); - viewport.append(error); - const stop = onLanguageChange(() => { error.textContent = t('webglStart'); }); - return { update: () => {}, resetCamera: (_id?: ExperimentId) => {}, setPaused: () => {}, dispose: () => stop() }; - } - renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); - renderer.domElement.setAttribute('aria-label', t('canvas')); - 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)); - const keyLight = new THREE.DirectionalLight(0xffffff, 2); - keyLight.position.set(3, 6, 5); - scene.add(keyLight); - const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100); - const controls = new OrbitControls(camera, renderer.domElement); - controls.enableDamping = true; - controls.minDistance = 3; - controls.maxDistance = 32; - controls.listenToKeyEvents(renderer.domElement); - 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(); - } - resetCamera(); - let group = new THREE.Group(); - scene.add(group); - let animate: Animate = () => {}; - let paused = window.matchMedia('(prefers-reduced-motion: reduce)').matches; - let lastTime = 0; - let pending: { id: ExperimentId; parameters: Parameters } | undefined; - let lostWarning: HTMLElement | undefined; - const stopLanguage = onLanguageChange(() => { - renderer.domElement.setAttribute('aria-label', t('canvas')); - if (lostWarning) lostWarning.textContent = t('webglLost'); - }); - function update(id: ExperimentId, parameters: Parameters) { - // Coalesce range events to one rebuild per animation frame. - pending = { id, parameters: { ...parameters } }; - } - const observer = new ResizeObserver(() => { - 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 => { - event.preventDefault(); - if (!viewport.querySelector('[role="alert"]')) { - const warning = document.createElement('p'); - warning.setAttribute('role', 'alert'); - warning.className = 'webgl-error'; - warning.textContent = t('webglLost'); - lostWarning = warning; - viewport.append(warning); - } - }); - renderer.setAnimationLoop(time => { - const delta = lastTime ? Math.min((time - lastTime) / 1000, 0.05) : 0; - lastTime = time; - if (pending) { - disposeGroup(group); - scene.remove(group); - group = new THREE.Group(); - scene.add(group); - animate = buildExperiment(group, pending.id, pending.parameters); - pending = undefined; - } - if (!paused && !document.hidden) animate(delta); - controls.update(); - renderer.render(scene, camera); - labels.render(scene, camera); - }); - return { - update, - resetCamera, - setPaused: (value: boolean) => { paused = value; }, - dispose: () => { - stopLanguage(); - renderer.setAnimationLoop(null); - observer.disconnect(); - controls.dispose(); - disposeGroup(group); - renderer.dispose(); - renderer.domElement.remove(); - labels.domElement.remove(); - }, - }; -} |