@inweb/viewer-visualize
Version:
JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS
370 lines (309 loc) • 12.3 kB
text/typescript
///////////////////////////////////////////////////////////////////////////////
// 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" });
}
}