mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 15:28:29 +08:00
143 lines
4.0 KiB
TypeScript
143 lines
4.0 KiB
TypeScript
![]() |
import CameraControls from "camera-controls";
|
||
|
import * as THREE from "three";
|
||
|
import {
|
||
|
Clock,
|
||
|
Color,
|
||
|
Group,
|
||
|
HemisphereLight,
|
||
|
OrthographicCamera,
|
||
|
PerspectiveCamera,
|
||
|
Scene,
|
||
|
Vector2,
|
||
|
Vector3,
|
||
|
} from "three";
|
||
|
import { Disposable } from "../observable/Disposable";
|
||
|
import { Renderer } from "./Renderer";
|
||
|
|
||
|
CameraControls.install({
|
||
|
// Hack to make panning and orbiting work the way we want.
|
||
|
THREE: {
|
||
|
...THREE,
|
||
|
MOUSE: { ...THREE.MOUSE, LEFT: THREE.MOUSE.RIGHT, RIGHT: THREE.MOUSE.LEFT },
|
||
|
},
|
||
|
});
|
||
|
|
||
|
export interface DisposableThreeRenderer extends THREE.Renderer, Disposable {}
|
||
|
|
||
|
/**
|
||
|
* Uses THREE.js for rendering.
|
||
|
*/
|
||
|
export abstract class ThreeRenderer extends Renderer {
|
||
|
private _debug = false;
|
||
|
|
||
|
get debug(): boolean {
|
||
|
return this._debug;
|
||
|
}
|
||
|
|
||
|
set debug(debug: boolean) {
|
||
|
this._debug = debug;
|
||
|
}
|
||
|
|
||
|
abstract readonly camera: PerspectiveCamera | OrthographicCamera;
|
||
|
readonly controls!: CameraControls;
|
||
|
readonly scene = new Scene();
|
||
|
readonly light_holder = new Group();
|
||
|
|
||
|
private readonly renderer: DisposableThreeRenderer;
|
||
|
private render_scheduled = false;
|
||
|
private animation_frame_handle?: number = undefined;
|
||
|
private readonly light = new HemisphereLight(0xffffff, 0x505050, 1.0);
|
||
|
private readonly controls_clock = new Clock();
|
||
|
private readonly size = new Vector2(0, 0);
|
||
|
|
||
|
protected constructor(three_renderer: DisposableThreeRenderer) {
|
||
|
super();
|
||
|
this.renderer = three_renderer;
|
||
|
this.renderer.domElement.tabIndex = 0;
|
||
|
this.renderer.domElement.addEventListener("mousedown", this.on_mouse_down);
|
||
|
this.renderer.domElement.style.outline = "none";
|
||
|
|
||
|
this.scene.background = new Color(0x181818);
|
||
|
this.light_holder.add(this.light);
|
||
|
this.scene.add(this.light_holder);
|
||
|
}
|
||
|
|
||
|
get canvas_element(): HTMLCanvasElement {
|
||
|
return this.renderer.domElement;
|
||
|
}
|
||
|
|
||
|
set_size(width: number, height: number): void {
|
||
|
this.size.set(width, height);
|
||
|
this.renderer.setSize(width, height);
|
||
|
this.schedule_render();
|
||
|
}
|
||
|
|
||
|
pointer_pos_to_device_coords(pos: Vector2): void {
|
||
|
pos.set((pos.x / this.size.width) * 2 - 1, (pos.y / this.size.height) * -2 + 1);
|
||
|
}
|
||
|
|
||
|
start_rendering(): void {
|
||
|
if (this.animation_frame_handle == undefined) {
|
||
|
this.schedule_render();
|
||
|
this.animation_frame_handle = requestAnimationFrame(this.call_render);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
stop_rendering(): void {
|
||
|
if (this.animation_frame_handle != undefined) {
|
||
|
cancelAnimationFrame(this.animation_frame_handle);
|
||
|
this.animation_frame_handle = undefined;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
schedule_render = (): void => {
|
||
|
this.render_scheduled = true;
|
||
|
};
|
||
|
|
||
|
reset_camera(position: Vector3, look_at: Vector3): void {
|
||
|
this.controls.setLookAt(
|
||
|
position.x,
|
||
|
position.y,
|
||
|
position.z,
|
||
|
look_at.x,
|
||
|
look_at.y,
|
||
|
look_at.z,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
dispose(): void {
|
||
|
this.renderer.dispose();
|
||
|
this.controls.dispose();
|
||
|
}
|
||
|
|
||
|
protected init_camera_controls(): void {
|
||
|
(this.controls as CameraControls) = new CameraControls(
|
||
|
this.camera,
|
||
|
this.renderer.domElement,
|
||
|
);
|
||
|
this.controls.dampingFactor = 1;
|
||
|
this.controls.draggingDampingFactor = 1;
|
||
|
}
|
||
|
|
||
|
protected render(): void {
|
||
|
this.renderer.render(this.scene, this.camera);
|
||
|
}
|
||
|
|
||
|
private on_mouse_down = (e: Event): void => {
|
||
|
if (e.currentTarget) (e.currentTarget as HTMLElement).focus();
|
||
|
};
|
||
|
|
||
|
private call_render = (): void => {
|
||
|
const controls_updated = this.controls.update(this.controls_clock.getDelta());
|
||
|
const should_render = this.render_scheduled || controls_updated;
|
||
|
|
||
|
this.render_scheduled = false;
|
||
|
|
||
|
if (should_render) {
|
||
|
this.render();
|
||
|
}
|
||
|
|
||
|
this.animation_frame_handle = requestAnimationFrame(this.call_render);
|
||
|
};
|
||
|
}
|