aboutsummaryrefslogtreecommitdiff
path: root/src/scene/lab.ts
diff options
context:
space:
mode:
authorvictor2026-10-24 15:57:01 +0200
committervictor2026-10-24 15:57:01 +0200
commit4533910bce394e7a0ee6debfc05ca1221ee51899 (patch)
tree73aaa5b2c7a48dbf226e6b81d499ba1a8ecc33a1 /src/scene/lab.ts
parentTilføj dansk (diff)
download3D-Electrophysics-4533910bce394e7a0ee6debfc05ca1221ee51899.tar.gz
3D-Electrophysics-4533910bce394e7a0ee6debfc05ca1221ee51899.zip
Astra!ny-start
Diffstat (limited to '')
-rw-r--r--src/scene/lab.ts124
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();
- },
- };
-}