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(); 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; 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); } } } }