2017-09-15 03:04:51 +00:00
|
|
|
var Class = require('../utils/Class');
|
2017-09-16 01:31:33 +00:00
|
|
|
var Matrix4 = require('../math/Matrix4');
|
|
|
|
var RotateVec3 = require('../math/RotateVec3');
|
2017-09-15 15:46:20 +00:00
|
|
|
var Vector3 = require('../math/Vector3');
|
|
|
|
var Vector4 = require('../math/Vector4');
|
2017-09-16 01:31:33 +00:00
|
|
|
|
|
|
|
// Local cache vars
|
2017-09-15 03:04:51 +00:00
|
|
|
|
|
|
|
var tmpVec3 = new Vector3();
|
|
|
|
var tmpVec4 = new Vector4();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Abstract base class for cameras to implement.
|
|
|
|
* @class Camera
|
|
|
|
* @abstract
|
|
|
|
*/
|
|
|
|
var Camera3D = new Class({
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
initialize: function ()
|
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
this.direction = new Vector3(0, 0, -1);
|
|
|
|
this.up = new Vector3(0, 1, 0);
|
|
|
|
this.position = new Vector3();
|
|
|
|
|
|
|
|
this.projection = new Matrix4();
|
|
|
|
this.view = new Matrix4();
|
|
|
|
this.combined = new Matrix4();
|
|
|
|
this.invProjectionView = new Matrix4();
|
|
|
|
|
|
|
|
this.near = 1;
|
|
|
|
this.far = 100;
|
|
|
|
|
|
|
|
this.ray = {
|
|
|
|
origin: new Vector3(),
|
|
|
|
direction: new Vector3()
|
|
|
|
};
|
|
|
|
|
|
|
|
this.viewportWidth = 0;
|
|
|
|
this.viewportHeight = 0;
|
|
|
|
},
|
|
|
|
|
2017-09-16 02:07:57 +00:00
|
|
|
setPosition: function (x, y, z)
|
|
|
|
{
|
|
|
|
this.position.set(x, y, z);
|
|
|
|
|
|
|
|
return this.update();
|
|
|
|
},
|
|
|
|
|
2017-09-15 03:04:51 +00:00
|
|
|
/**
|
|
|
|
* Sets the width and height of the viewport. Does not
|
|
|
|
* update any matrices.
|
|
|
|
*
|
|
|
|
* @method setViewport
|
|
|
|
* @param {Number} width the viewport width
|
|
|
|
* @param {Number} height the viewport height
|
|
|
|
*/
|
2017-09-16 01:31:33 +00:00
|
|
|
setViewport: function (width, height)
|
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
this.viewportWidth = width;
|
|
|
|
this.viewportHeight = height;
|
2017-09-16 01:31:33 +00:00
|
|
|
|
|
|
|
return this;
|
2017-09-15 03:04:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Translates this camera by a specified Vector3 object
|
|
|
|
* or x, y, z parameters. Any undefined x y z values will
|
|
|
|
* default to zero, leaving that component unaffected.
|
|
|
|
*
|
|
|
|
* @param {[type]} vec [description]
|
|
|
|
* @return {[type]} [description]
|
|
|
|
*/
|
2017-09-16 01:31:33 +00:00
|
|
|
translate: function (x, y, z)
|
|
|
|
{
|
|
|
|
if (typeof x === 'object')
|
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
this.position.x += x.x || 0;
|
|
|
|
this.position.y += x.y || 0;
|
|
|
|
this.position.z += x.z || 0;
|
2017-09-16 01:31:33 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
this.position.x += x || 0;
|
|
|
|
this.position.y += y || 0;
|
|
|
|
this.position.z += z || 0;
|
|
|
|
}
|
2017-09-16 01:31:33 +00:00
|
|
|
|
|
|
|
return this;
|
2017-09-15 03:04:51 +00:00
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
lookAt: function (x, y, z)
|
|
|
|
{
|
|
|
|
var dir = this.direction;
|
|
|
|
var up = this.up;
|
2017-09-15 03:04:51 +00:00
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
if (typeof x === 'object')
|
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
dir.copy(x);
|
2017-09-16 01:31:33 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
dir.set(x, y, z);
|
|
|
|
}
|
|
|
|
|
|
|
|
dir.sub(this.position).normalize();
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// Calculate right vector
|
2017-09-15 03:04:51 +00:00
|
|
|
tmpVec3.copy(dir).cross(up).normalize();
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// Calculate up vector
|
2017-09-15 03:04:51 +00:00
|
|
|
up.copy(tmpVec3).cross(dir).normalize();
|
2017-09-16 01:31:33 +00:00
|
|
|
|
|
|
|
return this;
|
2017-09-15 03:04:51 +00:00
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
rotate: function (radians, axis)
|
|
|
|
{
|
|
|
|
RotateVec3(this.direction, axis, radians);
|
|
|
|
RotateVec3(this.up, axis, radians);
|
|
|
|
|
|
|
|
return this;
|
2017-09-15 03:04:51 +00:00
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
rotateAround: function (point, radians, axis)
|
|
|
|
{
|
2017-09-16 03:00:45 +00:00
|
|
|
tmpVec3.copy(point).sub(this.position);
|
2017-09-16 01:31:33 +00:00
|
|
|
|
2017-09-16 03:00:45 +00:00
|
|
|
this.translate(tmpVec3);
|
2017-09-15 03:04:51 +00:00
|
|
|
this.rotate(radians, axis);
|
2017-09-16 03:00:45 +00:00
|
|
|
this.translate(tmpVec3.negate());
|
2017-09-16 01:31:33 +00:00
|
|
|
|
|
|
|
return this;
|
2017-09-15 03:04:51 +00:00
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
project: function (vec, out)
|
|
|
|
{
|
|
|
|
if (out === undefined) { out = new Vector4(); }
|
2017-09-15 03:04:51 +00:00
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// TODO: support viewport XY
|
|
|
|
var viewportWidth = this.viewportWidth;
|
|
|
|
var viewportHeight = this.viewportHeight;
|
|
|
|
var n = Camera3D.NEAR_RANGE;
|
|
|
|
var f = Camera3D.FAR_RANGE;
|
2017-09-15 03:04:51 +00:00
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// For useful Z and W values we should do the usual steps: clip space -> NDC -> window coords
|
2017-09-15 03:04:51 +00:00
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// Implicit 1.0 for w component
|
2017-09-15 03:04:51 +00:00
|
|
|
tmpVec4.set(vec.x, vec.y, vec.z, 1.0);
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// Transform into clip space
|
2017-09-15 03:04:51 +00:00
|
|
|
tmpVec4.transformMat4(this.combined);
|
|
|
|
|
2017-09-16 02:36:39 +00:00
|
|
|
// Avoid divide by zero when 0x0x0 camera projects to a 0x0x0 vec3
|
|
|
|
if (tmpVec4.w === 0)
|
|
|
|
{
|
|
|
|
tmpVec4.w = 1;
|
|
|
|
}
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// Now into NDC
|
2017-09-15 03:04:51 +00:00
|
|
|
tmpVec4.x = tmpVec4.x / tmpVec4.w;
|
|
|
|
tmpVec4.y = tmpVec4.y / tmpVec4.w;
|
|
|
|
tmpVec4.z = tmpVec4.z / tmpVec4.w;
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// And finally into window coordinates
|
2017-09-15 03:04:51 +00:00
|
|
|
out.x = viewportWidth / 2 * tmpVec4.x + (0 + viewportWidth / 2);
|
|
|
|
out.y = viewportHeight / 2 * tmpVec4.y + (0 + viewportHeight / 2);
|
|
|
|
out.z = (f - n) / 2 * tmpVec4.z + (f + n) / 2;
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
// If the out vector has a fourth component, we also store (1/clip.w), same idea as gl_FragCoord.w
|
2017-09-15 03:04:51 +00:00
|
|
|
if (out.w === 0 || out.w)
|
2017-09-16 01:31:33 +00:00
|
|
|
{
|
2017-09-15 03:04:51 +00:00
|
|
|
out.w = 1 / tmpVec4.w;
|
2017-09-16 01:31:33 +00:00
|
|
|
}
|
2017-09-15 03:04:51 +00:00
|
|
|
|
|
|
|
return out;
|
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
unproject: function (vec, out)
|
|
|
|
{
|
|
|
|
if (out === undefined) { out = new Vector3(); }
|
2017-09-15 03:04:51 +00:00
|
|
|
|
|
|
|
var viewport = tmpVec4.set(0, 0, this.viewportWidth, this.viewportHeight);
|
2017-09-16 01:31:33 +00:00
|
|
|
|
2017-09-15 03:04:51 +00:00
|
|
|
return out.copy(vec).unproject(viewport, this.invProjectionView);
|
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
getPickRay: function (x, y)
|
|
|
|
{
|
|
|
|
var origin = this.ray.origin.set(x, y, 0);
|
|
|
|
var direction = this.ray.direction.set(x, y, 1);
|
|
|
|
var viewport = tmpVec4.set(0, 0, this.viewportWidth, this.viewportHeight);
|
|
|
|
var mtx = this.invProjectionView;
|
2017-09-15 03:04:51 +00:00
|
|
|
|
|
|
|
origin.unproject(viewport, mtx);
|
2017-09-16 01:31:33 +00:00
|
|
|
|
2017-09-15 03:04:51 +00:00
|
|
|
direction.unproject(viewport, mtx);
|
|
|
|
|
|
|
|
direction.sub(origin).normalize();
|
2017-09-16 01:31:33 +00:00
|
|
|
|
2017-09-15 03:04:51 +00:00
|
|
|
return this.ray;
|
|
|
|
},
|
|
|
|
|
2017-09-16 01:31:33 +00:00
|
|
|
update: function ()
|
|
|
|
{
|
|
|
|
// Left empty for subclasses
|
2017-09-16 02:07:57 +00:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
x: {
|
|
|
|
get: function ()
|
|
|
|
{
|
|
|
|
return this.position.x;
|
|
|
|
},
|
|
|
|
|
|
|
|
set: function (value)
|
|
|
|
{
|
|
|
|
this.position.x = value;
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
y: {
|
|
|
|
get: function ()
|
|
|
|
{
|
|
|
|
return this.position.y;
|
|
|
|
},
|
|
|
|
|
|
|
|
set: function (value)
|
|
|
|
{
|
|
|
|
this.position.y = value;
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
z: {
|
|
|
|
get: function ()
|
|
|
|
{
|
|
|
|
return this.position.z;
|
|
|
|
},
|
|
|
|
|
|
|
|
set: function (value)
|
|
|
|
{
|
|
|
|
this.position.z = value;
|
|
|
|
this.update();
|
|
|
|
}
|
2017-09-15 03:04:51 +00:00
|
|
|
}
|
2017-09-16 02:07:57 +00:00
|
|
|
|
2017-09-15 03:04:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Camera3D.FAR_RANGE = 1.0;
|
|
|
|
Camera3D.NEAR_RANGE = 0.0;
|
|
|
|
|
|
|
|
module.exports = Camera3D;
|