Created
June 18, 2016 10:06
-
-
Save jarek-foksa/e379ee0414cf9fb03f38d4bed9140cae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @copyright | |
// © 2005, 2006, 2013 Apple Inc. All rights reserved. | |
// © 2009 Torch Mobile, Inc. | |
// © 2016 Jarosław Foksa | |
// @src | |
// https://drafts.csswg.org/css-transforms-1/#recomposing-to-a-3d-matrix | |
// http://trac.webkit.org/browser/trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp | |
let {abs, sqrt, asin, sin, cos, tan, atan2} = Math; | |
const SMALL_NUMBER = 1.e-8; | |
export default class DecomposedTransform { | |
constructor(rotation = "eulerAngles") { | |
this._translateX = 0; | |
this._translateY = 0; | |
this._translateZ = 0; | |
this._scaleX = 1; | |
this._scaleY = 1; | |
this._scaleZ = 1; | |
this._perspectiveX = 0; | |
this._perspectiveY = 0; | |
this._perspectiveZ = 0; | |
this._perspectiveW = 1; | |
this._skewXY = 0; | |
this._skewXZ = 0; | |
this._skewYZ = 0; | |
this._rotation = rotation; | |
if (rotation === "eulerAngles") { | |
this._rotateX = 0; | |
this._rotateY = 0; | |
this._rotateZ = 0; | |
} | |
else if (rotation === "quaternions" ) { | |
this._quaternionX = 0; | |
this._quaternionY = 0; | |
this._quaternionZ = 0; | |
this._quaternionW = 0; | |
} | |
} | |
static fromSVGMatrix(svgMatrix, rotation = "eulerAngles") { | |
let transform = new DecomposedTransform(rotation); | |
let matrix = [ | |
[svgMatrix.a, svgMatrix.b, 0, 0], | |
[svgMatrix.c, svgMatrix.d, 0, 0], | |
[ 0, 0, 1, 0], | |
[svgMatrix.e, svgMatrix.f, 0, 1] | |
]; | |
// Normalize the matrix. | |
if (matrix[3][3] == 0) { | |
return false; | |
} | |
for (let i = 0; i < 4; i++) { | |
for (let 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. | |
let perspectiveMatrix = []; | |
for (let i = 0; i < 4; i++) { | |
perspectiveMatrix[i] = []; | |
for (let j = 0; j < 4; j++) { | |
perspectiveMatrix[i][j] = matrix[i][j]; | |
} | |
} | |
for (let i = 0; i < 3; i++) { | |
perspectiveMatrix[i][3] = 0; | |
} | |
perspectiveMatrix[3][3] = 1; | |
if (determinant4x4(perspectiveMatrix) == 0) { | |
return false; | |
} | |
// First, isolate perspective. This is the messiest. | |
if (matrix[0][3] != 0 || matrix[1][3] != 0 || matrix[2][3] != 0) { | |
// rightHandSide is the right hand side of the equation. | |
let rightHandSide = [0, 0, 0, 0]; | |
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. (This is | |
// the easiest way, not necessarily the best.) | |
let inversePerspectiveMatrix = inverse(perspectiveMatrix); | |
let transposedInversePerspectiveMatrix = transposeMatrix4(inversePerspectiveMatrix); | |
let perspectivePoint = v4MulPointByMatrix(rightHandSide, transposedInversePerspectiveMatrix); | |
transform.perspectiveX = perspectivePoint[0]; | |
transform.perspectiveY = perspectivePoint[1]; | |
transform.perspectiveZ = perspectivePoint[2]; | |
transform.perspectiveW = perspectivePoint[3]; | |
// Clear the perspective partition | |
matrix[0][3] = matrix[1][3] = matrix[2][3] = 0; | |
matrix[3][3] = 1; | |
} | |
else { | |
// No perspective. | |
transform.perspectiveX = transform.perspectiveY = transform.perspectiveZ = 0; | |
transform.perspectiveW = 1; | |
} | |
// Next take care of translation (easy). | |
transform.translateX = matrix[3][0]; | |
matrix[3][0] = 0; | |
transform.translateY = matrix[3][1]; | |
matrix[3][1] = 0; | |
transform.translateZ = matrix[3][2]; | |
matrix[3][2] = 0; | |
// Vector4 type and functions need to be added to the common set. | |
let row = [[], [], []]; | |
let pdum3 = [0, 0, 0]; | |
// Now get scale and shear. | |
for (let 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. | |
transform.scaleX = v3Length(row[0]); | |
v3Scale(row[0], 1.0); | |
// Compute XY shear factor and make 2nd row orthogonal to 1st. | |
transform.skewXY = v3Dot(row[0], row[1]); | |
v3Combine(row[1], row[0], row[1], 1.0, -transform.skewXY); | |
// Now, compute Y scale and normalize 2nd row. | |
transform.scaleY = v3Length(row[1]); | |
v3Scale(row[1], 1.0); | |
transform.skewXY /= transform.scaleY; | |
// Compute XZ and YZ shears, orthogonalize 3rd row. | |
transform.skewXZ = v3Dot(row[0], row[2]); | |
v3Combine(row[2], row[0], row[2], 1.0, -transform.skewXZ); | |
transform.skewYZ = v3Dot(row[1], row[2]); | |
v3Combine(row[2], row[1], row[2], 1.0, -transform.skewYZ); | |
// Next, get Z scale and normalize 3rd row. | |
transform.scaleZ = v3Length(row[2]); | |
v3Scale(row[2], 1.0); | |
transform.skewXZ /= transform.scaleZ; | |
transform.skewYZ /= transform.scaleZ; | |
// 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. | |
v3Cross(row[1], row[2], pdum3); | |
if (v3Dot(row[0], pdum3) < 0) { | |
transform.scaleX *= -1; | |
transform.scaleY *= -1; | |
transform.scaleZ *= -1; | |
for (let i = 0; i < 3; i++) { | |
row[i][0] *= -1; | |
row[i][1] *= -1; | |
row[i][2] *= -1; | |
} | |
} | |
// Rotation | |
if (rotation === "eulerAngles") { | |
transform.rotateY = asin(-row[0][2]); | |
if (cos(transform.rotateY) !== 0) { | |
transform.rotateX = atan2(row[1][2], row[2][2]); | |
transform.rotateZ = atan2(row[0][1], row[0][0]); | |
} | |
else { | |
transform.rotateX = atan2(-row[2][0], row[1][1]); | |
transform.rotateZ = 0; | |
} | |
} | |
else if (rotation === "quaternions") { | |
let s, t, x, y, z, w; | |
t = row[0][0] + row[1][1] + row[2][2] + 1.0; | |
if (t > 1e-4) { | |
s = 0.5 / sqrt(t); | |
w = 0.25 / s; | |
x = (row[2][1] - row[1][2]) * s; | |
y = (row[0][2] - row[2][0]) * s; | |
z = (row[1][0] - row[0][1]) * s; | |
} | |
else if (row[0][0] > row[1][1] && row[0][0] > row[2][2]) { | |
s = sqrt(1.0 + row[0][0] - row[1][1] - row[2][2]) * 2.0; // S = 4 * qx. | |
x = 0.25 * s; | |
y = (row[0][1] + row[1][0]) / s; | |
z = (row[0][2] + row[2][0]) / s; | |
w = (row[2][1] - row[1][2]) / s; | |
} | |
else if (row[1][1] > row[2][2]) { | |
s = sqrt(1.0 + row[1][1] - row[0][0] - row[2][2]) * 2.0; // S = 4 * qy. | |
x = (row[0][1] + row[1][0]) / s; | |
y = 0.25 * s; | |
z = (row[1][2] + row[2][1]) / s; | |
w = (row[0][2] - row[2][0]) / s; | |
} | |
else { | |
s = sqrt(1.0 + row[2][2] - row[0][0] - row[1][1]) * 2.0; // S = 4 * qz. | |
x = (row[0][2] + row[2][0]) / s; | |
y = (row[1][2] + row[2][1]) / s; | |
z = 0.25 * s; | |
w = (row[1][0] - row[0][1]) / s; | |
} | |
transform.quaternionX = x; | |
transform.quaternionY = y; | |
transform.quaternionZ = z; | |
transform.quaternionW = w; | |
} | |
return transform; | |
} | |
// @info | |
// Recompose the decomposed tranasform back into a transformation matrix. | |
// @type | |
// () => SVGMatrix | |
toSVGMatrix() { | |
let matrix = [ | |
[1, 0, 0, 0], | |
[0, 1, 0, 0], | |
[0, 0, 1, 0], | |
[0, 0, 0, 1] | |
]; | |
// Perspective | |
matrix[0][3] = this.perspectiveX; | |
matrix[1][3] = this.perspectiveY; | |
matrix[2][3] = this.perspectiveZ; | |
matrix[3][3] = this.perspectiveW; | |
// Translate | |
translate3d(matrix, this.translateX, this.translateY, this.translateZ); | |
// Rotate | |
if (this.rotation === "eulerAngles") { | |
let a = cos(this.rotateX); | |
let b = sin(this.rotateX); | |
let c = cos(this.rotateY); | |
let d = sin(this.rotateY); | |
let e = cos(this.rotateZ); | |
let f = sin(this.rotateZ); | |
let ce = c * e; | |
let cf = c * f; | |
let de = d * e; | |
let df = d * f; | |
let rotationMatrix = [ | |
[ce - df * b, cf + de * b, -a * d, 0], | |
[ -a * f, a * e, b, 0], | |
[de + cf * b, df - ce * b, a * c, 0], | |
[ 0, 0, 0, 1] | |
]; | |
matrix = multiply(matrix, rotationMatrix); | |
} | |
else if (this.rotation === "quaternions") { | |
let xx = this.quaternionX * this.quaternionX; | |
let xy = this.quaternionX * this.quaternionY; | |
let xz = this.quaternionX * this.quaternionZ; | |
let xw = this.quaternionX * this.quaternionW; | |
let yy = this.quaternionY * this.quaternionY; | |
let yz = this.quaternionY * this.quaternionZ; | |
let yw = this.quaternionY * this.quaternionW; | |
let zz = this.quaternionZ * this.quaternionZ; | |
let zw = this.quaternionZ * this.quaternionW; | |
let rotationMatrix = [ | |
[1 - 2 * (yy + zz), 2 * (xy - zw), 2 * (xz + yw), 0], | |
[ 2 * (xy + zw), 1 - 2 * (xx + zz), 2 * (yz - xw), 0], | |
[ 2 * (xz - yw), 2 * (yz + xw), 1 - 2 * (xx + yy), 0], | |
[ 0, 0, 0, 1] | |
]; | |
matrix = multiply(matrix, rotationMatrix); | |
} | |
// Skew | |
if (this.skewYZ) { | |
let skewMatrix = [ | |
[1, 0, 0, 0], | |
[0, 1, 0, 0], | |
[0, 0, 1, 0], | |
[0, 0, 0, 1] | |
]; | |
skewMatrix[2][1] = this.skewYZ; | |
matrix = multiply(matrix, skewMatrix); | |
} | |
if (this.skewXZ) { | |
let skewMatrix = [ | |
[1, 0, 0, 0], | |
[0, 1, 0, 0], | |
[0, 0, 1, 0], | |
[0, 0, 0, 1] | |
]; | |
skewMatrix[2][1] = 0 | |
skewMatrix[2][0] = this.skewXZ; | |
matrix = multiply(matrix, skewMatrix); | |
} | |
if (this.skewXY) { | |
let skewMatrix = [ | |
[1, 0, 0, 0], | |
[0, 1, 0, 0], | |
[0, 0, 1, 0], | |
[0, 0, 0, 1] | |
]; | |
skewMatrix[2][0] = 0 | |
skewMatrix[1][0] = this.skewXY; | |
matrix = multiply(matrix, skewMatrix); | |
} | |
// Scale | |
scale3d(matrix, this.scaleX, this.scaleY, this.scaleZ); | |
// ... | |
return new SVGMatrix([matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1], matrix[3][0], matrix[3][1]]); | |
} | |
clone() { | |
let transform = new DecomposedTransform(this._rotation); | |
transform.translateX = this._translateX; | |
transform.translateY = this._translateY; | |
transform.translateZ = this._translateZ; | |
transform.scaleX = this._scaleX; | |
transform.scaleY = this._scaleY; | |
transform.scaleZ = this._scaleZ; | |
transform.perspectiveX = this._perspectiveX; | |
transform.perspectiveY = this._perspectiveY; | |
transform.perspectiveZ = this._perspectiveZ; | |
transform.perspectiveW = this._perspectiveW; | |
transform.skewXY = this._skewXY; | |
transform.skewXZ = this._skewXZ; | |
transform.skewYZ = this._skewYZ; | |
if (this._rotation === "eulerAngles") { | |
transform.rotateX = this._rotateX; | |
transform.rotateY = this._rotateY; | |
transform.rotateZ = this._rotateZ; | |
} | |
else if (this._rotation === "quaternions" ) { | |
transform.quaternionX = this._quaternionX; | |
transform.quaternionY = this._quaternionY; | |
transform.quaternionZ = this._quaternionZ; | |
transform.quaternionW = this._quaternionW; | |
} | |
return transform; | |
} | |
///////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// @info | |
// Translation along X axis. | |
// @type | |
// number | |
get translateX() { | |
return this._translateX; | |
} | |
set translateX(translateX) { | |
this._translateX = translateX; | |
} | |
// @info | |
// Translation along Y axis. | |
// @type | |
// number | |
get translateY() { | |
return this._translateY; | |
} | |
set translateY(translateY) { | |
this._translateY = translateY; | |
} | |
// @info | |
// Translation along Z axis. | |
// @type | |
// number | |
get translateZ() { | |
return this._translateZ; | |
} | |
set translateZ(translateZ) { | |
this._translateZ = translateZ; | |
} | |
// @info | |
// Scale along X axis | |
// @type | |
// number | |
get scaleX() { | |
return this._scaleX; | |
} | |
set scaleX(scaleX) { | |
this._scaleX = scaleX; | |
} | |
// @info | |
// Scale along Y axis | |
// @type | |
// number | |
get scaleY() { | |
return this._scaleY; | |
} | |
set scaleY(scaleY) { | |
this._scaleY = scaleY; | |
} | |
// @info | |
// Scale along Z axis | |
// @type | |
// number | |
get scaleZ() { | |
return this._scaleZ; | |
} | |
set scaleZ(scaleZ) { | |
this._scaleZ = scaleZ; | |
} | |
// @info | |
// Perspective, X component. | |
// @type | |
// number | |
get perspectiveX() { | |
return this._perspectiveX; | |
} | |
set perspectiveX(perspectiveX) { | |
this._perspectiveX = perspectiveX; | |
} | |
// @info | |
// Perspective, Y component. | |
// @type | |
// number | |
get perspectiveY() { | |
return this._perspectiveY; | |
} | |
set perspectiveY(perspectiveY) { | |
this._perspectiveY = perspectiveY; | |
} | |
// @info | |
// Perspective, Z component. | |
// @type | |
// number | |
get perspectiveZ() { | |
return this._perspectiveZ; | |
} | |
set perspectiveZ(perspectiveZ) { | |
this._perspectiveZ = perspectiveZ; | |
} | |
// @info | |
// Perspective, W component. | |
// @type | |
// number | |
get perspectiveW() { | |
return this._perspectiveW; | |
} | |
set perspectiveW(perspectiveW) { | |
this._perspectiveW = perspectiveW; | |
} | |
// @info | |
// Skew the XY plane | |
// @type | |
// number | |
get skewXY() { | |
return this._skewXY; | |
} | |
set skewXY(skewXY) { | |
this._skewXY = skewXY; | |
} | |
// @info | |
// Skew the XZ plane | |
// @type | |
// number | |
get skewXZ() { | |
return this._skewXZ; | |
} | |
set skewXZ(skewXZ) { | |
this._skewXZ = skewXZ; | |
} | |
// @info | |
// Skew the YZ plane | |
// @type | |
// number | |
get skewYZ() { | |
return this._skewYZ; | |
} | |
set skewYZ(skewYZ) { | |
this._skewYZ = skewYZ; | |
} | |
// @info | |
// Rotation representation. Either Euler angles which are easier to work with in 2D space or quaternions which | |
// are more flexible in 3D space (no gimbal locks). | |
// @type | |
// "eulerAngles" || "quaternions" | |
get rotation() { | |
return this._rotation; | |
} | |
set rotation(rotation) { | |
throw new Error(`"rotation" property is read-only.`); | |
} | |
// @info | |
// Rotation along the X axis, in Euler angles. | |
// @type | |
// number? | |
get rotateX() { | |
if (this._rotation === "eulerAngles") { | |
return this._rotateX; | |
} | |
else { | |
throw new Error(`Can't get "rotateX" propery - decomposed transform does not use Euler angles.`); | |
} | |
} | |
set rotateX(rotateX) { | |
if (this._rotation === "eulerAngles") { | |
this._rotateX = rotateX; | |
} | |
else { | |
throw new Error(`Can't set "rotateX" propery - decomposed transform does not use Euler angles.`); | |
} | |
} | |
// @info | |
// Rotation along the Y axis, in Euler angles. | |
// @type | |
// number? | |
get rotateY() { | |
if (this._rotation === "eulerAngles") { | |
return this._rotateY; | |
} | |
else { | |
throw new Error(`Can't get "rotateY" propery - decomposed transform does not use Euler angles.`); | |
} | |
} | |
set rotateY(rotateY) { | |
if (this._rotation === "eulerAngles") { | |
this._rotateY = rotateY; | |
} | |
else { | |
throw new Error(`Can't set "rotateY" propery - decomposed transform does not use Euler angles.`); | |
} | |
} | |
// @info | |
// Rotation along the Z axis, in Euler angles. | |
// @type | |
// number? | |
get rotateZ() { | |
if (this._rotation === "eulerAngles") { | |
return this._rotateZ; | |
} | |
else { | |
throw new Error(`Can't get "rotateZ" propery - decomposed transform does not use Euler angles.`); | |
} | |
} | |
set rotateZ(rotateZ) { | |
if (this._rotation === "eulerAngles") { | |
this._rotateZ = rotateZ; | |
} | |
else { | |
throw new Error(`Can't set "rotateZ" propery - decomposed transform does not use Euler angles.`); | |
} | |
} | |
// @info | |
// Quaternion, X component. | |
// @type | |
// number? | |
get quaternionX() { | |
if (this._rotation === "quaternions") { | |
return this._quaternionX; | |
} | |
else { | |
throw new Error(`Can't get "quaternionX" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
set quaternionX(quaternionX) { | |
if (this._rotation === "quaternions") { | |
this._quaternionX = quaternionX; | |
} | |
else { | |
throw new Error(`Can't set "quaternionX" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
// @info | |
// Quaternion, Y component. | |
// @type | |
// number? | |
get quaternionY() { | |
if (this._rotation === "quaternions") { | |
return this._quaternionY; | |
} | |
else { | |
throw new Error(`Can't get "quaternionY" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
set quaternionY(quaternionY) { | |
if (this._rotation === "quaternions") { | |
this._quaternionY = quaternionY; | |
} | |
else { | |
throw new Error(`Can't set "quaternionY" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
// @info | |
// Quaternion, Z component. | |
// @type | |
// number? | |
get quaternionZ() { | |
if (this._rotation === "quaternions") { | |
return this._quaternionZ; | |
} | |
else { | |
throw new Error(`Can't get "quaternionZ" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
set quaternionZ(quaternionZ) { | |
if (this._rotation === "quaternions") { | |
this._quaternionX = quaternionZ; | |
} | |
else { | |
throw new Error(`Can't set "quaternionZ" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
// @info | |
// Quaternion, W component. | |
// @type | |
// number? | |
get quaternionW() { | |
if (this._rotation === "quaternions") { | |
return this._quaternionW; | |
} | |
else { | |
throw new Error(`Can't get "quaternionW" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
set quaternionW(quaternionW) { | |
if (this._rotation === "quaternions") { | |
this._quaternionW = quaternionW; | |
} | |
else { | |
throw new Error(`Can't set "quaternionW" propery - decomposed transform does not use quaternions.`); | |
} | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
let determinant2x2 = (a, b, c, d) => { | |
return a * d - b * c; | |
}; | |
let determinant3x3 = (a1, a2, a3, b1, b2, b3, c1, c2, c3) => { | |
return a1 * determinant2x2(b2, b3, c2, c3) | |
- b1 * determinant2x2(a2, a3, c2, c3) | |
+ c1 * determinant2x2(a2, a3, b2, b3); | |
}; | |
let determinant4x4 = (m) => { | |
let a1 = m[0][0]; | |
let b1 = m[0][1]; | |
let c1 = m[0][2]; | |
let d1 = m[0][3]; | |
let a2 = m[1][0]; | |
let b2 = m[1][1]; | |
let c2 = m[1][2]; | |
let d2 = m[1][3]; | |
let a3 = m[2][0]; | |
let b3 = m[2][1]; | |
let c3 = m[2][2]; | |
let d3 = m[2][3]; | |
let a4 = m[3][0]; | |
let b4 = m[3][1]; | |
let c4 = m[3][2]; | |
let d4 = m[3][3]; | |
return a1 * determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4) | |
- b1 * determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4) | |
+ c1 * determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4) | |
- d1 * determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); | |
}; | |
let adjoint = (matrix) => { | |
let result = [[], [], [], []]; | |
// Assign to individual variable names to aid selecting correct values | |
let a1 = matrix[0][0]; | |
let b1 = matrix[0][1]; | |
let c1 = matrix[0][2]; | |
let d1 = matrix[0][3]; | |
let a2 = matrix[1][0]; | |
let b2 = matrix[1][1]; | |
let c2 = matrix[1][2]; | |
let d2 = matrix[1][3]; | |
let a3 = matrix[2][0]; | |
let b3 = matrix[2][1]; | |
let c3 = matrix[2][2]; | |
let d3 = matrix[2][3]; | |
let a4 = matrix[3][0]; | |
let b4 = matrix[3][1]; | |
let c4 = matrix[3][2]; | |
let d4 = matrix[3][3]; | |
// Row column labeling reversed since we transpose rows & columns | |
result[0][0] = determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4); | |
result[1][0] = - determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4); | |
result[2][0] = determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4); | |
result[3][0] = - determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); | |
result[0][1] = - determinant3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4); | |
result[1][1] = determinant3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4); | |
result[2][1] = - determinant3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4); | |
result[3][1] = determinant3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4); | |
result[0][2] = determinant3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4); | |
result[1][2] = - determinant3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4); | |
result[2][2] = determinant3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4); | |
result[3][2] = - determinant3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4); | |
result[0][3] = - determinant3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3); | |
result[1][3] = determinant3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3); | |
result[2][3] = - determinant3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3); | |
result[3][3] = determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3); | |
return; | |
}; | |
let inverse = (matrix) => { | |
// Calculate the adjoint matrix | |
let result = adjoint(matrix); | |
// Calculate the 4x4 determinant. If the determinant is zero, then the inverse matrix is not unique. | |
let det = determinant4x4(matrix); | |
if (abs(det) < SMALL_NUMBER) { | |
return false; | |
} | |
// Scale the adjoint matrix to get the inverse | |
for (let i = 0; i < 4; i++) { | |
for (let j = 0; j < 4; j++) { | |
result[i][j] = result[i][j] / det; | |
} | |
} | |
return result; | |
}; | |
let transposeMatrix4 = (a) => { | |
let b = [[], [], [], []]; | |
for (let i = 0; i < 4; i++) { | |
for (let j = 0; j < 4; j++) { | |
b[i][j] = a[j][i]; | |
} | |
} | |
return b; | |
}; | |
let v4MulPointByMatrix = (p, m) => { | |
let result = []; | |
result[0] = (p[0] * m[0][0]) + (p[1] * m[1][0]) + | |
(p[2] * m[2][0]) + (p[3] * m[3][0]); | |
result[1] = (p[0] * m[0][1]) + (p[1] * m[1][1]) + | |
(p[2] * m[2][1]) + (p[3] * m[3][1]); | |
result[2] = (p[0] * m[0][2]) + (p[1] * m[1][2]) + | |
(p[2] * m[2][2]) + (p[3] * m[3][2]); | |
result[3] = (p[0] * m[0][3]) + (p[1] * m[1][3]) + | |
(p[2] * m[2][3]) + (p[3] * m[3][3]); | |
return result; | |
}; | |
let v3Length = (a) => { | |
return sqrt((a[0] * a[0]) + (a[1] * a[1]) + (a[2] * a[2])); | |
} | |
let v3Scale = (v, desiredLength) => { | |
let len = v3Length(v); | |
if (len != 0) { | |
let l = desiredLength / len; | |
v[0] *= l; | |
v[1] *= l; | |
v[2] *= l; | |
} | |
}; | |
let v3Dot = (a, b) => { | |
return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]); | |
}; | |
// Make a linear combination of two vectors and return the result. | |
// result = (a * ascl) + (b * bscl) | |
let v3Combine = (a, b, result, ascl, 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 the cross product result = a cross b */ | |
let v3Cross = (a, b, result) => { | |
result[0] = (a[1] * b[2]) - (a[2] * b[1]); | |
result[1] = (a[2] * b[0]) - (a[0] * b[2]); | |
result[2] = (a[0] * b[1]) - (a[1] * b[0]); | |
}; | |
let translate3d = (m_matrix, tx, ty, tz) => { | |
m_matrix[3][0] += tx * m_matrix[0][0] + ty * m_matrix[1][0] + tz * m_matrix[2][0]; | |
m_matrix[3][1] += tx * m_matrix[0][1] + ty * m_matrix[1][1] + tz * m_matrix[2][1]; | |
m_matrix[3][2] += tx * m_matrix[0][2] + ty * m_matrix[1][2] + tz * m_matrix[2][2]; | |
m_matrix[3][3] += tx * m_matrix[0][3] + ty * m_matrix[1][3] + tz * m_matrix[2][3]; | |
}; | |
let multiply = (mat1, mat2) => { | |
let result = [[], [], [], []]; | |
result[0][0] = (mat2[0][0] * mat1[0][0] + mat2[0][1] * mat1[1][0] | |
+ mat2[0][2] * mat1[2][0] + mat2[0][3] * mat1[3][0]); | |
result[0][1] = (mat2[0][0] * mat1[0][1] + mat2[0][1] * mat1[1][1] | |
+ mat2[0][2] * mat1[2][1] + mat2[0][3] * mat1[3][1]); | |
result[0][2] = (mat2[0][0] * mat1[0][2] + mat2[0][1] * mat1[1][2] | |
+ mat2[0][2] * mat1[2][2] + mat2[0][3] * mat1[3][2]); | |
result[0][3] = (mat2[0][0] * mat1[0][3] + mat2[0][1] * mat1[1][3] | |
+ mat2[0][2] * mat1[2][3] + mat2[0][3] * mat1[3][3]); | |
result[1][0] = (mat2[1][0] * mat1[0][0] + mat2[1][1] * mat1[1][0] | |
+ mat2[1][2] * mat1[2][0] + mat2[1][3] * mat1[3][0]); | |
result[1][1] = (mat2[1][0] * mat1[0][1] + mat2[1][1] * mat1[1][1] | |
+ mat2[1][2] * mat1[2][1] + mat2[1][3] * mat1[3][1]); | |
result[1][2] = (mat2[1][0] * mat1[0][2] + mat2[1][1] * mat1[1][2] | |
+ mat2[1][2] * mat1[2][2] + mat2[1][3] * mat1[3][2]); | |
result[1][3] = (mat2[1][0] * mat1[0][3] + mat2[1][1] * mat1[1][3] | |
+ mat2[1][2] * mat1[2][3] + mat2[1][3] * mat1[3][3]); | |
result[2][0] = (mat2[2][0] * mat1[0][0] + mat2[2][1] * mat1[1][0] | |
+ mat2[2][2] * mat1[2][0] + mat2[2][3] * mat1[3][0]); | |
result[2][1] = (mat2[2][0] * mat1[0][1] + mat2[2][1] * mat1[1][1] | |
+ mat2[2][2] * mat1[2][1] + mat2[2][3] * mat1[3][1]); | |
result[2][2] = (mat2[2][0] * mat1[0][2] + mat2[2][1] * mat1[1][2] | |
+ mat2[2][2] * mat1[2][2] + mat2[2][3] * mat1[3][2]); | |
result[2][3] = (mat2[2][0] * mat1[0][3] + mat2[2][1] * mat1[1][3] | |
+ mat2[2][2] * mat1[2][3] + mat2[2][3] * mat1[3][3]); | |
result[3][0] = (mat2[3][0] * mat1[0][0] + mat2[3][1] * mat1[1][0] | |
+ mat2[3][2] * mat1[2][0] + mat2[3][3] * mat1[3][0]); | |
result[3][1] = (mat2[3][0] * mat1[0][1] + mat2[3][1] * mat1[1][1] | |
+ mat2[3][2] * mat1[2][1] + mat2[3][3] * mat1[3][1]); | |
result[3][2] = (mat2[3][0] * mat1[0][2] + mat2[3][1] * mat1[1][2] | |
+ mat2[3][2] * mat1[2][2] + mat2[3][3] * mat1[3][2]); | |
result[3][3] = (mat2[3][0] * mat1[0][3] + mat2[3][1] * mat1[1][3] | |
+ mat2[3][2] * mat1[2][3] + mat2[3][3] * mat1[3][3]); | |
return result; | |
}; | |
let scaleNonUniform = (m_matrix, sx, sy) => { | |
m_matrix[0][0] *= sx; | |
m_matrix[0][1] *= sx; | |
m_matrix[0][2] *= sx; | |
m_matrix[0][3] *= sx; | |
m_matrix[1][0] *= sy; | |
m_matrix[1][1] *= sy; | |
m_matrix[1][2] *= sy; | |
m_matrix[1][3] *= sy; | |
}; | |
let scale3d = (m_matrix, sx, sy, sz) => { | |
scaleNonUniform(m_matrix, sx, sy); | |
m_matrix[2][0] *= sz; | |
m_matrix[2][1] *= sz; | |
m_matrix[2][2] *= sz; | |
m_matrix[2][3] *= sz; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment