@inweb/viewer-three
Version:
JavaScript library for rendering CAD and BIM files in a browser using Three.js
165 lines (130 loc) • 5.74 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 { Euler, Quaternion, Object3D, Vector3 } from "three";
import { CommandEvent, IComponent, RenderEvent } from "@inweb/viewer-core";
import type { Viewer } from "../Viewer";
export const defaultViewPositions = {
top: new Vector3(0, 0, 1),
bottom: new Vector3(0, 0, -1),
left: new Vector3(-1, 0, 0),
right: new Vector3(1, 0, 0),
front: new Vector3(0, 1, 0),
back: new Vector3(0, -1, 0),
sw: new Vector3(-0.5, -0.5, 1.0).normalize(),
se: new Vector3(0.5, -0.5, 1.0).normalize(),
ne: new Vector3(0.5, 0.5, 1.0).normalize(),
nw: new Vector3(-0.5, 0.5, 1.0).normalize(),
};
export class ViewPositionComponent implements IComponent {
private position: string;
private center: Vector3;
private targetPosition: Vector3;
private targetQuaternion: Quaternion;
private radius: number;
private q1: Quaternion;
private q2: Quaternion;
private animating: boolean;
private viewer: Viewer;
constructor(viewer: Viewer) {
this.animating = false;
// this.center = new THREE.Vector3();
this.targetPosition = new Vector3();
this.targetQuaternion = new Quaternion();
this.q1 = new Quaternion();
this.q2 = new Quaternion();
this.viewer = viewer;
this.viewer.addEventListener("render", this.onRender);
this.viewer.addEventListener("command", this.onCommand);
console.log("--- ViewPositionComponent.constructor");
}
dispose() {
this.viewer.off("render", this.onRender);
this.viewer.off("viewposition", this.onCommand);
console.log("--- ViewPositionComponent.dispose");
}
onCommand = (event: CommandEvent) => {
if (this.animating) return;
this.position = event.data === "setDefaultViewPosition" ? event.args[0] : event.data;
console.log("--- ViewPositionComponent:onCommand", this.position);
switch (this.position) {
case "left":
this.targetPosition.set(-1, 0, 0);
this.targetQuaternion.setFromEuler(new Euler(0, -Math.PI * 0.5, 0));
break;
case "right":
this.targetPosition.set(1, 0, 0);
this.targetQuaternion.setFromEuler(new Euler(0, Math.PI * 0.5, 0));
break;
case "front":
this.targetPosition.set(0, 1, 0);
this.targetQuaternion.setFromEuler(new Euler(-Math.PI * 0.5, 0, 0));
// this.targetQuaternion.setFromEuler(new Euler(Math.PI * 0.5, 0, 0));
break;
case "back":
this.targetPosition.set(0, -1, 0);
this.targetQuaternion.setFromEuler(new Euler(Math.PI * 0.5, 0, 0));
break;
case "top":
default:
this.targetPosition.set(0, 0, 1);
this.targetQuaternion.setFromEuler(new Euler());
break;
case "bottom":
this.targetPosition.set(0, 0, -1);
this.targetQuaternion.setFromEuler(new Euler(0, Math.PI, 0));
break;
}
this.center = this.viewer.extents.getCenter(new Vector3());
this.radius = this.viewer.camera.position.distanceTo(this.center);
this.targetPosition.multiplyScalar(this.radius).add(this.center);
const dummy = new Object3D();
dummy.position.copy(this.center);
dummy.lookAt(this.viewer.camera.position);
this.q1.copy(dummy.quaternion);
dummy.lookAt(this.targetPosition);
this.q2.copy(dummy.quaternion);
this.animating = true;
// this.viewer.camera.position.set(0, 0, 1).applyQuaternion(this.q2).multiplyScalar(this.radius).add(this.center);
// this.viewer.camera.quaternion.copy(this.targetQuaternion);
// this.viewer.camera.updateProjectionMatrix();
this.viewer.update();
};
onRender = (event: RenderEvent) => {
if (!this.animating) return;
const turnRate = 2 * Math.PI; // turn rate in angles per second
const step = (event.deltaTime * turnRate) / 10;
// animate position by doing a slerp and then scaling the position on the unit sphere
this.q1.rotateTowards(this.q2, step);
this.viewer.camera.position.set(0, 0, 1).applyQuaternion(this.q1).multiplyScalar(this.radius).add(this.center);
// animate orientation
this.viewer.camera.quaternion.rotateTowards(this.targetQuaternion, step);
this.viewer.update();
if (this.q1.angleTo(this.q2) === 0) {
this.animating = false;
// console.log("--- ViewPositionComponent.onRender", event.deltaTime, step, this.animating);
this.viewer.emit({ type: "viewposition", data: this.position });
return;
}
// console.log("--- ViewPositionComponent.onRender", event.deltaTime, step, this.animating);
};
}