Skip to content

Instantly share code, notes, and snippets.

@thednp
Last active February 11, 2020 12:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thednp/294ae28d3ff3afd5e43c2d00b186ba38 to your computer and use it in GitHub Desktop.
Save thednp/294ae28d3ff3afd5e43c2d00b186ba38 to your computer and use it in GitHub Desktop.
const CSS3Matrix = typeof(DOMMatrix) !== 'undefined' ? DOMMatrix
: typeof(WebKitCSSMatrix) !== 'undefined' ? WebKitCSSMatrix
: typeof(CSSMatrix) !== 'undefined' ? CSSMatrix
: typeof(MSCSSMatrix) !== 'undefined' ? MSCSSMatrix
: null
const 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;
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;
};
this.length = function() {
this.checkValues();
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
};
this.normalise = function() {
var len = this.length(),
v = new Vector4(this.x / len, this.y / len, this.z / len);
return v;
};
this.dot = function(v) {
return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w;
};
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);
};
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) );
}
}
/**
* Object containing the decomposed components of a matrix
* @author Joe Lambert
* @constructor
*/
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 CSS3Matrix().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] = parseFloat((this[index][i] + (dm[index][i] - this[index][i]) * progress ).toFixed(10));
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.z+'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 CSS3Matrix(trans); return r; }
catch(e) { console.error('Invalid matrix string: '+trans); return '' };
};
};
/**
* Tween between two matrices
* @param {WebKitCSSMatrix} matrix The destination 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
*/
CSS3Matrix.prototype.tween = function(matrix, progress, fn) {
if(fn === undefined)
fn = function(pos) {return pos;}; // Default to a linear easing
var
// m = new CSS3Matrix(),
m1 = this.decompose(),
m2 = matrix.decompose()
// r = m.decompose(),
// trans = '',
// index = i = null;
// Tween between the two decompositions
return m1.tween(m2, progress, fn);
};
/**
* Transform a Vector4 object using the current matrix
* @param {Vector4} v The vector to transform
* @author Joe Lambert
* @returns {Vector4} The transformed vector
*/
CSS3Matrix.prototype.transformVector = function(v) {
// TODO: Do we need to mod this for Vector4?
return new Vector4( this.m11*v.x + this.m12*v.y + this.m13*v.z,
this.m21*v.x + this.m22*v.y + this.m23*v.z,
this.m31*v.x + this.m32*v.y + this.m33*v.z );
};
/**
* Transposes the matrix
* @author Joe Lambert
* @returns {WebKitCSSMatrix} The transposed matrix
*/
CSS3Matrix.prototype.transpose = function() {
var matrix = new CSS3Matrix(), n = m = 0;
for (n = 0; n <= 4-2; n++)
{
for (m = n + 1; m <= 4-1; m++)
{
matrix['m'+(n+1)+(m+1)] = this['m'+(m+1)+(n+1)];
matrix['m'+(m+1)+(n+1)] = this['m'+(n+1)+(m+1)];
}
}
return matrix;
};
/**
* Calculates the determinant
* @author Joe Lambert
* @returns {float} The determinant of the matrix
*/
CSS3Matrix.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;
};
/**
* Decomposes the matrix into its component parts.
* A Javascript implementation of the pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition
* @author Joe Lambert
* @returns {Object} An object with each of the components of the matrix (perspective, translate, skew, scale, rotate) or identity matrix on failure
*/
CSS3Matrix.prototype.decompose = function() {
let matrix = new CSS3Matrix(this.toString()),
perspectiveMatrix,rightHandSide, inversePerspectiveMatrix, transposedInversePerspectiveMatrix,
perspective, translate, row, i, j, scale, skew, pdum3, rotate;
if (matrix.m33 == 0)
return new CSSMatrixDecomposed( new CSS3Matrix().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 CSS3Matrix().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