UNPKG

decompose-dommatrix

Version:

A small library to decompose a DOMMatrix into transform values.

133 lines (115 loc) 4.92 kB
/* this code is copied from https://github.com/facebook/react-native/blob/master/Libraries/Utilities/MatrixMath.js#L572 and modified for some clarity and being able to work standalone. Expects the matrix to be a 4-element array of 4-element arrays of numbers. [ [column1 row1 value, column1 row2 value, column1 row3 value], [column2 row1 value, column2 row2 value, column2 row3 value], [column3 row1 value, column3 row2 value, column3 row3 value], [column4 row1 value, column4 row2 value, column4 row3 value] ] */ import * as vectorFns from './vectorFunctions.mjs'; import roundToThreePlaces from './roundToThreePlaces.mjs'; import quaternionToDegreesXYZ from './quaternionToDegreesXYZ.mjs'; const RAD_TO_DEG = 180 / Math.PI; export default function decomposeMatrix(matrix) { const quaternion = new Array(4); const scale = new Array(3); const skew = new Array(3); const translation = new Array(3); // translation is simple // it's the first 3 values in the last column // i.e. m41 is X translation, m42 is Y and m43 is Z for (let i = 0; i < 3; i++) { translation[i] = matrix[3][i]; } // Now get scale and shear. const normalizedColumns = new Array(3); for (let columnIndex = 0; columnIndex < 3; columnIndex++) { normalizedColumns[columnIndex] = matrix[columnIndex].slice(0, 3); } // Compute X scale factor and normalize first row. scale[0] = vectorFns.length(normalizedColumns[0]); normalizedColumns[0] = vectorFns.normalize(normalizedColumns[0], scale[0]); // Compute XY shear factor and make 2nd row orthogonal to 1st. skew[0] = vectorFns.dotProduct(normalizedColumns[0], normalizedColumns[1]); normalizedColumns[1] = vectorFns.linearCombination(normalizedColumns[1], normalizedColumns[0], 1.0, -skew[0]); // Now, compute Y scale and normalize 2nd row. scale[1] = vectorFns.length(normalizedColumns[1]); normalizedColumns[1] = vectorFns.normalize(normalizedColumns[1], scale[1]); skew[0] /= scale[1]; // Compute XZ and YZ shears, orthogonalize 3rd row skew[1] = vectorFns.dotProduct(normalizedColumns[0], normalizedColumns[2]); normalizedColumns[2] = vectorFns.linearCombination(normalizedColumns[2], normalizedColumns[0], 1.0, -skew[1]); skew[2] = vectorFns.dotProduct(normalizedColumns[1], normalizedColumns[2]); normalizedColumns[2] = vectorFns.linearCombination(normalizedColumns[2], normalizedColumns[1], 1.0, -skew[2]); // Next, get Z scale and normalize 3rd row. scale[2] = vectorFns.length(normalizedColumns[2]); normalizedColumns[2] = vectorFns.normalize(normalizedColumns[2], scale[2]); skew[1] /= scale[2]; skew[2] /= scale[2]; // At this point, the matrix defined in normalizedColumns is orthonormal. // Check for a coordinate system flip. If the determinant // is -1, then negate the matrix and the scaling factors. const pdum3 = vectorFns.crossProduct(normalizedColumns[1], normalizedColumns[2]); if (vectorFns.dotProduct(normalizedColumns[0], pdum3) < 0) { for (let i = 0; i < 3; i++) { scale[i] *= -1; normalizedColumns[i][0] *= -1; normalizedColumns[i][1] *= -1; normalizedColumns[i][2] *= -1; } } // Now, get the rotations out quaternion[0] = 0.5 * Math.sqrt(Math.max(1 + normalizedColumns[0][0] - normalizedColumns[1][1] - normalizedColumns[2][2], 0)); quaternion[1] = 0.5 * Math.sqrt(Math.max(1 - normalizedColumns[0][0] + normalizedColumns[1][1] - normalizedColumns[2][2], 0)); quaternion[2] = 0.5 * Math.sqrt(Math.max(1 - normalizedColumns[0][0] - normalizedColumns[1][1] + normalizedColumns[2][2], 0)); quaternion[3] = 0.5 * Math.sqrt(Math.max(1 + normalizedColumns[0][0] + normalizedColumns[1][1] + normalizedColumns[2][2], 0)); if (normalizedColumns[2][1] > normalizedColumns[1][2]) { quaternion[0] = -quaternion[0]; } if (normalizedColumns[0][2] > normalizedColumns[2][0]) { quaternion[1] = -quaternion[1]; } if (normalizedColumns[1][0] > normalizedColumns[0][1]) { quaternion[2] = -quaternion[2]; } // correct for occasional, weird Euler synonyms for 2d rotation let rotationDegrees; if ( quaternion[0] < 0.001 && quaternion[0] >= 0 && quaternion[1] < 0.001 && quaternion[1] >= 0 ) { // this is a 2d rotation on the z-axis rotationDegrees = [ 0, 0, roundToThreePlaces( (Math.atan2(normalizedColumns[0][1], normalizedColumns[0][0]) * 180) / Math.PI ) ]; } else { rotationDegrees = quaternionToDegreesXYZ(quaternion); } // expose both base data and convenience names return { rotateX: rotationDegrees[0], rotateY: rotationDegrees[1], rotateZ: rotationDegrees[2], scaleX: roundToThreePlaces(scale[0]), scaleY: roundToThreePlaces(scale[1]), scaleZ: roundToThreePlaces(scale[2]), translateX: translation[0], translateY: translation[1], translateZ: translation[2], skewXY: roundToThreePlaces(skew[0]) * RAD_TO_DEG, skewXZ: roundToThreePlaces(skew[1]) * RAD_TO_DEG, skewYZ: roundToThreePlaces(skew[2] * RAD_TO_DEG) }; }