'use strict';
import * as THREE from 'three';
/**
* Class for managing multiple cameras used in a viewport.
* @class FluxCameras
* @param {Number} width Width of the viewport
* @param {Number} height Height of the viewport
*/
export default function FluxCameras(width, height) {
// Initialize default cameras and frustums.
this._perspCamera = new THREE.PerspectiveCamera(30, width/height, 0.1, 100000);
// Flux is Z up
this._perspCamera.up = new THREE.Vector3( 0, 0, 1 );
this._orthoCamera = new THREE.OrthographicCamera(100, -100, 100, -100, -1000, 1000);
this.setView(FluxCameras.VIEWS.perspective);
this.updateCamera(width, height);
}
/**
* Enumeration of all possible views for the camera.
* Values are perspective, top, bottom, front, back, right, left.
* @type {Object}
*/
FluxCameras.VIEWS = {
perspective: 0,
top: 1,
bottom: 2,
front: 3,
back: 4,
right: 5,
left: 6,
END: 7
};
/**
* Get the current camera object
* @return {THREE.Camera} The current camera
*/
FluxCameras.prototype.getCamera = function () {
if (this._view === FluxCameras.VIEWS.perspective) {
return this._perspCamera;
}
return this._orthoCamera;
};
FluxCameras.DEFAULT_POSITIONS = [
[25, 10, 13], // perspective
[0, 0, -100], // top
[0, 0, 100], // bottom
[0, 0, 0], // front
[0, 0, 0], // back
[0, 0, 0], // right
[0, 0, 0] // left
];
FluxCameras.DEFAULT_ROTATIONS = [
[0, 0, 0], // perspective
[0, 0, 0], // top
[0, Math.PI, 0], // bottom
[Math.PI/2, 0, 0], // front
[Math.PI/2, Math.PI, 0], // back
[Math.PI/2, Math.PI/2, 0], // right
[Math.PI/2, -Math.PI/2, 0] // left
];
FluxCameras.isValidView = function (view) {
return view != null && view.constructor === Number && view > -1 && view < FluxCameras.VIEWS.END;
};
/**
* Set which camera view to use (ex perspective, top etc.).
* @param {FluxCameras.VIEWS} view The new view mode
*/
FluxCameras.prototype.setView = function (view) {
if (!FluxCameras.isValidView(view)) return;
this._view = view;
var camera = this.getCamera();
camera.position.set(FluxCameras.DEFAULT_POSITIONS[view][0],
FluxCameras.DEFAULT_POSITIONS[view][1],
FluxCameras.DEFAULT_POSITIONS[view][2]);
camera.rotation.set(FluxCameras.DEFAULT_ROTATIONS[view][0],
FluxCameras.DEFAULT_ROTATIONS[view][1],
FluxCameras.DEFAULT_ROTATIONS[view][2]);
};
/**
* Recompute derived state when the camera is changed.
* @param {Number} width Width of the viewport (used to calculate aspect ratio)
* @param {Number} height Height of the viewport (used to calculate aspect ratio)
*/
FluxCameras.prototype.updateCamera = function(width, height) {
this._perspCamera.aspect = width / height;
this._perspCamera.updateProjectionMatrix();
var a = width / height;
var h = this._orthoCamera.top - this._orthoCamera.bottom;
this._orthoCamera.top = h / 2;
this._orthoCamera.bottom = - h / 2;
this._orthoCamera.right = h / 2 * a;
this._orthoCamera.left = - h / 2 * a;
this._orthoCamera.updateProjectionMatrix();
};
/**
* Extract only relevant properties from a camera
* @param {THREE.Camera} camera The camera source
* @return {Object} The camera data
*/
FluxCameras.cameraToJSON = function(camera) {
var serializableCamera = {
px: camera.position.x,
py: camera.position.y,
pz: camera.position.z,
rx: camera.rotation.x,
ry: camera.rotation.y,
rz: camera.rotation.z,
near: camera.near,
far: camera.far
};
// Handle extra OrthographicCamera properties
if (camera instanceof THREE.OrthographicCamera) {
serializableCamera.top = camera.top;
serializableCamera.right = camera.right;
serializableCamera.bottom = camera.bottom;
serializableCamera.left = camera.left;
serializableCamera.type = 'orthographic';
} else {
serializableCamera.type = 'perspective';
}
return serializableCamera;
};
/**
* Check if something is anumber
* @param {Number} num The value
* @returns {boolean} True for numbers
* @private
*/
function _isNumber(num) {
return num != null && num.constructor === Number;
}
/**
* Check whether a set of properties are valid numbers
* @param {Array.<string>} schema The list of properties
* @param {Object} data The object with properties
* @returns {boolean} True if all numbers
* @private
*/
function _checkNumbers(schema, data) {
// Make sure all the properties are valid and exist
for (var i=0;i<schema.length;i++) {
if (!_isNumber(data[schema[i]])) {
return false;
}
}
return true;
}
/**
* Rehydrate camera instance from an object property tree.
* @param {THREE.camera} camera The camera to receive data
* @param {Object} data The data to parse and apply
*/
FluxCameras.cameraFromJSON = function(camera, data) {
var schema = ['px', 'py', 'pz', 'rx', 'ry', 'rz', 'near', 'far'];
if (!_checkNumbers(schema, data)) return;
camera.position.x = data.px;
camera.position.y = data.py;
camera.position.z = data.pz;
camera.rotation.x = data.rx;
camera.rotation.y = data.ry;
camera.rotation.z = data.rz;
camera.near = data.near;
camera.far = data.far;
// Handle extra OrthographicCamera properties
if (camera.constructor === THREE.OrthographicCamera) {
schema = ['top', 'right', 'bottom', 'left'];
if (!_checkNumbers(schema, data)) return;
camera.top = data.top;
camera.right = data.right;
camera.bottom = data.bottom;
camera.left = data.left;
}
};
/**
* Make serializable by pruning all references and building an object property tree
* @return {Object} The simplified model
*/
FluxCameras.prototype.toJSON = function() {
var serializableCameras = {
perspective: FluxCameras.cameraToJSON(this._perspCamera),
orthographic: FluxCameras.cameraToJSON(this._orthoCamera),
view: this._view
};
return serializableCameras;
};
/**
* Update the corresponding cameras in this object from a serialized object.
* @param {Object} serializableCameras The camera data to use.
*/
FluxCameras.prototype.fromJSON = function(serializableCameras) {
this.setView(serializableCameras.view);
FluxCameras.cameraFromJSON(this._perspCamera, serializableCameras.perspective);
FluxCameras.cameraFromJSON(this._orthoCamera, serializableCameras.orthographic);
};