This specification describes a transformation matrix interface with the dimension of 3x2 and 4x4.
The transformation matrix interface replaces the SVGMatrix interface from SVG [[SVG11]]. It is a common interface used to describe 2D and 3D transformations on a graphical context for SVG, Context2D [[CANVAS-2D]], CSS3 Transforms [[CSS3-TRANSFORMS]] and WebGL [[WEBGL]].
A 2D points and 3D points are represented by the following WebIDL dictionary:
A multiplication of a point with a matrix requires 4 values. Implementations usually normalize the point by w. It should be considered to do this here as well.
The resulting values of a decomposed matrix as defined by Decomposing the matrix and used in CSS Transforms [[CSS3-TRANSFORMS]] are represented by the following DecomposedMatrix dictionary:
It is doubtful that this can be very useful for authors. On the other hand, it allows scripted animations similar to the animations by CSS3 Transforms.
The Matrix interface represents a mathematical matrix with the purpose of describing transformations a graphical contexts. The following sections describe the details of the interface. For the full interface see Interface summary.
A series of constructors to create a Matrix object.
In the following example a CSS Transform string is passed to the Matrix constructor.
var matrix = new Matrix("translate(20px,20px), scale(2,3), rotate(45deg)"
The resulting matrix is equal to the following sequence of method calls:
var matrix = new Matrix();
matrix.translateBy(20,20);
matrix.scaleNonUniformBy(2,3);
matrix.rotateBy(45);
Should it be postponed to avoid CSS3 Transforms dependency?
Should unit-less values (like for SVG transform presentation attribute) be allowed too? "translate(20,20)"
a
to f
. An array of 16 items sets the element values m11
to m44
.
a
to f
. An array of 16 items sets the element values m11
to m44
.
a
to f
. A sequence of 16 items sets the element values m11
to m44
.
If a Matrix just consists of 2D transformations the 6 values a
to f
can represent the transformation matrix. If the Matrix object is immutable, a DOMException of type NoModificationAllowedError
must be thrown on setting the attributes.
The following attributes a
to f
are aliases to the two-dimensional elements of the 4x4 matrix.
Corresponds to the attribute m11 of the Matrix interface.
Corresponds to the attribute m12 of the Matrix interface.
Corresponds to the attribute m21 of the Matrix interface.
Corresponds to the attribute m22 of the Matrix interface.
Corresponds to the attribute m41 of the Matrix interface.
Corresponds to the attribute m42 of the Matrix interface.
The following attributes m11
to m44
represent the elements of the 4x4 matrix. The coordinates are in column-major order. If the Matrix object is immutable, a DOMException of type NoModificationAllowedError
must be thrown on setting the attributes.
The value of the element in column 1, row 1 of the matrix.
The value of the element in column 1, row 2 of the matrix.
The value of the element in column 1, row 3 of the matrix.
The value of the element in column 1, row 4 of the matrix.
The value of the element in column 2, row 1 of the matrix.
The value of the element in column 2, row 2 of the matrix.
The value of the element in column 2, row 3 of the matrix.
The value of the element in column 2, row 4 of the matrix.
The value of the element in column 3, row 1 of the matrix.
The value of the element in column 3, row 2 of the matrix.
The value of the element in column 3, row 3 of the matrix.
The value of the element in column 3, row 4 of the matrix.
The value of the element in column 4, row 1 of the matrix.
The value of the element in column 4, row 2 of the matrix.
The value of the element in column 4, row 3 of the matrix.
The value of the element in column 4, row 4 of the matrix.
The following methods do not modify the current matrix and return new Matrix object.
m11 = m22 = scale
) on the current matrix with the given origin and returns the resulting matrix. The current matrix is not modified.
m11 = m22 = m33 = scale
) on the current matrix with the given origin and returns the resulting matrix. The current matrix is not modified.
Is there a need for a 3D rotation?
Matrix(-1, 0, 0, 1, 0, 0)
and returns the resulting matrix. The current matrix is not modified.
Matrix(1, 0, 0, -1, 0, 0)
and returns the resulting matrix. The current matrix is not modified.
null
. The current matrix is not modified.
InvalidModificationError
The following methods do modify the current matrix. If the Matrix object is immutable, a DOMException of type NoModificationAllowedError
must be thrown on calling the operations below.
m11 = m22 = scale
) on the current matrix with the given origin.
m11 = m22 = m33 = scale
) on the current matrix with the given origin.
Is there a need for a 3D rotation?
InvalidModificationError
The following helper methods do not modify the Matrix object.
true
if m13
, m14
, m23
, m24
, m31
, m32
, m34
, m43
are equal to zero and m33
, m44
are equal to one.
point
is not modified.
matrix
function if the current matrix is a 2D transform or a CSS Transforms matrix3d
else. The syntax is as specified in CSS Transforms [[!CSS3-TRANSFORMS]].
Should be stringifier;
. Bug in old respec tool.
In this example a matrix is created and several methods with 2D transformations are called.
var matrix = new Matrix();
matrix.scaleBy(2);
matrix.translateBy(20,20);
The matrix.toString()
returns the DOM string:
"matrix(2,0,0,2,20,20)"
For 3D operations, the stringifier returns DOM string representing a 3D matrix.
var matrix = new Matrix();
matrix.scale3dBy(2);
Calling matrix.toString()
after the snippet above returns the DOM string:
"matrix3d(2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,1)"
The pseudocode below is based upon the "unmatrix" method in "Graphics Gems II, edited by Jim Arvo", but modified to use Quaternions instead of Euler angles to avoid the problem of Gimbal Locks.
The following pseudocode works on a 4x4 homogeneous matrix:
Input: matrix ; a 4x4 matrix Output: translation ; a 3 component vector scale ; a 3 component vector skew ; skew factors XY,XZ,YZ represented as a 3 component vector perspective ; a 4 component vector quaternion ; a 4 component vector Returns false if the matrix cannot be decomposed, true if it can Supporting functions (point is a 3 component vector, matrix is a 4x4 matrix): double determinant(matrix) returns the 4x4 determinant of the matrix matrix inverse(matrix) returns the inverse of the passed matrix matrix transpose(matrix) returns the transpose of the passed matrix point multVecMatrix(point, matrix) multiplies the passed point by the passed matrix and returns the transformed point double length(point) returns the length of the passed vector point normalize(point) normalizes the length of the passed point to 1 double dot(point, point) returns the dot product of the passed points double sqrt(double) returns the root square of passed value double max(double y, double x) returns the bigger value of the two passed values Decomposition also makes use of the following function: point combine(point a, point b, double ascl, double bscl) result[0] = (ascl * a[0]) + (bscl * b[0]) result[1] = (ascl * a[1]) + (bscl * b[1]) result[2] = (ascl * a[2]) + (bscl * b[2]) return result // Normalize the matrix. if (matrix[3][3] == 0) return false for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) matrix[i][j] /= matrix[3][3] // 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 = 0; i < 3; i++) perspectiveMatrix[i][3] = 0 perspectiveMatrix[3][3] = 1 if (determinant(perspectiveMatrix) == 0) return false // First, isolate perspective. if (matrix[0][3] != 0 || matrix[1][3] != 0 || matrix[2][3] != 0) // rightHandSide is the right hand side of the equation. rightHandSide[0] = matrix[0][3]; rightHandSide[1] = matrix[1][3]; rightHandSide[2] = matrix[2][3]; rightHandSide[3] = matrix[3][3]; // Solve the equation by inverting perspectiveMatrix and multiplying // rightHandSide by the inverse. inversePerspectiveMatrix = inverse(perspectiveMatrix) transposedInversePerspectiveMatrix = transposeMatrix4(inversePerspectiveMatrix) perspective = multVecMatrix(rightHandSide, transposedInversePerspectiveMatrix) else // No perspective. perspective[0] = perspective[1] = perspective[2] = 0 perspective[3] = 1 // Next take care of translation for (i = 0; i < 3; i++) translate[i] = matrix[3][i] // Now get scale and shear. 'row' is a 3 element array of 3 component vectors for (i = 0; i < 3; i++) row[i][0] = matrix[i][0] row[i][1] = matrix[i][1] row[i][2] = matrix[i][2] // Compute X scale factor and normalize first row. scale[0] = length(row[0]) row[0] = normalize(row[0]) // Compute XY shear factor and make 2nd row orthogonal to 1st. skew[0] = dot(row[0], row[1]) row[1] = combine(row[1], row[0], 1.0, -skew[0]) // Now, compute Y scale and normalize 2nd row. scale[1] = length(row[1]) row[1] = normalize(row[1]) skew[0] /= scale[1]; // Compute XZ and YZ shears, orthogonalize 3rd row skew[1] = dot(row[0], row[2]) row[2] = combine(row[2], row[0], 1.0, -skew[1]) skew[2] = dot(row[1], row[2]) row[2] = combine(row[2], row[1], 1.0, -skew[2]) // Next, get Z scale and normalize 3rd row. scale[2] = length(row[2]) row[2] = normalize(row[2]) skew[1] /= scale[2] skew[2] /= scale[2] // 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 = cross(row[1], row[2]) if (dot(row[0], pdum3) < 0) for (i = 0; i < 3; i++) scale[i] *= -1; row[i][0] *= -1 row[i][1] *= -1 row[i][2] *= -1 // Now, get the rotations out quaternion[0] = 0.5 * sqrt(max(1 + row[0][0] - row[1][1] - row[2][2], 0)) quaternion[1] = 0.5 * sqrt(max(1 - row[0][0] + row[1][1] - row[2][2], 0)) quaternion[2] = 0.5 * sqrt(max(1 - row[0][0] - row[1][1] + row[2][2], 0)) quaternion[3] = 0.5 * sqrt(max(1 + row[0][0] + row[1][1] + row[2][2], 0)) if (row[2][1] > row[1][2]) quaternion[0] = -quaternion[0] if (row[0][2] > row[2][0]) quaternion[1] = -quaternion[1] if (row[1][0] > row[0][1]) quaternion[2] = -quaternion[2] return true
After interpolation the resulting values are used to transform the elements user space. One way to use these values is to recompose them into a 4x4 matrix. This can be done following the pseudo-code below:
Input: translation ; a 3 component vector scale ; a 3 component vector skew ; skew factors XY,XZ,YZ represented as a 3 component vector perspective ; a 4 component vector quaternion ; a 4 component vector Output: matrix ; a 4x4 matrix Supporting functions (matrix is a 4x4 matrix): matrix multiply(matrix a, matrix b) returns the 4x4 matrix product of a * b // apply perspective for (i = 0; i < 4; i++) matrix[i][3] = perspective[i] // apply translation for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) matrix[3][i] += translation[j] * matrix[j][i] // apply rotation x = quaternion[0] y = quaternion[1] z = quaternion[2] w = quaternion[3] // Construct a composite rotation matrix from the quaternion values // rotationMatrix is a identity 4x4 matrix initially rotationMatrix[0][0] = 1 - 2 * (y * y + z * z) rotationMatrix[0][1] = 2 * (x * y - z * w) rotationMatrix[0][2] = 2 * (x * z + y * w) rotationMatrix[1][0] = 2 * (x * y + z * w) rotationMatrix[1][1] = 1 - 2 * (x * x + z * z) rotationMatrix[1][2] = 2 * (y * z - x * w) rotationMatrix[2][0] = 2 * (x * z - y * w) rotationMatrix[2][1] = 2 * (y * z + x * w) rotationMatrix[2][2] = 1 - 2 * (x * x + y * y) matrix = multiply(matrix, rotationMatrix) // apply skew // temp is a identity 4x4 matrix initially if (skew[2]) temp[2][1] = skew[2] matrix = multiply(matrix, temp) if (skew[1]) temp[2][1] = 0 temp[2][0] = skew[1] matrix = multiply(matrix, temp) if (skew[0]) temp[2][0] = 0 temp[1][0] = skew[0] matrix = multiply(matrix, temp) // apply scale for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) matrix[i][j] *= scale[i] return
Many thanks to Dean Jackson for his initial proposal to make this specification possible.