import { vec2, vec3 } from 'gl-matrix' import { AbstractCamera, OrthographicCamera, ORTHOGRAPHIC_CAMERA_DIRECTION } from '@shapediver/viewer.rendering-engine.camera-engine' import { ShapeDiverViewerViewportError } from '@shapediver/viewer.shared.services' import { IManager } from '@shapediver/viewer.rendering-engine.rendering-engine' import { IntersectionEngine } from '@shapediver/viewer.rendering-engine.intersection-engine' import { RenderingEngine } from '../RenderingEngine' import * as THREE from "three" export class SceneTracingManager implements IManager { // #region Properties (2) private readonly _raycaster = new THREE.Raycaster() // #endregion Properties (2) // #region Constructors (1) constructor(private readonly _renderingEngine: RenderingEngine) { } // #endregion Constructors (1) // #region Public Methods (3) public convert3Dto2D(p: vec3): { container: vec2, client: vec2, page: vec2, hidden: boolean } { const canvasPageCoordinates = this._renderingEngine.canvas.getBoundingClientRect(), width = this._renderingEngine.canvas.width, height = this._renderingEngine.canvas.height; const camera = this._renderingEngine.cameraEngine.camera; if (!camera) throw new ShapeDiverViewerViewportError('SceneTracingManager.convert3Dto2D: No camera is defined for this viewer.'); const direction = vec3.normalize(vec3.create(), vec3.subtract(vec3.create(), p, camera.position)); // should be anchor pos const screenVector = new THREE.Vector3() screenVector.set(p[0], p[1], p[2]); this._raycaster.ray.direction.copy(screenVector); this._raycaster.ray.origin.set(0, 0, 0); (camera.threeJsObject[this._renderingEngine.id] as THREE.Camera).localToWorld(this._raycaster.ray.origin); this._raycaster.ray.direction.sub(this._raycaster.ray.origin); this._raycaster.ray.direction.normalize(); let closestIntersectionDistance = Number.MAX_VALUE; this._renderingEngine.sceneTreeManager.scene.traverseVisible((obj: THREE.Object3D) => { if (obj instanceof THREE.Mesh) { let curIntersections = this._raycaster.intersectObject(obj); if (curIntersections.length) if (curIntersections[0].distance < closestIntersectionDistance) closestIntersectionDistance = curIntersections[0].distance; } }); const pos: vec2 = (camera).project(vec3.clone(p)); pos[0] = (pos[0] * (width / 2)) + (width / 2); pos[1] = - (pos[1] * (height / 2)) + (height / 2); // take care of correction by device pixel ratio pos[0] = pos[0] / devicePixelRatio; pos[1] = pos[1] / devicePixelRatio; // epsilon is added as a distance spacer as users tend to put the anchors of html elements directly at the vertices // with this we prevent flickering const eps = 0.0001; return { hidden: closestIntersectionDistance + eps < vec3.distance(camera.position, p), container: vec2.clone(pos), client: vec2.fromValues(pos[0] + canvasPageCoordinates.left, pos[1] + canvasPageCoordinates.top), page: vec2.fromValues(pos[0] + canvasPageCoordinates.left + window.pageXOffset, pos[1] + canvasPageCoordinates.top + window.pageYOffset) }; } public init(): void { } /** * Calculate the ray that is created by the mouse event and the camera. * * @param event * @returns */ public mouseEventToRay(event: MouseEvent): { origin: vec3, direction: vec3 } { const rect = this._renderingEngine.canvas.getBoundingClientRect(); const camera = this._renderingEngine.cameraEngine.camera; if (!camera) throw new ShapeDiverViewerViewportError('SceneTracingManager.mouseEventToRay: No camera is defined for this viewer.'); let _mouse_x = ((event.clientX - rect.left) / rect.width) * 2 - 1; let _mouse_y = - ((event.clientY - rect.top) / rect.height) * 2 + 1; let origin = vec3.clone(camera.position); if(camera instanceof OrthographicCamera) { if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.TOP) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.right, _mouse_y*camera.top, 0)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.BOTTOM) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.left, _mouse_y*camera.top, 0)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.LEFT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(0, _mouse_x*camera.left, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.RIGHT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(0, _mouse_x*camera.right, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.FRONT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.right, 0, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.BACK) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.left, 0, _mouse_y*camera.top)) } } let direction = vec3.normalize(vec3.create(), vec3.sub(vec3.create(), camera.unproject(vec3.fromValues(_mouse_x, _mouse_y, 0.5)), origin)); return { origin, direction }; } /** * Create the ray that is created by the touch event and the camera. * * @param event * @returns */ public touchEventToRay(event: TouchEvent): { origin: vec3, direction: vec3 } { if (event.touches.length < 1) throw new ShapeDiverViewerViewportError('SceneTracingManager.touchEventToRay: No touches in this event.'); const touch = event.changedTouches[0]; const rect = this._renderingEngine.canvas.getBoundingClientRect(); const camera = this._renderingEngine.cameraEngine.camera; if (!camera) throw new ShapeDiverViewerViewportError('SceneTracingManager.touchEventToRay: No camera is defined for this viewer.'); let _mouse_x = ((touch.clientX - rect.left) / rect.width) * 2 - 1; let _mouse_y = - ((touch.clientY - rect.top) / rect.height) * 2 + 1; let origin = vec3.clone(camera.position); if(camera instanceof OrthographicCamera) { if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.TOP) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.right, _mouse_y*camera.top, 0)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.BOTTOM) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.left, _mouse_y*camera.top, 0)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.LEFT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(0, _mouse_x*camera.left, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.RIGHT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(0, _mouse_x*camera.right, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.FRONT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.right, 0, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.BACK) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.left, 0, _mouse_y*camera.top)) } } let direction = vec3.normalize(vec3.create(), vec3.sub(vec3.create(), camera.unproject(vec3.fromValues(_mouse_x, _mouse_y, 0.5)), origin)); return { origin, direction }; } /** * Create the ray that is created by the touch event and the camera. * * @param event * @returns */ public touchToRay(event: Touch): { origin: vec3, direction: vec3 } { const rect = this._renderingEngine.canvas.getBoundingClientRect(); const camera = this._renderingEngine.cameraEngine.camera; if (!camera) throw new ShapeDiverViewerViewportError('SceneTracingManager.touchToRay: No camera is defined for this viewer.'); let _mouse_x = ((event.clientX - rect.left) / rect.width) * 2 - 1; let _mouse_y = - ((event.clientY - rect.top) / rect.height) * 2 + 1; let origin = vec3.clone(camera.position); if(camera instanceof OrthographicCamera) { if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.TOP) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.right, _mouse_y*camera.top, 0)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.BOTTOM) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.left, _mouse_y*camera.top, 0)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.LEFT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(0, _mouse_x*camera.left, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.RIGHT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(0, _mouse_x*camera.right, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.FRONT) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.right, 0, _mouse_y*camera.top)) } else if(camera.direction == ORTHOGRAPHIC_CAMERA_DIRECTION.BACK) { origin = vec3.add(vec3.create(), camera.position, vec3.fromValues(_mouse_x*camera.left, 0, _mouse_y*camera.top)) } } let direction = vec3.normalize(vec3.create(), vec3.sub(vec3.create(), camera.unproject(vec3.fromValues(_mouse_x, _mouse_y, 0.5)), origin)); return { origin, direction }; } // #endregion Public Methods (3) }