/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2023, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2021 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import * as ODA from "open-cloud-client"; import * as THREE from "three"; import type { ThreejsViewer } from "../ThreejsViewer"; export class WalkDragger { protected viewer: ThreejsViewer; protected camera: THREE.Camera; protected canvas: HTMLCanvasElement; protected _target: THREE.Vector3; protected quaternion: THREE.Quaternion; protected mouseStart: THREE.Vector2; protected mouseDelta: THREE.Vector2; protected xAxis: THREE.Vector3; protected yAxis: THREE.Vector3; protected zAxis: THREE.Vector3; protected speed: THREE.Vector3; protected _maxDistance: number; protected xRotation: THREE.Quaternion; protected yRotation: THREE.Quaternion; protected yRotationAxis: THREE.Vector3; protected touchStartDistance: number; public walkSpeed: number; public boostSpeed: number; constructor(viewer: ThreejsViewer) { this.viewer = viewer; this._target = new THREE.Vector3(); this.quaternion = this.viewer.camera.quaternion.clone(); this.xRotation = new THREE.Quaternion(); this.yRotation = new THREE.Quaternion(); this.mouseStart = new THREE.Vector2(); this.mouseDelta = new THREE.Vector2(); this.xAxis = new THREE.Vector3(); this.yAxis = new THREE.Vector3(); this.zAxis = new THREE.Vector3(); this.yRotationAxis = new THREE.Vector3(1, 0, 0); this.walkSpeed = 1; this.boostSpeed = 5; this.speed = new THREE.Vector3(); this._maxDistance = 1; this.touchStartDistance = 0; this.viewer.addEventListener("render", this.onRender); this.viewer.addEventListener("contextmenu", this.onContextMenu); this.viewer.addEventListener("mousedown", this.onMouseDown); this.viewer.addEventListener("mouseup", this.onMouseUp); this.viewer.addEventListener("touchstart", this.onTouchStart); this.viewer.addEventListener("touchmove", this.onTouchMove); this.viewer.addEventListener("touchend", this.onTouchEnd); this.viewer.addEventListener("wheel", this.onMouseWheel); document.addEventListener("keydown", this.onKeyDown); document.addEventListener("keyup", this.onKeyUp); } dispose() { this.viewer.removeEventListener("render", this.onRender); this.viewer.removeEventListener("contextmenu", this.onContextMenu); this.viewer.removeEventListener("mousedown", this.onMouseDown); this.viewer.removeEventListener("mouseup", this.onMouseUp); this.viewer.removeEventListener("mousemove", this.onMouseMove); this.viewer.removeEventListener("touchstart", this.onTouchStart); this.viewer.removeEventListener("touchmove", this.onTouchMove); this.viewer.removeEventListener("touchend", this.onTouchEnd); this.viewer.removeEventListener("wheel", this.onMouseWheel); document.removeEventListener("keydown", this.onKeyDown); document.removeEventListener("keyup", this.onKeyUp); } onKeyDown = (event: KeyboardEvent) => { switch (event.code) { case "KeyW": // Forward if (event.shiftKey) { this.speed.z = this.walkSpeed * this.boostSpeed; } else { this.speed.z = this.walkSpeed; } break; case "KeyS": // Backward if (event.shiftKey) { this.speed.z = -this.walkSpeed * this.boostSpeed; } else { this.speed.z = -this.walkSpeed; } break; case "KeyA": // Left if (event.shiftKey) { this.speed.x = this.walkSpeed * this.boostSpeed; } else { this.speed.x = this.walkSpeed; } break; case "KeyD": // Right if (event.shiftKey) { this.speed.x = -this.walkSpeed * this.boostSpeed; } else { this.speed.x = -this.walkSpeed; } break; default: break; } }; onKeyUp = (event: KeyboardEvent) => { switch (event.code) { case "KeyW": this.speed.z = 0; break; case "KeyS": this.speed.z = 0; break; case "KeyA": this.speed.x = 0; break; case "KeyD": this.speed.x = 0; break; default: break; } }; onMouseDown = (event: PointerEvent) => { const { clientX, clientY } = event; this.mouseStart.set(clientX, clientY); this.mouseDelta.set(0, 0); this.quaternion.copy(this.viewer.camera.quaternion); this.viewer.addEventListener("mousemove", this.onMouseMove); }; onMouseMove = (event: MouseEvent) => { const { clientX, clientY } = event; this.mouseDelta.set(this.mouseStart.x - clientX, this.mouseStart.y - clientY); this.rotateCamera(this.mouseDelta); }; onMouseUp = (event: PointerEvent) => { this.speed.set(0, 0, 0); this.mouseStart.set(0, 0); this.mouseDelta.set(0, 0); this.quaternion.copy(this.viewer.camera.quaternion); this.viewer.removeEventListener("mousemove", this.onMouseMove); }; onMouseWheel = (event: WheelEvent) => { event.preventDefault(); if (-event.deltaY < 0) { this.walkSpeed = Math.max(0.001, this.walkSpeed - 1); } else if (-event.deltaY > 0) { this.walkSpeed++; } }; onContextMenu = (event: MouseEvent) => { console.log(event); event.preventDefault(); event.stopImmediatePropagation(); }; onTouchStart = (event: TouchEvent) => { if (event.touches.length > 1) { this.touchStartDistance = this.getTouchsDistance(event.touches); } else { const { clientX, clientY } = event.touches[0]; this.mouseStart.set(clientX, clientY); this.mouseDelta.set(0, 0); this.quaternion.copy(this.viewer.camera.quaternion); } }; onTouchEnd = (event: TouchEvent) => { if (event.touches.length === 0) { this.touchStartDistance = 0; } this.speed.set(0, 0, 0); this.mouseStart.set(0, 0); this.mouseDelta.set(0, 0); this.quaternion.copy(this.viewer.camera.quaternion); }; onTouchMove = (event: TouchEvent) => { if (event.touches.length === 1 && this.touchStartDistance === 0) { const { clientX, clientY } = event.touches[0]; this.mouseDelta.set(this.mouseStart.x - clientX, this.mouseStart.y - clientY); this.rotateCamera(this.mouseDelta); } if (event.touches.length === 2) { const distance = this.getTouchsDistance(event.touches); this.speed.z = (distance - this.touchStartDistance) / 2; } }; getTouchsDistance(touches) { const [start, end] = touches; const dx = start.clientX - end.clientX; const dy = start.clientY - end.clientY; const distance = Math.sqrt(dx * dx + dy * dy); return distance; } onRender = (event: ODA.RenderEvent) => { this.viewer.camera.matrix.extractBasis(this.xAxis, this.yAxis, this.zAxis); this.viewer.camera.position.add(this.zAxis.multiplyScalar(-event.deltaTime * this.speed.z)); this.viewer.camera.position.add(this.xAxis.multiplyScalar(-event.deltaTime * this.speed.x)); }; update() {} rotateCamera(delta: THREE.Vector2) { const rotateX = (Math.PI * delta.x) / this.viewer.canvas.clientWidth; const rotateY = (Math.PI * delta.y) / this.viewer.canvas.clientHeight; const quaternion = this.quaternion.clone(); this.xRotation.setFromAxisAngle(this.viewer.camera.up, rotateX); this.yRotation.setFromAxisAngle(this.yRotationAxis, rotateY); quaternion.premultiply(this.xRotation).multiply(this.yRotation).normalize(); this.viewer.camera.setRotationFromQuaternion(quaternion); } }