From 83d985b4b290a806cfc8d248db4122b1930b42dd Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Fri, 15 Sep 2017 04:04:51 +0100 Subject: [PATCH] Testing out a 3D Camera --- v3/src/camera3d/Camera3D.js | 172 +++++ v3/src/camera3d/OrthographicCamera.js | 82 +++ v3/src/camera3d/PerspectiveCamera.js | 136 ++++ v3/src/camera3d/index.js | 6 + v3/src/camera3d/vecmath/Matrix3.js | 312 ++++++++++ v3/src/camera3d/vecmath/Matrix4.js | 690 +++++++++++++++++++++ v3/src/camera3d/vecmath/Quaternion.js | 281 +++++++++ v3/src/camera3d/vecmath/Vector2.js | 201 ++++++ v3/src/camera3d/vecmath/Vector3.js | 286 +++++++++ v3/src/camera3d/vecmath/Vector4.js | 138 +++++ v3/src/camera3d/vecmath/common.js | 186 ++++++ v3/src/camera3d/vecmath/index.js | 8 + v3/src/camera3d/vecutil.js | 34 + v3/src/gameobjects/components/Transform.js | 26 +- v3/src/math/Percent.js | 39 +- v3/src/math/Vector2.js | 221 +++++++ v3/src/phaser.js | 3 + 17 files changed, 2809 insertions(+), 12 deletions(-) create mode 100644 v3/src/camera3d/Camera3D.js create mode 100644 v3/src/camera3d/OrthographicCamera.js create mode 100644 v3/src/camera3d/PerspectiveCamera.js create mode 100644 v3/src/camera3d/index.js create mode 100644 v3/src/camera3d/vecmath/Matrix3.js create mode 100644 v3/src/camera3d/vecmath/Matrix4.js create mode 100644 v3/src/camera3d/vecmath/Quaternion.js create mode 100644 v3/src/camera3d/vecmath/Vector2.js create mode 100644 v3/src/camera3d/vecmath/Vector3.js create mode 100644 v3/src/camera3d/vecmath/Vector4.js create mode 100644 v3/src/camera3d/vecmath/common.js create mode 100644 v3/src/camera3d/vecmath/index.js create mode 100644 v3/src/camera3d/vecutil.js create mode 100644 v3/src/math/Vector2.js diff --git a/v3/src/camera3d/Camera3D.js b/v3/src/camera3d/Camera3D.js new file mode 100644 index 000000000..709016f37 --- /dev/null +++ b/v3/src/camera3d/Camera3D.js @@ -0,0 +1,172 @@ +var Class = require('../utils/Class'); + +var util = require('./vecutil'); + +var Vector2 = require('./vecmath/Vector2'); +var Vector3 = require('./vecmath/Vector3'); +var Vector4 = require('./vecmath/Vector4'); +var Matrix4 = require('./vecmath/Matrix4'); + +var tmpVec3 = new Vector3(); +var tmpVec4 = new Vector4(); + +/** + * Abstract base class for cameras to implement. + * @class Camera + * @abstract + */ +var Camera3D = new Class({ + + initialize: function() { + 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; + }, + + /** + * 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 + */ + setViewport: function(width, height) { + this.viewportWidth = width; + this.viewportHeight = height; + }, + + /** + * 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] + */ + translate: function(x, y, z) { + if (typeof x === "object") { + this.position.x += x.x || 0; + this.position.y += x.y || 0; + this.position.z += x.z || 0; + } else { + this.position.x += x || 0; + this.position.y += y || 0; + this.position.z += z || 0; + } + }, + + lookAt: function(x, y, z) { + var dir = this.direction, + up = this.up; + + if (typeof x === "object") { + dir.copy(x); + } else { + dir.set(x, y, z); + } + + dir.sub(this.position).normalize(); + + //calculate right vector + tmpVec3.copy(dir).cross(up).normalize(); + + //calculate up vector + up.copy(tmpVec3).cross(dir).normalize(); + }, + + rotate: function(radians, axis) { + util.rotate(this.direction, axis, radians); + util.rotate(this.up, axis, radians); + }, + + rotateAround: function(point, radians, axis) { + tmpVec.copy(point).sub(this.position); + this.translate(tmpVec); + this.rotate(radians, axis); + this.translate(tmpVec.negate()); + }, + + project: function(vec, out) { + if (!out) + out = new Vector4(); + + //TODO: support viewport XY + var viewportWidth = this.viewportWidth, + viewportHeight = this.viewportHeight, + n = Camera3D.NEAR_RANGE, + f = Camera3D.FAR_RANGE; + + // for useful Z and W values we should do the usual steps... + // clip space -> NDC -> window coords + + //implicit 1.0 for w component + tmpVec4.set(vec.x, vec.y, vec.z, 1.0); + + //transform into clip space + tmpVec4.transformMat4(this.combined); + + //now into NDC + tmpVec4.x = tmpVec4.x / tmpVec4.w; + tmpVec4.y = tmpVec4.y / tmpVec4.w; + tmpVec4.z = tmpVec4.z / tmpVec4.w; + + //and finally into window coordinates + 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; + + //if the out vector has a fourth component, we also store (1/clip.w) + //same idea as gl_FragCoord.w + if (out.w === 0 || out.w) + out.w = 1 / tmpVec4.w; + + return out; + }, + + unproject: function(vec, out) { + if (!out) + out = new Vector3(); + + var viewport = tmpVec4.set(0, 0, this.viewportWidth, this.viewportHeight); + return out.copy(vec).unproject(viewport, this.invProjectionView); + }, + + getPickRay: function(x, y) { + var origin = this.ray.origin.set(x, y, 0), + direction = this.ray.direction.set(x, y, 1), + viewport = tmpVec4.set(0, 0, this.viewportWidth, this.viewportHeight), + mtx = this.invProjectionView; + + origin.unproject(viewport, mtx); + direction.unproject(viewport, mtx); + + direction.sub(origin).normalize(); + return this.ray; + }, + + update: function() { + //left empty for subclasses + } +}); + +Camera3D.FAR_RANGE = 1.0; +Camera3D.NEAR_RANGE = 0.0; + +module.exports = Camera3D; diff --git a/v3/src/camera3d/OrthographicCamera.js b/v3/src/camera3d/OrthographicCamera.js new file mode 100644 index 000000000..e0eedd195 --- /dev/null +++ b/v3/src/camera3d/OrthographicCamera.js @@ -0,0 +1,82 @@ +var Class = require('../utils/Class'); + +var Vector3 = require('./vecmath/Vector3'); +var Vector4 = require('./vecmath/Vector4'); +var Matrix4 = require('./vecmath/Matrix4'); + +var Camera = require('./Camera3D'); + +var tmpVec3 = new Vector3(); + +var OrthographicCamera = new Class({ + + Extends: Camera, + + zoom: { + + set: function(v) { + if (typeof v !== 'number') + throw new Error("zoom must be a number"); + this._zoom = v; + }, + + get: function() { + return this._zoom; + } + }, + + initialize: function(viewportWidth, viewportHeight) { + Camera.call(this); + this.viewportWidth = viewportWidth||0; + this.viewportHeight = viewportHeight||0; + + this._zoom = 1.0; + this.near = 0; + this.update(); + }, + + setToOrtho: function(yDown, viewportWidth, viewportHeight) { + var zoom = this.zoom; + viewportWidth = typeof viewportWidth === "number" ? viewportWidth : this.viewportWidth; + viewportHeight = typeof viewportHeight === "number" ? viewportHeight : this.viewportHeight; + + this.up.set(0, yDown ? -1 : 1, 0); + this.direction.set(0, 0, yDown ? 1 : -1); + this.position.set(zoom * viewportWidth / 2, zoom * viewportHeight / 2, 0); + + this.viewportWidth = viewportWidth; + this.viewportHeight = viewportHeight; + this.update(); + }, + + update: function() { + //TODO: support x/y offset + var w = this.viewportWidth, + h = this.viewportHeight, + near = Math.abs(this.near), + far = Math.abs(this.far), + zoom = this.zoom; + + if (w===0||h===0) { + //What to do here... hmm ? + return; + } + + this.projection.ortho( + zoom * -w / 2, zoom * w / 2, + zoom * -h / 2, zoom * h / 2, + near, far); + + //build the view matrix + tmpVec3.copy(this.position).add(this.direction); + this.view.lookAt(this.position, tmpVec3, this.up); + + //projection * view matrix + this.combined.copy(this.projection).mul(this.view); + + //invert combined matrix, used for unproject + this.invProjectionView.copy(this.combined).invert(); + } +}); + +module.exports = OrthographicCamera; \ No newline at end of file diff --git a/v3/src/camera3d/PerspectiveCamera.js b/v3/src/camera3d/PerspectiveCamera.js new file mode 100644 index 000000000..11861f3bb --- /dev/null +++ b/v3/src/camera3d/PerspectiveCamera.js @@ -0,0 +1,136 @@ +var Class = require('../utils/Class'); + +var Matrix4 = require('./vecmath/Matrix4'); +var Vector2 = require('./vecmath/Vector2'); +var Vector3 = require('./vecmath/Vector3'); +var Camera = require('./Camera3D'); + +var tmpVec3 = new Vector3(); + +var PerspectiveCamera = new Class({ + + Extends: Camera, + + //fov in RADIANS! + initialize: function(fieldOfView, viewportWidth, viewportHeight) { + Camera.call(this); + this.viewportWidth = viewportWidth; + this.viewportHeight = viewportHeight; + + this.fieldOfView = fieldOfView; + this.update(); + }, + + update: function() { + var aspect = this.viewportWidth / this.viewportHeight; + + //create a perspective matrix for our camera + this.projection.perspective(this.fieldOfView, aspect, + Math.abs(this.near), Math.abs(this.far)); + + //build the view matrix + tmpVec3.copy(this.position).add(this.direction); + this.view.lookAt(this.position, tmpVec3, this.up); + + //projection * view matrix + this.combined.copy(this.projection).mul(this.view); + + //invert combined matrix, used for unproject + this.invProjectionView.copy(this.combined).invert(); + } +}); + +module.exports = PerspectiveCamera; + + + +/* +TODO: Billboarding should be moved to a separate module. + + updateBillboardMatrix: function() { + if (!dirvec) { + dirvec = new Vector3(); + rightvec = new Vector3(); + billboardMatrix = new Matrix4(); + } + + + var dir = dirvec.set(this.direction).negate(); + + // Better view-aligned billboards might use this: + // var dir = tmp.set(camera.position).sub(p).normalize(); + + var right = rightvec.set(this.up).cross(dir).normalize(); + var up = tmpVec3.set(dir).cross(right).normalize(); + + var out = billboardMatrix.val; + out[0] = right.x; + out[1] = right.y; + out[2] = right.z; + out[3] = 0; + out[4] = up.x; + out[5] = up.y; + out[6] = up.z; + out[7] = 0; + out[8] = dir.x; + out[9] = dir.y; + out[10] = dir.z; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + + this.billboardMatrixDirty = false; + }, + + + * This is a utility function for canvas 3D rendering, + * which determines the "point size" of a camera-facing + * sprite billboard given its 3D world position + * (origin at center of sprite) and its world width + * and height in x/y. + * + * We place into the output Vector2 the scaled width + * and height. If no `out` is specified, a new Vector2 + * will be created for convenience (this should be avoided + * in tight loops). + * + * @param {Vector3} vec the position of the 3D sprite + * @param {Vector2} size the x and y dimensions of the sprite + * @param {Vector2} out the result, scaled x and y dimensions in 3D space + * @return {Vector2} returns the out parameter, or a new Vector2 if none was given + + getPointSize: function(vec, size, out) { + //TODO: optimize this with a simple distance calculation: + //https://developer.valvesoftware.com/wiki/Field_of_View + + if (!out) + out = new Vector2(); + + if (this.billboardMatrixDirty) + this.updateBillboardMatrix(); + + var tmp = tmpVec3; + + var dx = size.x/2; + var dy = size.y/2; + + tmp.set(-dx, -dy, 0).transformMat4(billboardMatrix).add(vec); + this.project(tmp, tmp); + + var tlx = tmp.x; + var tly = tmp.y; + + tmp.set(dx, dy, 0).transformMat4(billboardMatrix).add(vec); + this.project(tmp, tmp); + + var brx = tmp.x; + var bry = tmp.y; + + var w = Math.abs(brx - tlx); + var h = Math.abs(bry - tly); + return out.set(w, h); + }, + +*/ \ No newline at end of file diff --git a/v3/src/camera3d/index.js b/v3/src/camera3d/index.js new file mode 100644 index 000000000..8542576cb --- /dev/null +++ b/v3/src/camera3d/index.js @@ -0,0 +1,6 @@ +module.exports = { + vecutil: require('./vecutil'), + Camera3D: require('./Camera3D'), + PerspectiveCamera: require('./PerspectiveCamera'), + OrthographicCamera: require('./OrthographicCamera') +}; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/Matrix3.js b/v3/src/camera3d/vecmath/Matrix3.js new file mode 100644 index 000000000..1b98450c4 --- /dev/null +++ b/v3/src/camera3d/vecmath/Matrix3.js @@ -0,0 +1,312 @@ +var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; + +function Matrix3(m) { + this.val = new ARRAY_TYPE(9); + + if (m) { //assume Matrix3 with val + this.copy(m); + } else { //default to identity + this.idt(); + } +} + +var mat3 = Matrix3.prototype; + +mat3.clone = function() { + return new Matrix3(this); +}; + +mat3.set = function(otherMat) { + return this.copy(otherMat); +}; + +mat3.copy = function(otherMat) { + var out = this.val, + a = otherMat.val; + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return this; +}; + +mat3.fromMat4 = function(m) { + var a = m.val, + out = this.val; + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[4]; + out[4] = a[5]; + out[5] = a[6]; + out[6] = a[8]; + out[7] = a[9]; + out[8] = a[10]; + return this; +}; + +mat3.fromArray = function(a) { + var out = this.val; + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return this; +}; + +mat3.identity = function() { + var out = this.val; + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 1; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return this; +}; + +mat3.transpose = function() { + var a = this.val, + a01 = a[1], + a02 = a[2], + a12 = a[5]; + a[1] = a[3]; + a[2] = a[6]; + a[3] = a01; + a[5] = a[7]; + a[6] = a02; + a[7] = a12; + return this; +}; + +mat3.invert = function() { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], + a10 = a[3], a11 = a[4], a12 = a[5], + a20 = a[6], a21 = a[7], a22 = a[8], + + b01 = a22 * a11 - a12 * a21, + b11 = -a22 * a10 + a12 * a20, + b21 = a21 * a10 - a11 * a20, + + // Calculate the determinant + det = a00 * b01 + a01 * b11 + a02 * b21; + + if (!det) { + return null; + } + det = 1.0 / det; + + a[0] = b01 * det; + a[1] = (-a22 * a01 + a02 * a21) * det; + a[2] = (a12 * a01 - a02 * a11) * det; + a[3] = b11 * det; + a[4] = (a22 * a00 - a02 * a20) * det; + a[5] = (-a12 * a00 + a02 * a10) * det; + a[6] = b21 * det; + a[7] = (-a21 * a00 + a01 * a20) * det; + a[8] = (a11 * a00 - a01 * a10) * det; + return this; +}; + +mat3.adjoint = function() { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], + a10 = a[3], a11 = a[4], a12 = a[5], + a20 = a[6], a21 = a[7], a22 = a[8]; + + a[0] = (a11 * a22 - a12 * a21); + a[1] = (a02 * a21 - a01 * a22); + a[2] = (a01 * a12 - a02 * a11); + a[3] = (a12 * a20 - a10 * a22); + a[4] = (a00 * a22 - a02 * a20); + a[5] = (a02 * a10 - a00 * a12); + a[6] = (a10 * a21 - a11 * a20); + a[7] = (a01 * a20 - a00 * a21); + a[8] = (a00 * a11 - a01 * a10); + return this; +}; + +mat3.determinant = function() { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], + a10 = a[3], a11 = a[4], a12 = a[5], + a20 = a[6], a21 = a[7], a22 = a[8]; + + return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20); +}; + +mat3.multiply = function(otherMat) { + var a = this.val, + b = otherMat.val, + a00 = a[0], a01 = a[1], a02 = a[2], + a10 = a[3], a11 = a[4], a12 = a[5], + a20 = a[6], a21 = a[7], a22 = a[8], + + b00 = b[0], b01 = b[1], b02 = b[2], + b10 = b[3], b11 = b[4], b12 = b[5], + b20 = b[6], b21 = b[7], b22 = b[8]; + + a[0] = b00 * a00 + b01 * a10 + b02 * a20; + a[1] = b00 * a01 + b01 * a11 + b02 * a21; + a[2] = b00 * a02 + b01 * a12 + b02 * a22; + + a[3] = b10 * a00 + b11 * a10 + b12 * a20; + a[4] = b10 * a01 + b11 * a11 + b12 * a21; + a[5] = b10 * a02 + b11 * a12 + b12 * a22; + + a[6] = b20 * a00 + b21 * a10 + b22 * a20; + a[7] = b20 * a01 + b21 * a11 + b22 * a21; + a[8] = b20 * a02 + b21 * a12 + b22 * a22; + return this; +}; + +mat3.translate = function(v) { + var a = this.val, + x = v.x, y = v.y; + a[6] = x * a[0] + y * a[3] + a[6]; + a[7] = x * a[1] + y * a[4] + a[7]; + a[8] = x * a[2] + y * a[5] + a[8]; + return this; +}; + +mat3.rotate = function(rad) { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], + a10 = a[3], a11 = a[4], a12 = a[5], + + s = Math.sin(rad), + c = Math.cos(rad); + + a[0] = c * a00 + s * a10; + a[1] = c * a01 + s * a11; + a[2] = c * a02 + s * a12; + + a[3] = c * a10 - s * a00; + a[4] = c * a11 - s * a01; + a[5] = c * a12 - s * a02; + return this; +}; + +mat3.scale = function(v) { + var a = this.val, + x = v.x, + y = v.y; + + a[0] = x * a[0]; + a[1] = x * a[1]; + a[2] = x * a[2]; + + a[3] = y * a[3]; + a[4] = y * a[4]; + a[5] = y * a[5]; + return this; +}; + +mat3.fromQuat = function(q) { + var x = q.x, y = q.y, z = q.z, w = q.w, + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + xy = x * y2, + xz = x * z2, + yy = y * y2, + yz = y * z2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2, + + out = this.val; + + out[0] = 1 - (yy + zz); + out[3] = xy + wz; + out[6] = xz - wy; + + out[1] = xy - wz; + out[4] = 1 - (xx + zz); + out[7] = yz + wx; + + out[2] = xz + wy; + out[5] = yz - wx; + out[8] = 1 - (xx + yy); + return this; +}; + +mat3.normalFromMat4 = function(m) { + var a = m.val, + out = this.val, + + a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + // Calculate the determinant + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + + out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + + out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + return this; +}; + +mat3.mul = mat3.multiply; + +mat3.idt = mat3.identity; + +//This is handy for Pool utilities, to "reset" a +//shared object to its default state +mat3.reset = mat3.idt; + +mat3.toString = function() { + var a = this.val; + return 'Matrix3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + + a[3] + ', ' + a[4] + ', ' + a[5] + ', ' + + a[6] + ', ' + a[7] + ', ' + a[8] + ')'; +}; + +mat3.str = mat3.toString; + +module.exports = Matrix3; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/Matrix4.js b/v3/src/camera3d/vecmath/Matrix4.js new file mode 100644 index 000000000..e69098ac5 --- /dev/null +++ b/v3/src/camera3d/vecmath/Matrix4.js @@ -0,0 +1,690 @@ +var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; +var EPSILON = 0.000001; + +function Matrix4(m) { + this.val = new ARRAY_TYPE(16); + + if (m) { //assume Matrix4 with val + this.copy(m); + } else { //default to identity + this.idt(); + } +} + +var mat4 = Matrix4.prototype; + +mat4.clone = function() { + return new Matrix4(this); +}; + +mat4.set = function(otherMat) { + return this.copy(otherMat); +}; + +mat4.copy = function(otherMat) { + var out = this.val, + a = otherMat.val; + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + out[9] = a[9]; + out[10] = a[10]; + out[11] = a[11]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + return this; +}; + +mat4.fromArray = function(a) { + var out = this.val; + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + out[9] = a[9]; + out[10] = a[10]; + out[11] = a[11]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + return this; +}; + +mat4.identity = function() { + var out = this.val; + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = 1; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 1; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return this; +}; + +mat4.transpose = function() { + var a = this.val, + a01 = a[1], a02 = a[2], a03 = a[3], + a12 = a[6], a13 = a[7], + a23 = a[11]; + + a[1] = a[4]; + a[2] = a[8]; + a[3] = a[12]; + a[4] = a01; + a[6] = a[9]; + a[7] = a[13]; + a[8] = a02; + a[9] = a12; + a[11] = a[14]; + a[12] = a03; + a[13] = a13; + a[14] = a23; + return this; +}; + +mat4.invert = function() { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + // Calculate the determinant + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + a[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + a[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + a[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + a[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + a[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + a[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + a[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + a[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + a[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + a[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + a[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + a[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + a[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + a[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + a[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + a[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + return this; +}; + +mat4.adjoint = function() { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; + + a[0] = (a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22)); + a[1] = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)); + a[2] = (a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12)); + a[3] = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)); + a[4] = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)); + a[5] = (a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22)); + a[6] = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)); + a[7] = (a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12)); + a[8] = (a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21)); + a[9] = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)); + a[10] = (a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11)); + a[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)); + a[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)); + a[13] = (a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21)); + a[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)); + a[15] = (a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11)); + return this; +}; + +mat4.determinant = function () { + var a = this.val, + a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32; + + // Calculate the determinant + return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; +}; + +mat4.multiply = function(otherMat) { + var a = this.val, + b = otherMat.val, + a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; + + // Cache only the current line of the second matrix + var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; + a[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + a[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + a[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + a[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; + a[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + a[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + a[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + a[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; + a[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + a[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + a[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + a[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; + a[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + a[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + a[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + a[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + return this; +}; + +mat4.translate = function(v) { + var x = v.x, y = v.y, z = v.z, + a = this.val; + a[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; + a[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; + a[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; + a[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; + return this; +}; + +mat4.scale = function(v) { + var x = v.x, y = v.y, z = v.z, a = this.val; + + a[0] = a[0] * x; + a[1] = a[1] * x; + a[2] = a[2] * x; + a[3] = a[3] * x; + a[4] = a[4] * y; + a[5] = a[5] * y; + a[6] = a[6] * y; + a[7] = a[7] * y; + a[8] = a[8] * z; + a[9] = a[9] * z; + a[10] = a[10] * z; + a[11] = a[11] * z; + a[12] = a[12]; + a[13] = a[13]; + a[14] = a[14]; + a[15] = a[15]; + return this; +}; + +mat4.rotate = function (rad, axis) { + var a = this.val, + x = axis.x, y = axis.y, z = axis.z, + len = Math.sqrt(x * x + y * y + z * z), + s, c, t, + a00, a01, a02, a03, + a10, a11, a12, a13, + a20, a21, a22, a23, + b00, b01, b02, + b10, b11, b12, + b20, b21, b22; + + if (Math.abs(len) < EPSILON) { return null; } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + + s = Math.sin(rad); + c = Math.cos(rad); + t = 1 - c; + + a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; + a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; + a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; + + // Construct the elements of the rotation matrix + b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s; + b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s; + b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c; + + // Perform rotation-specific matrix multiplication + a[0] = a00 * b00 + a10 * b01 + a20 * b02; + a[1] = a01 * b00 + a11 * b01 + a21 * b02; + a[2] = a02 * b00 + a12 * b01 + a22 * b02; + a[3] = a03 * b00 + a13 * b01 + a23 * b02; + a[4] = a00 * b10 + a10 * b11 + a20 * b12; + a[5] = a01 * b10 + a11 * b11 + a21 * b12; + a[6] = a02 * b10 + a12 * b11 + a22 * b12; + a[7] = a03 * b10 + a13 * b11 + a23 * b12; + a[8] = a00 * b20 + a10 * b21 + a20 * b22; + a[9] = a01 * b20 + a11 * b21 + a21 * b22; + a[10] = a02 * b20 + a12 * b21 + a22 * b22; + a[11] = a03 * b20 + a13 * b21 + a23 * b22; + return this; +}; + +mat4.rotateX = function(rad) { + var a = this.val, + s = Math.sin(rad), + c = Math.cos(rad), + a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7], + a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + + // Perform axis-specific matrix multiplication + a[4] = a10 * c + a20 * s; + a[5] = a11 * c + a21 * s; + a[6] = a12 * c + a22 * s; + a[7] = a13 * c + a23 * s; + a[8] = a20 * c - a10 * s; + a[9] = a21 * c - a11 * s; + a[10] = a22 * c - a12 * s; + a[11] = a23 * c - a13 * s; + return this; +}; + +mat4.rotateY = function(rad) { + var a = this.val, + s = Math.sin(rad), + c = Math.cos(rad), + a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3], + a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + + // Perform axis-specific matrix multiplication + a[0] = a00 * c - a20 * s; + a[1] = a01 * c - a21 * s; + a[2] = a02 * c - a22 * s; + a[3] = a03 * c - a23 * s; + a[8] = a00 * s + a20 * c; + a[9] = a01 * s + a21 * c; + a[10] = a02 * s + a22 * c; + a[11] = a03 * s + a23 * c; + return this; +}; + +mat4.rotateZ = function (rad) { + var a = this.val, + s = Math.sin(rad), + c = Math.cos(rad), + a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3], + a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + + // Perform axis-specific matrix multiplication + a[0] = a00 * c + a10 * s; + a[1] = a01 * c + a11 * s; + a[2] = a02 * c + a12 * s; + a[3] = a03 * c + a13 * s; + a[4] = a10 * c - a00 * s; + a[5] = a11 * c - a01 * s; + a[6] = a12 * c - a02 * s; + a[7] = a13 * c - a03 * s; + return this; +}; + +mat4.fromRotationTranslation = function (q, v) { + // Quaternion math + var out = this.val, + x = q.x, y = q.y, z = q.z, w = q.w, + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + xy = x * y2, + xz = x * z2, + yy = y * y2, + yz = y * z2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2; + + out[0] = 1 - (yy + zz); + out[1] = xy + wz; + out[2] = xz - wy; + out[3] = 0; + out[4] = xy - wz; + out[5] = 1 - (xx + zz); + out[6] = yz + wx; + out[7] = 0; + out[8] = xz + wy; + out[9] = yz - wx; + out[10] = 1 - (xx + yy); + out[11] = 0; + out[12] = v.x; + out[13] = v.y; + out[14] = v.z; + out[15] = 1; + return this; +}; + +mat4.fromQuat = function (q) { + var out = this.val, + x = q.x, y = q.y, z = q.z, w = q.w, + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + xy = x * y2, + xz = x * z2, + yy = y * y2, + yz = y * z2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2; + + out[0] = 1 - (yy + zz); + out[1] = xy + wz; + out[2] = xz - wy; + out[3] = 0; + + out[4] = xy - wz; + out[5] = 1 - (xx + zz); + out[6] = yz + wx; + out[7] = 0; + + out[8] = xz + wy; + out[9] = yz - wx; + out[10] = 1 - (xx + yy); + out[11] = 0; + + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + + return this; +}; + + +/** + * Generates a frustum matrix with the given bounds + * + * @param {Number} left Left bound of the frustum + * @param {Number} right Right bound of the frustum + * @param {Number} bottom Bottom bound of the frustum + * @param {Number} top Top bound of the frustum + * @param {Number} near Near bound of the frustum + * @param {Number} far Far bound of the frustum + * @returns {Matrix4} this for chaining + */ +mat4.frustum = function (left, right, bottom, top, near, far) { + var out = this.val, + rl = 1 / (right - left), + tb = 1 / (top - bottom), + nf = 1 / (near - far); + out[0] = (near * 2) * rl; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = (near * 2) * tb; + out[6] = 0; + out[7] = 0; + out[8] = (right + left) * rl; + out[9] = (top + bottom) * tb; + out[10] = (far + near) * nf; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[14] = (far * near * 2) * nf; + out[15] = 0; + return this; +}; + + +/** + * Generates a perspective projection matrix with the given bounds + * + * @param {number} fovy Vertical field of view in radians + * @param {number} aspect Aspect ratio. typically viewport width/height + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum + * @returns {Matrix4} this for chaining + */ +mat4.perspective = function (fovy, aspect, near, far) { + var out = this.val, + f = 1.0 / Math.tan(fovy / 2), + nf = 1 / (near - far); + out[0] = f / aspect; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = f; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = (far + near) * nf; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[14] = (2 * far * near) * nf; + out[15] = 0; + return this; +}; + +/** + * Generates a orthogonal projection matrix with the given bounds + * + * @param {number} left Left bound of the frustum + * @param {number} right Right bound of the frustum + * @param {number} bottom Bottom bound of the frustum + * @param {number} top Top bound of the frustum + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum + * @returns {Matrix4} this for chaining + */ +mat4.ortho = function (left, right, bottom, top, near, far) { + var out = this.val, + lr = left-right, + bt = bottom-top, + nf = near-far; + + //avoid division by zero + lr = lr===0 ? lr : 1/lr; + bt = bt===0 ? bt : 1/bt; + nf = nf===0 ? nf : 1/nf; + + out[0] = -2 * lr; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = -2 * bt; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 2 * nf; + out[11] = 0; + out[12] = (left + right) * lr; + out[13] = (top + bottom) * bt; + out[14] = (far + near) * nf; + out[15] = 1; + return this; +}; + +/** + * Generates a look-at matrix with the given eye position, focal point, and up axis + * + * @param {Vector3} eye Position of the viewer + * @param {Vector3} center Point the viewer is looking at + * @param {Vector3} up vec3 pointing up + * @returns {Matrix4} this for chaining + */ +mat4.lookAt = function (eye, center, up) { + var out = this.val, + + x0, x1, x2, y0, y1, y2, z0, z1, z2, len, + eyex = eye.x, + eyey = eye.y, + eyez = eye.z, + upx = up.x, + upy = up.y, + upz = up.z, + centerx = center.x, + centery = center.y, + centerz = center.z; + + if (Math.abs(eyex - centerx) < EPSILON && + Math.abs(eyey - centery) < EPSILON && + Math.abs(eyez - centerz) < EPSILON) { + return this.identity(); + } + + z0 = eyex - centerx; + z1 = eyey - centery; + z2 = eyez - centerz; + + len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); + z0 *= len; + z1 *= len; + z2 *= len; + + x0 = upy * z2 - upz * z1; + x1 = upz * z0 - upx * z2; + x2 = upx * z1 - upy * z0; + len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); + if (!len) { + x0 = 0; + x1 = 0; + x2 = 0; + } else { + len = 1 / len; + x0 *= len; + x1 *= len; + x2 *= len; + } + + y0 = z1 * x2 - z2 * x1; + y1 = z2 * x0 - z0 * x2; + y2 = z0 * x1 - z1 * x0; + + len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); + if (!len) { + y0 = 0; + y1 = 0; + y2 = 0; + } else { + len = 1 / len; + y0 *= len; + y1 *= len; + y2 *= len; + } + + out[0] = x0; + out[1] = y0; + out[2] = z0; + out[3] = 0; + out[4] = x1; + out[5] = y1; + out[6] = z1; + out[7] = 0; + out[8] = x2; + out[9] = y2; + out[10] = z2; + out[11] = 0; + out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); + out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); + out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); + out[15] = 1; + + return this; +}; + + +mat4.mul = mat4.multiply; + +mat4.idt = mat4.identity; + +//This is handy for Pool utilities, to "reset" a +//shared object to its default state +mat4.reset = mat4.idt; + +mat4.toString = function () { + var a = this.val; + return 'Matrix4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ', ' + + a[4] + ', ' + a[5] + ', ' + a[6] + ', ' + a[7] + ', ' + + a[8] + ', ' + a[9] + ', ' + a[10] + ', ' + a[11] + ', ' + + a[12] + ', ' + a[13] + ', ' + a[14] + ', ' + a[15] + ')'; +}; + +mat4.str = mat4.toString; + +module.exports = Matrix4; diff --git a/v3/src/camera3d/vecmath/Quaternion.js b/v3/src/camera3d/vecmath/Quaternion.js new file mode 100644 index 000000000..e1df8df90 --- /dev/null +++ b/v3/src/camera3d/vecmath/Quaternion.js @@ -0,0 +1,281 @@ +var Vector3 = require('./Vector3'); +var Matrix3 = require('./Matrix3'); +var common = require('./common'); + +//some shared 'private' arrays +var s_iNext = (typeof Int8Array !== 'undefined' ? new Int8Array([1,2,0]) : [1,2,0]); +var tmp = (typeof Float32Array !== 'undefined' ? new Float32Array([0,0,0]) : [0,0,0]); + +var xUnitVec3 = new Vector3(1, 0, 0); +var yUnitVec3 = new Vector3(0, 1, 0); +var tmpvec = new Vector3(); + +var tmpMat3 = new Matrix3(); + +function Quaternion(x, y, z, w) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + this.z = x.z||0; + this.w = x.w||0; + } else { + this.x = x||0; + this.y = y||0; + this.z = z||0; + this.w = w||0; + } +} + +var quat = Quaternion.prototype; + +//mixin common functions +for (var k in common) { + quat[k] = common[k]; +} + +quat.rotationTo = function(a, b) { + var dot = a.x * b.x + a.y * b.y + a.z * b.z; //a.dot(b) + if (dot < -0.999999) { + if (tmpvec.copy(xUnitVec3).cross(a).len() < 0.000001) + tmpvec.copy(yUnitVec3).cross(a); + + tmpvec.normalize(); + return this.setAxisAngle(tmpvec, Math.PI); + } else if (dot > 0.999999) { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + return this; + } else { + tmpvec.copy(a).cross(b); + this.x = tmpvec.x; + this.y = tmpvec.y; + this.z = tmpvec.z; + this.w = 1 + dot; + return this.normalize(); + } +}; + +quat.setAxes = function(view, right, up) { + var m = tmpMat3.val; + m[0] = right.x; + m[3] = right.y; + m[6] = right.z; + + m[1] = up.x; + m[4] = up.y; + m[7] = up.z; + + m[2] = -view.x; + m[5] = -view.y; + m[8] = -view.z; + + return this.fromMat3(tmpMat3).normalize(); +}; + +quat.identity = function() { + this.x = this.y = this.z = 0; + this.w = 1; + return this; +}; + +quat.setAxisAngle = function(axis, rad) { + rad = rad * 0.5; + var s = Math.sin(rad); + this.x = s * axis.x; + this.y = s * axis.y; + this.z = s * axis.z; + this.w = Math.cos(rad); + return this; +}; + +quat.multiply = function(b) { + var ax = this.x, ay = this.y, az = this.z, aw = this.w, + bx = b.x, by = b.y, bz = b.z, bw = b.w; + + this.x = ax * bw + aw * bx + ay * bz - az * by; + this.y = ay * bw + aw * by + az * bx - ax * bz; + this.z = az * bw + aw * bz + ax * by - ay * bx; + this.w = aw * bw - ax * bx - ay * by - az * bz; + return this; +}; + +quat.slerp = function (b, t) { + // benchmarks: + // http://jsperf.com/quaternion-slerp-implementations + + var ax = this.x, ay = this.y, az = this.y, aw = this.y, + bx = b.x, by = b.y, bz = b.z, bw = b.w; + + var omega, cosom, sinom, scale0, scale1; + + // calc cosine + cosom = ax * bx + ay * by + az * bz + aw * bw; + // adjust signs (if necessary) + if ( cosom < 0.0 ) { + cosom = -cosom; + bx = - bx; + by = - by; + bz = - bz; + bw = - bw; + } + // calculate coefficients + if ( (1.0 - cosom) > 0.000001 ) { + // standard case (slerp) + omega = Math.acos(cosom); + sinom = Math.sin(omega); + scale0 = Math.sin((1.0 - t) * omega) / sinom; + scale1 = Math.sin(t * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0 - t; + scale1 = t; + } + // calculate final values + this.x = scale0 * ax + scale1 * bx; + this.y = scale0 * ay + scale1 * by; + this.z = scale0 * az + scale1 * bz; + this.w = scale0 * aw + scale1 * bw; + return this; +}; + +quat.invert = function() { + var a0 = this.x, a1 = this.y, a2 = this.z, a3 = this.w, + dot = a0*a0 + a1*a1 + a2*a2 + a3*a3, + invDot = dot ? 1.0/dot : 0; + + // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0 + + this.x = -a0*invDot; + this.y = -a1*invDot; + this.z = -a2*invDot; + this.w = a3*invDot; + return this; +}; + +quat.conjugate = function() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + return this; +}; + +quat.rotateX = function (rad) { + rad *= 0.5; + + var ax = this.x, ay = this.y, az = this.z, aw = this.w, + bx = Math.sin(rad), bw = Math.cos(rad); + + this.x = ax * bw + aw * bx; + this.y = ay * bw + az * bx; + this.z = az * bw - ay * bx; + this.w = aw * bw - ax * bx; + return this; +}; + +quat.rotateY = function (rad) { + rad *= 0.5; + + var ax = this.x, ay = this.y, az = this.z, aw = this.w, + by = Math.sin(rad), bw = Math.cos(rad); + + this.x = ax * bw - az * by; + this.y = ay * bw + aw * by; + this.z = az * bw + ax * by; + this.w = aw * bw - ay * by; + return this; +}; + +quat.rotateZ = function (rad) { + rad *= 0.5; + + var ax = this.x, ay = this.y, az = this.z, aw = this.w, + bz = Math.sin(rad), bw = Math.cos(rad); + + this.x = ax * bw + ay * bz; + this.y = ay * bw - ax * bz; + this.z = az * bw + aw * bz; + this.w = aw * bw - az * bz; + return this; +}; + +quat.calculateW = function () { + var x = this.x, y = this.y, z = this.z; + + this.x = x; + this.y = y; + this.z = z; + this.w = -Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z)); + return this; +}; + +quat.fromMat3 = function(mat) { + // benchmarks: + // http://jsperf.com/typed-array-access-speed + // http://jsperf.com/conversion-of-3x3-matrix-to-quaternion + + // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes + // article "Quaternion Calculus and Fast Animation". + var m = mat.val, + fTrace = m[0] + m[4] + m[8]; + var fRoot; + + if ( fTrace > 0.0 ) { + // |w| > 1/2, may as well choose w > 1/2 + fRoot = Math.sqrt(fTrace + 1.0); // 2w + this.w = 0.5 * fRoot; + fRoot = 0.5/fRoot; // 1/(4w) + this.x = (m[7]-m[5])*fRoot; + this.y = (m[2]-m[6])*fRoot; + this.z = (m[3]-m[1])*fRoot; + } else { + // |w| <= 1/2 + var i = 0; + if ( m[4] > m[0] ) + i = 1; + if ( m[8] > m[i*3+i] ) + i = 2; + var j = s_iNext[i]; + var k = s_iNext[j]; + + //This isn't quite as clean without array access... + fRoot = Math.sqrt(m[i*3+i]-m[j*3+j]-m[k*3+k] + 1.0); + tmp[i] = 0.5 * fRoot; + + fRoot = 0.5 / fRoot; + tmp[j] = (m[j*3+i] + m[i*3+j]) * fRoot; + tmp[k] = (m[k*3+i] + m[i*3+k]) * fRoot; + + this.x = tmp[0]; + this.y = tmp[1]; + this.z = tmp[2]; + this.w = (m[k*3+j] - m[j*3+k]) * fRoot; + } + + return this; +}; + +quat.idt = quat.identity; + +quat.sub = quat.subtract; + +quat.mul = quat.multiply; + +quat.len = quat.length; + +quat.lenSq = quat.lengthSq; + +//This is handy for Pool utilities, to "reset" a +//shared object to its default state +quat.reset = quat.idt; + + +quat.toString = function() { + return 'Quaternion(' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + ')'; +}; + +quat.str = quat.toString; + +module.exports = Quaternion; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/Vector2.js b/v3/src/camera3d/vecmath/Vector2.js new file mode 100644 index 000000000..adcd312f6 --- /dev/null +++ b/v3/src/camera3d/vecmath/Vector2.js @@ -0,0 +1,201 @@ +function Vector2(x, y) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + } else { + this.x = x||0; + this.y = y||0; + } +} + +//shorthand it for better minification +var vec2 = Vector2.prototype; + +/** + * Returns a new instance of Vector2 with + * this vector's components. + * @return {Vector2} a clone of this vector + */ +vec2.clone = function() { + return new Vector2(this.x, this.y); +}; + +/** + * Copies the x, y components from the specified + * Vector. Any undefined components from `otherVec` + * will default to zero. + * + * @param {otherVec} the other Vector2 to copy + * @return {Vector2} this, for chaining + */ +vec2.copy = function(otherVec) { + this.x = otherVec.x||0; + this.y = otherVec.y||0; + return this; +}; + +/** + * A convenience function to set the components of + * this vector as x and y. Falsy or undefined + * parameters will default to zero. + * + * You can also pass a vector object instead of + * individual components, to copy the object's components. + * + * @param {Number} x the x component + * @param {Number} y the y component + * @return {Vector2} this, for chaining + */ +vec2.set = function(x, y) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + } else { + this.x = x||0; + this.y = y||0; + } + return this; +}; + +vec2.add = function(v) { + this.x += v.x; + this.y += v.y; + return this; +}; + +vec2.subtract = function(v) { + this.x -= v.x; + this.y -= v.y; + return this; +}; + +vec2.multiply = function(v) { + this.x *= v.x; + this.y *= v.y; + return this; +}; + +vec2.scale = function(s) { + this.x *= s; + this.y *= s; + return this; +}; + +vec2.divide = function(v) { + this.x /= v.x; + this.y /= v.y; + return this; +}; + +vec2.negate = function() { + this.x = -this.x; + this.y = -this.y; + return this; +}; + +vec2.distance = function(v) { + var dx = v.x - this.x, + dy = v.y - this.y; + return Math.sqrt(dx*dx + dy*dy); +}; + +vec2.distanceSq = function(v) { + var dx = v.x - this.x, + dy = v.y - this.y; + return dx*dx + dy*dy; +}; + +vec2.length = function() { + var x = this.x, + y = this.y; + return Math.sqrt(x*x + y*y); +}; + +vec2.lengthSq = function() { + var x = this.x, + y = this.y; + return x*x + y*y; +}; + +vec2.normalize = function() { + var x = this.x, + y = this.y; + var len = x*x + y*y; + if (len > 0) { + len = 1 / Math.sqrt(len); + this.x = x*len; + this.y = y*len; + } + return this; +}; + +vec2.dot = function(v) { + return this.x * v.x + this.y * v.y; +}; + +//Unlike Vector3, this returns a scalar +//http://allenchou.net/2013/07/cross-product-of-2d-vectors/ +vec2.cross = function(v) { + return this.x * v.y - this.y * v.x; +}; + +vec2.lerp = function(v, t) { + var ax = this.x, + ay = this.y; + t = t||0; + this.x = ax + t * (v.x - ax); + this.y = ay + t * (v.y - ay); + return this; +}; + +vec2.transformMat3 = function(mat) { + var x = this.x, y = this.y, m = mat.val; + this.x = m[0] * x + m[3] * y + m[6]; + this.y = m[1] * x + m[4] * y + m[7]; + return this; +}; + +vec2.transformMat4 = function(mat) { + var x = this.x, + y = this.y, + m = mat.val; + this.x = m[0] * x + m[4] * y + m[12]; + this.y = m[1] * x + m[5] * y + m[13]; + return this; +}; + +vec2.reset = function() { + this.x = 0; + this.y = 0; + return this; +}; + +vec2.sub = vec2.subtract; + +vec2.mul = vec2.multiply; + +vec2.div = vec2.divide; + +vec2.dist = vec2.distance; + +vec2.distSq = vec2.distanceSq; + +vec2.len = vec2.length; + +vec2.lenSq = vec2.lengthSq; + +vec2.toString = function() { + return 'Vector2(' + this.x + ', ' + this.y + ')'; +}; + +vec2.random = function(scale) { + scale = scale || 1.0; + var r = Math.random() * 2.0 * Math.PI; + this.x = Math.cos(r) * scale; + this.y = Math.sin(r) * scale; + return this; +}; + +vec2.str = vec2.toString; + +module.exports = Vector2; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/Vector3.js b/v3/src/camera3d/vecmath/Vector3.js new file mode 100644 index 000000000..efb29a377 --- /dev/null +++ b/v3/src/camera3d/vecmath/Vector3.js @@ -0,0 +1,286 @@ +function Vector3(x, y, z) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + this.z = x.z||0; + } else { + this.x = x||0; + this.y = y||0; + this.z = z||0; + } +} + +//shorthand it for better minification +var vec3 = Vector3.prototype; + +vec3.clone = function() { + return new Vector3(this.x, this.y, this.z); +}; + +vec3.copy = function(otherVec) { + this.x = otherVec.x; + this.y = otherVec.y; + this.z = otherVec.z; + return this; +}; + +vec3.set = function(x, y, z) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + this.z = x.z||0; + } else { + this.x = x||0; + this.y = y||0; + this.z = z||0; + } + return this; +}; + +vec3.add = function(v) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + return this; +}; + +vec3.subtract = function(v) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + return this; +}; + +vec3.multiply = function(v) { + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + return this; +}; + +vec3.scale = function(s) { + this.x *= s; + this.y *= s; + this.z *= s; + return this; +}; + +vec3.divide = function(v) { + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + return this; +}; + +vec3.negate = function() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + return this; +}; + +vec3.distance = function(v) { + var dx = v.x - this.x, + dy = v.y - this.y, + dz = v.z - this.z; + return Math.sqrt(dx*dx + dy*dy + dz*dz); +}; + +vec3.distanceSq = function(v) { + var dx = v.x - this.x, + dy = v.y - this.y, + dz = v.z - this.z; + return dx*dx + dy*dy + dz*dz; +}; + +vec3.length = function() { + var x = this.x, + y = this.y, + z = this.z; + return Math.sqrt(x*x + y*y + z*z); +}; + +vec3.lengthSq = function() { + var x = this.x, + y = this.y, + z = this.z; + return x*x + y*y + z*z; +}; + +vec3.normalize = function() { + var x = this.x, + y = this.y, + z = this.z; + var len = x*x + y*y + z*z; + if (len > 0) { + len = 1 / Math.sqrt(len); + this.x = x*len; + this.y = y*len; + this.z = z*len; + } + return this; +}; + +vec3.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +}; + +vec3.cross = function(v) { + var ax = this.x, ay = this.y, az = this.z, + bx = v.x, by = v.y, bz = v.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + return this; +}; + +vec3.lerp = function(v, t) { + var ax = this.x, + ay = this.y, + az = this.z; + t = t||0; + this.x = ax + t * (v.x - ax); + this.y = ay + t * (v.y - ay); + this.z = az + t * (v.z - az); + return this; +}; + +vec3.transformMat4 = function(mat) { + var x = this.x, y = this.y, z = this.z, m = mat.val; + this.x = m[0] * x + m[4] * y + m[8] * z + m[12]; + this.y = m[1] * x + m[5] * y + m[9] * z + m[13]; + this.z = m[2] * x + m[6] * y + m[10] * z + m[14]; + return this; +}; + +vec3.transformMat3 = function(mat) { + var x = this.x, y = this.y, z = this.z, m = mat.val; + this.x = x * m[0] + y * m[3] + z * m[6]; + this.y = x * m[1] + y * m[4] + z * m[7]; + this.z = x * m[2] + y * m[5] + z * m[8]; + return this; +}; + +vec3.transformQuat = function(q) { + // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations + var x = this.x, y = this.y, z = this.z, + qx = q.x, qy = q.y, qz = q.z, qw = q.w, + + // calculate quat * vec + ix = qw * x + qy * z - qz * y, + iy = qw * y + qz * x - qx * z, + iz = qw * z + qx * y - qy * x, + iw = -qx * x - qy * y - qz * z; + + // calculate result * inverse quat + this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; + this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; + this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; + return this; +}; + +/** + * Multiplies this Vector3 by the specified matrix, + * applying a W divide. This is useful for projection, + * e.g. unprojecting a 2D point into 3D space. + * + * @method prj + * @param {Matrix4} the 4x4 matrix to multiply with + * @return {Vector3} this object for chaining + */ +vec3.project = function(mat) { + var x = this.x, + y = this.y, + z = this.z, + m = mat.val, + a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3], + a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7], + a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11], + a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[15]; + + var l_w = 1 / (x * a03 + y * a13 + z * a23 + a33); + + this.x = (x * a00 + y * a10 + z * a20 + a30) * l_w; + this.y = (x * a01 + y * a11 + z * a21 + a31) * l_w; + this.z = (x * a02 + y * a12 + z * a22 + a32) * l_w; + return this; +}; + +/** + * Unproject this point from 2D space to 3D space. + * The point should have its x and y properties set to + * 2D screen space, and the z either at 0 (near plane) + * or 1 (far plane). The provided matrix is assumed to already + * be combined, i.e. projection * view * model. + * + * After this operation, this vector's (x, y, z) components will + * represent the unprojected 3D coordinate. + * + * @param {Vector4} viewport screen x, y, width and height in pixels + * @param {Matrix4} invProjectionView combined projection and view matrix + * @return {Vector3} this object, for chaining + */ +vec3.unproject = function(viewport, invProjectionView) { + var viewX = viewport.x, + viewY = viewport.y, + viewWidth = viewport.z, + viewHeight = viewport.w; + + var x = this.x, + y = this.y, + z = this.z; + + x = x - viewX; + y = viewHeight - y - 1; + y = y - viewY; + + this.x = (2 * x) / viewWidth - 1; + this.y = (2 * y) / viewHeight - 1; + this.z = 2 * z - 1; + + return this.project(invProjectionView); +}; + +vec3.random = function(scale) { + scale = scale || 1.0; + + var r = Math.random() * 2.0 * Math.PI; + var z = (Math.random() * 2.0) - 1.0; + var zScale = Math.sqrt(1.0-z*z) * scale; + + this.x = Math.cos(r) * zScale; + this.y = Math.sin(r) * zScale; + this.z = z * scale; + return this; +}; + +vec3.reset = function() { + this.x = 0; + this.y = 0; + this.z = 0; + return this; +}; + + +vec3.sub = vec3.subtract; + +vec3.mul = vec3.multiply; + +vec3.div = vec3.divide; + +vec3.dist = vec3.distance; + +vec3.distSq = vec3.distanceSq; + +vec3.len = vec3.length; + +vec3.lenSq = vec3.lengthSq; + +vec3.toString = function() { + return 'Vector3(' + this.x + ', ' + this.y + ', ' + this.z + ')'; +}; + +vec3.str = vec3.toString; + +module.exports = Vector3; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/Vector4.js b/v3/src/camera3d/vecmath/Vector4.js new file mode 100644 index 000000000..6574381a8 --- /dev/null +++ b/v3/src/camera3d/vecmath/Vector4.js @@ -0,0 +1,138 @@ +var common = require('./common'); + +function Vector4(x, y, z, w) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + this.z = x.z||0; + this.w = x.w||0; + } else { + this.x = x||0; + this.y = y||0; + this.z = z||0; + this.w = w||0; + } +} + +//shorthand it for better minification +var vec4 = Vector4.prototype; + +//mixin common functions +for (var k in common) { + vec4[k] = common[k]; +} + +vec4.clone = function() { + return new Vector4(this.x, this.y, this.z, this.w); +}; + +vec4.multiply = function(v) { + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + this.w *= v.w; + return this; +}; + +vec4.divide = function(v) { + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + this.w /= v.w; + return this; +}; + +vec4.distance = function(v) { + var dx = v.x - this.x, + dy = v.y - this.y, + dz = v.z - this.z, + dw = v.w - this.w; + return Math.sqrt(dx*dx + dy*dy + dz*dz + dw*dw); +}; + +vec4.distanceSq = function(v) { + var dx = v.x - this.x, + dy = v.y - this.y, + dz = v.z - this.z, + dw = v.w - this.w; + return dx*dx + dy*dy + dz*dz + dw*dw; +}; + +vec4.negate = function() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + this.w = -this.w; + return this; +}; + +vec4.transformMat4 = function(mat) { + var m = mat.val, x = this.x, y = this.y, z = this.z, w = this.w; + this.x = m[0] * x + m[4] * y + m[8] * z + m[12] * w; + this.y = m[1] * x + m[5] * y + m[9] * z + m[13] * w; + this.z = m[2] * x + m[6] * y + m[10] * z + m[14] * w; + this.w = m[3] * x + m[7] * y + m[11] * z + m[15] * w; + return this; +}; + +//// TODO: is this really the same as Vector3 ?? +/// Also, what about this: +/// http://molecularmusings.wordpress.com/2013/05/24/a-faster-quaternion-vector-multiplication/ +vec4.transformQuat = function(q) { + // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations + var x = this.x, y = this.y, z = this.z, + qx = q.x, qy = q.y, qz = q.z, qw = q.w, + + // calculate quat * vec + ix = qw * x + qy * z - qz * y, + iy = qw * y + qz * x - qx * z, + iz = qw * z + qx * y - qy * x, + iw = -qx * x - qy * y - qz * z; + + // calculate result * inverse quat + this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; + this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; + this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; + return this; +}; + +vec4.random = function(scale) { + scale = scale || 1.0; + + //Not spherical; should fix this for more uniform distribution + this.x = (Math.random() * 2 - 1) * scale; + this.y = (Math.random() * 2 - 1) * scale; + this.z = (Math.random() * 2 - 1) * scale; + this.w = (Math.random() * 2 - 1) * scale; + return this; +}; + +vec4.reset = function() { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 0; + return this; +}; + +vec4.sub = vec4.subtract; + +vec4.mul = vec4.multiply; + +vec4.div = vec4.divide; + +vec4.dist = vec4.distance; + +vec4.distSq = vec4.distanceSq; + +vec4.len = vec4.length; + +vec4.lenSq = vec4.lengthSq; + +vec4.toString = function() { + return 'Vector4(' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + ')'; +}; + +vec4.str = vec4.toString; + +module.exports = Vector4; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/common.js b/v3/src/camera3d/vecmath/common.js new file mode 100644 index 000000000..6fca187cb --- /dev/null +++ b/v3/src/camera3d/vecmath/common.js @@ -0,0 +1,186 @@ +//common vec4 functions +module.exports = { + +/** + * Copies the x, y, z, w components from the specified + * Vector. Unlike most other operations, this function + * will default undefined components on `otherVec` to zero. + * + * @method copy + * @param {otherVec} the other Vector4 to copy + * @return {Vector} this, for chaining + */ + + +/** + * A convenience function to set the components of + * this vector as x, y, z, w. Falsy or undefined + * parameters will default to zero. + * + * You can also pass a vector object instead of + * individual components, to copy the object's components. + * + * @method set + * @param {Number} x the x component + * @param {Number} y the y component + * @param {Number} z the z component + * @param {Number} w the w component + * @return {Vector2} this, for chaining + */ + +/** + * Adds the components of the other Vector4 to + * this vector. + * + * @method add + * @param {Vector4} otherVec other vector, right operand + * @return {Vector2} this, for chaining + */ + +/** + * Subtracts the components of the other Vector4 + * from this vector. Aliased as `sub()` + * + * @method subtract + * @param {Vector4} otherVec other vector, right operand + * @return {Vector2} this, for chaining + */ + +/** + * Multiplies the components of this Vector4 + * by a scalar amount. + * + * @method scale + * @param {Number} s the scale to multiply by + * @return {Vector4} this, for chaining + */ + +/** + * Returns the magnitude (length) of this vector. + * + * Aliased as `len()` + * + * @method length + * @return {Number} the length of this vector + */ + +/** + * Returns the squared magnitude (length) of this vector. + * + * Aliased as `lenSq()` + * + * @method lengthSq + * @return {Number} the squared length of this vector + */ + +/** + * Normalizes this vector to a unit vector. + * @method normalize + * @return {Vector4} this, for chaining + */ + +/** + * Returns the dot product of this vector + * and the specified Vector4. + * + * @method dot + * @return {Number} the dot product + */ + copy: function(otherVec) { + this.x = otherVec.x||0; + this.y = otherVec.y||0; + this.z = otherVec.z||0; + this.w = otherVec.w||0; + return this; + }, + + set: function(x, y, z, w) { + if (typeof x === "object") { + this.x = x.x||0; + this.y = x.y||0; + this.z = x.z||0; + this.w = x.w||0; + } else { + this.x = x||0; + this.y = y||0; + this.z = z||0; + this.w = w||0; + + } + return this; + }, + + add: function(v) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; + return this; + }, + + subtract: function(v) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; + return this; + }, + + scale: function(s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.w *= s; + return this; + }, + + + length: function() { + var x = this.x, + y = this.y, + z = this.z, + w = this.w; + return Math.sqrt(x*x + y*y + z*z + w*w); + }, + + lengthSq: function() { + var x = this.x, + y = this.y, + z = this.z, + w = this.w; + return x*x + y*y + z*z + w*w; + }, + + normalize: function() { + var x = this.x, + y = this.y, + z = this.z, + w = this.w; + var len = x*x + y*y + z*z + w*w; + if (len > 0) { + len = 1 / Math.sqrt(len); + this.x = x*len; + this.y = y*len; + this.z = z*len; + this.w = w*len; + } + return this; + }, + + dot: function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + }, + + lerp: function(v, t) { + var ax = this.x, + ay = this.y, + az = this.z, + aw = this.w; + t = t||0; + this.x = ax + t * (v.x - ax); + this.y = ay + t * (v.y - ay); + this.z = az + t * (v.z - az); + this.w = aw + t * (v.w - aw); + return this; + } +}; \ No newline at end of file diff --git a/v3/src/camera3d/vecmath/index.js b/v3/src/camera3d/vecmath/index.js new file mode 100644 index 000000000..2edcfc566 --- /dev/null +++ b/v3/src/camera3d/vecmath/index.js @@ -0,0 +1,8 @@ +module.exports = { + Vector2: require('./Vector2'), + Vector3: require('./Vector3'), + Vector4: require('./Vector4'), + Matrix3: require('./Matrix3'), + Matrix4: require('./Matrix4'), + Quaternion: require('./Quaternion') +}; \ No newline at end of file diff --git a/v3/src/camera3d/vecutil.js b/v3/src/camera3d/vecutil.js new file mode 100644 index 000000000..d15db2508 --- /dev/null +++ b/v3/src/camera3d/vecutil.js @@ -0,0 +1,34 @@ + +var Vector3 = require('./vecmath/Vector3'); +var Matrix4 = require('./vecmath/Matrix4'); +var Quaternion = require('./vecmath/Quaternion'); + +var tmpMat4 = new Matrix4(); +var tmpQuat = new Quaternion(); +var tmpVec3 = new Vector3(); + +var util = {}; + +/** + * Rotates a vector in place by axis angle. + * + * This is the same as transforming a point by an + * axis-angle quaternion, but it has higher precision. + * + * @param {Vector3} vec [description] + * @param {Vector3} axis [description] + * @param {float} radians [description] + * @return {Vector3} [description] + */ +util.rotate = function(vec, axis, radians) { + //set the quaternion to our axis angle + tmpQuat.setAxisAngle(axis, radians); + + //create a rotation matrix from the axis angle + tmpMat4.fromRotationTranslation( tmpQuat, tmpVec3.set(0, 0, 0) ); + + //multiply our vector by the rotation matrix + return vec.transformMat4( tmpMat4 ); +}; + +module.exports = util; \ No newline at end of file diff --git a/v3/src/gameobjects/components/Transform.js b/v3/src/gameobjects/components/Transform.js index 329899832..fa81be1e7 100644 --- a/v3/src/gameobjects/components/Transform.js +++ b/v3/src/gameobjects/components/Transform.js @@ -19,6 +19,8 @@ var Transform = { x: 0, y: 0, + z: 0, + w: 0, depth: { @@ -109,13 +111,17 @@ var Transform = { } }, - setPosition: function (x, y) + setPosition: function (x, y, z, w) { if (x === undefined) { x = 0; } if (y === undefined) { y = x; } + if (z === undefined) { z = 0; } + if (w === undefined) { w = 0; } this.x = x; this.y = y; + this.z = z; + this.w = w; return this; }, @@ -149,6 +155,24 @@ var Transform = { return this; }, + setZ: function (value) + { + if (value === undefined) { value = 0; } + + this.z = value; + + return this; + }, + + setW: function (value) + { + if (value === undefined) { value = 0; } + + this.w = value; + + return this; + }, + setDepth: function (value) { if (value === undefined) { value = 0; } diff --git a/v3/src/math/Percent.js b/v3/src/math/Percent.js index 65b94712d..e22e62610 100644 --- a/v3/src/math/Percent.js +++ b/v3/src/math/Percent.js @@ -1,19 +1,36 @@ -var Percent = function (a, b, base) -{ - if (base === undefined) { base = 0; } +// Work out what % value is of the range between min and max. +// If max isn't given then you get the % of value to min. +// You can optionally specify an upperMax, which is a mid-way point in the range +// that represents 100%, after which the % starts to go down to zero again. - if (a > b || base > b) +var Percent = function (value, min, max, upperMax) +{ + if (max === undefined) { max = min + 1; } + + var percentage = (value - min) / (max - min); + + if (percentage > 1) { - return 1; + if (upperMax !== undefined) + { + percentage = ((upperMax - value)) / (upperMax - max); + + if (percentage < 0) + { + percentage = 0; + } + } + else + { + percentage = 1; + } } - else if (a < base || base > a) + else if (percentage < 0) { - return 0; - } - else - { - return (a - base) / b; + percentage = 0; } + + return percentage; }; module.exports = Percent; diff --git a/v3/src/math/Vector2.js b/v3/src/math/Vector2.js new file mode 100644 index 000000000..837b8eb70 --- /dev/null +++ b/v3/src/math/Vector2.js @@ -0,0 +1,221 @@ +// Based on vecmath lib by mattdesl https://github.com/mattdesl/vecmath and gl-matrix + +var Class = require('../utils/Class'); + +var Vector2 = new Class({ + + initialize: + + function Vector2 (x, y) + { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + + this.x = x; + this.y = y; + }, + + clone: function () + { + return new Vector2(this.x, this.y); + }, + + + copy: function (src) + { + this.x = src.x || 0; + this.y = src.y || 0; + + return this; + }, + + set: function (x, y) + { + if (typeof x === 'object') + { + this.x = x.x || 0; + this.y = x.y || 0; + } + else + { + this.x = x; + this.y = y; + } + + return this; + }, + + add: function (src) + { + this.x += src.x; + this.y += src.y; + + return this; + }, + + subtract: function (src) + { + this.x -= src.x; + this.y -= src.y; + + return this; + }, + + multiply: function (src) + { + this.x *= src.x; + this.y *= src.y; + + return this; + }, + + scale: function (value) + { + this.x *= value; + this.y *= value; + + return this; + }, + + divide: function (src) + { + this.x /= src.x; + this.y /= src.y; + + return this; + }, + + negate: function () + { + this.x = -this.x; + this.y = -this.y; + + return this; + }, + + distance: function (src) + { + var dx = src.x - this.x; + var dy = src.y - this.y; + + return Math.sqrt(dx * dx + dy * dy); + }, + + distanceSq: function (src) + { + var dx = src.x - this.x; + var dy = src.y - this.y; + + return dx * dx + dy * dy; + }, + + length: function () + { + var x = this.x; + var y = this.y; + + return Math.sqrt(x * x + y * y); + }, + + lengthSq: function () + { + var x = this.x; + var y = this.y; + + return x * x + y * y; + }, + + normalize: function () + { + var x = this.x; + var y = this.y; + var len = x * x + y * y; + + if (len > 0) + { + len = 1 / Math.sqrt(len); + this.x = x * len; + this.y = y * len; + } + + return this; + }, + + dot: function (src) + { + return this.x * src.x + this.y * src.y; + }, + + cross: function (src) + { + return this.x * src.y - this.y * src.x; + }, + + lerp: function (src, t) + { + if (t === undefined) { t = 0; } + + var ax = this.x; + var ay = this.y; + + this.x = ax + t * (src.x - ax); + this.y = ay + t * (src.y - ay); + + return this; + }, + + transformMat3: function (mat) + { + var x = this.x; + var y = this.y; + var m = mat.val; + + this.x = m[0] * x + m[3] * y + m[6]; + this.y = m[1] * x + m[4] * y + m[7]; + + return this; + }, + + transformMat4: function (mat) + { + var x = this.x; + var y = this.y; + var m = mat.val; + + this.x = m[0] * x + m[4] * y + m[12]; + this.y = m[1] * x + m[5] * y + m[13]; + + return this; + }, + + reset: function () + { + this.x = 0; + this.y = 0; + + return this; + }, + + random: function (scale) + { + if (scale === undefined) { scale = 1; } + + var r = Math.random() * 2 * Math.PI; + + this.x = Math.cos(r) * scale; + this.y = Math.sin(r) * scale; + + return this; + } + +}); + +Vector2.sub = Vector2.subtract; +Vector2.mul = Vector2.multiply; +Vector2.div = Vector2.divide; +Vector2.dist = Vector2.distance; +Vector2.distSq = Vector2.distanceSq; +Vector2.len = Vector2.length; +Vector2.lenSq = Vector2.lengthSq; + +module.exports = Vector2; diff --git a/v3/src/phaser.js b/v3/src/phaser.js index 94ca48d60..e881facc4 100644 --- a/v3/src/phaser.js +++ b/v3/src/phaser.js @@ -19,6 +19,9 @@ var Phaser = { Create: require('./create/'), + Cameras3D: require('./camera3d/'), + VecMath: require('./camera3d/vecmath/'), + DOM: require('./dom/'), Game: require('./boot/Game'),