"use strict";
import * as math from "../math.js";
import { Events } from "../Events.js";
import { Frustum } from "./Frustum.js";
import { Vec2 } from "../math/Vec2.js";
import { Vec3 } from "../math/Vec3.js";
import { Vec4 } from "../math/Vec4.js";
import { Mat3 } from "../math/Mat3.js";
import { Mat4 } from "../math/Mat4.js";
/**
* Camera class.
* @class
* @param {Renderer} [renderer] - Renderer uses the camera instance.
* @param {Object} [options] - Camera options:
* @param {Object} [options.name] - Camera name.
* @param {number} [options.viewAngle=38] - Camera angle of view. Default is 30.0
* @param {number} [options.near=1] - Camera near plane distance. Default is 1.0
* @param {number} [options.far=og.math.MAX] - Camera far plane distance. Deafult is og.math.MAX
* @param {Vec3} [options.eye=[0,0,0]] - Camera eye position. Default (0,0,0)
* @param {Vec3} [options.look=[0,0,0]] - Camera look position. Default (0,0,0)
* @param {Vec3} [options.up=[0,1,0]] - Camera eye position. Default (0,1,0)
*
* @fires og.Camera#viewchange
*/
class Camera {
/**
* @param {Renderer} [renderer] - Renderer uses the camera instance.
* @param {Object} [options] - Camera options:
*/
constructor(renderer, options) {
/**
* Assigned renderer
* @public
* @type {Renderer}
*/
this.renderer = renderer;
/**
* Camera events handler
* @public
* @type {Events}
*/
this.events = new Events(EVENT_NAMES, this);
/**
* Camera position.
* @public
* @type {Vec3}
*/
this.eye = new Vec3();
/**
* Camera RTE high position
* @public
* @type {Vec3}
*/
this.eyeHigh = new Float32Array(3);
/**
* Camera RTE low position
* @public
* @type {Vec3}
*/
this.eyeLow = new Float32Array(3);
/**
* Aspect ratio.
* @protected
* @type {Number}
*/
this._aspect = options.aspect || this.renderer.handler.getClientAspect();
/**
* Camera view angle in degrees
* @protected
* @type {Number}
*/
this._viewAngle = options.viewAngle || 38.0;
/**
* Camera normal matrix.
* @protected
* @type {Mat3}
*/
this._normalMatrix = new Mat3();
/**
* Camera view matrix.
* @protected
* @type {Mat4}
*/
this._viewMatrix = new Mat4();
/**
* Camera right vector.
* @protected
* @type {Vec3}
*/
this._r = new Vec3(1.0, 0.0, 0.0); // up x n
/**
* Camera up vector.
* @protected
* @type {Vec3}
*/
this._u = new Vec3(0.0, 1.0, 0.0); // n x u - UP
/**
* Camera forward vector.
* @protected
* @type {Vec3}
*/
this._b = new Vec3(0.0, 0.0, 1.0); // eye - look - FORWARD
// Previous frame values
this._pu = this._r.clone();
this._pv = this._u.clone();
this._pn = this._b.clone();
this._peye = this.eye.clone();
this.isMoved = false;
this._tanViewAngle_hrad = 0.0;
this._tanViewAngle_hradOneByHeight = 0.0;
this.frustums = [];
this.nearFarArr = [];
this.frustumColors = [];
if (options.frustums) {
for (let i = 0, len = options.frustums.length; i < len; i++) {
let fi = options.frustums[i];
let fr = new Frustum({
fov: this._viewAngle,
aspect: this._aspect,
near: fi[0],
far: fi[1]
});
fr._cameraFrustumIndex = this.frustums.length;
this.frustums.push(fr);
this.renderer.assignPickingColor(fr);
this.nearFarArr.push.apply(this.nearFarArr, [fi[0], fi[1]]);
this.frustumColors.push.apply(this.frustumColors, fr._pickingColorU);
}
} else {
let near = 1.0,
far = 10000.0;
let fr = new Frustum({
fov: this._viewAngle,
aspect: this._aspect,
near: near,
far: far
});
fr._cameraFrustumIndex = this.frustums.length;
this.frustums.push(fr);
this.renderer.assignPickingColor(fr);
this.nearFarArr = new Array([near, far]);
this.frustumColors.push.apply(this.frustumColors, fr._pickingColorU);
}
this.FARTHEST_FRUSTUM_INDEX = this.frustums.length - 1;
this._currentFrustum = 0;
renderer && this._init(options);
}
checkMoveEnd() {
var u = this._r,
v = this._u,
n = this._b,
eye = this.eye;
if (this._peye.equal(eye) && this._pu.equal(u) && this._pv.equal(v) && this._pn.equal(n)) {
if (this.isMoved) {
this.events.dispatch(this.events.moveend, this);
}
this.isMoved = false;
} else {
this.isMoved = true;
}
this._pu.copy(u);
this._pv.copy(v);
this._pn.copy(n);
this._peye.copy(eye);
}
/**
* Camera initialization.
* @public
* @param {Renderer} renderer - OpenGlobus renderer object.
* @param {Object} [options] - Camera options:
* @param {number} [options.viewAngle] - Camera angle of view. Default is 30.0
* @param {number} [options.near] - Camera near plane distance. Default is 1.0
* @param {number} [options.far] - Camera far plane distance. Deafult is og.math.MAX
* @param {Vec3} [options.eye] - Camera eye position. Default (0,0,0)
* @param {Vec3} [options.look] - Camera look position. Default (0,0,0)
* @param {Vec3} [options.up] - Camera eye position. Default (0,1,0)
*/
_init(options) {
this._setProj(this._viewAngle, this._aspect);
this.set(
options.eye || new Vec3(0.0, 0.0, 1.0),
options.look || new Vec3(),
options.up || new Vec3(0.0, 1.0, 0.0)
);
}
getUp() {
return this._u.clone();
}
getDown() {
return this._u.negateTo();
}
getRight() {
return this._r.clone();
}
getLeft() {
return this._r.negateTo();
}
getForward() {
return this._b.negateTo();
}
getBackward() {
return this._b.clone();
}
/**
* Updates camera view space
* @public
* @virtual
*/
update() {
var u = this._r,
v = this._u,
n = this._b,
eye = this.eye;
Vec3.doubleToTwoFloat32Array(eye, this.eyeHigh, this.eyeLow);
this._viewMatrix.set([
u.x,
v.x,
n.x,
0.0,
u.y,
v.y,
n.y,
0.0,
u.z,
v.z,
n.z,
0.0,
-eye.dot(u),
-eye.dot(v),
-eye.dot(n),
1.0
]);
this._normalMatrix = this._viewMatrix.toMatrix3(); // this._viewMatrix.toInverseMatrix3().transposeTo();
for (let i = 0, len = this.frustums.length; i < len; i++) {
this.frustums[i].setViewMatrix(this._viewMatrix);
}
this.events.dispatch(this.events.viewchange, this);
}
/**
* Refresh camera matrices
* @public
*/
refresh() {
this._setProj(this._viewAngle, this._aspect);
this.update();
}
/**
* Sets aspect ratio
* @public
* @param {Number} aspect - Camera aspect ratio
*/
setAspectRatio(aspect) {
this._aspect = aspect;
this.refresh();
}
/**
* Returns aspect ratio
* @public
* @returns {number} - Aspect ratio
*/
getAspectRatio() {
return this._aspect;
}
/**
* Sets up camera projection
* @public
* @param {nnumber} angle - Camera's view angle
* @param {number} aspect - Screen aspect ration
*/
_setProj(angle, aspect) {
this._viewAngle = angle;
this._aspect = aspect;
this._tanViewAngle_hrad = Math.tan(angle * math.RADIANS_HALF);
this._tanViewAngle_hradOneByHeight =
this._tanViewAngle_hrad * this.renderer.handler._oneByHeight;
var c = this.renderer.handler.canvas;
this._projSizeConst = Math.min(c.clientWidth < 256 ? 256 : c.clientWidth, c.clientHeight < 256 ? 256 : c.clientHeight) / (angle * math.RADIANS);
for (let i = 0, len = this.frustums.length; i < len; i++) {
this.frustums[i].setProjectionMatrix(
angle,
aspect,
this.frustums[i].near,
this.frustums[i].far
);
}
}
/**
* Sets camera view angle in degrees
* @public
* @param {number} angle - View angle
*/
setViewAngle(angle) {
this._viewAngle = angle;
this.refresh();
}
/**
* Gets camera view angle in degrees
* @public
* @returns {number} angle -
*/
getViewAngle() {
return this._viewAngle;
}
/**
* Sets camera to eye position
* @public
* @param {Vec3} eye - Camera position
* @param {Vec3} look - Look point
* @param {Vec3} up - Camera up vector
* @returns {Camera} - This camera
*/
set(eye, look, up) {
this.eye.x = eye.x;
this.eye.y = eye.y;
this.eye.z = eye.z;
look = look || this._b;
up = up || this._u;
this._b.x = eye.x - look.x;
this._b.y = eye.y - look.y;
this._b.z = eye.z - look.z;
this._r.copy(up.cross(this._b));
this._b.normalize();
this._r.normalize();
this._u.copy(this._b.cross(this._r));
return this;
}
/**
* Sets camera look point
* @public
* @param {Vec3} look - Look point
* @param {Vec3} [up] - Camera up vector otherwise camera current up vector(this._u)
*/
look(look, up) {
this._b.set(this.eye.x - look.x, this.eye.y - look.y, this.eye.z - look.z);
this._r.copy((up || this._u).cross(this._b));
this._b.normalize();
this._r.normalize();
this._u.copy(this._b.cross(this._r));
}
/**
* Slides camera to vector d - (du, dv, dn)
* @public
* @param {number} du - delta X
* @param {number} dv - delta Y
* @param {number} dn - delta Z
*/
slide(du, dv, dn) {
this.eye.x += du * this._r.x + dv * this._u.x + dn * this._b.x;
this.eye.y += du * this._r.y + dv * this._u.y + dn * this._b.y;
this.eye.z += du * this._r.z + dv * this._u.z + dn * this._b.z;
}
/**
* Roll the camera to the angle in degrees
* @public
* @param {number} angle - Delta roll angle in degrees
*/
roll(angle) {
var cs = Math.cos(math.RADIANS * angle);
var sn = Math.sin(math.RADIANS * angle);
var t = this._r.clone();
this._r.set(
cs * t.x - sn * this._u.x,
cs * t.y - sn * this._u.y,
cs * t.z - sn * this._u.z
);
this._u.set(
sn * t.x + cs * this._u.x,
sn * t.y + cs * this._u.y,
sn * t.z + cs * this._u.z
);
}
/**
* Pitch the camera to the angle in degrees
* @public
* @param {number} angle - Delta pitch angle in degrees
*/
pitch(angle) {
var cs = Math.cos(math.RADIANS * angle);
var sn = Math.sin(math.RADIANS * angle);
var t = this._b.clone();
this._b.set(
cs * t.x - sn * this._u.x,
cs * t.y - sn * this._u.y,
cs * t.z - sn * this._u.z
);
this._u.set(
sn * t.x + cs * this._u.x,
sn * t.y + cs * this._u.y,
sn * t.z + cs * this._u.z
);
}
/**
* Yaw the camera to the angle in degrees
* @public
* @param {number} angle - Delta yaw angle in degrees
*/
yaw(angle) {
var cs = Math.cos(math.RADIANS * angle);
var sn = Math.sin(math.RADIANS * angle);
var t = this._r.clone();
this._r.set(
cs * t.x - sn * this._b.x,
cs * t.y - sn * this._b.y,
cs * t.z - sn * this._b.z
);
this._b.set(
sn * t.x + cs * this._b.x,
sn * t.y + cs * this._b.y,
sn * t.z + cs * this._b.z
);
}
/**
* Returns normal vector direction to to the unprojected screen point from camera eye
* @public
* @param {number} x - Scren X coordinate
* @param {number} y - Scren Y coordinate
* @returns {Vec3} - Direction vector
*/
unproject(x, y) {
var c = this.renderer.handler.canvas,
w = c.width * 0.5,
h = c.height * 0.5;
var px = (x - w) / w,
py = -(y - h) / h;
var world1 = this.frustums[0]._inverseProjectionViewMatrix.mulVec4(new Vec4(px, py, -1.0, 1.0)).affinity(),
world2 = this.frustums[0]._inverseProjectionViewMatrix.mulVec4(new Vec4(px, py, 0.0, 1.0)).affinity();
return world2.subA(world1).toVec3().normalize();
}
/**
* Gets projected 3d point to the 2d screen coordiantes
* @public
* @param {Vec3} v - Cartesian 3d coordiantes
* @returns {Vec2} - Screen point coordinates
*/
project(v) {
var r = this.frustums[0]._projectionViewMatrix.mulVec4(v.toVec4()),
c = this.renderer.handler.canvas;
return new Vec2((1 + r.x / r.w) * c.width * 0.5, (1 - r.y / r.w) * c.height * 0.5);
}
/**
* Rotates camera around center point
* @public
* @param {number} angle - Rotation angle in radians
* @param {boolean} isArc - If true camera up vector gets from current up vector every frame,
* otherwise up is always input parameter.
* @param {Vec3} center - Point that the camera rotates around
* @param {Vecto3} [up] - Camera up vector
*/
rotateAround(angle, isArc, center, up) {
center = center || Vec3.ZERO;
up = up || Vec3.UP;
var rot = new Mat4().setRotation(isArc ? this._u : up, angle);
var tr = new Mat4().setIdentity().translate(center);
var ntr = new Mat4().setIdentity().translate(center.negateTo());
var trm = tr.mul(rot).mul(ntr);
this.eye = trm.mulVec3(this.eye);
this._u = rot.mulVec3(this._u).normalize();
this._r = rot.mulVec3(this._r).normalize();
this._b = rot.mulVec3(this._b).normalize();
}
/**
* Rotates camera around center point by horizontal.
* @public
* @param {number} angle - Rotation angle in radians.
* @param {boolaen} isArc - If true camera up vector gets from current up vector every frame,
* otherwise up is always input parameter.
* @param {Vec3} center - Point that the camera rotates around.
* @param {Vec3} [up] - Camera up vector.
*/
rotateHorizontal(angle, isArc, center, up) {
this.rotateAround(angle, isArc, center, up);
}
/**
* Rotates camera around center point by vecrtical.
* @param {number} angle - Rotation angle in radians.
* @param {Vec3} center - Point that the camera rotates around.
*/
rotateVertical(angle, center) {
this.rotateAround(angle, false, center, this._r);
}
/**
* Gets 3d size factor. Uses in LOD distance calculation.
* @public
* @param {Vec3} p - Far point.
* @param {Vec3} r - Far point.
* @returns {number} - Size factor.
*/
projectedSize(p, r) {
return Math.atan(r / this.eye.distance(p)) * this._projSizeConst;
}
/**
* Returns normal matrix.
* @public
* @returns {Mat3} - Normal matrix.
*/
getNormalMatrix() {
return this._normalMatrix._m;
}
/**
* Returns model matrix.
* @public
* @returns {Mat4} - View matrix.
*/
getViewMatrix() {
return this._viewMatrix._m;
}
setCurrentFrustum(k) {
this._currentFrustum = k;
}
getCurrentFrustum() {
return this._currentFrustum;
}
get frustum() {
return this.frustums[this._currentFrustum];
}
/**
* Returns projection matrix.
* @public
* @returns {Mat4} - Projection matrix.
*/
getProjectionMatrix() {
return this.frustum._projectionMatrix._m;
}
/**
* Returns projection and model matrix product.
* @public
* @return {Mat4} - Projection-view matrix.
*/
getProjectionViewMatrix() {
return this.frustum._projectionViewMatrix._m;
}
/**
* Returns inverse projection and model matrix product.
* @public
* @returns {Mat4} - Inversed projection-view matrix.
*/
getInverseProjectionViewMatrix() {
return this.frustum._inverseProjectionViewMatrix._m;
}
/**
* Returns inverse projection matrix.
* @public
* @returns {Mat4} - Inversed projection-view matrix.
*/
getInverseProjectionMatrix() {
return this.frustum._inverseProjectionMatrix._m;
}
}
const EVENT_NAMES = [
/**
* When camera has been updated.
* @event og.Camera#viewchange
*/
"viewchange",
/**
* Camera is stopped.
* @event og.Camera#moveend
*/
"moveend"
];
export { Camera };