aboutsummaryrefslogtreecommitdiff
path: root/src/scene/primitives.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/scene/primitives.ts')
-rw-r--r--src/scene/primitives.ts107
1 files changed, 0 insertions, 107 deletions
diff --git a/src/scene/primitives.ts b/src/scene/primitives.ts
deleted file mode 100644
index 384ea1d..0000000
--- a/src/scene/primitives.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import * as THREE from 'three';
-import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
-import { renderProse } from '../math';
-import type { Point } from '../physics/electricField';
-
-export const colours = { positive: '#ff967b', negative: '#73d7ff', field: '#91e2cb', gold: '#f5d58b', muted: '#8294a8' };
-export const vector = (p: Point) => new THREE.Vector3(p.x, p.y, p.z);
-export function sphere(group: THREE.Group, position: Point, radius: number, colour: string): THREE.Mesh {
- const mesh = new THREE.Mesh(new THREE.SphereGeometry(radius, 18, 12), new THREE.MeshStandardMaterial({ color: colour, roughness: 0.3, emissive: colour, emissiveIntensity: 0.12 }));
- mesh.position.copy(vector(position));
- group.add(mesh);
- return mesh;
-}
-export function box(group: THREE.Group, position: Point, dimensions: Point, colour: string, opacity = 1): THREE.Mesh {
- const mesh = new THREE.Mesh(new THREE.BoxGeometry(dimensions.x, dimensions.y, dimensions.z), new THREE.MeshStandardMaterial({ color: colour, transparent: opacity < 1, opacity, depthWrite: opacity === 1, side: THREE.DoubleSide, roughness: 0.45 }));
- mesh.position.copy(vector(position));
- group.add(mesh);
- return mesh;
-}
-export function line(group: THREE.Group, points: Point[], colour: string, opacity = 1): THREE.Line {
- const mesh = new THREE.Line(new THREE.BufferGeometry().setFromPoints(points.map(vector)), new THREE.LineBasicMaterial({ color: colour, transparent: opacity < 1, opacity }));
- group.add(mesh);
- return mesh;
-}
-export function arrow(group: THREE.Group, origin: Point, direction: Point, length: number, colour = colours.field): void {
- const dir = vector(direction);
- if (dir.lengthSq() < 1e-24) return;
- group.add(new THREE.ArrowHelper(dir.normalize(), vector(origin), length, colour, Math.min(0.16, length * 0.3), Math.min(0.1, length * 0.2)));
-}
-export function label(group: THREE.Group, text: string, position: Point, colour: string = '#dbe7f0', width = 2, minViewportWidth = 0): THREE.Sprite | CSS2DObject {
- if (text.includes('$')) {
- const element = document.createElement('div');
- element.className = 'scene-math-label';
- element.style.color = colour;
- renderProse(element, text);
- const annotation = new CSS2DObject(element);
- annotation.position.copy(vector(position));
- annotation.onBeforeRender = () => {
- element.style.visibility = (element.parentElement?.clientWidth ?? Infinity) < minViewportWidth ? 'hidden' : 'visible';
- };
- group.add(annotation);
- return annotation;
- }
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d')!;
- context.font = '500 44px system-ui, sans-serif';
- canvas.width = Math.ceil(context.measureText(text).width) + 36;
- canvas.height = 80;
- context.fillStyle = 'rgba(16, 24, 34, 0.85)';
- context.fillRect(0, 0, canvas.width, canvas.height);
- context.font = '500 44px system-ui, sans-serif';
- context.fillStyle = colour;
- context.textAlign = 'center';
- context.textBaseline = 'middle';
- context.fillText(text, canvas.width / 2, 40);
- const texture = new THREE.CanvasTexture(canvas);
- texture.colorSpace = THREE.SRGBColorSpace;
- const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: texture, depthTest: false }));
- sprite.position.copy(vector(position));
- const aspect = canvas.width / canvas.height;
- const screenHeight = 23 + Math.min(2, Math.max(-1, width - 2)) * 2;
- const viewportSize = new THREE.Vector2();
- const cameraPosition = new THREE.Vector3();
- // Keep labels legible when the camera or viewport changes. Width hints
- // affect text size slightly; the actual texture fits the text content.
- sprite.onBeforeRender = (renderer, _scene, camera) => {
- if (!(camera instanceof THREE.PerspectiveCamera)) return;
- sprite.getWorldPosition(cameraPosition).applyMatrix4(camera.matrixWorldInverse);
- renderer.getSize(viewportSize);
- sprite.material.opacity = viewportSize.x < minViewportWidth ? 0 : 1;
- const responsiveHeight = viewportSize.x < 600 ? screenHeight * 0.78 : screenHeight;
- const worldHeight = 2 * Math.abs(cameraPosition.z) * Math.tan(THREE.MathUtils.degToRad(camera.getEffectiveFOV()) / 2) * responsiveHeight / Math.max(1, viewportSize.y);
- sprite.scale.set(worldHeight * aspect, worldHeight, 1);
- sprite.updateMatrixWorld();
- };
- group.add(sprite);
- return sprite;
-}
-export function grid(group: THREE.Group, y = -0.25) {
- const helper = new THREE.GridHelper(12, 12, 0x375066, 0x22313f);
- helper.position.y = y;
- group.add(helper);
-}
-
-/** Dispose shared geometries/materials once per scene replacement, including
- * textures owned by generated labels. No GPU resources survive a lesson. */
-export function disposeGroup(group: THREE.Group) {
- const geometries = new Set<THREE.BufferGeometry>();
- const materials = new Set<THREE.Material>();
- const textures = new Set<THREE.Texture>();
- group.traverse(object => {
- const mesh = object as THREE.Mesh;
- if (mesh.geometry) geometries.add(mesh.geometry);
- if (mesh.material) {
- const list = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
- for (const material of list) {
- materials.add(material);
- const map = (material as THREE.MeshBasicMaterial).map;
- if (map) textures.add(map);
- }
- }
- });
- geometries.forEach(geometry => geometry.dispose());
- materials.forEach(material => material.dispose());
- textures.forEach(texture => texture.dispose());
- group.clear();
-}