UNPKG

@inweb/viewer-visualize

Version:

JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS

370 lines (309 loc) 12.3 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 { Viewer } from "../Viewer"; import { Point2d, Vector3 } from "./Common/Geometry"; import { OdBaseDragger } from "./Common/OdBaseDragger"; import { OdJoyStickDragger } from "./OdJoyStickDragger"; const FocalLengthConst = 42.0; const calcFocalLength = (lensLength: number, fieldWidth: number, fieldHeight: number): number => { return (lensLength / FocalLengthConst) * Math.sqrt(fieldWidth * fieldWidth + fieldHeight * fieldHeight); }; export class OdaFlyDragger extends OdBaseDragger { protected lastCoord: Point2d; protected speed: number; protected delta: number; protected keyPressMap: Set<string>; protected oldWCSEnableValue: boolean; protected viewParams: any; protected cameraId: any; protected cameraFlyer: any; protected viewer: any; protected multiplier: number; protected lastFrameTS: number; protected animationId: any; protected deltaAngle: number; protected enableZoomWheelPreviousValue: boolean; protected dragPosition: Point2d; protected lastJoyStickCoord: Point2d; protected lastFrameJoyStickTS: number; protected isJoyStickMoving: boolean; protected joyStickOverlayElement: HTMLDivElement; protected joyStickDragger: OdJoyStickDragger; constructor(subject: Viewer) { super(subject); this.viewer = undefined; this.multiplier = 5; this.speed = 1; this.keyPressMap = new Set(); this.keydown = this.keydown.bind(this); this.keyup = this.keyup.bind(this); this.lastFrameTS = 0; this.lastFrameJoyStickTS = 0; this.animationId = undefined; this.processMovement = this.processMovement.bind(this); this.processJoyStickMovement = this.processJoyStickMovement.bind(this); this.deltaAngle = Math.PI / 3600; this.autoSelect = true; this.isJoyStickMoving = false; this.addJoyStickDragger(subject.canvas.parentElement); } override initialize() { super.initialize(); this.viewer = this.getViewer(); window.addEventListener("keydown", this.keydown, false); window.addEventListener("keyup", this.keyup, false); this.oldWCSEnableValue = this.viewer.getEnableWCS(); this.viewer.setEnableWCS(false); const view = this.viewer.activeView; const maxDimension = this.getMaxDimension(view); this.speed = maxDimension / 30000; this.subject.emitEvent({ type: "flystart" }); this.viewParams = this.getViewParams(); //this.viewParams.lensLength = view.lensLength; this.setViewParams(this.viewParams); //view.lensLength = (view.lensLength * 42) / 120; const model = this.viewer.getActiveModel(); this.cameraId = model.appendCamera("Camera0"); this.setupCamera(view); model.delete(); //pCamera.setAdjustLensLength(true); this.cameraFlyer = new this.m_module.OdTvCameraWalker(); this.cameraFlyer.setCamera(this.cameraId); this.subject.update(); this.enableZoomWheelPreviousValue = this.subject.options.enableZoomWheel; this.subject.options.enableZoomWheel = false; } override dispose() { this.oldWCSEnableValue = this.oldWCSEnableValue !== undefined ? this.oldWCSEnableValue : this.subject.options.showWCS; this.viewer.setEnableWCS(this.oldWCSEnableValue); super.dispose(); this.keyPressMap.clear(); window.removeEventListener("keydown", this.keydown); window.removeEventListener("keyup", this.keyup); if (this.animationId) { window.cancelAnimationFrame(this.animationId); this.animationId = undefined; } if (this.cameraId) { const model = this.viewer.getActiveModel(); model.removeEntity(this.cameraId); model.delete(); this.cameraFlyer?.delete(); } if (this.viewParams) { this.setViewParams(this.viewParams); const avp = this.viewer.activeView; //avp.lensLength = this.viewParams.lensLength; avp.delete(); } // CLOUD-5359 Demo Viewer crashes after the Fly Mode this.subject.update(true); this.subject.options.enableZoomWheel = this.enableZoomWheelPreviousValue; this.joyStickOverlayElement.remove(); this.joyStickDragger.cleanup(); } keydown(ev) { switch (ev.code) { case "NumpadSubtract": case "Minus": if (this.multiplier > 1) { this.multiplier = this.multiplier - 1; this.subject.emitEvent({ type: "flyspeedchange", data: this.multiplier }); } break; case "NumpadAdd": case "Equal": if (this.multiplier < 10) { this.multiplier = this.multiplier + 1; this.subject.emitEvent({ type: "flyspeedchange", data: this.multiplier }); } break; case "KeyW": case "KeyA": case "KeyS": case "KeyD": case "KeyQ": case "KeyE": this.keyPressMap.add(ev.code); if (!this.animationId) this.processMovement(0); break; } } keyup(ev) { this.keyPressMap.delete(ev.code); if (this.keyPressMap.size < 1 && this.animationId) { window.cancelAnimationFrame(this.animationId); this.animationId = undefined; this.lastFrameTS = 0; } } processMovement(timestamp) { this.animationId = requestAnimationFrame(this.processMovement); if (this.lastFrameTS !== 0) { const deltaTS = timestamp - this.lastFrameTS; const currentDelta = this.multiplier * deltaTS * this.speed; for (const keyCode of this.keyPressMap) { switch (keyCode) { case "KeyW": this.cameraFlyer.moveForward(currentDelta); break; case "KeyS": this.cameraFlyer.moveBackward(currentDelta); break; case "KeyA": this.cameraFlyer.moveLeft(currentDelta); break; case "KeyD": this.cameraFlyer.moveRight(currentDelta); break; case "KeyQ": this.cameraFlyer.moveUp(currentDelta); break; case "KeyE": this.cameraFlyer.moveDown(currentDelta); break; } } this.subject.update(); this.subject.emitEvent({ type: "changecamera" }); } this.lastFrameTS = timestamp; } override start(x: number, y: number): void { this.dragPosition = { x, y }; } override drag(x: number, y: number) { if (this.cameraId && this.isDragging) { const dltX = x - this.dragPosition.x; const dltY = y - this.dragPosition.y; this.dragPosition = { x, y }; if (dltX !== 0.0) this.turnLeft(-dltX * this.deltaAngle); if (dltY !== 0.0) this.cameraFlyer.turnDown(dltY * this.deltaAngle); this.subject.update(); this.subject.emitEvent({ type: "changecamera" }); } } turnLeft(angle) { //TODO: migrate to VisualizeJS const pCamera = this.cameraFlyer.camera().openObjectAsCamera(); const dir = this.toVector(pCamera.direction()); const up = this.toVector(pCamera.upVector()); const pos = pCamera.position(); const rotMatrix = this.createMatrix3d(); const zAxisVector: Vector3 = [0, 0, 1]; rotMatrix.setToRotation(angle, zAxisVector, pos); dir.transformBy(rotMatrix); up.transformBy(rotMatrix); pCamera.setupCameraByDirection(pos, dir.toArray(), up.toArray()); pCamera.delete(); } setupCamera(view) { const pCamera = this.cameraId.openObjectAsCamera(); const target = view.viewTarget; pCamera.setDisplayGlyph(false); pCamera.setDisplayTarget(false); pCamera.setAutoAdjust(true); pCamera.setupCamera(view.viewPosition, target, view.upVector); pCamera.setNearClip(false, 1.0); pCamera.setFarClip(false, 0); pCamera.setViewParameters(view.viewFieldWidth, view.viewFieldHeight, true); const focalL = calcFocalLength(view.lensLength, view.viewFieldWidth, view.viewFieldHeight); const pTarget = this.toPoint(view.viewTarget); const viewDir = this.toPoint(view.viewPosition); const viewDirSub = viewDir.sub(pTarget); const viewDirVec = viewDirSub.asVector(); const viewDirVecNormal = viewDirVec.normalize(); const geViewDir = this.toGeVector(viewDirVecNormal); const newGeViewDir = [geViewDir[0] * focalL, geViewDir[1] * focalL, geViewDir[2] * focalL]; const pTarget2 = this.toPoint(view.viewTarget); const newGeViewDirPt = this.toPoint(newGeViewDir); const newPos = pTarget2.add(newGeViewDirPt); pCamera.setupCamera(this.toGePoint(newPos), view.viewTarget, view.upVector); this.deleteAll([pTarget, viewDir, viewDirSub, viewDirVec, viewDirVecNormal, pTarget2, newGeViewDirPt, newPos]); pCamera.assignView(view); pCamera.delete(); } getMaxDimension(view) { const [xmax, ymax, zmax] = view.sceneExtents.max(); const [xmin, ymin, zmin] = view.sceneExtents.min(); const volume = [xmax - xmin, ymax - ymin, zmax - zmin]; return Math.max(...volume); } addJoyStickDragger(parentElement: HTMLElement) { this.joyStickOverlayElement = document.createElement("div"); this.joyStickOverlayElement.id = "joyStickDiv"; this.joyStickOverlayElement.style.background = "rgba(0,0,0,0)"; this.joyStickOverlayElement.style.position = "fixed"; this.joyStickOverlayElement.style.zIndex = "0"; parentElement.appendChild(this.joyStickOverlayElement); this.joyStickDragger = new OdJoyStickDragger( this, this.joyStickOverlayElement, (stickData) => { if (Math.sqrt(stickData.x * stickData.x + stickData.y * stickData.y) > 20) { this.lastJoyStickCoord = { x: stickData.x, y: stickData.y }; if (!this.animationId && !this.isJoyStickMoving) { this.isJoyStickMoving = true; this.processJoyStickMovement(0); } } else { this.isJoyStickMoving = false; window.cancelAnimationFrame(this.animationId); this.animationId = undefined; this.lastFrameJoyStickTS = 0; } }, this.m_module.canvas ); } processJoyStickMovement(timestamp: number) { if (!this.isJoyStickMoving) return; this.animationId = requestAnimationFrame(this.processJoyStickMovement); if (this.lastFrameJoyStickTS !== 0) { const deltaTS = timestamp - this.lastFrameJoyStickTS; if (deltaTS > 0) { const maxJoystickDistance = 100; const forward = this.lastJoyStickCoord.y / maxJoystickDistance; const right = this.lastJoyStickCoord.x / maxJoystickDistance; const currentDelta = this.multiplier * deltaTS * this.speed; this.moveTotal(currentDelta, forward, right); } } this.lastFrameJoyStickTS = timestamp; } moveTotal(currentDelta: number, forward: number, right: number): void { if (forward !== 0) { if (forward > 0) { this.cameraFlyer.moveForward(currentDelta * forward); } else { this.cameraFlyer.moveBackward(currentDelta * Math.abs(forward)); } } if (right !== 0) { this.cameraFlyer.moveRight(currentDelta * right); } this.subject.update(); this.subject.emitEvent({ type: "changecamera" }); } }