UNPKG

@inweb/viewer-three

Version:

JavaScript library for rendering CAD and BIM files in a browser using Three.js

206 lines (164 loc) 7.23 kB
/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, 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-2025 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 { Clock, Camera, Controls, Quaternion, Vector2, Vector3 } from "three"; interface FlyControlsEventMap { change: { type: "change" }; flyspeedchange: { type: "flyspeedchange"; data: number }; } export class FlyControls extends Controls<FlyControlsEventMap> { public movementSpeed = 0.2; public lookSpeed = 5; public multiplier = 5; private moveKeys: Set<string>; private moveWheel = 0; private moveClock: Clock; private quaternion: Quaternion; private downPosition: Vector2; private mouseDragOn = false; public rotateDelta: Vector2; constructor(camera: Camera, canvas: HTMLElement) { super(camera, canvas); this.moveKeys = new Set(); this.moveClock = new Clock(); this.quaternion = camera.quaternion.clone(); this.downPosition = new Vector2(0, 0); this.rotateDelta = new Vector2(0, 0); this.domElement.addEventListener("pointerdown", this.onPointerDown); this.domElement.addEventListener("pointermove", this.onPointerMove); this.domElement.addEventListener("pointerup", this.onPointerUp); this.domElement.addEventListener("pointercancel", this.onPointerCancel); this.domElement.addEventListener("wheel", this.onWheel); window.addEventListener("keydown", this.onKeyDown); window.addEventListener("keyup", this.onKeyUp); } override dispose() { this.domElement.removeEventListener("pointerdown", this.onPointerDown); this.domElement.removeEventListener("pointermove", this.onPointerMove); this.domElement.removeEventListener("pointerup", this.onPointerUp); this.domElement.removeEventListener("pointercancel", this.onPointerCancel); this.domElement.removeEventListener("wheel", this.onWheel); window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keyup", this.onKeyUp); super.dispose(); } onPointerDown = (event: PointerEvent) => { if (event.button !== 0) return; this.domElement.setPointerCapture(event.pointerId); this.downPosition.set(event.clientX, event.clientY); this.quaternion.copy(this.object.quaternion); this.mouseDragOn = true; }; onPointerMove = (event: PointerEvent) => { if (!this.mouseDragOn) return; const movePosition = new Vector2(event.clientX, event.clientY); if (this.downPosition.distanceTo(movePosition) === 0) return; this.rotateDelta.copy(this.downPosition).sub(movePosition); this.rotateCamera(this.rotateDelta); this.dispatchEvent({ type: "change" }); }; onPointerUp = (event: PointerEvent) => { this.domElement.releasePointerCapture(event.pointerId); this.mouseDragOn = false; }; onPointerCancel = (event: PointerEvent) => { this.domElement.dispatchEvent(new PointerEvent("pointerup", event)); }; onWheel = (event: WheelEvent) => { this.moveWheel = event.deltaY; this.update(); }; onKeyDown = (event: KeyboardEvent) => { switch (event.code) { case "NumpadSubtract": case "Minus": if (this.multiplier > 1) { this.multiplier = this.multiplier - 1; this.dispatchEvent({ type: "flyspeedchange", data: this.multiplier }); } break; case "NumpadAdd": case "Equal": if (this.multiplier < 10) { this.multiplier = this.multiplier + 1; this.dispatchEvent({ type: "flyspeedchange", data: this.multiplier }); } break; case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown": case "KeyW": case "KeyS": case "KeyA": case "KeyD": case "KeyQ": case "KeyE": this.moveKeys.add(event.code); this.update(); break; } }; onKeyUp = (event: KeyboardEvent) => { if (this.moveKeys.delete(event.code)) this.update(); }; override update() { if (this.moveKeys.size > 0) { const timeDelta = this.moveClock.getDelta(); const moveDelta = timeDelta * this.movementSpeed * this.multiplier; if (this.moveKeys.has("KeyW")) this.object.translateZ(-moveDelta); if (this.moveKeys.has("KeyS")) this.object.translateZ(moveDelta); if (this.moveKeys.has("KeyA")) this.object.translateX(-moveDelta); if (this.moveKeys.has("KeyD")) this.object.translateX(moveDelta); if (this.moveKeys.has("KeyQ")) this.object.translateY(moveDelta); if (this.moveKeys.has("KeyE")) this.object.translateY(-moveDelta); const lookDelta = this.lookSpeed + (this.multiplier - 1); if (this.moveKeys.has("ArrowUp")) this.rotateCamera(this.rotateDelta.add(new Vector2(0, -lookDelta / 2))); if (this.moveKeys.has("ArrowDown")) this.rotateCamera(this.rotateDelta.add(new Vector2(0, lookDelta / 2))); if (this.moveKeys.has("ArrowLeft")) this.rotateCamera(this.rotateDelta.add(new Vector2(lookDelta, 0))); if (this.moveKeys.has("ArrowRight")) this.rotateCamera(this.rotateDelta.add(new Vector2(-lookDelta, 0))); this.moveWheel = 0; this.dispatchEvent({ type: "change" }); } if (this.moveWheel !== 0) { const moveDelta = this.moveWheel * 0.0001 * this.movementSpeed * this.multiplier; this.object.translateZ(-moveDelta); this.moveWheel += -1 * Math.sign(this.moveWheel); this.dispatchEvent({ type: "change" }); } if (this.moveKeys.size === 0 && this.moveWheel === 0) { this.moveClock.stop(); this.moveClock.autoStart = true; } } rotateCamera(delta: Vector2) { const rotateX = (Math.PI * delta.x) / this.domElement.clientWidth; const rotateY = (Math.PI * delta.y) / this.domElement.clientHeight; const xRotation = new Quaternion(); xRotation.setFromAxisAngle(this.object.up, rotateX); const yRotation = new Quaternion(); yRotation.setFromAxisAngle(new Vector3(1, 0, 0), rotateY); const quaternion = this.quaternion.clone(); quaternion.premultiply(xRotation).multiply(yRotation).normalize(); this.object.setRotationFromQuaternion(quaternion); } }