|
//please see https://github.com/florianguenther/zui53 |
|
//all of this zui53.js is copied from that repo |
|
|
|
|
|
|
|
// # 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 element i of the vector |
|
e: function(i) { |
|
return (i < 1 || i > this.elements.length) ? null : this.elements[i-1]; |
|
}, |
|
|
|
// Returns the number of elements the vector has |
|
dimensions: function() { |
|
return this.elements.length; |
|
}, |
|
|
|
// Returns the modulus ('length') of the vector |
|
modulus: function() { |
|
return Math.sqrt(this.dot(this)); |
|
}, |
|
|
|
// Returns true iff the vector is equal to the argument |
|
eql: function(vector) { |
|
var n = this.elements.length; |
|
var V = vector.elements || vector; |
|
if (n != V.length) { return false; } |
|
do { |
|
if (Math.abs(this.elements[n-1] - V[n-1]) > Sylvester.precision) { return false; } |
|
} while (--n); |
|
return true; |
|
}, |
|
|
|
// Returns a copy of the vector |
|
dup: function() { |
|
return Vector.create(this.elements); |
|
}, |
|
|
|
// Maps the vector to another vector according to the given function |
|
map: function(fn) { |
|
var elements = []; |
|
this.each(function(x, i) { |
|
elements.push(fn(x, i)); |
|
}); |
|
return Vector.create(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 a new vector created by normalizing the receiver |
|
toUnitVector: function() { |
|
var r = this.modulus(); |
|
if (r === 0) { return this.dup(); } |
|
return this.map(function(x) { return x/r; }); |
|
}, |
|
|
|
// 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 true iff the vector is parallel to the argument |
|
isParallelTo: function(vector) { |
|
var angle = this.angleFrom(vector); |
|
return (angle === null) ? null : (angle <= Sylvester.precision); |
|
}, |
|
|
|
// Returns true iff the vector is antiparallel to the argument |
|
isAntiparallelTo: function(vector) { |
|
var angle = this.angleFrom(vector); |
|
return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision); |
|
}, |
|
|
|
// Returns true iff the vector is perpendicular to the argument |
|
isPerpendicularTo: function(vector) { |
|
var dot = this.dot(vector); |
|
return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision); |
|
}, |
|
|
|
// Returns the result of adding the argument to the vector |
|
add: function(vector) { |
|
var V = vector.elements || vector; |
|
if (this.elements.length != V.length) { return null; } |
|
return this.map(function(x, i) { return x + V[i-1]; }); |
|
}, |
|
|
|
// Returns the result of subtracting the argument from the vector |
|
subtract: function(vector) { |
|
var V = vector.elements || vector; |
|
if (this.elements.length != V.length) { return null; } |
|
return this.map(function(x, i) { return x - V[i-1]; }); |
|
}, |
|
|
|
// Returns the result of multiplying the elements of the vector by the argument |
|
multiply: function(k) { |
|
return this.map(function(x) { return x*k; }); |
|
}, |
|
|
|
x: function(k) { return this.multiply(k); }, |
|
|
|
// 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; |
|
}, |
|
|
|
// Returns the vector product of the vector with the argument |
|
// Both vectors must have dimensionality 3 |
|
cross: function(vector) { |
|
var B = vector.elements || vector; |
|
if (this.elements.length != 3 || B.length != 3) { return null; } |
|
var A = this.elements; |
|
return Vector.create([ |
|
(A[1] * B[2]) - (A[2] * B[1]), |
|
(A[2] * B[0]) - (A[0] * B[2]), |
|
(A[0] * B[1]) - (A[1] * B[0]) |
|
]); |
|
}, |
|
|
|
// Returns the (absolute) largest element of the vector |
|
max: function() { |
|
var m = 0, n = this.elements.length, k = n, i; |
|
do { i = k - n; |
|
if (Math.abs(this.elements[i]) > Math.abs(m)) { m = this.elements[i]; } |
|
} while (--n); |
|
return m; |
|
}, |
|
|
|
// Returns the index of the first match found |
|
indexOf: function(x) { |
|
var index = null, n = this.elements.length, k = n, i; |
|
do { i = k - n; |
|
if (index === null && this.elements[i] == x) { |
|
index = i + 1; |
|
} |
|
} while (--n); |
|
return index; |
|
}, |
|
|
|
// Returns a diagonal matrix with the vector's elements as its diagonal elements |
|
toDiagonalMatrix: function() { |
|
return Matrix.Diagonal(this.elements); |
|
}, |
|
|
|
// Returns the result of rounding the elements of the vector |
|
round: function() { |
|
return this.map(function(x) { return Math.round(x); }); |
|
}, |
|
|
|
// Returns a copy of the vector with elements set to the given value if they |
|
// differ from it by less than Sylvester.precision |
|
snapTo: function(x) { |
|
return this.map(function(y) { |
|
return (Math.abs(y - x) <= Sylvester.precision) ? x : y; |
|
}); |
|
}, |
|
|
|
// Returns the vector's distance from the argument, when considered as a point in space |
|
distanceFrom: function(obj) { |
|
if (obj.anchor) { return obj.distanceFrom(this); } |
|
var V = obj.elements || obj; |
|
if (V.length != this.elements.length) { return null; } |
|
var sum = 0, part; |
|
this.each(function(x, i) { |
|
part = x - V[i-1]; |
|
sum += part * part; |
|
}); |
|
return Math.sqrt(sum); |
|
}, |
|
|
|
// Returns true if the vector is point on the given line |
|
liesOn: function(line) { |
|
return line.contains(this); |
|
}, |
|
|
|
// Return true iff the vector is a point in the given plane |
|
liesIn: function(plane) { |
|
return plane.contains(this); |
|
}, |
|
|
|
// 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; |
|
} |
|
}, |
|
|
|
// Returns the result of reflecting the point in the given point, line or plane |
|
reflectionIn: function(obj) { |
|
if (obj.anchor) { |
|
// obj is a plane or line |
|
var P = this.elements.slice(); |
|
var C = obj.pointClosestTo(P).elements; |
|
return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]); |
|
} else { |
|
// obj is a point |
|
var Q = obj.elements || obj; |
|
if (this.elements.length != Q.length) { return null; } |
|
return this.map(function(x, i) { return Q[i-1] + (Q[i-1] - x); }); |
|
} |
|
}, |
|
|
|
// Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added |
|
to3D: function() { |
|
var V = this.dup(); |
|
switch (V.elements.length) { |
|
case 3: break; |
|
case 2: V.elements.push(0); break; |
|
default: return null; |
|
} |
|
return V; |
|
}, |
|
|
|
// Returns a string representation of the vector |
|
inspect: function() { |
|
return '[' + this.elements.join(', ') + ']'; |
|
}, |
|
|
|
// 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); |
|
}; |
|
|
|
// i, j, k unit vectors |
|
Vector.i = Vector.create([1,0,0]); |
|
Vector.j = Vector.create([0,1,0]); |
|
Vector.k = Vector.create([0,0,1]); |
|
|
|
// Random vector of size n |
|
Vector.Random = function(n) { |
|
var elements = []; |
|
do { elements.push(Math.random()); |
|
} while (--n); |
|
return Vector.create(elements); |
|
}; |
|
|
|
// Vector filled with zeros |
|
Vector.Zero = function(n) { |
|
var elements = []; |
|
do { elements.push(0); |
|
} while (--n); |
|
return Vector.create(elements); |
|
}; |
|
|
|
|
|
|
|
function Matrix() {} |
|
Matrix.prototype = { |
|
|
|
// Returns element (i,j) of the matrix |
|
e: function(i,j) { |
|
if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) { return null; } |
|
return this.elements[i-1][j-1]; |
|
}, |
|
|
|
// Returns row k of the matrix as a vector |
|
row: function(i) { |
|
if (i > this.elements.length) { return null; } |
|
return Vector.create(this.elements[i-1]); |
|
}, |
|
|
|
// Returns column k of the matrix as a vector |
|
col: function(j) { |
|
if (j > this.elements[0].length) { return null; } |
|
var col = [], n = this.elements.length, k = n, i; |
|
do { i = k - n; |
|
col.push(this.elements[i][j-1]); |
|
} while (--n); |
|
return Vector.create(col); |
|
}, |
|
|
|
// Returns the number of rows/columns the matrix has |
|
dimensions: function() { |
|
return {rows: this.elements.length, cols: this.elements[0].length}; |
|
}, |
|
|
|
// Returns the number of rows in the matrix |
|
rows: function() { |
|
return this.elements.length; |
|
}, |
|
|
|
// Returns the number of columns in the matrix |
|
cols: function() { |
|
return this.elements[0].length; |
|
}, |
|
|
|
// Returns true iff the matrix is equal to the argument. You can supply |
|
// a vector as the argument, in which case the receiver must be a |
|
// one-column matrix equal to the vector. |
|
eql: function(matrix) { |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
if (this.elements.length != M.length || |
|
this.elements[0].length != M[0].length) { return false; } |
|
var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j; |
|
do { i = ki - ni; |
|
nj = kj; |
|
do { j = kj - nj; |
|
if (Math.abs(this.elements[i][j] - M[i][j]) > Sylvester.precision) { return false; } |
|
} while (--nj); |
|
} while (--ni); |
|
return true; |
|
}, |
|
|
|
// Returns a copy of the matrix |
|
dup: function() { |
|
return Matrix.create(this.elements); |
|
}, |
|
|
|
// Maps the matrix to another matrix (of the same dimensions) according to the given function |
|
map: function(fn) { |
|
var els = [], ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j; |
|
do { i = ki - ni; |
|
nj = kj; |
|
els[i] = []; |
|
do { j = kj - nj; |
|
els[i][j] = fn(this.elements[i][j], i + 1, j + 1); |
|
} while (--nj); |
|
} while (--ni); |
|
return Matrix.create(els); |
|
}, |
|
|
|
// Returns true iff the argument has the same dimensions as the matrix |
|
isSameSizeAs: function(matrix) { |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
return (this.elements.length == M.length && |
|
this.elements[0].length == M[0].length); |
|
}, |
|
|
|
// Returns the result of adding the argument to the matrix |
|
add: function(matrix) { |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
if (!this.isSameSizeAs(M)) { return null; } |
|
return this.map(function(x, i, j) { return x + M[i-1][j-1]; }); |
|
}, |
|
|
|
// Returns the result of subtracting the argument from the matrix |
|
subtract: function(matrix) { |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
if (!this.isSameSizeAs(M)) { return null; } |
|
return this.map(function(x, i, j) { return x - M[i-1][j-1]; }); |
|
}, |
|
|
|
// Returns true iff the matrix can multiply the argument from the left |
|
canMultiplyFromLeft: function(matrix) { |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
// this.columns should equal matrix.rows |
|
return (this.elements[0].length == M.length); |
|
}, |
|
|
|
// Returns the result of multiplying the matrix from the right by the argument. |
|
// If the argument is a scalar then just multiply all the elements. If the argument is |
|
// a vector, a vector is returned, which saves you having to remember calling |
|
// col(1) on the result. |
|
multiply: function(matrix) { |
|
if (!matrix.elements) { |
|
return this.map(function(x) { return x * matrix; }); |
|
} |
|
var returnVector = matrix.modulus ? true : false; |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
if (!this.canMultiplyFromLeft(M)) { return null; } |
|
var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j; |
|
var cols = this.elements[0].length, elements = [], sum, nc, c; |
|
do { i = ki - ni; |
|
elements[i] = []; |
|
nj = kj; |
|
do { j = kj - nj; |
|
sum = 0; |
|
nc = cols; |
|
do { c = cols - nc; |
|
sum += this.elements[i][c] * M[c][j]; |
|
} while (--nc); |
|
elements[i][j] = sum; |
|
} while (--nj); |
|
} while (--ni); |
|
var M = Matrix.create(elements); |
|
return returnVector ? M.col(1) : M; |
|
}, |
|
|
|
x: function(matrix) { return this.multiply(matrix); }, |
|
|
|
// Returns a submatrix taken from the matrix |
|
// Argument order is: start row, start col, nrows, ncols |
|
// Element selection wraps if the required index is outside the matrix's bounds, so you could |
|
// use this to perform row/column cycling or copy-augmenting. |
|
minor: function(a, b, c, d) { |
|
var elements = [], ni = c, i, nj, j; |
|
var rows = this.elements.length, cols = this.elements[0].length; |
|
do { i = c - ni; |
|
elements[i] = []; |
|
nj = d; |
|
do { j = d - nj; |
|
elements[i][j] = this.elements[(a+i-1)%rows][(b+j-1)%cols]; |
|
} while (--nj); |
|
} while (--ni); |
|
return Matrix.create(elements); |
|
}, |
|
|
|
// Returns the transpose of the matrix |
|
transpose: function() { |
|
var rows = this.elements.length, cols = this.elements[0].length; |
|
var elements = [], ni = cols, i, nj, j; |
|
do { i = cols - ni; |
|
elements[i] = []; |
|
nj = rows; |
|
do { j = rows - nj; |
|
elements[i][j] = this.elements[j][i]; |
|
} while (--nj); |
|
} while (--ni); |
|
return Matrix.create(elements); |
|
}, |
|
|
|
// Returns true iff the matrix is square |
|
isSquare: function() { |
|
return (this.elements.length == this.elements[0].length); |
|
}, |
|
|
|
// Returns the (absolute) largest element of the matrix |
|
max: function() { |
|
var m = 0, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j; |
|
do { i = ki - ni; |
|
nj = kj; |
|
do { j = kj - nj; |
|
if (Math.abs(this.elements[i][j]) > Math.abs(m)) { m = this.elements[i][j]; } |
|
} while (--nj); |
|
} while (--ni); |
|
return m; |
|
}, |
|
|
|
// Returns the indeces of the first match found by reading row-by-row from left to right |
|
indexOf: function(x) { |
|
var index = null, ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j; |
|
do { i = ki - ni; |
|
nj = kj; |
|
do { j = kj - nj; |
|
if (this.elements[i][j] == x) { return {i: i+1, j: j+1}; } |
|
} while (--nj); |
|
} while (--ni); |
|
return null; |
|
}, |
|
|
|
// If the matrix is square, returns the diagonal elements as a vector. |
|
// Otherwise, returns null. |
|
diagonal: function() { |
|
if (!this.isSquare) { return null; } |
|
var els = [], n = this.elements.length, k = n, i; |
|
do { i = k - n; |
|
els.push(this.elements[i][i]); |
|
} while (--n); |
|
return Vector.create(els); |
|
}, |
|
|
|
// Make the matrix upper (right) triangular by Gaussian elimination. |
|
// This method only adds multiples of rows to other rows. No rows are |
|
// scaled up or switched, and the determinant is preserved. |
|
toRightTriangular: function() { |
|
var M = this.dup(), els; |
|
var n = this.elements.length, k = n, i, np, kp = this.elements[0].length, p; |
|
do { i = k - n; |
|
if (M.elements[i][i] == 0) { |
|
for (j = i + 1; j < k; j++) { |
|
if (M.elements[j][i] != 0) { |
|
els = []; np = kp; |
|
do { p = kp - np; |
|
els.push(M.elements[i][p] + M.elements[j][p]); |
|
} while (--np); |
|
M.elements[i] = els; |
|
break; |
|
} |
|
} |
|
} |
|
if (M.elements[i][i] != 0) { |
|
for (j = i + 1; j < k; j++) { |
|
var multiplier = M.elements[j][i] / M.elements[i][i]; |
|
els = []; np = kp; |
|
do { p = kp - np; |
|
// Elements with column numbers up to an including the number |
|
// of the row that we're subtracting can safely be set straight to |
|
// zero, since that's the point of this routine and it avoids having |
|
// to loop over and correct rounding errors later |
|
els.push(p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier); |
|
} while (--np); |
|
M.elements[j] = els; |
|
} |
|
} |
|
} while (--n); |
|
return M; |
|
}, |
|
|
|
toUpperTriangular: function() { return this.toRightTriangular(); }, |
|
|
|
// Returns the determinant for square matrices |
|
determinant: function() { |
|
if (!this.isSquare()) { return null; } |
|
var M = this.toRightTriangular(); |
|
var det = M.elements[0][0], n = M.elements.length - 1, k = n, i; |
|
do { i = k - n + 1; |
|
det = det * M.elements[i][i]; |
|
} while (--n); |
|
return det; |
|
}, |
|
|
|
det: function() { return this.determinant(); }, |
|
|
|
// Returns true iff the matrix is singular |
|
isSingular: function() { |
|
return (this.isSquare() && this.determinant() === 0); |
|
}, |
|
|
|
// Returns the trace for square matrices |
|
trace: function() { |
|
if (!this.isSquare()) { return null; } |
|
var tr = this.elements[0][0], n = this.elements.length - 1, k = n, i; |
|
do { i = k - n + 1; |
|
tr += this.elements[i][i]; |
|
} while (--n); |
|
return tr; |
|
}, |
|
|
|
tr: function() { return this.trace(); }, |
|
|
|
// Returns the rank of the matrix |
|
rank: function() { |
|
var M = this.toRightTriangular(), rank = 0; |
|
var ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j; |
|
do { i = ki - ni; |
|
nj = kj; |
|
do { j = kj - nj; |
|
if (Math.abs(M.elements[i][j]) > Sylvester.precision) { rank++; break; } |
|
} while (--nj); |
|
} while (--ni); |
|
return rank; |
|
}, |
|
|
|
rk: function() { return this.rank(); }, |
|
|
|
// Returns the result of attaching the given argument to the right-hand side of the matrix |
|
augment: function(matrix) { |
|
var M = matrix.elements || matrix; |
|
if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; } |
|
var T = this.dup(), cols = T.elements[0].length; |
|
var ni = T.elements.length, ki = ni, i, nj, kj = M[0].length, j; |
|
if (ni != M.length) { return null; } |
|
do { i = ki - ni; |
|
nj = kj; |
|
do { j = kj - nj; |
|
T.elements[i][cols + j] = M[i][j]; |
|
} while (--nj); |
|
} while (--ni); |
|
return T; |
|
}, |
|
|
|
// Returns the inverse (if one exists) using Gauss-Jordan |
|
inverse: function() { |
|
if (!this.isSquare() || this.isSingular()) { return null; } |
|
var ni = this.elements.length, ki = ni, i, j; |
|
var M = this.augment(Matrix.I(ni)).toRightTriangular(); |
|
var np, kp = M.elements[0].length, p, els, divisor; |
|
var inverse_elements = [], new_element; |
|
// Matrix is non-singular so there will be no zeros on the diagonal |
|
// Cycle through rows from last to first |
|
do { i = ni - 1; |
|
// First, normalise diagonal elements to 1 |
|
els = []; np = kp; |
|
inverse_elements[i] = []; |
|
divisor = M.elements[i][i]; |
|
do { p = kp - np; |
|
new_element = M.elements[i][p] / divisor; |
|
els.push(new_element); |
|
// Shuffle of the current row of the right hand side into the results |
|
// array as it will not be modified by later runs through this loop |
|
if (p >= ki) { inverse_elements[i].push(new_element); } |
|
} while (--np); |
|
M.elements[i] = els; |
|
// Then, subtract this row from those above it to |
|
// give the identity matrix on the left hand side |
|
for (j = 0; j < i; j++) { |
|
els = []; np = kp; |
|
do { p = kp - np; |
|
els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i]); |
|
} while (--np); |
|
M.elements[j] = els; |
|
} |
|
} while (--ni); |
|
return Matrix.create(inverse_elements); |
|
}, |
|
|
|
inv: function() { return this.inverse(); }, |
|
|
|
// Returns the result of rounding all the elements |
|
round: function() { |
|
return this.map(function(x) { return Math.round(x); }); |
|
}, |
|
|
|
// Returns a copy of the matrix with elements set to the given value if they |
|
// differ from it by less than Sylvester.precision |
|
snapTo: function(x) { |
|
return this.map(function(p) { |
|
return (Math.abs(p - x) <= Sylvester.precision) ? x : p; |
|
}); |
|
}, |
|
|
|
// Returns a string representation of the matrix |
|
inspect: function() { |
|
var matrix_rows = []; |
|
var n = this.elements.length, k = n, i; |
|
do { i = k - n; |
|
matrix_rows.push(Vector.create(this.elements[i]).inspect()); |
|
} while (--n); |
|
return matrix_rows.join('\n'); |
|
}, |
|
|
|
// 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); |
|
}; |
|
|
|
// Identity matrix of size n |
|
Matrix.I = function(n) { |
|
var els = [], k = n, i, nj, j; |
|
do { i = k - n; |
|
els[i] = []; nj = k; |
|
do { j = k - nj; |
|
els[i][j] = (i == j) ? 1 : 0; |
|
} while (--nj); |
|
} while (--n); |
|
return Matrix.create(els); |
|
}; |
|
|
|
// Diagonal matrix - all off-diagonal elements are zero |
|
Matrix.Diagonal = function(elements) { |
|
var n = elements.length, k = n, i; |
|
var M = Matrix.I(n); |
|
do { i = k - n; |
|
M.elements[i][i] = elements[i]; |
|
} while (--n); |
|
return M; |
|
}; |
|
|
|
// Rotation matrix about some axis. If no axis is |
|
// supplied, assume we're after a 2D transform |
|
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 ] |
|
]); |
|
}; |
|
|
|
// Special case rotations |
|
Matrix.RotationX = function(t) { |
|
var c = Math.cos(t), s = Math.sin(t); |
|
return Matrix.create([ |
|
[ 1, 0, 0 ], |
|
[ 0, c, -s ], |
|
[ 0, s, c ] |
|
]); |
|
}; |
|
Matrix.RotationY = function(t) { |
|
var c = Math.cos(t), s = Math.sin(t); |
|
return Matrix.create([ |
|
[ c, 0, s ], |
|
[ 0, 1, 0 ], |
|
[ -s, 0, c ] |
|
]); |
|
}; |
|
Matrix.RotationZ = function(t) { |
|
var c = Math.cos(t), s = Math.sin(t); |
|
return Matrix.create([ |
|
[ c, -s, 0 ], |
|
[ s, c, 0 ], |
|
[ 0, 0, 1 ] |
|
]); |
|
}; |
|
|
|
// Random matrix of n rows, m columns |
|
Matrix.Random = function(n, m) { |
|
return Matrix.Zero(n, m).map( |
|
function() { return Math.random(); } |
|
); |
|
}; |
|
|
|
// Matrix filled with zeros |
|
Matrix.Zero = function(n, m) { |
|
var els = [], ni = n, i, nj, j; |
|
do { i = n - ni; |
|
els[i] = []; |
|
nj = m; |
|
do { j = m - nj; |
|
els[i][j] = 0; |
|
} while (--nj); |
|
} while (--ni); |
|
return Matrix.create(els); |
|
}; |
|
|
|
|
|
|
|
function Line() {} |
|
Line.prototype = { |
|
|
|
// Returns true if the argument occupies the same space as the line |
|
eql: function(line) { |
|
return (this.isParallelTo(line) && this.contains(line.anchor)); |
|
}, |
|
|
|
// Returns a copy of the line |
|
dup: function() { |
|
return Line.create(this.anchor, this.direction); |
|
}, |
|
|
|
// Returns the result of translating the line by the given vector/array |
|
translate: function(vector) { |
|
var V = vector.elements || vector; |
|
return Line.create([ |
|
this.anchor.elements[0] + V[0], |
|
this.anchor.elements[1] + V[1], |
|
this.anchor.elements[2] + (V[2] || 0) |
|
], this.direction); |
|
}, |
|
|
|
// Returns true if the line is parallel to the argument. Here, 'parallel to' |
|
// means that the argument's direction is either parallel or antiparallel to |
|
// the line's own direction. A line is parallel to a plane if the two do not |
|
// have a unique intersection. |
|
isParallelTo: function(obj) { |
|
if (obj.normal) { return obj.isParallelTo(this); } |
|
var theta = this.direction.angleFrom(obj.direction); |
|
return (Math.abs(theta) <= Sylvester.precision || Math.abs(theta - Math.PI) <= Sylvester.precision); |
|
}, |
|
|
|
// 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 true iff the line lies in the given plane |
|
liesIn: function(plane) { |
|
return plane.contains(this); |
|
}, |
|
|
|
// Returns true iff the line has a unique point of intersection with the argument |
|
intersects: function(obj) { |
|
if (obj.normal) { return obj.intersects(this); } |
|
return (!this.isParallelTo(obj) && this.distanceFrom(obj) <= Sylvester.precision); |
|
}, |
|
|
|
// Returns the unique intersection point with the argument, if one exists |
|
intersectionWith: function(obj) { |
|
if (obj.normal) { return obj.intersectionWith(this); } |
|
if (!this.intersects(obj)) { return null; } |
|
var P = this.anchor.elements, X = this.direction.elements, |
|
Q = obj.anchor.elements, Y = obj.direction.elements; |
|
var X1 = X[0], X2 = X[1], X3 = X[2], Y1 = Y[0], Y2 = Y[1], Y3 = Y[2]; |
|
var PsubQ1 = P[0] - Q[0], PsubQ2 = P[1] - Q[1], PsubQ3 = P[2] - Q[2]; |
|
var XdotQsubP = - X1*PsubQ1 - X2*PsubQ2 - X3*PsubQ3; |
|
var YdotPsubQ = Y1*PsubQ1 + Y2*PsubQ2 + Y3*PsubQ3; |
|
var XdotX = X1*X1 + X2*X2 + X3*X3; |
|
var YdotY = Y1*Y1 + Y2*Y2 + Y3*Y3; |
|
var XdotY = X1*Y1 + X2*Y2 + X3*Y3; |
|
var k = (XdotQsubP * YdotY / XdotX + XdotY * YdotPsubQ) / (YdotY - XdotY * XdotY); |
|
return Vector.create([P[0] + k*X1, P[1] + k*X2, P[2] + k*X3]); |
|
}, |
|
|
|
// 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] |
|
]); |
|
}, |
|
|
|
// Returns the line's reflection in the given point or line |
|
reflectionIn: function(obj) { |
|
if (obj.normal) { |
|
// obj is a plane |
|
var A = this.anchor.elements, D = this.direction.elements; |
|
var A1 = A[0], A2 = A[1], A3 = A[2], D1 = D[0], D2 = D[1], D3 = D[2]; |
|
var newA = this.anchor.reflectionIn(obj).elements; |
|
// Add the line's direction vector to its anchor, then mirror that in the plane |
|
var AD1 = A1 + D1, AD2 = A2 + D2, AD3 = A3 + D3; |
|
var Q = obj.pointClosestTo([AD1, AD2, AD3]).elements; |
|
var newD = [Q[0] + (Q[0] - AD1) - newA[0], Q[1] + (Q[1] - AD2) - newA[1], Q[2] + (Q[2] - AD3) - newA[2]]; |
|
return Line.create(newA, newD); |
|
} else if (obj.direction) { |
|
// obj is a line - reflection obtained by rotating PI radians about obj |
|
return this.rotate(Math.PI, obj); |
|
} else { |
|
// obj is a point - just reflect the line's anchor in it |
|
var P = obj.elements || obj; |
|
return Line.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.direction); |
|
} |
|
}, |
|
|
|
// 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); |
|
}; |
|
|
|
// Axes |
|
Line.X = Line.create(Vector.Zero(3), Vector.i); |
|
Line.Y = Line.create(Vector.Zero(3), Vector.j); |
|
Line.Z = Line.create(Vector.Zero(3), Vector.k); |
|
|
|
|
|
|
|
function Plane() {} |
|
Plane.prototype = { |
|
|
|
// Returns true iff the plane occupies the same space as the argument |
|
eql: function(plane) { |
|
return (this.contains(plane.anchor) && this.isParallelTo(plane)); |
|
}, |
|
|
|
// Returns a copy of the plane |
|
dup: function() { |
|
return Plane.create(this.anchor, this.normal); |
|
}, |
|
|
|
// Returns the result of translating the plane by the given vector |
|
translate: function(vector) { |
|
var V = vector.elements || vector; |
|
return Plane.create([ |
|
this.anchor.elements[0] + V[0], |
|
this.anchor.elements[1] + V[1], |
|
this.anchor.elements[2] + (V[2] || 0) |
|
], this.normal); |
|
}, |
|
|
|
// Returns true iff the plane is parallel to the argument. Will return true |
|
// if the planes are equal, or if you give a line and it lies in the plane. |
|
isParallelTo: function(obj) { |
|
var theta; |
|
if (obj.normal) { |
|
// obj is a plane |
|
theta = this.normal.angleFrom(obj.normal); |
|
return (Math.abs(theta) <= Sylvester.precision || Math.abs(Math.PI - theta) <= Sylvester.precision); |
|
} else if (obj.direction) { |
|
// obj is a line |
|
return this.normal.isPerpendicularTo(obj.direction); |
|
} |
|
return null; |
|
}, |
|
|
|
// Returns true iff the receiver is perpendicular to the argument |
|
isPerpendicularTo: function(plane) { |
|
var theta = this.normal.angleFrom(plane.normal); |
|
return (Math.abs(Math.PI/2 - theta) <= Sylvester.precision); |
|
}, |
|
|
|
// Returns the plane's distance from the given object (point, line or plane) |
|
distanceFrom: function(obj) { |
|
if (this.intersects(obj) || this.contains(obj)) { return 0; } |
|
if (obj.anchor) { |
|
// obj is a plane or line |
|
var A = this.anchor.elements, B = obj.anchor.elements, N = this.normal.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, N = this.normal.elements; |
|
return Math.abs((A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2]); |
|
} |
|
}, |
|
|
|
// Returns true iff the plane contains the given point or line |
|
contains: function(obj) { |
|
if (obj.normal) { return null; } |
|
if (obj.direction) { |
|
return (this.contains(obj.anchor) && this.contains(obj.anchor.add(obj.direction))); |
|
} else { |
|
var P = obj.elements || obj; |
|
var A = this.anchor.elements, N = this.normal.elements; |
|
var diff = Math.abs(N[0]*(A[0] - P[0]) + N[1]*(A[1] - P[1]) + N[2]*(A[2] - (P[2] || 0))); |
|
return (diff <= Sylvester.precision); |
|
} |
|
}, |
|
|
|
// Returns true iff the plane has a unique point/line of intersection with the argument |
|
intersects: function(obj) { |
|
if (typeof(obj.direction) == 'undefined' && typeof(obj.normal) == 'undefined') { return null; } |
|
return !this.isParallelTo(obj); |
|
}, |
|
|
|
// Returns the unique intersection with the argument, if one exists. The result |
|
// will be a vector if a line is supplied, and a line if a plane is supplied. |
|
intersectionWith: function(obj) { |
|
if (!this.intersects(obj)) { return null; } |
|
if (obj.direction) { |
|
// obj is a line |
|
var A = obj.anchor.elements, D = obj.direction.elements, |
|
P = this.anchor.elements, N = this.normal.elements; |
|
var multiplier = (N[0]*(P[0]-A[0]) + N[1]*(P[1]-A[1]) + N[2]*(P[2]-A[2])) / (N[0]*D[0] + N[1]*D[1] + N[2]*D[2]); |
|
return Vector.create([A[0] + D[0]*multiplier, A[1] + D[1]*multiplier, A[2] + D[2]*multiplier]); |
|
} else if (obj.normal) { |
|
// obj is a plane |
|
var direction = this.normal.cross(obj.normal).toUnitVector(); |
|
// To find an anchor point, we find one co-ordinate that has a value |
|
// of zero somewhere on the intersection, and remember which one we picked |
|
var N = this.normal.elements, A = this.anchor.elements, |
|
O = obj.normal.elements, B = obj.anchor.elements; |
|
var solver = Matrix.Zero(2,2), i = 0; |
|
while (solver.isSingular()) { |
|
i++; |
|
solver = Matrix.create([ |
|
[ N[i%3], N[(i+1)%3] ], |
|
[ O[i%3], O[(i+1)%3] ] |
|
]); |
|
} |
|
// Then we solve the simultaneous equations in the remaining dimensions |
|
var inverse = solver.inverse().elements; |
|
var x = N[0]*A[0] + N[1]*A[1] + N[2]*A[2]; |
|
var y = O[0]*B[0] + O[1]*B[1] + O[2]*B[2]; |
|
var intersection = [ |
|
inverse[0][0] * x + inverse[0][1] * y, |
|
inverse[1][0] * x + inverse[1][1] * y |
|
]; |
|
var anchor = []; |
|
for (var j = 1; j <= 3; j++) { |
|
// This formula picks the right element from intersection by |
|
// cycling depending on which element we set to zero above |
|
anchor.push((i == j) ? 0 : intersection[(j + (5 - i)%3)%3]); |
|
} |
|
return Line.create(anchor, direction); |
|
} |
|
}, |
|
|
|
// Returns the point in the plane closest to the given point |
|
pointClosestTo: function(point) { |
|
var P = point.elements || point; |
|
var A = this.anchor.elements, N = this.normal.elements; |
|
var dot = (A[0] - P[0]) * N[0] + (A[1] - P[1]) * N[1] + (A[2] - (P[2] || 0)) * N[2]; |
|
return Vector.create([P[0] + N[0] * dot, P[1] + N[1] * dot, (P[2] || 0) + N[2] * dot]); |
|
}, |
|
|
|
// Returns a copy of the plane, rotated by t radians about the given line |
|
// See notes on Line#rotate. |
|
rotate: function(t, line) { |
|
var R = Matrix.Rotation(t, line.direction).elements; |
|
var C = line.pointClosestTo(this.anchor).elements; |
|
var A = this.anchor.elements, N = this.normal.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 Plane.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] * N[0] + R[0][1] * N[1] + R[0][2] * N[2], |
|
R[1][0] * N[0] + R[1][1] * N[1] + R[1][2] * N[2], |
|
R[2][0] * N[0] + R[2][1] * N[1] + R[2][2] * N[2] |
|
]); |
|
}, |
|
|
|
// Returns the reflection of the plane in the given point, line or plane. |
|
reflectionIn: function(obj) { |
|
if (obj.normal) { |
|
// obj is a plane |
|
var A = this.anchor.elements, N = this.normal.elements; |
|
var A1 = A[0], A2 = A[1], A3 = A[2], N1 = N[0], N2 = N[1], N3 = N[2]; |
|
var newA = this.anchor.reflectionIn(obj).elements; |
|
// Add the plane's normal to its anchor, then mirror that in the other plane |
|
var AN1 = A1 + N1, AN2 = A2 + N2, AN3 = A3 + N3; |
|
var Q = obj.pointClosestTo([AN1, AN2, AN3]).elements; |
|
var newN = [Q[0] + (Q[0] - AN1) - newA[0], Q[1] + (Q[1] - AN2) - newA[1], Q[2] + (Q[2] - AN3) - newA[2]]; |
|
return Plane.create(newA, newN); |
|
} else if (obj.direction) { |
|
// obj is a line |
|
return this.rotate(Math.PI, obj); |
|
} else { |
|
// obj is a point |
|
var P = obj.elements || obj; |
|
return Plane.create(this.anchor.reflectionIn([P[0], P[1], (P[2] || 0)]), this.normal); |
|
} |
|
}, |
|
|
|
// Sets the anchor point and normal to the plane. If three arguments are specified, |
|
// the normal is calculated by assuming the three points should lie in the same plane. |
|
// If only two are sepcified, the second is taken to be the normal. Normal vector is |
|
// normalised before storage. |
|
setVectors: function(anchor, v1, v2) { |
|
anchor = Vector.create(anchor); |
|
anchor = anchor.to3D(); if (anchor === null) { return null; } |
|
v1 = Vector.create(v1); |
|
v1 = v1.to3D(); if (v1 === null) { return null; } |
|
if (typeof(v2) == 'undefined') { |
|
v2 = null; |
|
} else { |
|
v2 = Vector.create(v2); |
|
v2 = v2.to3D(); if (v2 === null) { return null; } |
|
} |
|
var A1 = anchor.elements[0], A2 = anchor.elements[1], A3 = anchor.elements[2]; |
|
var v11 = v1.elements[0], v12 = v1.elements[1], v13 = v1.elements[2]; |
|
var normal, mod; |
|
if (v2 !== null) { |
|
var v21 = v2.elements[0], v22 = v2.elements[1], v23 = v2.elements[2]; |
|
normal = Vector.create([ |
|
(v12 - A2) * (v23 - A3) - (v13 - A3) * (v22 - A2), |
|
(v13 - A3) * (v21 - A1) - (v11 - A1) * (v23 - A3), |
|
(v11 - A1) * (v22 - A2) - (v12 - A2) * (v21 - A1) |
|
]); |
|
mod = normal.modulus(); |
|
if (mod === 0) { return null; } |
|
normal = Vector.create([normal.elements[0] / mod, normal.elements[1] / mod, normal.elements[2] / mod]); |
|
} else { |
|
mod = Math.sqrt(v11*v11 + v12*v12 + v13*v13); |
|
if (mod === 0) { return null; } |
|
normal = Vector.create([v1.elements[0] / mod, v1.elements[1] / mod, v1.elements[2] / mod]); |
|
} |
|
this.anchor = anchor; |
|
this.normal = normal; |
|
return this; |
|
} |
|
}; |
|
|
|
// Constructor function |
|
Plane.create = function(anchor, v1, v2) { |
|
var P = new Plane(); |
|
return P.setVectors(anchor, v1, v2); |
|
}; |
|
|
|
// X-Y-Z planes |
|
Plane.XY = Plane.create(Vector.Zero(3), Vector.k); |
|
Plane.YZ = Plane.create(Vector.Zero(3), Vector.i); |
|
Plane.ZX = Plane.create(Vector.Zero(3), Vector.j); |
|
Plane.YX = Plane.XY; Plane.ZY = Plane.YZ; Plane.XZ = Plane.ZX; |
|
|
|
// Utility functions |
|
var $V = Vector.create; |
|
var $M = Matrix.create; |
|
var $L = Line.create; |
|
var $P = Plane.create; |
|
/*! |
|
* jQuery 2d Transform v0.9.3 |
|
* http://wiki.github.com/heygrady/transform/ |
|
* |
|
* Copyright 2010, Grady Kuhnline |
|
* Dual licensed under the MIT or GPL Version 2 licenses. |
|
* http://jquery.org/license |
|
* |
|
* Date: Sat Dec 4 15:46:09 2010 -0800 |
|
*/ |
|
/////////////////////////////////////////////////////// |
|
// Transform |
|
/////////////////////////////////////////////////////// |
|
(function($, window, document, undefined) { |
|
/** |
|
* @var Regex identify the matrix filter in IE |
|
*/ |
|
var rmatrix = /progid:DXImageTransform\.Microsoft\.Matrix\(.*?\)/, |
|
rfxnum = /^([\+\-]=)?([\d+.\-]+)(.*)$/, |
|
rperc = /%/; |
|
|
|
// Steal some code from Modernizr |
|
var m = document.createElement( 'modernizr' ), |
|
m_style = m.style; |
|
|
|
function stripUnits(arg) { |
|
return parseFloat(arg); |
|
} |
|
|
|
/** |
|
* Find the prefix that this browser uses |
|
*/ |
|
function getVendorPrefix() { |
|
var property = { |
|
transformProperty : '', |
|
MozTransform : '-moz-', |
|
WebkitTransform : '-webkit-', |
|
OTransform : '-o-', |
|
msTransform : '-ms-' |
|
}; |
|
for (var p in property) { |
|
if (typeof m_style[p] != 'undefined') { |
|
return property[p]; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
function supportCssTransforms() { |
|
if (typeof(window.Modernizr) !== 'undefined') { |
|
return Modernizr.csstransforms; |
|
} |
|
|
|
var props = [ 'transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ]; |
|
for ( var i in props ) { |
|
if ( m_style[ props[i] ] !== undefined ) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
// Capture some basic properties |
|
var vendorPrefix = getVendorPrefix(), |
|
transformProperty = vendorPrefix !== null ? vendorPrefix + 'transform' : false, |
|
transformOriginProperty = vendorPrefix !== null ? vendorPrefix + 'transform-origin' : false; |
|
|
|
// store support in the jQuery Support object |
|
$.support.csstransforms = supportCssTransforms(); |
|
|
|
// IE9 public preview 6 requires the DOM names |
|
if (vendorPrefix == '-ms-') { |
|
transformProperty = 'msTransform'; |
|
transformOriginProperty = 'msTransformOrigin'; |
|
} |
|
|
|
/** |
|
* Class for creating cross-browser transformations |
|
* @constructor |
|
*/ |
|
$.extend({ |
|
transform: function(elem) { |
|
// Cache the transform object on the element itself |
|
elem.transform = this; |
|
|
|
/** |
|
* The element we're working with |
|
* @var jQueryCollection |
|
*/ |
|
this.$elem = $(elem); |
|
|
|
/** |
|
* Remember the matrix we're applying to help the safeOuterLength func |
|
*/ |
|
this.applyingMatrix = false; |
|
this.matrix = null; |
|
|
|
/** |
|
* Remember the css height and width to save time |
|
* This is only really used in IE |
|
* @var Number |
|
*/ |
|
this.height = null; |
|
this.width = null; |
|
this.outerHeight = null; |
|
this.outerWidth = null; |
|
|
|
/** |
|
* We need to know the box-sizing in IE for building the outerHeight and outerWidth |
|
* @var string |
|
*/ |
|
this.boxSizingValue = null; |
|
this.boxSizingProperty = null; |
|
|
|
this.attr = null; |
|
this.transformProperty = transformProperty; |
|
this.transformOriginProperty = transformOriginProperty; |
|
} |
|
}); |
|
|
|
$.extend($.transform, { |
|
/** |
|
* @var Array list of all valid transform functions |
|
*/ |
|
funcs: ['matrix', 'origin', 'reflect', 'reflectX', 'reflectXY', 'reflectY', 'rotate', 'scale', 'scaleX', 'scaleY', 'skew', 'skewX', 'skewY', 'translate', 'translateX', 'translateY'] |
|
}); |
|
|
|
/** |
|
* Create Transform as a jQuery plugin |
|
* @param Object funcs |
|
* @param Object options |
|
*/ |
|
$.fn.transform = function(funcs, options) { |
|
return this.each(function() { |
|
var t = this.transform || new $.transform(this); |
|
if (funcs) { |
|
t.exec(funcs, options); |
|
} |
|
}); |
|
}; |
|
|
|
$.transform.prototype = { |
|
/** |
|
* Applies all of the transformations |
|
* @param Object funcs |
|
* @param Object options |
|
* forceMatrix - uses the matrix in all browsers |
|
* preserve - tries to preserve the values from previous runs |
|
*/ |
|
exec: function(funcs, options) { |
|
// extend options |
|
options = $.extend(true, { |
|
forceMatrix: false, |
|
preserve: false |
|
}, options); |
|
|
|
// preserve the funcs from the previous run |
|
this.attr = null; |
|
if (options.preserve) { |
|
funcs = $.extend(true, this.getAttrs(true, true), funcs); |
|
} else { |
|
funcs = $.extend(true, {}, funcs); // copy the object to prevent weirdness |
|
} |
|
|
|
// Record the custom attributes on the element itself |
|
this.setAttrs(funcs); |
|
|
|
// apply the funcs |
|
if ($.support.csstransforms && !options.forceMatrix) { |
|
// CSS3 is supported |
|
return this.execFuncs(funcs); |
|
} else if ($.browser.msie || ($.support.csstransforms && options.forceMatrix)) { |
|
// Internet Explorer or Forced matrix |
|
return this.execMatrix(funcs); |
|
} |
|
return false; |
|
}, |
|
|
|
/** |
|
* Applies all of the transformations as functions |
|
* @param Object funcs |
|
*/ |
|
execFuncs: function(funcs) { |
|
var values = []; |
|
|
|
// construct a CSS string |
|
for (var func in funcs) { |
|
// handle origin separately |
|
if (func == 'origin') { |
|
this[func].apply(this, $.isArray(funcs[func]) ? funcs[func] : [funcs[func]]); |
|
} else if ($.inArray(func, $.transform.funcs) !== -1) { |
|
values.push(this.createTransformFunc(func, funcs[func])); |
|
} |
|
} |
|
this.$elem.css(transformProperty, values.join(' ')); |
|
return true; |
|
}, |
|
|
|
/** |
|
* Applies all of the transformations as a matrix |
|
* @param Object funcs |
|
*/ |
|
execMatrix: function(funcs) { |
|
var matrix, |
|
tempMatrix, |
|
args; |
|
|
|
var elem = this.$elem[0], |
|
_this = this; |
|
function normalPixels(val, i) { |
|
if (rperc.test(val)) { |
|
// this really only applies to translation |
|
return parseFloat(val) / 100 * _this['safeOuter' + (i ? 'Height' : 'Width')](); |
|
} |
|
return toPx(elem, val); |
|
} |
|
|
|
var rtranslate = /translate[X|Y]?/, |
|
trans = []; |
|
|
|
for (var func in funcs) { |
|
switch ($.type(funcs[func])) { |
|
case 'array': args = funcs[func]; break; |
|
case 'string': args = $.map(funcs[func].split(','), $.trim); break; |
|
default: args = [funcs[func]]; |
|
} |
|
|
|
if ($.matrix[func]) { |
|
|
|
if ($.cssAngle[func]) { |
|
// normalize on degrees |
|
args = $.map(args, $.angle.toDegree); |
|
} else if (!$.cssNumber[func]) { |
|
// normalize to pixels |
|
args = $.map(args, normalPixels); |
|
} else { |
|
// strip units |
|
args = $.map(args, stripUnits); |
|
} |
|
|
|
tempMatrix = $.matrix[func].apply(this, args); |
|
if (rtranslate.test(func)) { |
|
//defer translation |
|
trans.push(tempMatrix); |
|
} else { |
|
matrix = matrix ? matrix.x(tempMatrix) : tempMatrix; |
|
} |
|
} else if (func == 'origin') { |
|
this[func].apply(this, args); |
|
} |
|
} |
|
|
|
// check that we have a matrix |
|
matrix = matrix || $.matrix.identity(); |
|
|
|
// Apply translation |
|
$.each(trans, function(i, val) { matrix = matrix.x(val); }); |
|
|
|
// pull out the relevant values |
|
var a = parseFloat(matrix.e(1,1).toFixed(6)), |
|
b = parseFloat(matrix.e(2,1).toFixed(6)), |
|
c = parseFloat(matrix.e(1,2).toFixed(6)), |
|
d = parseFloat(matrix.e(2,2).toFixed(6)), |
|
tx = matrix.rows === 3 ? parseFloat(matrix.e(1,3).toFixed(6)) : 0, |
|
ty = matrix.rows === 3 ? parseFloat(matrix.e(2,3).toFixed(6)) : 0; |
|
|
|
//apply the transform to the element |
|
if ($.support.csstransforms && vendorPrefix === '-moz-') { |
|
// -moz- |
|
this.$elem.css(transformProperty, 'matrix(' + a + ', ' + b + ', ' + c + ', ' + d + ', ' + tx + 'px, ' + ty + 'px)'); |
|
} else if ($.support.csstransforms) { |
|
// -webkit, -o-, w3c |
|
// NOTE: WebKit and Opera don't allow units on the translate variables |
|
this.$elem.css(transformProperty, 'matrix(' + a + ', ' + b + ', ' + c + ', ' + d + ', ' + tx + ', ' + ty + ')'); |
|
} else if ($.browser.msie) { |
|
// IE requires the special transform Filter |
|
|
|
//TODO: Use Nearest Neighbor during animation FilterType=\'nearest neighbor\' |
|
var filterType = ', FilterType=\'nearest neighbor\''; //bilinear |
|
var style = this.$elem[0].style; |
|
var matrixFilter = 'progid:DXImageTransform.Microsoft.Matrix(' + |
|
'M11=' + a + ', M12=' + c + ', M21=' + b + ', M22=' + d + |
|
', sizingMethod=\'auto expand\'' + filterType + ')'; |
|
var filter = style.filter || $.curCSS( this.$elem[0], "filter" ) || ""; |
|
style.filter = rmatrix.test(filter) ? filter.replace(rmatrix, matrixFilter) : filter ? filter + ' ' + matrixFilter : matrixFilter; |
|
|
|
// Let's know that we're applying post matrix fixes and the height/width will be static for a bit |
|
this.applyingMatrix = true; |
|
this.matrix = matrix; |
|
|
|
// IE can't set the origin or translate directly |
|
this.fixPosition(matrix, tx, ty); |
|
|
|
this.applyingMatrix = false; |
|
this.matrix = null; |
|
} |
|
return true; |
|
}, |
|
|
|
/** |
|
* Sets the transform-origin |
|
* This really needs to be percentages |
|
* @param Number x length |
|
* @param Number y length |
|
*/ |
|
origin: function(x, y) { |
|
// use CSS in supported browsers |
|
if ($.support.csstransforms) { |
|
if (typeof y === 'undefined') { |
|
this.$elem.css(transformOriginProperty, x); |
|
} else { |
|
this.$elem.css(transformOriginProperty, x + ' ' + y); |
|
} |
|
return true; |
|
} |
|
|
|
// correct for keyword lengths |
|
switch (x) { |
|
case 'left': x = '0'; break; |
|
case 'right': x = '100%'; break; |
|
case 'center': // no break |
|
case undefined: x = '50%'; |
|
} |
|
switch (y) { |
|
case 'top': y = '0'; break; |
|
case 'bottom': y = '100%'; break; |
|
case 'center': // no break |
|
case undefined: y = '50%'; //TODO: does this work? |
|
} |
|
|
|
// store mixed values with units, assumed pixels |
|
this.setAttr('origin', [ |
|
rperc.test(x) ? x : toPx(this.$elem[0], x) + 'px', |
|
rperc.test(y) ? y : toPx(this.$elem[0], y) + 'px' |
|
]); |
|
//console.log(this.getAttr('origin')); |
|
return true; |
|
}, |
|
|
|
/** |
|
* Create a function suitable for a CSS value |
|
* @param string func |
|
* @param Mixed value |
|
*/ |
|
createTransformFunc: function(func, value) { |
|
if (func.substr(0, 7) === 'reflect') { |
|
// let's fake reflection, false value |
|
// falsey sets an identity matrix |
|
var m = value ? $.matrix[func]() : $.matrix.identity(); |
|
return 'matrix(' + m.e(1,1) + ', ' + m.e(2,1) + ', ' + m.e(1,2) + ', ' + m.e(2,2) + ', 0, 0)'; |
|
} |
|
|
|
//value = _correctUnits(func, value); |
|
|
|
if (func == 'matrix') { |
|
if (vendorPrefix === '-moz-') { |
|
value[4] = value[4] ? value[4] + 'px' : 0; |
|
value[5] = value[5] ? value[5] + 'px' : 0; |
|
} |
|
} |
|
return func + '(' + ($.isArray(value) ? value.join(', ') : value) + ')'; |
|
}, |
|
|
|
/** |
|
* @param Matrix matrix |
|
* @param Number tx |
|
* @param Number ty |
|
* @param Number height |
|
* @param Number width |
|
*/ |
|
fixPosition: function(matrix, tx, ty, height, width) { |
|
// now we need to fix it! |
|
var calc = new $.matrix.calc(matrix, this.safeOuterHeight(), this.safeOuterWidth()), |
|
origin = this.getAttr('origin'); // mixed percentages and px |
|
|
|
// translate a 0, 0 origin to the current origin |
|
var offset = calc.originOffset(new $.matrix.V2( |
|
rperc.test(origin[0]) ? parseFloat(origin[0])/100*calc.outerWidth : parseFloat(origin[0]), |
|
rperc.test(origin[1]) ? parseFloat(origin[1])/100*calc.outerHeight : parseFloat(origin[1]) |
|
)); |
|
|
|
// IE glues the top-most and left-most pixels of the transformed object to top/left of the original object |
|
//TODO: This seems wrong in the calculations |
|
var sides = calc.sides(); |
|
|
|
// Protect against an item that is already positioned |
|
var cssPosition = this.$elem.css('position'); |
|
if (cssPosition == 'static') { |
|
cssPosition = 'relative'; |
|
} |
|
|
|
//TODO: if the element is already positioned, we should attempt to respect it (somehow) |
|
//NOTE: we could preserve our offset top and left in an attr on the elem |
|
var pos = {top: 0, left: 0}; |
|
|
|
// Approximates transform-origin, tx, and ty |
|
var css = { |
|
'position': cssPosition, |
|
'top': (offset.top + ty + sides.top + pos.top) + 'px', |
|
'left': (offset.left + tx + sides.left + pos.left) + 'px', |
|
'zoom': 1 |
|
}; |
|
|
|
this.$elem.css(css); |
|
} |
|
}; |
|
|
|
/** |
|
* Ensure that values have the appropriate units on them |
|
* @param string func |
|
* @param Mixed value |
|
*/ |
|
function toPx(elem, val) { |
|
var parts = rfxnum.exec($.trim(val)); |
|
|
|
if (parts[3] && parts[3] !== 'px') { |
|
var prop = 'paddingBottom', |
|
orig = $.style( elem, prop ); |
|
|
|
$.style( elem, prop, val ); |
|
val = cur( elem, prop ); |
|
$.style( elem, prop, orig ); |
|
return val; |
|
} |
|
return parseFloat( val ); |
|
} |
|
|
|
function cur(elem, prop) { |
|
if ( elem[prop] != null && (!elem.style || elem.style[prop] == null) ) { |
|
return elem[ prop ]; |
|
} |
|
|
|
var r = parseFloat( $.css( elem, prop ) ); |
|
return r && r > -10000 ? r : 0; |
|
} |
|
})(jQuery, this, this.document); |
|
|
|
|
|
/////////////////////////////////////////////////////// |
|
// Safe Outer Length |
|
/////////////////////////////////////////////////////// |
|
(function($, window, document, undefined) { |
|
$.extend($.transform.prototype, { |
|
/** |
|
* @param void |
|
* @return Number |
|
*/ |
|
safeOuterHeight: function() { |
|
return this.safeOuterLength('height'); |
|
}, |
|
|
|
/** |
|
* @param void |
|
* @return Number |
|
*/ |
|
safeOuterWidth: function() { |
|
return this.safeOuterLength('width'); |
|
}, |
|
|
|
/** |
|
* Returns reliable outer dimensions for an object that may have been transformed. |
|
* Only use this if the matrix isn't handy |
|
* @param String dim height or width |
|
* @return Number |
|
*/ |
|
safeOuterLength: function(dim) { |
|
var funcName = 'outer' + (dim == 'width' ? 'Width' : 'Height'); |
|
|
|
if (!$.support.csstransforms && $.browser.msie) { |
|
// make the variables more generic |
|
dim = dim == 'width' ? 'width' : 'height'; |
|
|
|
// if we're transforming and have a matrix; we can shortcut. |
|
// the true outerHeight is the transformed outerHeight divided by the ratio. |
|
// the ratio is equal to the height of a 1px by 1px box that has been transformed by the same matrix. |
|
if (this.applyingMatrix && !this[funcName] && this.matrix) { |
|
// calculate and return the correct size |
|
var calc = new $.matrix.calc(this.matrix, 1, 1), |
|
ratio = calc.offset(), |
|
length = this.$elem[funcName]() / ratio[dim]; |
|
this[funcName] = length; |
|
|
|
return length; |
|
} else if (this.applyingMatrix && this[funcName]) { |
|
// return the cached calculation |
|
return this[funcName]; |
|
} |
|
|
|
// map dimensions to box sides |
|
var side = { |
|
height: ['top', 'bottom'], |
|
width: ['left', 'right'] |
|
}; |
|
|
|
// setup some variables |
|
var elem = this.$elem[0], |
|
outerLen = parseFloat($.curCSS(elem, dim, true)), //TODO: this can be cached on animations that do not animate height/width |
|
boxSizingProp = this.boxSizingProperty, |
|
boxSizingValue = this.boxSizingValue; |
|
|
|
// IE6 && IE7 will never have a box-sizing property, so fake it |
|
if (!this.boxSizingProperty) { |
|
boxSizingProp = this.boxSizingProperty = _findBoxSizingProperty() || 'box-sizing'; |
|
boxSizingValue = this.boxSizingValue = this.$elem.css(boxSizingProp) || 'content-box'; |
|
} |
|
|
|
// return it immediately if we already know it |
|
if (this[funcName] && this[dim] == outerLen) { |
|
return this[funcName]; |
|
} else { |
|
this[dim] = outerLen; |
|
} |
|
|
|
// add in the padding and border |
|
if (boxSizingProp && (boxSizingValue == 'padding-box' || boxSizingValue == 'content-box')) { |
|
outerLen += parseFloat($.curCSS(elem, 'padding-' + side[dim][0], true)) || 0 + |
|
parseFloat($.curCSS(elem, 'padding-' + side[dim][1], true)) || 0; |
|
} |
|
if (boxSizingProp && boxSizingValue == 'content-box') { |
|
outerLen += parseFloat($.curCSS(elem, 'border-' + side[dim][0] + '-width', true)) || 0 + |
|
parseFloat($.curCSS(elem, 'border-' + side[dim][1] + '-width', true)) || 0; |
|
} |
|
|
|
// remember and return the outerHeight |
|
this[funcName] = outerLen; |
|
return outerLen; |
|
} |
|
return this.$elem[funcName](); |
|
} |
|
}); |
|
|
|
/** |
|
* Determine the correct property for checking the box-sizing property |
|
* @param void |
|
* @return string |
|
*/ |
|
var _boxSizingProperty = null; |
|
function _findBoxSizingProperty () { |
|
if (_boxSizingProperty) { |
|
return _boxSizingProperty; |
|
} |
|
|
|
var property = { |
|
boxSizing : 'box-sizing', |
|
MozBoxSizing : '-moz-box-sizing', |
|
WebkitBoxSizing : '-webkit-box-sizing', |
|
OBoxSizing : '-o-box-sizing' |
|
}, |
|
elem = document.body; |
|
|
|
for (var p in property) { |
|
if (typeof elem.style[p] != 'undefined') { |
|
_boxSizingProperty = property[p]; |
|
return _boxSizingProperty; |
|
} |
|
} |
|
return null; |
|
} |
|
})(jQuery, this, this.document); |
|
|
|
|
|
/////////////////////////////////////////////////////// |
|
// Attr |
|
/////////////////////////////////////////////////////// |
|
(function($, window, document, undefined) { |
|
var rfuncvalue = /([\w\-]*?)\((.*?)\)/g, // with values |
|
attr = 'data-transform', |
|
rspace = /\s/, |
|
rcspace = /,\s?/; |
|
|
|
$.extend($.transform.prototype, { |
|
/** |
|
* This overrides all of the attributes |
|
* @param Object funcs a list of transform functions to store on this element |
|
* @return void |
|
*/ |
|
setAttrs: function(funcs) { |
|
var string = '', |
|
value; |
|
for (var func in funcs) { |
|
value = funcs[func]; |
|
if ($.isArray(value)) { |
|
value = value.join(', '); |
|
} |
|
string += ' ' + func + '(' + value + ')'; |
|
} |
|
this.attr = $.trim(string); |
|
this.$elem.attr(attr, this.attr); |
|
}, |
|
|
|
/** |
|
* This sets only a specific atribute |
|
* @param string func name of a transform function |
|
* @param mixed value with proper units |
|
* @return void |
|
*/ |
|
setAttr: function(func, value) { |
|
// stringify the value |
|
if ($.isArray(value)) { |
|
value = value.join(', '); |
|
} |
|
|
|
// pull from a local variable to look it up |
|
var transform = this.attr || this.$elem.attr(attr); |
|
if (!transform || transform.indexOf(func) == -1) { |
|
// we don't have any existing values, save it |
|
// we don't have this function yet, save it |
|
this.attr = $.trim(transform + ' ' + func + '(' + value + ')'); |
|
this.$elem.attr(attr, this.attr); |
|
} else { |
|
// replace the existing value |
|
var funcs = [], parts; |
|
|
|
// regex split |
|
rfuncvalue.lastIndex = 0; // reset the regex pointer |
|
while (parts = rfuncvalue.exec(transform)) { |
|
if (func == parts[1]) { |
|
funcs.push(func + '(' + value + ')'); |
|
} else { |
|
funcs.push(parts[0]); |
|
} |
|
} |
|
this.attr = funcs.join(' '); |
|
this.$elem.attr(attr, this.attr); |
|
} |
|
}, |
|
|
|
/** |
|
* @return Object |
|
*/ |
|
getAttrs: function() { |
|
var transform = this.attr || this.$elem.attr(attr); |
|
if (!transform) { |
|
// We don't have any existing values, return empty object |
|
return {}; |
|
} |
|
|
|
// replace the existing value |
|
var attrs = {}, parts, value; |
|
|
|
rfuncvalue.lastIndex = 0; // reset the regex pointer |
|
while ((parts = rfuncvalue.exec(transform)) !== null) { |
|
if (parts) { |
|
value = parts[2].split(rcspace); |
|
attrs[parts[1]] = value.length == 1 ? value[0] : value; |
|
} |
|
} |
|
return attrs; |
|
}, |
|
|
|
/** |
|
* @param String func |
|
* @return mixed |
|
*/ |
|
getAttr: function(func) { |
|
var attrs = this.getAttrs(); |
|
if (typeof attrs[func] !== 'undefined') { |
|
return attrs[func]; |
|
} |
|
|
|
//TODO: move the origin to a function |
|
if (func === 'origin' && $.support.csstransforms) { |
|
// supported browsers return percentages always |
|
return this.$elem.css(this.transformOriginProperty).split(rspace); |
|
} else if (func === 'origin') { |
|
// just force IE to also return a percentage |
|
return ['50%', '50%']; |
|
} |
|
|
|
return $.cssDefault[func] || 0; |
|
} |
|
}); |
|
|
|
// Define |
|
if (typeof($.cssAngle) == 'undefined') { |
|
$.cssAngle = {}; |
|
} |
|
$.extend($.cssAngle, { |
|
rotate: true, |
|
skew: true, |
|
skewX: true, |
|
skewY: true |
|
}); |
|
|
|
// Define default values |
|
if (typeof($.cssDefault) == 'undefined') { |
|
$.cssDefault = {}; |
|
} |
|
|
|
$.extend($.cssDefault, { |
|
scale: [1, 1], |
|
scaleX: 1, |
|
scaleY: 1, |
|
matrix: [1, 0, 0, 1, 0, 0], |
|
origin: ['50%', '50%'], // TODO: allow this to be a function, like get |
|
reflect: [1, 0, 0, 1, 0, 0], |
|
reflectX: [1, 0, 0, 1, 0, 0], |
|
reflectXY: [1, 0, 0, 1, 0, 0], |
|
reflectY: [1, 0, 0, 1, 0, 0] |
|
}); |
|
|
|
// Define functons with multiple values |
|
if (typeof($.cssMultipleValues) == 'undefined') { |
|
$.cssMultipleValues = {}; |
|
} |
|
$.extend($.cssMultipleValues, { |
|
matrix: 6, |
|
origin: { |
|
length: 2, |
|
duplicate: true |
|
}, |
|
reflect: 6, |
|
reflectX: 6, |
|
reflectXY: 6, |
|
reflectY: 6, |
|
scale: { |
|
length: 2, |
|
duplicate: true |
|
}, |
|
skew: 2, |
|
translate: 2 |
|
}); |
|
|
|
// specify unitless funcs |
|
$.extend($.cssNumber, { |
|
matrix: true, |
|
reflect: true, |
|
reflectX: true, |
|
reflectXY: true, |
|
reflectY: true, |
|
scale: true, |
|
scaleX: true, |
|
scaleY: true |
|
}); |
|
|
|
// override all of the css functions |
|
$.each($.transform.funcs, function(i, func) { |
|
$.cssHooks[func] = { |
|
set: function(elem, value) { |
|
var transform = elem.transform || new $.transform(elem), |
|
funcs = {}; |
|
funcs[func] = value; |
|
transform.exec(funcs, {preserve: true}); |
|
}, |
|
get: function(elem, computed) { |
|
var transform = elem.transform || new $.transform(elem); |
|
return transform.getAttr(func); |
|
} |
|
}; |
|
}); |
|
|
|
// Support Reflection animation better by returning a matrix |
|
$.each(['reflect', 'reflectX', 'reflectXY', 'reflectY'], function(i, func) { |
|
$.cssHooks[func].get = function(elem, computed) { |
|
var transform = elem.transform || new $.transform(elem); |
|
return transform.getAttr('matrix') || $.cssDefault[func]; |
|
}; |
|
}); |
|
})(jQuery, this, this.document); |
|
/////////////////////////////////////////////////////// |
|
// Animation |
|
/////////////////////////////////////////////////////// |
|
(function($, window, document, undefined) { |
|
/** |
|
* @var Regex looks for units on a string |
|
*/ |
|
var rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/; |
|
|
|
/** |
|
* Doctors prop values in the event that they contain spaces |
|
* @param Object prop |
|
* @param String speed |
|
* @param String easing |
|
* @param Function callback |
|
* @return bool |
|
*/ |
|
var _animate = $.fn.animate; |
|
$.fn.animate = function( prop, speed, easing, callback ) { |
|
var optall = $.speed(speed, easing, callback), |
|
mv = $.cssMultipleValues; |
|
|
|
// Speed always creates a complete function that must be reset |
|
optall.complete = optall.old; |
|
|
|
// Capture multiple values |
|
if (!$.isEmptyObject(prop)) { |
|
if (typeof optall.original === 'undefined') { |
|
optall.original = {}; |
|
} |
|
$.each( prop, function( name, val ) { |
|
if (mv[name] |
|
|| $.cssAngle[name] |
|
|| (!$.cssNumber[name] && $.inArray(name, $.transform.funcs) !== -1)) { |
|
|
|
// Handle special easing |
|
var specialEasing = null; |
|
if (jQuery.isArray(prop[name])) { |
|
var mvlen = 1, len = val.length; |
|
if (mv[name]) { |
|
mvlen = (typeof mv[name].length === 'undefined' ? mv[name] : mv[name].length); |
|
} |
|
if ( len > mvlen |
|
|| (len < mvlen && len == 2) |
|
|| (len == 2 && mvlen == 2 && isNaN(parseFloat(val[len - 1])))) { |
|
|
|
specialEasing = val[len - 1]; |
|
val.splice(len - 1, 1); |
|
} |
|
} |
|
|
|
// Store the original values onto the optall |
|
optall.original[name] = val.toString(); |
|
|
|
// reduce to a unitless number (to trick animate) |
|
prop[name] = parseFloat(val); |
|
} |
|
} ); |
|
} |
|
|
|
//NOTE: we edited prop above to trick animate |
|
//NOTE: we pre-convert to an optall so we can doctor it |
|
return _animate.apply(this, [arguments[0], optall]); |
|
}; |
|
|
|
var prop = 'paddingBottom'; |
|
function cur(elem, prop) { |
|
if ( elem[prop] != null && (!elem.style || elem.style[prop] == null) ) { |
|
//return elem[ prop ]; |
|
} |
|
|
|
var r = parseFloat( $.css( elem, prop ) ); |
|
return r && r > -10000 ? r : 0; |
|
} |
|
|
|
var _custom = $.fx.prototype.custom; |
|
$.fx.prototype.custom = function(from, to, unit) { |
|
var multiple = $.cssMultipleValues[this.prop], |
|
angle = $.cssAngle[this.prop]; |
|
|
|
//TODO: simply check for the existence of CSS Hooks? |
|
if (multiple || (!$.cssNumber[this.prop] && $.inArray(this.prop, $.transform.funcs) !== -1)) { |
|
this.values = []; |
|
|
|
if (!multiple) { |
|
multiple = 1; |
|
} |
|
|
|
// Pull out the known values |
|
var values = this.options.original[this.prop], |
|
currentValues = $(this.elem).css(this.prop), |
|
defaultValues = $.cssDefault[this.prop] || 0; |
|
|
|
// make sure the current css value is an array |
|
if (!$.isArray(currentValues)) { |
|
currentValues = [currentValues]; |
|
} |
|
|
|
// make sure the new values are an array |
|
if (!$.isArray(values)) { |
|
if ($.type(values) === 'string') { |
|
values = values.split(','); |
|
} else { |
|
values = [values]; |
|
} |
|
} |
|
|
|
// make sure we have enough new values |
|
var length = multiple.length || multiple, i = 0; |
|
while (values.length < length) { |
|
values.push(multiple.duplicate ? values[0] : defaultValues[i] || 0); |
|
i++; |
|
} |
|
|
|
// calculate a start, end and unit for each new value |
|
var start, parts, end, //unit, |
|
fx = this, |
|
transform = fx.elem.transform; |
|
orig = $.style(fx.elem, prop); |
|
|
|
$.each(values, function(i, val) { |
|
// find a sensible start value |
|
if (currentValues[i]) { |
|
start = currentValues[i]; |
|
} else if (defaultValues[i] && !multiple.duplicate) { |
|
start = defaultValues[i]; |
|
} else if (multiple.duplicate) { |
|
start = currentValues[0]; |
|
} else { |
|
start = 0; |
|
} |
|
|
|
// Force the correct unit on the start |
|
if (angle) { |
|
start = $.angle.toDegree(start); |
|
} else if (!$.cssNumber[fx.prop]) { |
|
parts = rfxnum.exec($.trim(start)); |
|
if (parts[3] && parts[3] !== 'px') { |
|
if (parts[3] === '%') { |
|
start = parseFloat( parts[2] ) / 100 * transform['safeOuter' + (i ? 'Height' : 'Width')](); |
|
} else { |
|
$.style( fx.elem, prop, start); |
|
start = cur(fx.elem, prop); |
|
$.style( fx.elem, prop, orig); |
|
} |
|
} |
|
} |
|
start = parseFloat(start); |
|
|
|
// parse the value with a regex |
|
parts = rfxnum.exec($.trim(val)); |
|
|
|
if (parts) { |
|
// we found a sensible value and unit |
|
end = parseFloat( parts[2] ); |
|
unit = parts[3] || "px"; //TODO: change to an appropriate default unit |
|
|
|
if (angle) { |
|
end = $.angle.toDegree(end + unit); |
|
unit = 'deg'; |
|
} else if (!$.cssNumber[fx.prop] && unit === '%') { |
|
start = (start / transform['safeOuter' + (i ? 'Height' : 'Width')]()) * 100; |
|
} else if (!$.cssNumber[fx.prop] && unit !== 'px') { |
|
$.style( fx.elem, prop, (end || 1) + unit); |
|
start = ((end || 1) / cur(fx.elem, prop)) * start; |
|
$.style( fx.elem, prop, orig); |
|
} |
|
|
|
// If a +=/-= token was provided, we're doing a relative animation |
|
if (parts[1]) { |
|
end = ((parts[1] === "-=" ? -1 : 1) * end) + start; |
|
} |
|
} else { |
|
// I don't know when this would happen |
|
end = val; |
|
unit = ''; |
|
} |
|
|
|
// Save the values |
|
fx.values.push({ |
|
start: start, |
|
end: end, |
|
unit: unit |
|
}); |
|
}); |
|
} |
|
return _custom.apply(this, arguments); |
|
}; |
|
|
|
/** |
|
* Animates a multi value attribute |
|
* @param Object fx |
|
* @return null |
|
*/ |
|
$.fx.multipleValueStep = { |
|
_default: function(fx) { |
|
$.each(fx.values, function(i, val) { |
|
fx.values[i].now = val.start + ((val.end - val.start) * fx.pos); |
|
}); |
|
} |
|
}; |
|
$.each(['matrix', 'reflect', 'reflectX', 'reflectXY', 'reflectY'], function(i, func) { |
|
$.fx.multipleValueStep[func] = function(fx) { |
|
var d = fx.decomposed, |
|
$m = $.matrix; |
|
m = $m.identity(); |
|
|
|
d.now = {}; |
|
|
|
// increment each part of the decomposition and recompose it |
|
$.each(d.start, function(k) { |
|
// calculate the current value |
|
d.now[k] = parseFloat(d.start[k]) + ((parseFloat(d.end[k]) - parseFloat(d.start[k])) * fx.pos); |
|
|
|
// skip functions that won't affect the transform |
|
if (((k === 'scaleX' || k === 'scaleY') && d.now[k] === 1) || |
|
(k !== 'scaleX' && k !== 'scaleY' && d.now[k] === 0)) { |
|
return true; |
|
} |
|
|
|
// calculating |
|
m = m.x($m[k](d.now[k])); |
|
}); |
|
|
|
// save the correct matrix values for the value of now |
|
var val; |
|
$.each(fx.values, function(i) { |
|
switch (i) { |
|
case 0: val = parseFloat(m.e(1, 1).toFixed(6)); break; |
|
case 1: val = parseFloat(m.e(2, 1).toFixed(6)); break; |
|
case 2: val = parseFloat(m.e(1, 2).toFixed(6)); break; |
|
case 3: val = parseFloat(m.e(2, 2).toFixed(6)); break; |
|
case 4: val = parseFloat(m.e(1, 3).toFixed(6)); break; |
|
case 5: val = parseFloat(m.e(2, 3).toFixed(6)); break; |
|
} |
|
fx.values[i].now = val; |
|
}); |
|
}; |
|
}); |
|
/** |
|
* Step for animating tranformations |
|
*/ |
|
$.each($.transform.funcs, function(i, func) { |
|
$.fx.step[func] = function(fx) { |
|
var transform = fx.elem.transform || new $.transform(fx.elem), |
|
funcs = {}; |
|
|
|
if ($.cssMultipleValues[func] || (!$.cssNumber[func] && $.inArray(func, $.transform.funcs) !== -1)) { |
|
($.fx.multipleValueStep[fx.prop] || $.fx.multipleValueStep._default)(fx); |
|
funcs[fx.prop] = []; |
|
$.each(fx.values, function(i, val) { |
|
funcs[fx.prop].push(val.now + ($.cssNumber[fx.prop] ? '' : val.unit)); |
|
}); |
|
} else { |
|
funcs[fx.prop] = fx.now + ($.cssNumber[fx.prop] ? '' : fx.unit); |
|
} |
|
|
|
transform.exec(funcs, {preserve: true}); |
|
}; |
|
}); |
|
|
|
// Support matrix animation |
|
$.each(['matrix', 'reflect', 'reflectX', 'reflectXY', 'reflectY'], function(i, func) { |
|
$.fx.step[func] = function(fx) { |
|
var transform = fx.elem.transform || new $.transform(fx.elem), |
|
funcs = {}; |
|
|
|
if (!fx.initialized) { |
|
fx.initialized = true; |
|
|
|
// Reflections need a sensible end value set |
|
if (func !== 'matrix') { |
|
var values = $.matrix[func]().elements; |
|
var val; |
|
$.each(fx.values, function(i) { |
|
switch (i) { |
|
case 0: val = values[0]; break; |
|
case 1: val = values[2]; break; |
|
case 2: val = values[1]; break; |
|
case 3: val = values[3]; break; |
|
default: val = 0; |
|
} |
|
fx.values[i].end = val; |
|
}); |
|
} |
|
|
|
// Decompose the start and end |
|
fx.decomposed = {}; |
|
var v = fx.values; |
|
|
|
fx.decomposed.start = $.matrix.matrix(v[0].start, v[1].start, v[2].start, v[3].start, v[4].start, v[5].start).decompose(); |
|
fx.decomposed.end = $.matrix.matrix(v[0].end, v[1].end, v[2].end, v[3].end, v[4].end, v[5].end).decompose(); |
|
} |
|
|
|
($.fx.multipleValueStep[fx.prop] || $.fx.multipleValueStep._default)(fx); |
|
funcs.matrix = []; |
|
$.each(fx.values, function(i, val) { |
|
funcs.matrix.push(val.now); |
|
}); |
|
|
|
transform.exec(funcs, {preserve: true}); |
|
}; |
|
}); |
|
})(jQuery, this, this.document); |
|
/////////////////////////////////////////////////////// |
|
// Angle |
|
/////////////////////////////////////////////////////// |
|
(function($, window, document, undefined) { |
|
/** |
|
* Converting a radian to a degree |
|
* @const |
|
*/ |
|
var RAD_DEG = 180/Math.PI; |
|
|
|
/** |
|
* Converting a radian to a grad |
|
* @const |
|
*/ |
|
var RAD_GRAD = 200/Math.PI; |
|
|
|
/** |
|
* Converting a degree to a radian |
|
* @const |
|
*/ |
|
var DEG_RAD = Math.PI/180; |
|
|
|
/** |
|
* Converting a degree to a grad |
|
* @const |
|
*/ |
|
var DEG_GRAD = 2/1.8; |
|
|
|
/** |
|
* Converting a grad to a degree |
|
* @const |
|
*/ |
|
var GRAD_DEG = 0.9; |
|
|
|
/** |
|
* Converting a grad to a radian |
|
* @const |
|
*/ |
|
var GRAD_RAD = Math.PI/200; |
|
|
|
|
|
var rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/; |
|
|
|
/** |
|
* Functions for converting angles |
|
* @var Object |
|
*/ |
|
$.extend({ |
|
angle: { |
|
/** |
|
* available units for an angle |
|
* @var Regex |
|
*/ |
|
runit: /(deg|g?rad)/, |
|
|
|
/** |
|
* Convert a radian into a degree |
|
* @param Number rad |
|
* @return Number |
|
*/ |
|
radianToDegree: function(rad) { |
|
return rad * RAD_DEG; |
|
}, |
|
|
|
/** |
|
* Convert a radian into a degree |
|
* @param Number rad |
|
* @return Number |
|
*/ |
|
radianToGrad: function(rad) { |
|
return rad * RAD_GRAD; |
|
}, |
|
|
|
/** |
|
* Convert a degree into a radian |
|
* @param Number deg |
|
* @return Number |
|
*/ |
|
degreeToRadian: function(deg) { |
|
return deg * DEG_RAD; |
|
}, |
|
|
|
/** |
|
* Convert a degree into a radian |
|
* @param Number deg |
|
* @return Number |
|
*/ |
|
degreeToGrad: function(deg) { |
|
return deg * DEG_GRAD; |
|
}, |
|
|
|
/** |
|
* Convert a grad into a degree |
|
* @param Number grad |
|
* @return Number |
|
*/ |
|
gradToDegree: function(grad) { |
|
return grad * GRAD_DEG; |
|
}, |
|
|
|
/** |
|
* Convert a grad into a radian |
|
* @param Number grad |
|
* @return Number |
|
*/ |
|
gradToRadian: function(grad) { |
|
return grad * GRAD_RAD; |
|
}, |
|
|
|
/** |
|
* Convert an angle with a unit to a degree |
|
* @param String val angle with a unit |
|
* @return Number |
|
*/ |
|
toDegree: function (val) { |
|
|