aboutsummaryrefslogtreecommitdiff
path: root/src/scene/lab.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/scene/lab.ts')
-rw-r--r--src/scene/lab.ts24
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();
},
};
}