You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
5.3 KiB
147 lines
5.3 KiB
import { EventEmitter } from '@angular/core';
|
|
import { Scene, Camera, OrthographicCamera, PerspectiveCamera, WebGLRenderer, AmbientLight, PointLight, Raycaster, Intersection, Vector3, Object3D } from 'three';
|
|
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer';
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
|
import * as Stats from 'stats.js';
|
|
|
|
export class Three {
|
|
|
|
private _element: HTMLElement;
|
|
private _render: FrameRequestCallback;
|
|
|
|
constructor(element: HTMLElement) {
|
|
this._element = element;
|
|
this.onrender = new EventEmitter<any>();
|
|
this._render = () => {
|
|
requestAnimationFrame(this._render);
|
|
this.renderer.render(this.scene, this.camera);
|
|
this.css2dRenderer?.render(this.scene, this.camera);
|
|
this.controls?.update();
|
|
this.stats?.update();
|
|
this.onrender.emit();
|
|
};
|
|
}
|
|
|
|
public scene: Scene;
|
|
public camera: Camera;
|
|
public renderer: WebGLRenderer;
|
|
public css2dRenderer: CSS2DRenderer;
|
|
public ambientLight: AmbientLight;
|
|
public pointLight: PointLight;
|
|
public controls: OrbitControls;
|
|
public stats: Stats;
|
|
public raycaster: Raycaster;
|
|
public onrender: EventEmitter<any>;
|
|
|
|
public render(): void {
|
|
this._render(0);
|
|
}
|
|
|
|
public initScene(): void {
|
|
this.scene = new Scene();
|
|
}
|
|
public initCamera(): void {
|
|
this.camera = new PerspectiveCamera(60, this._element.clientWidth / this._element.clientHeight, 0.1, 10000);
|
|
this.camera.position.set(-100, 100, 100);
|
|
this.camera.lookAt(new Vector3(0, 0, 0));
|
|
}
|
|
|
|
public initRenderer(): void {
|
|
this.renderer = new WebGLRenderer({
|
|
alpha: true,
|
|
antialias: true
|
|
});
|
|
this.renderer.setSize(this._element.clientWidth, this._element.clientHeight);
|
|
this.renderer.setClearColor(0xffffff, 0);
|
|
this._element.appendChild(this.renderer.domElement);
|
|
}
|
|
|
|
public initCSS2DRenderer(): void {
|
|
this.css2dRenderer = new CSS2DRenderer();
|
|
this.css2dRenderer.setSize(this._element.clientWidth, this._element.clientHeight);
|
|
this.css2dRenderer.domElement.style.position = 'absolute';
|
|
this.css2dRenderer.domElement.style.top = '0';
|
|
this._element.appendChild(this.css2dRenderer.domElement);
|
|
}
|
|
|
|
public initAmbientLight(): void {
|
|
this.ambientLight = new AmbientLight(0xffffff);
|
|
this.scene.add(this.ambientLight);
|
|
}
|
|
|
|
public initPointLight(): void {
|
|
this.pointLight = new PointLight(0xffffff);
|
|
this.pointLight.position.set(-100, 100, -100);
|
|
this.scene.add(this.pointLight);
|
|
}
|
|
|
|
public initControls(): void {
|
|
this.controls = new OrbitControls(this.camera, this.css2dRenderer?.domElement || this.renderer.domElement);
|
|
}
|
|
|
|
public initStats(): void {
|
|
this.stats = new Stats();
|
|
this.stats.dom.style.position = 'absolute';
|
|
this.stats.dom.style.left = '16px';
|
|
this.stats.dom.style.top = '16px';
|
|
this._element.appendChild(this.stats.dom);
|
|
}
|
|
|
|
public initRaycaster(): void {
|
|
this.raycaster = new Raycaster();
|
|
}
|
|
|
|
public getIntersects(event: MouseEvent | TouchEvent, objects?: Object3D[]): Intersection[] {
|
|
event.preventDefault();
|
|
const domElement = this.css2dRenderer?.domElement || this.renderer.domElement;
|
|
let mouse: { x: number, y: number };
|
|
if (event instanceof MouseEvent) {
|
|
mouse = {
|
|
x: event.offsetX,
|
|
y: event.offsetY
|
|
};
|
|
}
|
|
else {
|
|
const rect = domElement.getBoundingClientRect();
|
|
mouse = {
|
|
x: event.touches[0].clientX - rect.x,
|
|
y: event.touches[0].clientY - rect.y
|
|
};
|
|
}
|
|
this.raycaster.setFromCamera({
|
|
x: (mouse.x / domElement.offsetWidth) * 2 - 1,
|
|
y: -(mouse.y / domElement.offsetHeight) * 2 + 1
|
|
}, this.camera);
|
|
return this.raycaster.intersectObjects(objects || this.scene.children);
|
|
}
|
|
|
|
public changeCamera(type: 'orthographic' | 'perspective'): void {
|
|
if (type == 'orthographic') {
|
|
if (this.camera instanceof PerspectiveCamera) {
|
|
const camera = this.camera as PerspectiveCamera;
|
|
const depth = camera.position.sub(this.scene.position).length();
|
|
const height = depth * 2 * Math.atan(camera.fov * (Math.PI / 180) / 2);
|
|
const width = height * camera.aspect;
|
|
this.camera = new OrthographicCamera(width / -2, width / 2, height / 2, height / -2, camera.near, camera.far);
|
|
this.camera.position.copy(camera.position);
|
|
this.camera.quaternion.copy(camera.quaternion);
|
|
this.camera.lookAt(camera.getWorldDirection(new Vector3(0, 0, - 1).applyQuaternion(camera.quaternion)));
|
|
this.controls && (this.controls.object = this.camera);
|
|
}
|
|
}
|
|
else {
|
|
if (this.camera instanceof OrthographicCamera) {
|
|
const camera = this.camera as OrthographicCamera;
|
|
const depth = camera.position.sub(this.scene.position).length();
|
|
const width = camera.right - camera.left;
|
|
const height = camera.top - camera.bottom;
|
|
const fov = 2 * Math.tan(height / depth / 2) / (Math.PI / 180);
|
|
this.camera = new PerspectiveCamera(fov, width / height, camera.near, camera.far);
|
|
this.camera.position.copy(camera.position);
|
|
this.camera.quaternion.copy(camera.quaternion);
|
|
this.camera.lookAt(camera.getWorldDirection(new Vector3(0, 0, - 1).applyQuaternion(camera.quaternion)));
|
|
this.controls && (this.controls.object = this.camera);
|
|
}
|
|
}
|
|
}
|
|
}
|