Created
December 6, 2012 20:56
-
-
Save wellcaffeinated/4228260 to your computer and use it in GitHub Desktop.
Photon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Photon | |
* http://photon.attasi.com | |
* | |
* Licensed under the MIT license. | |
* Copyright 2012 Tom Giannattasio | |
*/ | |
var Photon = { | |
version: '0.0.3', | |
degToRad: function(deg) { | |
return deg * Math.PI / 180; | |
}, | |
radToDeg: function(rad) { | |
return rad * 180 / Math.PI; | |
}, | |
getRotationVector: function(originVector, rotations) { | |
var xVector = originVector.rotate(rotations.x, Line.create([0, 0, 0], [1, 0, 0])); | |
var yVector = xVector.rotate(rotations.y, Line.create([0, 0, 0], [0, 1, 0])); | |
var zVector = yVector.rotate(rotations.z, Line.create([0, 0, 0], [0, 0, 1])); | |
return zVector; | |
}, | |
getTransformString: function() { | |
if(Photon.transformString) { | |
return Photon.transformString; | |
} | |
var transformString; | |
var tests = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform']; | |
var element = document.createElement('div'); | |
for(var i = 0; i < tests.length; i++) { | |
if(element.style[tests[i]] == '') { | |
transformString = tests[i]; | |
} | |
} | |
Photon.transformString = transformString; | |
return transformString; | |
}, | |
// converts transform matrix into a WebKitCSSMatrix object. | |
// multiplies values to avoid whackification | |
buildMatrix: function(faceTransform) { | |
var matrix = new FirminCSSMatrix(faceTransform); | |
matrix.m11 = matrix.m11 * 1e16; | |
matrix.m12 = matrix.m12 * 1e16; | |
matrix.m13 = matrix.m13 * 1e16; | |
matrix.m14 = matrix.m14 * 1e16; | |
matrix.m21 = matrix.m21 * 1e16; | |
matrix.m22 = matrix.m22 * 1e16; | |
matrix.m23 = matrix.m23 * 1e16; | |
matrix.m24 = matrix.m24 * 1e16; | |
matrix.m31 = matrix.m31 * 1e16; | |
matrix.m32 = matrix.m32 * 1e16; | |
matrix.m33 = matrix.m33 * 1e16; | |
matrix.m34 = matrix.m34 * 1e16; | |
matrix.m41 = matrix.m41 * 1e16; | |
matrix.m42 = matrix.m42 * 1e16; | |
matrix.m43 = matrix.m43 * 1e16; | |
matrix.m44 = matrix.m44 * 1e16; | |
return matrix; | |
} | |
}; | |
Photon.Light = function(xVal, yVal, zVal) { | |
this.moveTo(xVal || 0, yVal || 0, zVal || 100); | |
this.calculateVector(); | |
} | |
Photon.Light.prototype = { | |
moveTo: function(x, y, z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
this.calculateVector(); | |
}, | |
// covert the light coordinates into a vector | |
calculateVector: function() { | |
this.magnitude = Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z)); | |
this.vector = $V([this.x / this.magnitude, this.y / this.magnitude, this.z / this.magnitude]); | |
} | |
} | |
Photon.Face = function(element, maxShade, maxTint, isBackfaced) { | |
// set properties | |
this.element = element; | |
this.maxShade = maxShade || .5; | |
this.maxTint = maxTint || 0; | |
this.isBackfaced = isBackfaced || false; | |
// create shader element | |
this.shaderElement = new Photon.ShaderElement(this.element); | |
this.element.insertBefore(this.shaderElement, this.element.firstChild); | |
this.transformString = Photon.getTransformString(); | |
// calculate absolute rotations | |
this.getRotations(); | |
} | |
Photon.Face.prototype = { | |
getRotations: function() { | |
// pull the transform property | |
var faceTransform = window.getComputedStyle(this.element)[this.transformString] || 'matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)'; | |
// convert the transform data into a matrix | |
this.matrix = Photon.buildMatrix(faceTransform); | |
// extract the individual transform values | |
var faceDecomp = this.matrix.decompose(); | |
this.rotations = { | |
x: faceDecomp.rotate.x, | |
y: faceDecomp.rotate.y, | |
z: faceDecomp.rotate.z | |
}; | |
// set the face's vector | |
this.vector = Photon.getRotationVector($V([0, 0, 1]), this.rotations); | |
}, | |
render: function(light, getNewRotations, parentRotations) { | |
if(getNewRotations) { | |
this.getRotations(); | |
} | |
// calculate the absolute vector | |
var fullVector; | |
if(parentRotations) { | |
fullVector = Photon.getRotationVector(this.vector, parentRotations); | |
} else { | |
fullVector = this.vector; | |
} | |
// calculate the anglar distance from the light | |
this.angleFrom = Photon.radToDeg(light.vector.angleFrom(fullVector)); | |
// determine the background color of the shader element | |
var background; | |
// var anglePercentage = this.angleFrom / 180; | |
var anglePercentage = this.isBackfaced ? this.angleFrom / 180 : this.angleFrom / 90; | |
if(this.isBackfaced && anglePercentage > .5) { | |
anglePercentage = 1 - anglePercentage; | |
} | |
var range = Math.abs(this.maxShade + this.maxTint); | |
var rangedPercentage = range * anglePercentage; | |
this.rangedPercentage = rangedPercentage; | |
// determine whether to shade or tint | |
if(rangedPercentage <= this.maxTint) { | |
background = 'rgba(255, 255, 255, ' + Math.abs(this.maxTint - rangedPercentage) + ')'; | |
} else { | |
background = 'rgba(0, 0, 0, ' + Math.abs(rangedPercentage - this.maxTint) + ')'; | |
} | |
// apply the shading | |
this.shaderElement.style.background = background; | |
}, | |
setMaxShade: function(value) { | |
this.maxShade = value; | |
}, | |
setMaxTint: function(value) { | |
this.maxTint = value; | |
} | |
}; | |
// create the element to used for shading and tinting | |
Photon.ShaderElement = function(parent) { | |
var shaderElement = document.createElement('div'); | |
shaderElement.className = 'photon-shader'; | |
shaderElement.style.position = 'absolute'; | |
shaderElement.style.top = '0'; | |
shaderElement.style.left = '0'; | |
shaderElement.style.width = window.getComputedStyle(parent).width; | |
shaderElement.style.height = window.getComputedStyle(parent).height; | |
return shaderElement; | |
} | |
// a group of faces within a single parent object | |
Photon.FaceGroup = function(parent, faces, maxShade, maxTint, isBackfaced) { | |
this.element = parent; | |
this.faces = []; | |
this.transformString = Photon.getTransformString(); | |
var childFaces = faces; | |
for(var i = 0; i < childFaces.length; i++) { | |
this.faces[i] = new Photon.Face(childFaces[i], maxShade, maxTint, isBackfaced); | |
} | |
} | |
Photon.FaceGroup.prototype = { | |
getRotations: function() { | |
var faceTransform = window.getComputedStyle(this.element)[this.transformString] || 'matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)'; | |
this.matrix = Photon.buildMatrix(faceTransform); | |
var faceDecomp = this.matrix.decompose(); | |
this.rotations = { | |
x: faceDecomp.rotate.x, | |
y: faceDecomp.rotate.y, | |
z: faceDecomp.rotate.z | |
}; | |
this.vector = Photon.getRotationVector($V([0, 0, 1]), this.rotations); | |
}, | |
render: function(light, getNewGroupRotations, getNewFaceRotations) { | |
if(getNewGroupRotations) { | |
this.getRotations(); | |
} | |
this.angleFrom = Photon.radToDeg(light.vector.angleFrom(this.vector)); | |
for(var i = 0, length = this.faces.length; i < length; i++) { | |
this.faces[i].render(light, getNewFaceRotations, this.rotations); | |
} | |
}, | |
setMaxShade: function(value) { | |
for(var i = 0; i < this.faces.length; i++) { | |
this.faces[i].setMaxShade(value); | |
} | |
}, | |
setMaxTint: function(value) { | |
for(var i = 0; i < this.faces.length; i++) { | |
this.faces[i].setMaxTint(value); | |
} | |
} | |
}; | |
// === Sylvester === | |
// Vector and Matrix mathematics modules for JavaScript | |
// Copyright (c) 2007 James Coglan | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining | |
// a copy of this software and associated documentation files (the "Software"), | |
// to deal in the Software without restriction, including without limitation | |
// the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
// and/or sell copies of the Software, and to permit persons to whom the | |
// Software is furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included | |
// in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
// DEALINGS IN THE SOFTWARE. | |
var Sylvester = { | |
version: '0.1.3', | |
precision: 1e-6 | |
}; | |
function Vector() {} | |
Vector.prototype = { | |
// Returns the modulus ('length') of the vector | |
modulus: function() { | |
return Math.sqrt(this.dot(this)); | |
}, | |
// Returns a copy of the vector | |
dup: function() { | |
return Vector.create(this.elements); | |
}, | |
// Calls the iterator for each element of the vector in turn | |
each: function(fn) { | |
var n = this.elements.length, k = n, i; | |
do { i = k - n; | |
fn(this.elements[i], i+1); | |
} while (--n); | |
}, | |
// Returns the angle between the vector and the argument (also a vector) | |
angleFrom: function(vector) { | |
var V = vector.elements || vector; | |
var n = this.elements.length, k = n, i; | |
if (n != V.length) { return null; } | |
var dot = 0, mod1 = 0, mod2 = 0; | |
// Work things out in parallel to save time | |
this.each(function(x, i) { | |
dot += x * V[i-1]; | |
mod1 += x * x; | |
mod2 += V[i-1] * V[i-1]; | |
}); | |
mod1 = Math.sqrt(mod1); mod2 = Math.sqrt(mod2); | |
if (mod1*mod2 === 0) { return null; } | |
var theta = dot / (mod1*mod2); | |
if (theta < -1) { theta = -1; } | |
if (theta > 1) { theta = 1; } | |
return Math.acos(theta); | |
}, | |
// Returns the scalar product of the vector with the argument | |
// Both vectors must have equal dimensionality | |
dot: function(vector) { | |
var V = vector.elements || vector; | |
var i, product = 0, n = this.elements.length; | |
if (n != V.length) { return null; } | |
do { product += this.elements[n-1] * V[n-1]; } while (--n); | |
return product; | |
}, | |
// Rotates the vector about the given object. The object should be a | |
// point if the vector is 2D, and a line if it is 3D. Be careful with line directions! | |
rotate: function(t, obj) { | |
var V, R, x, y, z; | |
switch (this.elements.length) { | |
case 2: | |
V = obj.elements || obj; | |
if (V.length != 2) { return null; } | |
R = Matrix.Rotation(t).elements; | |
x = this.elements[0] - V[0]; | |
y = this.elements[1] - V[1]; | |
return Vector.create([ | |
V[0] + R[0][0] * x + R[0][1] * y, | |
V[1] + R[1][0] * x + R[1][1] * y | |
]); | |
break; | |
case 3: | |
if (!obj.direction) { return null; } | |
var C = obj.pointClosestTo(this).elements; | |
R = Matrix.Rotation(t, obj.direction).elements; | |
x = this.elements[0] - C[0]; | |
y = this.elements[1] - C[1]; | |
z = this.elements[2] - C[2]; | |
return Vector.create([ | |
C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z, | |
C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z, | |
C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z | |
]); | |
break; | |
default: | |
return null; | |
} | |
}, | |
// Set vector's elements from an array | |
setElements: function(els) { | |
this.elements = (els.elements || els).slice(); | |
return this; | |
} | |
}; | |
// Constructor function | |
Vector.create = function(elements) { | |
var V = new Vector(); | |
return V.setElements(elements); | |
}; | |
var $V = Vector.create; | |
function Line() {} | |
Line.prototype = { | |
// Returns the line's perpendicular distance from the argument, | |
// which can be a point, a line or a plane | |
distanceFrom: function(obj) { | |
if (obj.normal) { return obj.distanceFrom(this); } | |
if (obj.direction) { | |
// obj is a line | |
if (this.isParallelTo(obj)) { return this.distanceFrom(obj.anchor); } | |
var N = this.direction.cross(obj.direction).toUnitVector().elements; | |
var A = this.anchor.elements, B = obj.anchor.elements; | |
return Math.abs((A[0] - B[0]) * N[0] + (A[1] - B[1]) * N[1] + (A[2] - B[2]) * N[2]); | |
} else { | |
// obj is a point | |
var P = obj.elements || obj; | |
var A = this.anchor.elements, D = this.direction.elements; | |
var PA1 = P[0] - A[0], PA2 = P[1] - A[1], PA3 = (P[2] || 0) - A[2]; | |
var modPA = Math.sqrt(PA1*PA1 + PA2*PA2 + PA3*PA3); | |
if (modPA === 0) return 0; | |
// Assumes direction vector is normalized | |
var cosTheta = (PA1 * D[0] + PA2 * D[1] + PA3 * D[2]) / modPA; | |
var sin2 = 1 - cosTheta*cosTheta; | |
return Math.abs(modPA * Math.sqrt(sin2 < 0 ? 0 : sin2)); | |
} | |
}, | |
// Returns true iff the argument is a point on the line | |
contains: function(point) { | |
var dist = this.distanceFrom(point); | |
return (dist !== null && dist <= Sylvester.precision); | |
}, | |
// Returns the point on the line that is closest to the given point or line | |
pointClosestTo: function(obj) { | |
if (obj.direction) { | |
// obj is a line | |
if (this.intersects(obj)) { return this.intersectionWith(obj); } | |
if (this.isParallelTo(obj)) { return null; } | |
var D = this.direction.elements, E = obj.direction.elements; | |
var D1 = D[0], D2 = D[1], D3 = D[2], E1 = E[0], E2 = E[1], E3 = E[2]; | |
// Create plane containing obj and the shared normal and intersect this with it | |
// Thank you: http://www.cgafaq.info/wiki/Line-line_distance | |
var x = (D3 * E1 - D1 * E3), y = (D1 * E2 - D2 * E1), z = (D2 * E3 - D3 * E2); | |
var N = Vector.create([x * E3 - y * E2, y * E1 - z * E3, z * E2 - x * E1]); | |
var P = Plane.create(obj.anchor, N); | |
return P.intersectionWith(this); | |
} else { | |
// obj is a point | |
var P = obj.elements || obj; | |
if (this.contains(P)) { return Vector.create(P); } | |
var A = this.anchor.elements, D = this.direction.elements; | |
var D1 = D[0], D2 = D[1], D3 = D[2], A1 = A[0], A2 = A[1], A3 = A[2]; | |
var x = D1 * (P[1]-A2) - D2 * (P[0]-A1), y = D2 * ((P[2] || 0) - A3) - D3 * (P[1]-A2), | |
z = D3 * (P[0]-A1) - D1 * ((P[2] || 0) - A3); | |
var V = Vector.create([D2 * x - D3 * z, D3 * y - D1 * x, D1 * z - D2 * y]); | |
var k = this.distanceFrom(P) / V.modulus(); | |
return Vector.create([ | |
P[0] + V.elements[0] * k, | |
P[1] + V.elements[1] * k, | |
(P[2] || 0) + V.elements[2] * k | |
]); | |
} | |
}, | |
// Returns a copy of the line rotated by t radians about the given line. Works by | |
// finding the argument's closest point to this line's anchor point (call this C) and | |
// rotating the anchor about C. Also rotates the line's direction about the argument's. | |
// Be careful with this - the rotation axis' direction affects the outcome! | |
rotate: function(t, line) { | |
// If we're working in 2D | |
if (typeof(line.direction) == 'undefined') { line = Line.create(line.to3D(), Vector.k); } | |
var R = Matrix.Rotation(t, line.direction).elements; | |
var C = line.pointClosestTo(this.anchor).elements; | |
var A = this.anchor.elements, D = this.direction.elements; | |
var C1 = C[0], C2 = C[1], C3 = C[2], A1 = A[0], A2 = A[1], A3 = A[2]; | |
var x = A1 - C1, y = A2 - C2, z = A3 - C3; | |
return Line.create([ | |
C1 + R[0][0] * x + R[0][1] * y + R[0][2] * z, | |
C2 + R[1][0] * x + R[1][1] * y + R[1][2] * z, | |
C3 + R[2][0] * x + R[2][1] * y + R[2][2] * z | |
], [ | |
R[0][0] * D[0] + R[0][1] * D[1] + R[0][2] * D[2], | |
R[1][0] * D[0] + R[1][1] * D[1] + R[1][2] * D[2], | |
R[2][0] * D[0] + R[2][1] * D[1] + R[2][2] * D[2] | |
]); | |
}, | |
// Set the line's anchor point and direction. | |
setVectors: function(anchor, direction) { | |
// Need to do this so that line's properties are not | |
// references to the arguments passed in | |
anchor = Vector.create(anchor); | |
direction = Vector.create(direction); | |
if (anchor.elements.length == 2) {anchor.elements.push(0); } | |
if (direction.elements.length == 2) { direction.elements.push(0); } | |
if (anchor.elements.length > 3 || direction.elements.length > 3) { return null; } | |
var mod = direction.modulus(); | |
if (mod === 0) { return null; } | |
this.anchor = anchor; | |
this.direction = Vector.create([ | |
direction.elements[0] / mod, | |
direction.elements[1] / mod, | |
direction.elements[2] / mod | |
]); | |
return this; | |
} | |
}; | |
// Constructor function | |
Line.create = function(anchor, direction) { | |
var L = new Line(); | |
return L.setVectors(anchor, direction); | |
}; | |
function Matrix() {} | |
Matrix.prototype = { | |
// Set the matrix's elements from an array. If the argument passed | |
// is a vector, the resulting matrix will be a single column. | |
setElements: function(els) { | |
var i, elements = els.elements || els; | |
if (typeof(elements[0][0]) != 'undefined') { | |
var ni = elements.length, ki = ni, nj, kj, j; | |
this.elements = []; | |
do { i = ki - ni; | |
nj = elements[i].length; kj = nj; | |
this.elements[i] = []; | |
do { j = kj - nj; | |
this.elements[i][j] = elements[i][j]; | |
} while (--nj); | |
} while(--ni); | |
return this; | |
} | |
var n = elements.length, k = n; | |
this.elements = []; | |
do { i = k - n; | |
this.elements.push([elements[i]]); | |
} while (--n); | |
return this; | |
} | |
}; | |
// Constructor function | |
Matrix.create = function(elements) { | |
var M = new Matrix(); | |
return M.setElements(elements); | |
}; | |
Matrix.Rotation = function(theta, a) { | |
if (!a) { | |
return Matrix.create([ | |
[Math.cos(theta), -Math.sin(theta)], | |
[Math.sin(theta), Math.cos(theta)] | |
]); | |
} | |
var axis = a.dup(); | |
if (axis.elements.length != 3) { return null; } | |
var mod = axis.modulus(); | |
var x = axis.elements[0]/mod, y = axis.elements[1]/mod, z = axis.elements[2]/mod; | |
var s = Math.sin(theta), c = Math.cos(theta), t = 1 - c; | |
// Formula derived here: http://www.gamedev.net/reference/articles/article1199.asp | |
// That proof rotates the co-ordinate system so theta | |
// becomes -theta and sin becomes -sin here. | |
return Matrix.create([ | |
[ t*x*x + c, t*x*y - s*z, t*x*z + s*y ], | |
[ t*x*y + s*z, t*y*y + c, t*y*z - s*x ], | |
[ t*x*z - s*y, t*y*z + s*x, t*z*z + c ] | |
]); | |
}; | |
/** | |
* class FirminCSSMatrix | |
* | |
* The [[FirminCSSMatrix]] class is a concrete implementation of the | |
* `CSSMatrix` interface defined in the [CSS 2D Transforms][2d] and | |
* [CSS 3D Transforms][3d] Module specifications. | |
* | |
* [2d]: http://www.w3.org/TR/css3-2d-transforms/ | |
* [3d]: http://www.w3.org/TR/css3-3d-transforms/ | |
* | |
* The implementation was largely copied from the `WebKitCSSMatrix` class, and | |
* the supparting maths libraries in the [WebKit][webkit] project. This is one | |
* reason why much of the code looks more like C++ than JavaScript. | |
* | |
* [webkit]: http://webkit.org/ | |
* | |
* Its API is a superset of that provided by `WebKitCSSMatrix`, largely | |
* because various pieces of supporting code have been added as instance | |
* methods rather than pollute the global namespace. Examples of these include | |
* [[FirminCSSMatrix#isAffine]], [[FirminCSSMatrix#isIdentityOrTranslation]] | |
* and [[FirminCSSMatrix#adjoint]]. | |
**/ | |
/** | |
* new FirminCSSMatrix(domstr) | |
* - domstr (String): a string representation of a 2D or 3D transform matrix | |
* in the form given by the CSS transform property, i.e. just like the | |
* output from [[FirminCSSMatrix#toString]]. | |
**/ | |
FirminCSSMatrix = function(domstr) { | |
this.m11 = this.m22 = this.m33 = this.m44 = 1; | |
this.m12 = this.m13 = this.m14 = | |
this.m21 = this.m23 = this.m24 = | |
this.m31 = this.m32 = this.m34 = | |
this.m41 = this.m42 = this.m43 = 0; | |
if (typeof domstr == "string") { | |
this.setMatrixValue(domstr); | |
} | |
}; | |
/** | |
* FirminCSSMatrix.displayName = "FirminCSSMatrix" | |
**/ | |
FirminCSSMatrix.displayName = "FirminCSSMatrix"; | |
/** | |
* FirminCSSMatrix.degreesToRadians(angle) -> Number | |
* - angle (Number): an angle in degrees. | |
* | |
* Converts angles in degrees, which are used by the external API, to angles | |
* in radians used in internal calculations. | |
**/ | |
FirminCSSMatrix.degreesToRadians = function(angle) { | |
return angle * Math.PI / 180; | |
}; | |
/** | |
* FirminCSSMatrix#isAffine() -> Boolean | |
* | |
* Determines whether the matrix is affine. | |
**/ | |
FirminCSSMatrix.prototype.isAffine = function() { | |
return this.m13 === 0 && this.m14 === 0 && | |
this.m23 === 0 && this.m24 === 0 && | |
this.m31 === 0 && this.m32 === 0 && | |
this.m33 === 1 && this.m34 === 0 && | |
this.m43 === 0 && this.m44 === 1; | |
}; | |
/** | |
* FirminCSSMatrix#setMatrixValue(domstr) -> undefined | |
* - domstr (String): a string representation of a 2D or 3D transform matrix | |
* in the form given by the CSS transform property, i.e. just like the | |
* output from [[FirminCSSMatrix#toString]]. | |
* | |
* Sets the matrix values using a string representation, such as that produced | |
* by the [[FirminCSSMatrix#toString]] method. | |
**/ | |
FirminCSSMatrix.prototype.setMatrixValue = function(domstr) { | |
domstr = domstr.trim(); | |
var mstr = domstr.match(/^matrix(3d)?\(\s*(.+)\s*\)$/), | |
is3d, chunks, len, points, i, chunk; | |
if (!mstr) return; | |
is3d = !!mstr[1]; | |
chunks = mstr[2].split(/\s*,\s*/); | |
len = chunks.length; | |
points = new Array(len); | |
if ((is3d && len !== 16) || !(is3d || len === 6)) return; | |
for (i = 0; i < len; i++) { | |
chunk = chunks[i]; | |
if (chunk.match(/^-?\d+(\.\d+)?$/)) { | |
points[i] = parseFloat(chunk); | |
} else return; | |
} | |
for (i = 0; i < len; i++) { | |
point = is3d ? | |
("m" + (Math.floor(i / 4) + 1)) + (i % 4 + 1) : | |
String.fromCharCode(i + 97); // ASCII char 97 == 'a' | |
this[point] = points[i]; | |
} | |
}; | |
/** | |
* FirminCSSMatrix#toString() -> String | |
* | |
* Returns a string representation of the matrix. | |
**/ | |
FirminCSSMatrix.prototype.toString = function() { | |
var self = this, points, prefix; | |
if (this.isAffine()) { | |
prefix = "matrix("; | |
points = ["a", "b", "c", "d", "e", "f"]; | |
} else { | |
prefix = "matrix3d("; | |
points = ["m11", "m12", "m13", "m14", | |
"m21", "m22", "m23", "m24", | |
"m31", "m32", "m33", "m34", | |
"m41", "m42", "m43", "m44"]; | |
} | |
return prefix + points.map(function(p) { | |
return (self[p] || 0).toFixed(6); | |
}).join(", ") + ")"; | |
}; | |
/* | |
* @preserve Morf v0.1.5 | |
* http://www.joelambert.co.uk/morf | |
* | |
* Copyright 2011, Joe Lambert. | |
* Free to use under the MIT license. | |
* http://www.opensource.org/licenses/mit-license.php | |
*/ | |
var CSSMatrixDecomposed = function(obj) { | |
obj === undefined ? obj = {} : null; | |
var components = {perspective: null, translate: null, skew: null, scale: null, rotate: null}; | |
for(var i in components) | |
this[i] = obj[i] ? obj[i] : new Vector4(); | |
/** | |
* Tween between two decomposed matrices | |
* @param {CSSMatrixDecomposed} dm The destination decomposed matrix | |
* @param {float} progress A float value between 0-1, representing the percentage of completion | |
* @param {function} fn An easing function following the prototype function(pos){} | |
* @author Joe Lambert | |
* @returns {WebKitCSSMatrix} A new matrix for the tweened state | |
*/ | |
this.tween = function(dm, progress, fn) { | |
if(fn === undefined) | |
fn = function(pos) {return pos;}; // Default to a linear easing | |
if(!dm) | |
dm = new CSSMatrixDecomposed(new FirminCSSMatrix().decompose()); | |
var r = new CSSMatrixDecomposed(), | |
i = index = null, | |
trans = ''; | |
progress = fn(progress); | |
for(index in components) | |
for(i in {x:'x', y:'y', z:'z', w:'w'}) | |
r[index][i] = (this[index][i] + (dm[index][i] - this[index][i]) * progress ).toFixed(5); | |
trans = 'matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, '+r.perspective.x+', '+r.perspective.y+', '+r.perspective.z+', '+r.perspective.w+') ' + | |
'translate3d('+r.translate.x+'px, '+r.translate.y+'px, '+r.translate.y+'px) ' + | |
'rotateX('+r.rotate.x+'rad) rotateY('+r.rotate.y+'rad) rotateZ('+r.rotate.z+'rad) ' + | |
'matrix3d(1,0,0,0, 0,1,0,0, 0,'+r.skew.z+',1,0, 0,0,0,1) ' + | |
'matrix3d(1,0,0,0, 0,1,0,0, '+r.skew.y+',0,1,0, 0,0,0,1) ' + | |
'matrix3d(1,0,0,0, '+r.skew.x+',1,0,0, 0,0,1,0, 0,0,0,1) ' + | |
'scale3d('+r.scale.x+', '+r.scale.y+', '+r.scale.z+')'; | |
try { r = new FirminCSSMatrix(trans); return r; } | |
catch(e) { console.error('Invalid matrix string: '+trans); return '' }; | |
}; | |
}; | |
var Vector4 = function(x, y, z, w) | |
{ | |
this.x = x ? x : 0; | |
this.y = y ? y : 0; | |
this.z = z ? z : 0; | |
this.w = w ? w : 0; | |
/** | |
* Ensure that values are not undefined | |
* @author Joe Lambert | |
* @returns null | |
*/ | |
this.checkValues = function() { | |
this.x = this.x ? this.x : 0; | |
this.y = this.y ? this.y : 0; | |
this.z = this.z ? this.z : 0; | |
this.w = this.w ? this.w : 0; | |
}; | |
/** | |
* Get the length of the vector | |
* @author Joe Lambert | |
* @returns {float} | |
*/ | |
this.length = function() { | |
this.checkValues(); | |
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); | |
}; | |
/** | |
* Get a normalised representation of the vector | |
* @author Joe Lambert | |
* @returns {Vector4} | |
*/ | |
this.normalise = function() { | |
var len = this.length(), | |
v = new Vector4(this.x / len, this.y / len, this.z / len); | |
return v; | |
}; | |
/** | |
* Vector Dot-Product | |
* @param {Vector4} v The second vector to apply the product to | |
* @author Joe Lambert | |
* @returns {float} The Dot-Product of this and v. | |
*/ | |
this.dot = function(v) { | |
return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w; | |
}; | |
/** | |
* Vector Cross-Product | |
* @param {Vector4} v The second vector to apply the product to | |
* @author Joe Lambert | |
* @returns {Vector4} The Cross-Product of this and v. | |
*/ | |
this.cross = function(v) { | |
return new Vector4(this.y*v.z - this.z*v.y, this.z*v.x - this.x*v.z, this.x*v.y - this.y*v.x); | |
}; | |
/** | |
* Helper function required for matrix decomposition | |
* A Javascript implementation of pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition | |
* @param {Vector4} aPoint A 3D point | |
* @param {float} ascl | |
* @param {float} bscl | |
* @author Joe Lambert | |
* @returns {Vector4} | |
*/ | |
this.combine = function(aPoint, ascl, bscl) { | |
return new Vector4( (ascl * this.x) + (bscl * aPoint.x), | |
(ascl * this.y) + (bscl * aPoint.y), | |
(ascl * this.z) + (bscl * aPoint.z) ); | |
} | |
}; | |
FirminCSSMatrix.prototype.determinant = function() { | |
return this.m14 * this.m23 * this.m32 * this.m41-this.m13 * this.m24 * this.m32 * this.m41 - | |
this.m14 * this.m22 * this.m33 * this.m41+this.m12 * this.m24 * this.m33 * this.m41 + | |
this.m13 * this.m22 * this.m34 * this.m41-this.m12 * this.m23 * this.m34 * this.m41 - | |
this.m14 * this.m23 * this.m31 * this.m42+this.m13 * this.m24 * this.m31 * this.m42 + | |
this.m14 * this.m21 * this.m33 * this.m42-this.m11 * this.m24 * this.m33 * this.m42 - | |
this.m13 * this.m21 * this.m34 * this.m42+this.m11 * this.m23 * this.m34 * this.m42 + | |
this.m14 * this.m22 * this.m31 * this.m43-this.m12 * this.m24 * this.m31 * this.m43 - | |
this.m14 * this.m21 * this.m32 * this.m43+this.m11 * this.m24 * this.m32 * this.m43 + | |
this.m12 * this.m21 * this.m34 * this.m43-this.m11 * this.m22 * this.m34 * this.m43 - | |
this.m13 * this.m22 * this.m31 * this.m44+this.m12 * this.m23 * this.m31 * this.m44 + | |
this.m13 * this.m21 * this.m32 * this.m44-this.m11 * this.m23 * this.m32 * this.m44 - | |
this.m12 * this.m21 * this.m33 * this.m44+this.m11 * this.m22 * this.m33 * this.m44; | |
}; | |
FirminCSSMatrix.prototype.decompose = function() { | |
var matrix = new FirminCSSMatrix(this.toString()), | |
perspectiveMatrix = rightHandSide = inversePerspectiveMatrix = transposedInversePerspectiveMatrix = | |
perspective = translate = row = i = scale = skew = pdum3 = rotate = null; | |
if (matrix.m33 == 0) | |
return new CSSMatrixDecomposed(new FirminCSSMatrix().decompose()); // Return the identity matrix | |
// Normalize the matrix. | |
for (i = 1; i <= 4; i++) | |
for (j = 1; j <= 4; j++) | |
matrix['m'+i+j] /= matrix.m44; | |
// perspectiveMatrix is used to solve for perspective, but it also provides | |
// an easy way to test for singularity of the upper 3x3 component. | |
perspectiveMatrix = matrix; | |
for (i = 1; i <= 3; i++) | |
perspectiveMatrix['m'+i+'4'] = 0; | |
perspectiveMatrix.m44 = 1; | |
if (perspectiveMatrix.determinant() == 0) | |
return new CSSMatrixDecomposed(new FirminCSSMatrix().decompose()); // Return the identity matrix | |
// First, isolate perspective. | |
if (matrix.m14 != 0 || matrix.m24 != 0 || matrix.m34 != 0) | |
{ | |
// rightHandSide is the right hand side of the equation. | |
rightHandSide = new Vector4(matrix.m14, matrix.m24, matrix.m34, matrix.m44); | |
// Solve the equation by inverting perspectiveMatrix and multiplying | |
// rightHandSide by the inverse. | |
inversePerspectiveMatrix = perspectiveMatrix.inverse(); | |
transposedInversePerspectiveMatrix = inversePerspectiveMatrix.transpose(); | |
perspective = transposedInversePerspectiveMatrix.transformVector(rightHandSide); | |
// Clear the perspective partition | |
matrix.m14 = matrix.m24 = matrix.m34 = 0; | |
matrix.m44 = 1; | |
} | |
else | |
{ | |
// No perspective. | |
perspective = new Vector4(0,0,0,1); | |
} | |
// Next take care of translation | |
translate = new Vector4(matrix.m41, matrix.m42, matrix.m43); | |
matrix.m41 = 0; | |
matrix.m42 = 0; | |
matrix.m43 = 0; | |
// Now get scale and shear. 'row' is a 3 element array of 3 component vectors | |
row = [ | |
new Vector4(), new Vector4(), new Vector4() | |
]; | |
for (i = 1; i <= 3; i++) | |
{ | |
row[i-1].x = matrix['m'+i+'1']; | |
row[i-1].y = matrix['m'+i+'2']; | |
row[i-1].z = matrix['m'+i+'3']; | |
} | |
// Compute X scale factor and normalize first row. | |
scale = new Vector4(); | |
skew = new Vector4(); | |
scale.x = row[0].length(); | |
row[0] = row[0].normalise(); | |
// Compute XY shear factor and make 2nd row orthogonal to 1st. | |
skew.x = row[0].dot(row[1]); | |
row[1] = row[1].combine(row[0], 1.0, -skew.x); | |
// Now, compute Y scale and normalize 2nd row. | |
scale.y = row[1].length(); | |
row[1] = row[1].normalise(); | |
skew.x /= scale.y; | |
// Compute XZ and YZ shears, orthogonalize 3rd row | |
skew.y = row[0].dot(row[2]); | |
row[2] = row[2].combine(row[0], 1.0, -skew.y); | |
skew.z = row[1].dot(row[2]); | |
row[2] = row[2].combine(row[1], 1.0, -skew.z); | |
// Next, get Z scale and normalize 3rd row. | |
scale.z = row[2].length(); | |
row[2] = row[2].normalise(); | |
skew.y /= scale.z; | |
skew.y /= scale.z; | |
// At this point, the matrix (in rows) is orthonormal. | |
// Check for a coordinate system flip. If the determinant | |
// is -1, then negate the matrix and the scaling factors. | |
pdum3 = row[1].cross(row[2]) | |
if (row[0].dot(pdum3) < 0) | |
{ | |
for (i = 0; i < 3; i++) | |
{ | |
scale.x *= -1; | |
row[i].x *= -1; | |
row[i].y *= -1; | |
row[i].z *= -1; | |
} | |
} | |
// Now, get the rotations out | |
rotate = new Vector4(); | |
rotate.y = Math.asin(-row[0].z); | |
if (Math.cos(rotate.y) != 0) | |
{ | |
rotate.x = Math.atan2(row[1].z, row[2].z); | |
rotate.z = Math.atan2(row[0].y, row[0].x); | |
} | |
else | |
{ | |
rotate.x = Math.atan2(-row[2].x, row[1].y); | |
rotate.z = 0; | |
} | |
return new CSSMatrixDecomposed({ | |
perspective: perspective, | |
translate: translate, | |
skew: skew, | |
scale: scale, | |
rotate: rotate | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment