127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
import * as THREE from "three";
|
|
|
|
export type ThreeHint = {
|
|
mesh: THREE.Mesh,
|
|
position: THREE.Vector3Like,
|
|
options: ThreeHintOptions,
|
|
}
|
|
|
|
export type ThreeBaseHintOptions = {
|
|
color: THREE.ColorRepresentation,
|
|
}
|
|
|
|
|
|
export type ThreePointHintOptions = ThreeBaseHintOptions & {
|
|
kind: 'point',
|
|
size: number,
|
|
}
|
|
|
|
export type ThreeCircleHintOptions = ThreeBaseHintOptions & {
|
|
kind: 'circle',
|
|
radius: number,
|
|
thickness: number,
|
|
}
|
|
|
|
export type ThreeHintOptions = ThreePointHintOptions | ThreeCircleHintOptions;
|
|
|
|
export class ThreeHintDisplay {
|
|
|
|
private scene: THREE.Scene;
|
|
private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera;
|
|
private renderer: THREE.WebGLRenderer;
|
|
|
|
private readonly baseMaterial = new THREE.MeshBasicMaterial({ color: 'red' });
|
|
|
|
private readonly hints: Record<string, ThreeHint> = {};
|
|
|
|
constructor(
|
|
scene: THREE.Scene,
|
|
camera: THREE.PerspectiveCamera | THREE.OrthographicCamera,
|
|
renderer: THREE.WebGLRenderer,
|
|
) {
|
|
this.scene = scene;
|
|
this.camera = camera;
|
|
this.renderer = renderer;
|
|
}
|
|
|
|
private createGeometry(options: ThreeHintOptions): THREE.BufferGeometry {
|
|
switch (options.kind) {
|
|
case 'point':
|
|
return new THREE.SphereGeometry(1);
|
|
case 'circle':
|
|
return new THREE.TorusGeometry(options.radius, options.thickness * options.radius);
|
|
default:
|
|
throw new Error('Unknown volatile geometry type');
|
|
}
|
|
}
|
|
|
|
private ensure(id: string, options: ThreeHintOptions): ThreeHint {
|
|
if (!this.hints[id]) {
|
|
const material = this.baseMaterial.clone();
|
|
material.color.set(options.color);
|
|
this.hints[id] = {
|
|
mesh: new THREE.Mesh(
|
|
this.createGeometry(options),
|
|
material,
|
|
),
|
|
position: { x: 0, y: 0, z: 0 },
|
|
options,
|
|
};
|
|
this.scene.add(this.hints[id].mesh);
|
|
}
|
|
|
|
return this.hints[id];
|
|
}
|
|
|
|
private disposeHint(id: string) {
|
|
const point = this.hints[id];
|
|
if (point) {
|
|
this.scene.remove(point.mesh);
|
|
point.mesh.geometry.dispose();
|
|
delete (this.hints[id]);
|
|
}
|
|
}
|
|
|
|
public dispose() {
|
|
for (const id in this.hints)
|
|
this.disposeHint(id);
|
|
}
|
|
|
|
public set(id: string, position: THREE.Vector3Like, options: ThreeHintOptions) {
|
|
const object = this.ensure(id, options);
|
|
object.position = position;
|
|
this.applyCameraToHint(object);
|
|
}
|
|
|
|
private applyCameraToHint(hint: ThreeHint) {
|
|
|
|
const rendererSize = new THREE.Vector2();
|
|
this.renderer.getSize(rendererSize);
|
|
|
|
// additional actions
|
|
switch (hint.options.kind) {
|
|
case 'point':
|
|
let scale: number;
|
|
if (this.camera instanceof THREE.PerspectiveCamera) {
|
|
const distance = this.camera.position.distanceTo(hint.position);
|
|
const fovRad = THREE.MathUtils.degToRad(this.camera.fov);
|
|
scale = (hint.options.size * distance * Math.tan(fovRad / 2)) / (rendererSize.height / 2);
|
|
}
|
|
else {
|
|
scale = (hint.options.size * (this.camera.top - this.camera.bottom)) / rendererSize.height;
|
|
}
|
|
hint.mesh.scale.setScalar(scale);
|
|
break;
|
|
case 'circle':
|
|
hint.mesh.lookAt(this.camera.position);
|
|
break;
|
|
}
|
|
|
|
hint.mesh.position.copy(hint.position);
|
|
}
|
|
|
|
public applyCamera() {
|
|
for (const hint of Object.values(this.hints))
|
|
this.applyCameraToHint(hint);
|
|
}
|
|
} |