js/Mobilizing/renderer/3D/three/scene/Camera.js
import * as THREE from 'three';
import Transform from './Transform';
import Vector3 from '../types/Vector3';
import Rect from '../types/Rect';
import * as _Math from '../../../../core/util/Math';
import * as debug from '../../../../core/util/Debug';
/**
* Camera class. Must be created by the user so that the current scene can be seen.
* If no camera is added to the scene, rendering is not done (nothing is seen, so nothing is rendered).
*
* A Camera contains a transform object to be moved and rotated in space.
* Viewport can be defined by the user to create multicam rendering.
*/
export default class Camera {
/**
* @example
* //this is how to use a parameters object in order to instanciate a Mobilizing.js object
* var mobilizingObject = new Mobilizing.Class({paramName1: value, paramName2: value});
*
* @param {Object} params Parameters object, given by the constructor.
* @param {Context} [params.context] mobilizing context to use
* @param {String} [params.type="perspective"] One of "perspective", "ortho" or "cube"
* @param {Number} [params.fov] vertical field of view
* @param {Number} [params.cubeResolution=1024] if the type is "cube", defines the size of the cubemap, must be a power of 2
* @param {Number} [params.near=1] near plane
* @param {Number} [params.far=5000] far plane
* @param {Rect} [params.viewport=new Rect()] the Rect defining the viewport in normalize % (0 ~ 1), that is the portion of the rendering canvas to be used as a rendering surface for this camera
* @param {Context} [params.layer] the layer in which to render the camera, that is the scene it is
* @param {Vector3} [params.position=Vector(0,0,0)] the position where to create the camera in space
* @param {Number} [params.verticalshift=0] vertical lens shift in %
* @param {Number} [params.horizontalshift=0] horizontal lens shift in %
* @param {Boolean} [params.autoRender=true] Should the camera automatically renders itself or not
* @param {Boolean} [params.autoClear=true] Should the camera automatically clears itself or not
* @param {Boolean} [params.autoUpdateMatrix=true] Should the camera transformation matrix automatically updates itself or not
*/
constructor({
context = null,
type = "perspective",
fov = 35,
aspect = null,
cubeResolution = 1024,
near = 0.1,
far = 5000,
viewport = new Rect(),
layers = ["default"],
verticalshift = 0,
horizontalshift = 0,
autoRender = true,
autoClear = true,
autoUpdateMatrix = false,
name = null,
} = {}) {
this.context = context;
this.type = type;
this.fov = fov;
this.cubeResolution = cubeResolution;
this.aspect = aspect;
this.near = near;
this.far = far;
this.viewport = viewport;
this.layers = layers;
this.verticalshift = verticalshift;
this.horizontalshift = horizontalshift;
this.autoRender = autoRender;
this.autoClear = autoClear;
this.autoUpdateMatrix = autoUpdateMatrix;
this.name = name;
this.autoClearColor = true;
this.autoClearDepth = true;
this.autoClearStencil = true;
if (this.type === "perspective") {
let size = null;
if (this.context) {
size = this.context.getCanvasSize();
}
else {
if (this.aspect) {
size = { "width": this.aspect, "height": this.aspect };
}
else {
size = { "width": window.innerWidth, "height": window.innerHeight };
}
}
this._camera = new THREE.PerspectiveCamera(this.fov, (size.width) / (size.height), this.near, this.far);
debug.log("made perspective cam");
}
else if (this.type === "ortho") {
let size;
if (this.context) {
size = this.context.getCanvasSize();
}
else {
size = { "width": window.innerWidth, "height": window.innerHeight };
}
this._camera = new THREE.OrthographicCamera(size.width / -2, size.width / 2, size.height / 2, size.height / -2, this.near, this.far);
}
else if (this.type === "cube") {
/* this.cubeRenderTarget = new THREE.WebGLCubeRenderTarget(this.cubeResolution, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter });
this._camera = new THREE.CubeCamera(0.5, 10000, this.cubeRenderTarget); */
}
this.transform = new Transform(this);
/**
@type Boolean flag to know if something has changed
@default false
*/
this.dirty = false;
/**
* flag to know if we used setToPixel method to auto adjust camera pos to mimic 2D graphical spaces, that is with (0,0) in the top-left corner.
*/
this.isToPixel = false;
}
/**
* @returns the Three.js native object used in this class
*/
getNativeObject() {
return this._camera;
}
/**
Set the RenderTexture to render on. By default, the camera renders on the Context canvas.
@param {RenderTexture} renderTexture Mobilizing.RenderTexture object
*/
setRenderTexture(renderTexture) {
this.renderTexture = renderTexture;
}
getRenderTexture() {
return this.renderTexture;
}
/**
* Set this camera layer's name, which should match a renderer's scene name
* @param {String} name
*/
setLayer(name) {
this.layers = [name];
}
/**
* @param {String} name
*/
addLayer(name) {
this.layers.push(name);
}
/**
* set the vertical field of view in degrees
* @param fov {Number} default to 35 degree
*/
setFOV(fov) {
this.fov = fov;
if (this.type === "perspective") {
this._camera.fov = this.fov;
this.updateProjectionMatrix();
}
}
/**
* get the vertical field of view in degrees
* @return {Number} Field Of View value
*/
getFOV() {
return this.fov;
}
/**
* Change the autoClear property of this camera. Needed to had a "trail effect"
* @param {Boolean} val
*/
setAutoClear(val) {
this.autoClear = val;
}
/**
* Change the autoClearColor property of this camera. Needed to had a "trail effect"
* @param {Boolean} val
*/
setAutoClearColor(val) {
this.autoClearColor = val;
}
/**
* Change the setAutoClearDepth property of this camera.
* @param {Boolean} val
*/
setAutoClearDepth(val) {
this.autoClearDepth = val;
}
/**
* Change the setAutoClearStencil property of this camera.
* @param {Boolean} val
*/
setAutoClearStencil(val) {
this.autoClearStencil = val;
}
/**
* Set the clear color, which is the color used to paint the backgroud.
* Note: this is camera independant, each cam on the scene can have a different
* clear color, or a different background color.
*
* @param {Color} Color Mobilizing.Color object
* @param {Number} Alpha A number >= 0 && <= 1
* */
setClearColor(color, alpha) {
this.clearColor = color;
this.clearColorAplha = alpha;
}
/**
Gets the clear color, which is the color used to paint the backgroud.
Note: this is camera independant, each cam on the scene can have a different
clear color, or a different background color.
@return {Color} Color object associated to this clearColor
*/
getClearColor() {
return this.clearColor;
}
getClearAlpha() {
return this.clearColorAplha;
}
/**
Set the autoRender flag, which means that the camera will render itself automatically.
@param {Bool} val flag true/false
*/
setAutoRender(val) {
this.autoRender = val;
}
/**
Makes the cam "looks at" the argements coordinates. Handy way to orient the cam
or to make it follow an object in space.
@param {Object} Vector3 the coordinates to look at.
*/
lookAt(vec) {
this._camera.lookAt(vec);
}
/**
* @method getWolrdDirection
* return {Vector3} the world direction vector of this camera
*/
getWolrdDirection() {
const result = new Vector3();
this._camera.getWorldDirection(result);
return result;
}
/**
Sets the aspect ratio of the camera view
@param {Number} the ratio (ex. 4/3)
*/
setAspect(ratio) {
this._camera.aspect = ratio;
this.updateProjectionMatrix();
}
/**
gets the aspect ratio of the camera view
@return {Number} cam aspect value
*/
getAspect() {
return this._camera.aspect;
}
/**
Sets the vertical shift ratio of the camera view
@param {Number} the ratio (1 -> 100%)
*/
setVerticalShift(ratio) {
this.verticalshift = ratio;
this.updateProjectionMatrix();
}
/**
Sets the horizontal shift ratio of the camera view
@param {Number} the ratio (1 -> 100%)
*/
setHorizontalShift(ratio) {
this.horizontalshift = ratio;
this.updateProjectionMatrix();
}
/**
* recompute the projection matrix of this camera to reflect properly properties changes
* @private
*/
updateProjectionMatrix() {
//FIXME : this call does an updateProjectionMatrix internally :
const w = 1;
const h = 1 / this._camera.aspect;
if (this.type !== "cube") {
if (this.type !== "ortho") {
this._camera.setViewOffset(w, h, w * this.horizontalshift, h * this.verticalshift, w, h);
}
if (this.autoUpdateMatrix) {
this._camera.updateProjectionMatrix();
}
}
}
/**
Method to recompute the frame of ortho cam. Is used internally for window resizing.
@param {Number} left
@param {Number} right
@param {Number} top
@param {Number} bottom
*/
setOrthoPlanes(left, right, top, bottom) {
if (this.type === "ortho") {
this._camera.left = left;
this._camera.right = right;
this._camera.top = top;
this._camera.bottom = bottom;
}
else {
console.warn("setOrthoBounds() can't be used on perspective cams!");
}
}
/**
Zoom is for ortho cam and mimics the Z translation of perspective cams.
This is expressed like a scale, zoom = 2 will double, .5 make it half.
@param {Number} zoom value
*/
setZoom(val) {
if (this.type === "ortho") {
this._camera.zoom = val;
}
else {
console.warn("setZoom() can't be used on perspective cams!");
}
}
/**
Gets current zoom value.
@return {Number} zoom value
*/
getZoom() {
if (this.type === "ortho") {
return this._camera.zoom;
}
console.warn("getZoom() can't be used on perspective cams!");
return null;
}
/**
Sets cam far plane
@param {Number} far plane value
*/
setFarPlane(far) {
this.far = far;
if (this.type !== "cube") {
this._camera.far = this.far;
this.updateProjectionMatrix();
}
else if (this.type === "cube") {
this._camera.children.forEach((cam) => {
cam.far = this.far;
cam.updateProjectionMatrix();
});
}
}
/**
Gets cam far plane
@return {Number} far plane value
*/
getFarPlane() {
this.far = this._camera.far;
return this.far;
}
/**
Sets cam near plane
@param {Number} near plane value
*/
setNearPlane(near) {
this.near = near;
if (this.type !== "cube") {
this._camera.near = this.near;
this.updateProjectionMatrix();
}
else if (this.type === "cube") {
this._camera.children.forEach((cam) => {
cam.near = this.near;
cam.updateProjectionMatrix();
});
}
}
/**
Gets cam near plane
@return {Number} far plane value
*/
getNearPlane() {
this.near = this._camera.near;
return this.near;
}
/**
Sets cam far and near planes
@param {Number} near near plane value
@param {Number} far far plane value
*/
setPlanes(near, far) {
this.near = near;
this.far = far;
this._camera.far = this.far;
this._camera.near = this.near;
this.updateProjectionMatrix();
}
/**
Tries to adjust the cam z distance so that 1 world unit == 1 screen pixel.
Useful to make object move at the mouse or touch position x and y.
For perspective cam only.
*/
setToPixel() {
if (this.type === "perspective") {
let newPos;
if (this.context) {
const canvasSize = this.context.getCanvasSize();
newPos = new Vector3(canvasSize.width / 2, -canvasSize.height / 2, 1 / (2 * Math.tan((_Math.degToRad(this.fov / 2.0)) / canvasSize.height)));
}
else {
newPos = new Vector3(window.innerWidth / 2, -window.innerHeight / 2, 1 / (2 * Math.tan((_Math.degToRad(this.fov / 2.0)) / window.innerHeight)));
}
this.transform.setLocalPosition(newPos);
this.isToPixel = true;
}
else {
debug.error("only perspective camera can be setted to screen pixel z position");
}
}
/**
* Compute a perspective with offsets matrix
* @param {Number} left
* @param {Number} right
* @param {Number} bottom
* @param {Number} top
* @param {Number} near
* @param {Number} far
*/
perspectiveOffCenter(left, right, bottom, top, near, far) {
const x = 2.0 * near / (right - left);
const y = 2.0 * near / (top - bottom);
const a = (right + left) / (right - left);
const b = (top + bottom) / (top - bottom);
const c = -(far + near) / (far - near);
const d = -(2.0 * far * near) / (far - near);
const e = -1.0;
const m = this._camera.projectionMatrix;
m.set(x, 0, a, 0, 0, y, b, 0, 0, 0, c, d, 0, 0, e, 0);
}
}