src/viewer/scene/camera/Camera.js
import {math} from '../math/math.js';
import {Component} from '../Component.js';
import {RenderState} from '../webgl/RenderState.js';
import {Perspective} from './Perspective.js';
import {Ortho} from './Ortho.js';
import {Frustum} from './Frustum.js';
import {CustomProjection} from './CustomProjection.js';
const tempVec3 = math.vec3();
const tempVec3b = math.vec3();
const tempVec3c = math.vec3();
const tempVec3d = math.vec3();
const tempVec3e = math.vec3();
const tempVec3f = math.vec3();
const tempVec4a = math.vec4();
const tempVec4b = math.vec4();
const tempVec4c = math.vec4();
const tempMat = math.mat4();
const tempMatb = math.mat4();
const eyeLookVec = math.vec3();
const eyeLookVecNorm = math.vec3();
const eyeLookOffset = math.vec3();
const offsetEye = math.vec3();
/**
* @desc Manages viewing and projection transforms for its {@link Scene}.
*
* * One Camera per {@link Scene}
* * Scene is located at {@link Viewer#scene} and Camera is located at {@link Scene#camera}
* * Controls viewing and projection transforms
* * Has methods to pan, zoom and orbit (or first-person rotation)
* * Dynamically configurable World-space axis
* * Has {@link Perspective}, {@link Ortho} and {@link Frustum} and {@link CustomProjection}, which you can dynamically switch it between
* * Switchable gimbal lock
* * Can be "flown" to look at targets using a {@link CameraFlightAnimation}
* * Can be animated along a path using a {@link CameraPathAnimation}
*
* ## Getting the Camera
*
* There is exactly one Camera per {@link Scene}:
*
* ````javascript
* import {Viewer} from "xeokit-sdk.es.js";
*
* var camera = viewer.scene.camera;
*
* ````
*
* ## Setting the Camera Position
*
* Get and set the Camera's absolute position via {@link Camera#eye}, {@link Camera#look} and {@link Camera#up}:
*
* ````javascript
* camera.eye = [-10,0,0];
* camera.look = [-10,0,0];
* camera.up = [0,1,0];
* ````
*
* ## Camera View and Projection Matrices
*
* The Camera's view matrix transforms coordinates from World-space to View-space.
*
* Getting the view matrix:
*
* ````javascript
* var viewMatrix = camera.viewMatrix;
* var viewNormalMatrix = camera.normalMatrix;
* ````
*
* The Camera's view normal matrix transforms normal vectors from World-space to View-space.
*
* Getting the view normal matrix:
*
* ````javascript
* var viewNormalMatrix = camera.normalMatrix;
* ````
*
* The Camera fires a ````"viewMatrix"```` event whenever the {@link Camera#viewMatrix} and {@link Camera#viewNormalMatrix} updates.
*
* Listen for view matrix updates:
*
* ````javascript
* camera.on("viewMatrix", function(matrix) { ... });
* ````
*
* ## Rotating the Camera
*
* Orbiting the {@link Camera#look} position:
*
* ````javascript
* camera.orbitYaw(20.0);
* camera.orbitPitch(10.0);
* ````
*
* First-person rotation, rotates {@link Camera#look} and {@link Camera#up} about {@link Camera#eye}:
*
* ````javascript
* camera.yaw(5.0);
* camera.pitch(-10.0);
* ````
*
* ## Panning the Camera
*
* Panning along the Camera's local axis (ie. left/right, up/down, forward/backward):
*
* ````javascript
* camera.pan([-20, 0, 10]);
* ````
*
* ## Zooming the Camera
*
* Zoom to vary distance between {@link Camera#eye} and {@link Camera#look}:
*
* ````javascript
* camera.zoom(-5); // Move five units closer
* ````
*
* Get the current distance between {@link Camera#eye} and {@link Camera#look}:
*
* ````javascript
* var distance = camera.eyeLookDist;
* ````
*
* ## Projection
*
* The Camera has a Component to manage each projection type, which are: {@link Perspective}, {@link Ortho}
* and {@link Frustum} and {@link CustomProjection}.
*
* You can configure those components at any time, regardless of which is currently active:
*
* The Camera has a {@link Perspective} to manage perspective
* ````javascript
*
* // Set some properties on Perspective
* camera.perspective.near = 0.4;
* camera.perspective.fov = 45;
*
* // Set some properties on Ortho
* camera.ortho.near = 0.8;
* camera.ortho.far = 1000;
*
* // Set some properties on Frustum
* camera.frustum.left = -1.0;
* camera.frustum.right = 1.0;
* camera.frustum.far = 1000.0;
*
* // Set the matrix property on CustomProjection
* camera.customProjection.matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
*
* // Switch between the projection types
* camera.projection = "perspective"; // Switch to perspective
* camera.projection = "frustum"; // Switch to frustum
* camera.projection = "ortho"; // Switch to ortho
* camera.projection = "customProjection"; // Switch to custom
* ````
*
* Camera provides the projection matrix for the currently active projection in {@link Camera#projMatrix}.
*
* Get the projection matrix:
*
* ````javascript
* var projMatrix = camera.projMatrix;
* ````
*
* Listen for projection matrix updates:
*
* ````javascript
* camera.on("projMatrix", function(matrix) { ... });
* ````
*
* ## Configuring World up direction
*
* We can dynamically configure the directions of the World-space coordinate system.
*
* Setting the +Y axis as World "up", +X as right and -Z as forwards (convention in some modeling software):
*
* ````javascript
* camera.worldAxis = [
* 1, 0, 0, // Right
* 0, 1, 0, // Up
* 0, 0,-1 // Forward
* ];
* ````
*
* Setting the +Z axis as World "up", +X as right and -Y as "up" (convention in most CAD and BIM viewers):
*
* ````javascript
* camera.worldAxis = [
* 1, 0, 0, // Right
* 0, 0, 1, // Up
* 0,-1, 0 // Forward
* ];
* ````
*
* The Camera has read-only convenience properties that provide each axis individually:
*
* ````javascript
* var worldRight = camera.worldRight;
* var worldForward = camera.worldForward;
* var worldUp = camera.worldUp;
* ````
*
* ### Gimbal locking
*
* By default, the Camera locks yaw rotation to pivot about the World-space "up" axis. We can dynamically lock and unlock that at any time:
*
* ````javascript
* camera.gimbalLock = false; // Yaw rotation now happens about Camera's local Y-axis
* camera.gimbalLock = true; // Yaw rotation now happens about World's "up" axis
* ````
*
* See: <a href="https://en.wikipedia.org/wiki/Gimbal_lock">https://en.wikipedia.org/wiki/Gimbal_lock</a>
*/
class Camera extends Component {
/**
@private
*/
get type() {
return "Camera";
}
/**
* @constructor
* @private
*/
constructor(owner, cfg = {}) {
super(owner, cfg);
this._state = new RenderState({
deviceMatrix: math.mat4(),
hasDeviceMatrix: false, // True when deviceMatrix set to other than identity
matrix: math.mat4(),
normalMatrix: math.mat4(),
inverseMatrix: math.mat4()
});
this._perspective = new Perspective(this);
this._ortho = new Ortho(this);
this._frustum = new Frustum(this);
this._customProjection = new CustomProjection(this);
this._project = this._perspective;
this._eye = math.vec3([0, 0, 10.0]);
this._look = math.vec3([0, 0, 0]);
this._up = math.vec3([0, 1, 0]);
this._worldUp = math.vec3([0, 1, 0]);
this._worldRight = math.vec3([1, 0, 0]);
this._worldForward = math.vec3([0, 0, -1]);
this.deviceMatrix = cfg.deviceMatrix;
this.eye = cfg.eye;
this.look = cfg.look;
this.up = cfg.up;
this.worldAxis = cfg.worldAxis;
this.gimbalLock = cfg.gimbalLock;
this.constrainPitch = cfg.constrainPitch;
this.projection = cfg.projection;
this._perspective.on("matrix", () => {
if (this._projectionType === "perspective") {
this.fire("projMatrix", this._perspective.matrix);
}
});
this._ortho.on("matrix", () => {
if (this._projectionType === "ortho") {
this.fire("projMatrix", this._ortho.matrix);
}
});
this._frustum.on("matrix", () => {
if (this._projectionType === "frustum") {
this.fire("projMatrix", this._frustum.matrix);
}
});
this._customProjection.on("matrix", () => {
if (this._projectionType === "customProjection") {
this.fire("projMatrix", this._customProjection.matrix);
}
});
}
_update() {
const state = this._state;
// In ortho mode, build the view matrix with an eye position that's translated
// well back from look, so that the front sectionPlane plane doesn't unexpectedly cut
// the front off the view (not a problem with perspective, since objects close enough
// to be clipped by the front plane are usually too big to see anything of their cross-sections).
let eye;
if (this.projection === "ortho") {
math.subVec3(this._eye, this._look, eyeLookVec);
math.normalizeVec3(eyeLookVec, eyeLookVecNorm);
math.mulVec3Scalar(eyeLookVecNorm, 1000.0, eyeLookOffset);
math.addVec3(this._look, eyeLookOffset, offsetEye);
eye = offsetEye;
} else {
eye = this._eye;
}
if (state.hasDeviceMatrix) {
math.lookAtMat4v(eye, this._look, this._up, tempMatb);
math.mulMat4(state.deviceMatrix, tempMatb, state.matrix);
//state.matrix.set(state.deviceMatrix);
} else {
math.lookAtMat4v(eye, this._look, this._up, state.matrix);
}
math.inverseMat4(this._state.matrix, this._state.inverseMatrix);
math.transposeMat4(this._state.inverseMatrix, this._state.normalMatrix);
this.glRedraw();
this.fire("matrix", this._state.matrix);
this.fire("viewMatrix", this._state.matrix);
}
/**
* Rotates {@link Camera#eye} about {@link Camera#look}, around the {@link Camera#up} vector
*
* @param {Number} angleInc Angle of rotation in degrees
*/
orbitYaw(angleInc) {
let lookEyeVec = math.subVec3(this._eye, this._look, tempVec3);
math.rotationMat4v(angleInc * 0.0174532925, this._gimbalLock ? this._worldUp : this._up, tempMat);
lookEyeVec = math.transformPoint3(tempMat, lookEyeVec, tempVec3b);
this.eye = math.addVec3(this._look, lookEyeVec, tempVec3c); // Set eye position as 'look' plus 'eye' vector
this.up = math.transformPoint3(tempMat, this._up, tempVec3d); // Rotate 'up' vector
}
/**
* Rotates {@link Camera#eye} about {@link Camera#look} around the right axis (orthogonal to {@link Camera#up} and "look").
*
* @param {Number} angleInc Angle of rotation in degrees
*/
orbitPitch(angleInc) {
if (this._constrainPitch) {
angleInc = math.dotVec3(this._up, this._worldUp) / math.DEGTORAD;
if (angleInc < 1) {
return;
}
}
let eye2 = math.subVec3(this._eye, this._look, tempVec3);
const left = math.cross3Vec3(math.normalizeVec3(eye2, tempVec3b), math.normalizeVec3(this._up, tempVec3c));
math.rotationMat4v(angleInc * 0.0174532925, left, tempMat);
eye2 = math.transformPoint3(tempMat, eye2, tempVec3d);
this.up = math.transformPoint3(tempMat, this._up, tempVec3e);
this.eye = math.addVec3(eye2, this._look, tempVec3f);
}
/**
* Rotates {@link Camera#look} about {@link Camera#eye}, around the {@link Camera#up} vector.
*
* @param {Number} angleInc Angle of rotation in degrees
*/
yaw(angleInc) {
let look2 = math.subVec3(this._look, this._eye, tempVec3);
math.rotationMat4v(angleInc * 0.0174532925, this._gimbalLock ? this._worldUp : this._up, tempMat);
look2 = math.transformPoint3(tempMat, look2, tempVec3b);
this.look = math.addVec3(look2, this._eye, tempVec3c);
if (this._gimbalLock) {
this.up = math.transformPoint3(tempMat, this._up, tempVec3d);
}
}
/**
* Rotates {@link Camera#look} about {@link Camera#eye}, around the right axis (orthogonal to {@link Camera#up} and "look").
* @param {Number} angleInc Angle of rotation in degrees
*/
pitch(angleInc) {
if (this._constrainPitch) {
angleInc = math.dotVec3(this._up, this._worldUp) / math.DEGTORAD;
if (angleInc < 1) {
return;
}
}
let look2 = math.subVec3(this._look, this._eye, tempVec3);
const left = math.cross3Vec3(math.normalizeVec3(look2, tempVec3b), math.normalizeVec3(this._up, tempVec3c));
math.rotationMat4v(angleInc * 0.0174532925, left, tempMat);
this.up = math.transformPoint3(tempMat, this._up, tempVec3f);
look2 = math.transformPoint3(tempMat, look2, tempVec3d);
this.look = math.addVec3(look2, this._eye, tempVec3e);
}
/**
* Pans the Camera along its local X, Y and Z axis.
*
* @param pan The pan vector
*/
pan(pan) {
const eye2 = math.subVec3(this._eye, this._look, tempVec3);
const vec = [0, 0, 0];
let v;
if (pan[0] !== 0) {
const left = math.cross3Vec3(math.normalizeVec3(eye2, []), math.normalizeVec3(this._up, tempVec3b));
v = math.mulVec3Scalar(left, pan[0]);
vec[0] += v[0];
vec[1] += v[1];
vec[2] += v[2];
}
if (pan[1] !== 0) {
v = math.mulVec3Scalar(math.normalizeVec3(this._up, tempVec3c), pan[1]);
vec[0] += v[0];
vec[1] += v[1];
vec[2] += v[2];
}
if (pan[2] !== 0) {
v = math.mulVec3Scalar(math.normalizeVec3(eye2, tempVec3d), pan[2]);
vec[0] += v[0];
vec[1] += v[1];
vec[2] += v[2];
}
this.eye = math.addVec3(this._eye, vec, tempVec3e);
this.look = math.addVec3(this._look, vec, tempVec3f);
}
/**
* Increments/decrements the Camera's zoom factor, which is the distance between {@link Camera#eye} and {@link Camera#look}.
*
* @param {Number} delta Zoom factor increment.
*/
zoom(delta) {
const vec = math.subVec3(this._eye, this._look, tempVec3);
const lenLook = Math.abs(math.lenVec3(vec, tempVec3b));
const newLenLook = Math.abs(lenLook + delta);
if (newLenLook < 0.5) {
return;
}
const dir = math.normalizeVec3(vec, tempVec3c);
this.eye = math.addVec3(this._look, math.mulVec3Scalar(dir, newLenLook), tempVec3d);
}
/**
* Sets the position of the Camera's eye.
*
* Default value is ````[0,0,10]````.
*
* @emits "eye" event on change, with the value of this property.
* @type {Number[]} New eye position.
*/
set eye(eye) {
this._eye.set(eye || [0, 0, 10]);
this._needUpdate(0); // Ensure matrix built on next "tick"
this.fire("eye", this._eye);
}
/**
* Gets the position of the Camera's eye.
*
* Default vale is ````[0,0,10]````.
*
* @type {Number[]} New eye position.
*/
get eye() {
return this._eye;
}
/**
* Sets the position of this Camera's point-of-interest.
*
* Default value is ````[0,0,0]````.
*
* @emits "look" event on change, with the value of this property.
*
* @param {Number[]} look Camera look position.
*/
set look(look) {
this._look.set(look || [0, 0, 0]);
this._needUpdate(0); // Ensure matrix built on next "tick"
this.fire("look", this._look);
}
/**
* Gets the position of this Camera's point-of-interest.
*
* Default value is ````[0,0,0]````.
*
* @returns {Number[]} Camera look position.
*/
get look() {
return this._look;
}
/**
* Sets the direction of this Camera's {@link Camera#up} vector.
*
* @emits "up" event on change, with the value of this property.
*
* @param {Number[]} up Direction of "up".
*/
set up(up) {
this._up.set(up || [0, 1, 0]);
this._needUpdate(0);
this.fire("up", this._up);
}
/**
* Gets the direction of this Camera's {@link Camera#up} vector.
*
* @returns {Number[]} Direction of "up".
*/
get up() {
return this._up;
}
/**
* Sets an optional matrix to premultiply into {@link Camera#matrix} matrix.
*
* This is intended to be used for stereo rendering with WebVR etc.
*
* @param {Number[]} matrix The matrix.
*/
set deviceMatrix(matrix) {
this._state.deviceMatrix.set(matrix || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
this._state.hasDeviceMatrix = !!matrix;
this._needUpdate(0);
this.fire("deviceMatrix", this._state.deviceMatrix);
}
/**
* Gets an optional matrix to premultiply into {@link Camera#matrix} matrix.
*
* @returns {Number[]} The matrix.
*/
get deviceMatrix() {
return this._state.deviceMatrix;
}
/**
* Sets the up, right and forward axis of the World coordinate system.
*
* Has format: ````[rightX, rightY, rightZ, upX, upY, upZ, forwardX, forwardY, forwardZ]````
*
* Default axis is ````[1, 0, 0, 0, 1, 0, 0, 0, 1]````
*
* @param {Number[]} axis The new Wworld coordinate axis.
*/
set worldAxis(axis) {
axis = axis || [1, 0, 0, 0, 1, 0, 0, 0, 1];
if (!this._worldAxis) {
this._worldAxis = math.vec3(axis);
} else {
this._worldAxis.set(axis);
}
this._worldRight[0] = this._worldAxis[0];
this._worldRight[1] = this._worldAxis[1];
this._worldRight[2] = this._worldAxis[2];
this._worldUp[0] = this._worldAxis[3];
this._worldUp[1] = this._worldAxis[4];
this._worldUp[2] = this._worldAxis[5];
this._worldForward[0] = this._worldAxis[6];
this._worldForward[1] = this._worldAxis[7];
this._worldForward[2] = this._worldAxis[8];
this.fire("worldAxis", this._worldAxis);
}
/**
* Gets the up, right and forward axis of the World coordinate system.
*
* Has format: ````[rightX, rightY, rightZ, upX, upY, upZ, forwardX, forwardY, forwardZ]````
*
* Default axis is ````[1, 0, 0, 0, 1, 0, 0, 0, 1]````
*
* @returns {Number[]} The current World coordinate axis.
*/
get worldAxis() {
return this._worldAxis;
}
/**
* Gets the direction of World-space "up".
*
* This is set by {@link Camera#worldAxis}.
*
* Default value is ````[0,1,0]````.
*
* @returns {Number[]} The "up" vector.
*/
get worldUp() {
return this._worldUp;
}
/**
* Gets if the World-space X-axis is "up".
* @returns {Boolean}
*/
get xUp() {
return this._worldUp[0] > this._worldUp[1] && this._worldUp[0] > this._worldUp[2];
}
/**
* Gets if the World-space Y-axis is "up".
* @returns {Boolean}
*/
get yUp() {
return this._worldUp[1] > this._worldUp[0] && this._worldUp[1] > this._worldUp[2];
}
/**
* Gets if the World-space Z-axis is "up".
* @returns {Boolean}
*/
get zUp() {
return this._worldUp[2] > this._worldUp[0] && this._worldUp[2] > this._worldUp[1];
}
/**
* Gets the direction of World-space "right".
*
* This is set by {@link Camera#worldAxis}.
*
* Default value is ````[1,0,0]````.
*
* @returns {Number[]} The "up" vector.
*/
get worldRight() {
return this._worldRight;
}
/**
* Gets the direction of World-space "forwards".
*
* This is set by {@link Camera#worldAxis}.
*
* Default value is ````[0,0,1]````.
*
* @returns {Number[]} The "up" vector.
*/
get worldForward() {
return this._worldForward;
}
/**
* Sets whether to lock yaw rotation to pivot about the World-space "up" axis.
*
* Fires a {@link Camera#gimbalLock:event} event on change.
*
* @params {Boolean} gimbalLock Set true to lock gimbal.
*/
set gimbalLock(value) {
this._gimbalLock = value !== false;
this.fire("gimbalLock", this._gimbalLock);
}
/**
* Gets whether to lock yaw rotation to pivot about the World-space "up" axis.
*
* @returns {Boolean} Returns ````true```` if gimbal is locked.
*/
get gimbalLock() {
return this._gimbalLock;
}
/**
* Sets whether to prevent camera from being pitched upside down.
*
* The camera is upside down when the angle between {@link Camera#up} and {@link Camera#worldUp} is less than one degree.
*
* Fires a {@link Camera#constrainPitch:event} event on change.
*
* Default value is ````false````.
*
* @param {Boolean} value Set ````true```` to contrain pitch rotation.
*/
set constrainPitch(value) {
this._constrainPitch = !!value;
this.fire("constrainPitch", this._constrainPitch);
}
/**
* Gets whether to prevent camera from being pitched upside down.
*
* The camera is upside down when the angle between {@link Camera#up} and {@link Camera#worldUp} is less than one degree.
*
* Default value is ````false````.
*
* @returns {Boolean} ````true```` if pitch rotation is currently constrained.
get constrainPitch() {
return this._constrainPitch;
}
/**
* Gets distance from {@link Camera#look} to {@link Camera#eye}.
*
* @returns {Number} The distance.
*/
get eyeLookDist() {
return math.lenVec3(math.subVec3(this._look, this._eye, tempVec3));
}
/**
* Gets the Camera's viewing transformation matrix.
*
* Fires a {@link Camera#matrix:event} event on change.
*
* @returns {Number[]} The viewing transform matrix.
*/
get matrix() {
if (this._updateScheduled) {
this._doUpdate();
}
return this._state.matrix;
}
/**
* Gets the Camera's viewing transformation matrix.
*
* Fires a {@link Camera#matrix:event} event on change.
*
* @returns {Number[]} The viewing transform matrix.
*/
get viewMatrix() {
if (this._updateScheduled) {
this._doUpdate();
}
return this._state.matrix;
}
/**
* The Camera's viewing normal transformation matrix.
*
* Fires a {@link Camera#matrix:event} event on change.
*
* @returns {Number[]} The viewing normal transform matrix.
*/
get normalMatrix() {
if (this._updateScheduled) {
this._doUpdate();
}
return this._state.normalMatrix;
}
/**
* The Camera's viewing normal transformation matrix.
*
* Fires a {@link Camera#matrix:event} event on change.
*
* @returns {Number[]} The viewing normal transform matrix.
*/
get viewNormalMatrix() {
if (this._updateScheduled) {
this._doUpdate();
}
return this._state.normalMatrix;
}
/**
* Gets the inverse of the Camera's viewing transform matrix.
*
* This has the same value as {@link Camera#normalMatrix}.
*
* @returns {Number[]} The inverse viewing transform matrix.
*/
get inverseViewMatrix() {
if (this._updateScheduled) {
this._doUpdate();
}
return this._state.inverseMatrix;
}
/**
* Gets the Camera's projection transformation projMatrix.
*
* Fires a {@link Camera#projMatrix:event} event on change.
*
* @returns {Number[]} The projection matrix.
*/
get projMatrix() {
return this[this.projection].matrix;
}
/**
* Gets the Camera's perspective projection.
*
* The Camera uses this while {@link Camera#projection} equals ````perspective````.
*
* @returns {Perspective} The Perspective component.
*/
get perspective() {
return this._perspective;
}
/**
* Gets the Camera's orthographic projection.
*
* The Camera uses this while {@link Camera#projection} equals ````ortho````.
*
* @returns {Ortho} The Ortho component.
*/
get ortho() {
return this._ortho;
}
/**
* Gets the Camera's frustum projection.
*
* The Camera uses this while {@link Camera#projection} equals ````frustum````.
*
* @returns {Frustum} The Ortho component.
*/
get frustum() {
return this._frustum;
}
/**
* Gets the Camera's custom projection.
*
* This is used while {@link Camera#projection} equals "customProjection".
*
* @returns {CustomProjection} The custom projection.
*/
get customProjection() {
return this._customProjection;
}
/**
* Sets the active projection type.
*
* Accepted values are ````"perspective"````, ````"ortho"````, ````"frustum"```` and ````"customProjection"````.
*
* Default value is ````"perspective"````.
*
* @param {String} value Identifies the active projection type.
*/
set projection(value) {
value = value || "perspective";
if (this._projectionType === value) {
return;
}
if (value === "perspective") {
this._project = this._perspective;
} else if (value === "ortho") {
this._project = this._ortho;
} else if (value === "frustum") {
this._project = this._frustum;
} else if (value === "customProjection") {
this._project = this._customProjection;
} else {
this.error("Unsupported value for 'projection': " + value + " defaulting to 'perspective'");
this._project = this._perspective;
value = "perspective";
}
this._project._update();
this._projectionType = value;
this.glRedraw();
this._update(); // Need to rebuild lookat matrix with full eye, look & up
this.fire("dirty");
this.fire("projection", this._projectionType);
this.fire("projMatrix", this._project.matrix);
}
/**
* Gets the active projection type.
*
* Possible values are ````"perspective"````, ````"ortho"````, ````"frustum"```` and ````"customProjection"````.
*
* Default value is ````"perspective"````.
*
* @returns {String} Identifies the active projection type.
*/
get projection() {
return this._projectionType;
}
/**
* Gets the currently active projection for this Camera.
*
* The currently active project is selected with {@link Camera#projection}.
*
* @returns {Perspective|Ortho|Frustum|CustomProjection} The currently active projection is active.
*/
get project() {
return this._project;
}
/**
* Get the 2D canvas position of a 3D world position.
*
* @param {[number, number, number]} worldPos
* @returns {[number, number]} the canvas position
*/
projectWorldPos(worldPos) {
const _worldPos = tempVec4a;
const viewPos = tempVec4b;
const screenPos = tempVec4c;
_worldPos[0] = worldPos[0];
_worldPos[1] = worldPos[1];
_worldPos[2] = worldPos[2];
_worldPos[3] = 1;
math.mulMat4v4(this.viewMatrix, _worldPos, viewPos);
math.mulMat4v4(this.projMatrix, viewPos, screenPos);
math.mulVec3Scalar(screenPos, 1.0 / screenPos[3]);
screenPos[3] = 1.0;
screenPos[1] *= -1;
const canvas = this.scene.canvas.canvas;
const halfCanvasWidth = canvas.offsetWidth / 2.0;
const halfCanvasHeight = canvas.offsetHeight / 2.0;
const canvasPos = [
screenPos[0] * halfCanvasWidth + halfCanvasWidth,
screenPos[1] * halfCanvasHeight + halfCanvasHeight,
]
return canvasPos;
}
/**
* Destroys this Camera.
*/
destroy() {
super.destroy();
this._state.destroy();
}
}
export {Camera};