/**
* Matrix utilities.
*
* @class MatrixUtil
* @module matrix
**/
var MatrixUtil = {
/**
* Used as value for the _rounding method.
*
* @property _rounder
* @private
*/
_rounder: 100000,
/**
* Rounds values
*
* @method _round
* @private
*/
_round: function(val) {
val = Math.round(val * MatrixUtil._rounder) / MatrixUtil._rounder;
return val;
},
/**
* Converts a radian value to a degree.
*
* @method rad2deg
* @param {Number} rad Radian value to be converted.
* @return Number
*/
rad2deg: function(rad) {
var deg = rad * (180 / Math.PI);
return deg;
},
/**
* Converts a degree value to a radian.
*
* @method deg2rad
* @param {Number} deg Degree value to be converted to radian.
* @return Number
*/
deg2rad: function(deg) {
var rad = deg * (Math.PI / 180);
return rad;
},
/**
* Converts an angle to a radian
*
* @method angle2rad
* @param {Objecxt} val Value to be converted to radian.
* @return Number
*/
angle2rad: function(val) {
if (typeof val === 'string' && val.indexOf('rad') > -1) {
val = parseFloat(val);
} else { // default to deg
val = MatrixUtil.deg2rad(parseFloat(val));
}
return val;
},
/**
* Converts a transform object to an array of column vectors.
*
* / \
* | matrix[0][0] matrix[1][0] matrix[2][0] |
* | matrix[0][1] matrix[1][1] matrix[2][1] |
* | matrix[0][2] matrix[1][2] matrix[2][2] |
* \ /
*
* @method getnxn
* @return Array
*/
convertTransformToArray: function(matrix)
{
var matrixArray = [
[matrix.a, matrix.c, matrix.dx],
[matrix.b, matrix.d, matrix.dy],
[0, 0, 1]
];
return matrixArray;
},
/**
* Returns the determinant of a given matrix.
*
* / \
* | matrix[0][0] matrix[1][0] matrix[2][0] |
* | matrix[0][1] matrix[1][1] matrix[2][1] |
* | matrix[0][2] matrix[1][2] matrix[2][2] |
* | matrix[0][3] matrix[1][3] matrix[2][3] |
* \ /
*
* @method getDeterminant
* @param {Array} matrix An nxn matrix represented an array of vector (column) arrays. Each vector array has index for each row.
* @return Number
*/
getDeterminant: function(matrix)
{
var determinant = 0,
len = matrix.length,
i = 0,
multiplier;
if(len == 2)
{
return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
}
for(; i < len; ++i)
{
multiplier = matrix[i][0];
if(i % 2 === 0 || i === 0)
{
determinant += multiplier * MatrixUtil.getDeterminant(MatrixUtil.getMinors(matrix, i, 0));
}
else
{
determinant -= multiplier * MatrixUtil.getDeterminant(MatrixUtil.getMinors(matrix, i, 0));
}
}
return determinant;
},
/**
* Returns the inverse of a matrix
*
* @method inverse
* @param Array matrix An array representing an nxn matrix
* @return Array
*
* / \
* | matrix[0][0] matrix[1][0] matrix[2][0] |
* | matrix[0][1] matrix[1][1] matrix[2][1] |
* | matrix[0][2] matrix[1][2] matrix[2][2] |
* | matrix[0][3] matrix[1][3] matrix[2][3] |
* \ /
*/
inverse: function(matrix)
{
var determinant = 0,
len = matrix.length,
i = 0,
j,
inverse,
adjunct = [],
//vector representing 2x2 matrix
minor = [];
if(len === 2)
{
determinant = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
inverse = [
[matrix[1][1] * determinant, -matrix[1][0] * determinant],
[-matrix[0][1] * determinant, matrix[0][0] * determinant]
];
}
else
{
determinant = MatrixUtil.getDeterminant(matrix);
for(; i < len; ++i)
{
adjunct[i] = [];
for(j = 0; j < len; ++j)
{
minor = MatrixUtil.getMinors(matrix, j, i);
adjunct[i][j] = MatrixUtil.getDeterminant(minor);
if((i + j) % 2 !== 0 && (i + j) !== 0)
{
adjunct[i][j] *= -1;
}
}
}
inverse = MatrixUtil.scalarMultiply(adjunct, 1/determinant);
}
return inverse;
},
/**
* Multiplies a matrix by a numeric value.
*
* @method scalarMultiply
* @param {Array} matrix The matrix to be altered.
* @param {Number} multiplier The number to multiply against the matrix.
* @return Array
*/
scalarMultiply: function(matrix, multiplier)
{
var i = 0,
j,
len = matrix.length;
for(; i < len; ++i)
{
for(j = 0; j < len; ++j)
{
matrix[i][j] = MatrixUtil._round(matrix[i][j] * multiplier);
}
}
return matrix;
},
/**
* Returns the transpose for an nxn matrix.
*
* @method transpose
* @param matrix An nxn matrix represented by an array of vector arrays.
* @return Array
*/
transpose: function(matrix)
{
var len = matrix.length,
i = 0,
j = 0,
transpose = [];
for(; i < len; ++i)
{
transpose[i] = [];
for(j = 0; j < len; ++j)
{
transpose[i].push(matrix[j][i]);
}
}
return transpose;
},
/**
* Returns a matrix of minors based on a matrix, column index and row index.
*
* @method getMinors
* @param {Array} matrix The matrix from which to extract the matrix of minors.
* @param {Number} columnIndex A zero-based index representing the specified column to exclude.
* @param {Number} rowIndex A zero-based index represeenting the specified row to exclude.
* @return Array
*/
getMinors: function(matrix, columnIndex, rowIndex)
{
var minors = [],
len = matrix.length,
i = 0,
j,
column;
for(; i < len; ++i)
{
if(i !== columnIndex)
{
column = [];
for(j = 0; j < len; ++j)
{
if(j !== rowIndex)
{
column.push(matrix[i][j]);
}
}
minors.push(column);
}
}
return minors;
},
/**
* Returns the sign of value
*
* @method sign
* @param {Number} val value to be interpreted
* @return Number
*/
sign: function(val)
{
return val === 0 ? 1 : val/Math.abs(val);
},
/**
* Multiplies a vector and a matrix
*
* @method vectorMatrixProduct
* @param {Array} vector Array representing a column vector
* @param {Array} matrix Array representing an nxn matrix
* @return Array
*/
vectorMatrixProduct: function(vector, matrix)
{
var i,
j,
len = vector.length,
product = [],
rowProduct;
for(i = 0; i < len; ++i)
{
rowProduct = 0;
for(j = 0; j < len; ++j)
{
rowProduct += vector[i] * matrix[i][j];
}
product[i] = rowProduct;
}
return product;
},
/**
* Breaks up a 2d transform matrix into a series of transform operations.
*
* @method decompose
* @param {Array} matrix A 3x3 multidimensional array
* @return Array
*/
decompose: function(matrix)
{
var a = parseFloat(matrix[0][0]),
b = parseFloat(matrix[1][0]),
c = parseFloat(matrix[0][1]),
d = parseFloat(matrix[1][1]),
dx = parseFloat(matrix[0][2]),
dy = parseFloat(matrix[1][2]),
rotate,
sx,
sy,
shear;
if((a * d - b * c) === 0)
{
return false;
}
//get length of vector(ab)
sx = MatrixUtil._round(Math.sqrt(a * a + b * b));
//normalize components of vector(ab)
a /= sx;
b /= sx;
shear = MatrixUtil._round(a * c + b * d);
c -= a * shear;
d -= b * shear;
//get length of vector(cd)
sy = MatrixUtil._round(Math.sqrt(c * c + d * d));
//normalize components of vector(cd)
c /= sy;
d /= sy;
shear /=sy;
shear = MatrixUtil._round(MatrixUtil.rad2deg(Math.atan(shear)));
rotate = MatrixUtil._round(MatrixUtil.rad2deg(Math.atan2(matrix[1][0], matrix[0][0])));
return [
["translate", dx, dy],
["rotate", rotate],
["skewX", shear],
["scale", sx, sy]
];
},
/**
* Parses a transform string and returns an array of transform arrays.
*
* @method getTransformArray
* @param {String} val A transform string
* @return Array
*/
getTransformArray: function(transform) {
var re = /\s*([a-z]*)\(([\w,\.,\-,\s]*)\)/gi,
transforms = [],
args,
m,
decomp,
methods = MatrixUtil.transformMethods;
while ((m = re.exec(transform))) {
if (methods.hasOwnProperty(m[1]))
{
args = m[2].split(',');
args.unshift(m[1]);
transforms.push(args);
}
else if(m[1] == "matrix")
{
args = m[2].split(',');
decomp = MatrixUtil.decompose([
[args[0], args[2], args[4]],
[args[1], args[3], args[5]],
[0, 0, 1]
]);
transforms.push(decomp[0]);
transforms.push(decomp[1]);
transforms.push(decomp[2]);
transforms.push(decomp[3]);
}
}
return transforms;
},
/**
* Returns an array of transform arrays representing transform functions and arguments.
*
* @method getTransformFunctionArray
* @return Array
*/
getTransformFunctionArray: function(transform) {
var list;
switch(transform)
{
case "skew" :
list = [transform, 0, 0];
break;
case "scale" :
list = [transform, 1, 1];
break;
case "scaleX" :
list = [transform, 1];
break;
case "scaleY" :
list = [transform, 1];
break;
case "translate" :
list = [transform, 0, 0];
break;
default :
list = [transform, 0];
break;
}
return list;
},
/**
* Compares to arrays or transform functions to ensure both contain the same functions in the same
* order.
*
* @method compareTransformSequence
* @param {Array} list1 Array to compare
* @param {Array} list2 Array to compare
* @return Boolean
*/
compareTransformSequence: function(list1, list2)
{
var i = 0,
len = list1.length,
len2 = list2.length,
isEqual = len === len2;
if(isEqual)
{
for(; i < len; ++i)
{
if(list1[i][0] != list2[i][0])
{
isEqual = false;
break;
}
}
}
return isEqual;
},
/**
* Mapping of possible transform method names.
*
* @property transformMethods
* @type Object
*/
transformMethods: {
rotate: "rotate",
skew: "skew",
skewX: "skewX",
skewY: "skewY",
translate: "translate",
translateX: "translateX",
translateY: "tranlsateY",
scale: "scale",
scaleX: "scaleX",
scaleY: "scaleY"
}
};
Y.MatrixUtil = MatrixUtil;