@threeify/math
Version:
Computer graphics oriented, High performance, tree-shakeable, TypeScript 3D vector math library.
1,134 lines (998 loc) • 24.3 kB
text/typescript
import { Euler3, EulerOrder3 } from './Euler3';
import {
degToRad,
delta,
EPSILON,
equalsTolerance,
parseSafeFloats,
toSafeString
} from './Functions';
import { Mat3 } from './Mat3';
import { quatToMat3 } from './Mat3.Functions';
import { Mat4 } from './Mat4';
import { Quat } from './Quat';
import { mat4ToQuat } from './Quat.Functions';
import { Vec2 } from './Vec2';
import { Vec3 } from './Vec3';
import {
vec3Cross,
vec3Length,
vec3MultiplyByScalar,
vec3Normalize,
vec3Subtract
} from './Vec3.Functions';
export function mat4Delta(a: Mat4, b: Mat4): number {
let deltaSum = 0;
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
deltaSum = delta(a.elements[i], b.elements[i]);
}
return deltaSum;
}
export function mat4Zero(result = new Mat4()): Mat4 {
return result.set([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
export function mat4Identity(result = new Mat4()): Mat4 {
return result.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
}
export function mat4SetColumn3(
m: Mat4,
index: number,
v: Vec3,
result = new Mat4()
): Mat4 {
const re = result.set(m.elements).elements;
const base = Number(index) * Mat4.NUM_ROWS;
re[base + 0] = v.x;
re[base + 1] = v.y;
re[base + 2] = v.z;
return result;
}
export function mat4GetColumn3(
m: Mat4,
index: number,
result = new Vec3()
): Vec3 {
const base = index * Mat4.NUM_ROWS;
return result.set(
m.elements[base + 0],
m.elements[base + 1],
m.elements[base + 2]
);
}
export function mat4SetRow3(
m: Mat4,
index: number,
v: Vec3,
result = new Mat4()
): Mat4 {
const re = result.set(m.elements).elements;
const stride = Mat4.NUM_COLUMNS;
re[index + stride * 0] = v.x;
re[index + stride * 1] = v.y;
re[index + stride * 2] = v.z;
return result;
}
export function mat4GetRow3(m: Mat3, index: number, result = new Vec3()): Vec3 {
const stride = Mat4.NUM_COLUMNS;
return result.set(
m.elements[index + stride * 0],
m.elements[index + stride * 1],
m.elements[index + stride * 2]
);
}
export function basis3ToMat4(
xAxis: Vec3,
yAxis: Vec3,
zAxis: Vec3,
result = new Mat4()
): Mat4 {
return result.set([
xAxis.x,
xAxis.y,
xAxis.z,
0,
yAxis.x,
yAxis.y,
yAxis.z,
0,
zAxis.x,
zAxis.y,
zAxis.z,
0,
0,
0,
0,
1
]);
}
export function mat4ToBasis3(
m: Mat4,
xAxis = new Vec3(),
yAxis = new Vec3(),
zAxis = new Vec3()
): { xAxis: Vec3; yAxis: Vec3; zAxis: Vec3 } {
mat4GetColumn3(m, 0, xAxis);
mat4GetColumn3(m, 1, yAxis);
mat4GetColumn3(m, 2, zAxis);
return { xAxis, yAxis, zAxis };
}
export function mat4Equals(
a: Mat4,
b: Mat4,
tolerance: number = EPSILON
): boolean {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
if (!equalsTolerance(a.elements[i], b.elements[i], tolerance)) return false;
}
return true;
}
export function mat4Add(a: Mat4, b: Mat4, result: Mat4 = new Mat4()): Mat4 {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
result.elements[i] = a.elements[i] + b.elements[i];
}
return result;
}
export function mat4Subtract(
a: Mat4,
b: Mat4,
result: Mat4 = new Mat4()
): Mat4 {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
result.elements[i] = a.elements[i] - b.elements[i];
}
return result;
}
export function mat4MultiplyByScalar(
a: Mat4,
b: number,
result: Mat4 = new Mat4()
): Mat4 {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
result.elements[i] = a.elements[i] * b;
}
return result;
}
export function mat4Negate(a: Mat4, result: Mat4 = new Mat4()): Mat4 {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
result.elements[i] = -a.elements[i];
}
return result;
}
export function mat4Multiply(a: Mat4, b: Mat4, result = new Mat4()): Mat4 {
const ae = a.elements;
const be = b.elements;
const te = result.elements;
const a11 = ae[0],
a12 = ae[4],
a13 = ae[8],
a14 = ae[12];
const a21 = ae[1],
a22 = ae[5],
a23 = ae[9],
a24 = ae[13];
const a31 = ae[2],
a32 = ae[6],
a33 = ae[10],
a34 = ae[14];
const a41 = ae[3],
a42 = ae[7],
a43 = ae[11],
a44 = ae[15];
const b11 = be[0],
b12 = be[4],
b13 = be[8],
b14 = be[12];
const b21 = be[1],
b22 = be[5],
b23 = be[9],
b24 = be[13];
const b31 = be[2],
b32 = be[6],
b33 = be[10],
b34 = be[14];
const b41 = be[3],
b42 = be[7],
b43 = be[11],
b44 = be[15];
// TODO: Replace with set(...)
te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
return result;
}
export function mat4Determinant(m: Mat4): number {
// based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
const me = m.elements,
n11 = me[0],
n21 = me[1],
n31 = me[2],
n41 = me[3],
n12 = me[4],
n22 = me[5],
n32 = me[6],
n42 = me[7],
n13 = me[8],
n23 = me[9],
n33 = me[10],
n43 = me[11],
n14 = me[12],
n24 = me[13],
n34 = me[14],
n44 = me[15],
t11 =
n23 * n34 * n42 -
n24 * n33 * n42 +
n24 * n32 * n43 -
n22 * n34 * n43 -
n23 * n32 * n44 +
n22 * n33 * n44,
t12 =
n14 * n33 * n42 -
n13 * n34 * n42 -
n14 * n32 * n43 +
n12 * n34 * n43 +
n13 * n32 * n44 -
n12 * n33 * n44,
t13 =
n13 * n24 * n42 -
n14 * n23 * n42 +
n14 * n22 * n43 -
n12 * n24 * n43 -
n13 * n22 * n44 +
n12 * n23 * n44,
t14 =
n14 * n23 * n32 -
n13 * n24 * n32 -
n14 * n22 * n33 +
n12 * n24 * n33 +
n13 * n22 * n34 -
n12 * n23 * n34;
return n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
}
export function mat4Adjoint(m: Mat4, result = new Mat4()): Mat4 {
// from gl-matrix
const a = m.elements;
const out = result.elements;
const a00 = a[0],
a01 = a[1],
a02 = a[2],
a03 = a[3];
const a10 = a[4],
a11 = a[5],
a12 = a[6],
a13 = a[7];
const a20 = a[8],
a21 = a[9],
a22 = a[10],
a23 = a[11];
const a30 = a[12],
a31 = a[13],
a32 = a[14],
a33 = a[15];
const b00 = a00 * a11 - a01 * a10;
const b01 = a00 * a12 - a02 * a10;
const b02 = a00 * a13 - a03 * a10;
const b03 = a01 * a12 - a02 * a11;
const b04 = a01 * a13 - a03 * a11;
const b05 = a02 * a13 - a03 * a12;
const b06 = a20 * a31 - a21 * a30;
const b07 = a20 * a32 - a22 * a30;
const b08 = a20 * a33 - a23 * a30;
const b09 = a21 * a32 - a22 * a31;
const b10 = a21 * a33 - a23 * a31;
const b11 = a22 * a33 - a23 * a32;
out[0] = a11 * b11 - a12 * b10 + a13 * b09;
out[1] = a02 * b10 - a01 * b11 - a03 * b09;
out[2] = a31 * b05 - a32 * b04 + a33 * b03;
out[3] = a22 * b04 - a21 * b05 - a23 * b03;
out[4] = a12 * b08 - a10 * b11 - a13 * b07;
out[5] = a00 * b11 - a02 * b08 + a03 * b07;
out[6] = a32 * b02 - a30 * b05 - a33 * b01;
out[7] = a20 * b05 - a22 * b02 + a23 * b01;
out[8] = a10 * b10 - a11 * b08 + a13 * b06;
out[9] = a01 * b08 - a00 * b10 - a03 * b06;
out[10] = a30 * b04 - a31 * b02 + a33 * b00;
out[11] = a21 * b02 - a20 * b04 - a23 * b00;
out[12] = a11 * b07 - a10 * b09 - a12 * b06;
out[13] = a00 * b09 - a01 * b07 + a02 * b06;
out[14] = a31 * b01 - a30 * b03 - a32 * b00;
out[15] = a20 * b03 - a21 * b01 + a22 * b00;
return result;
}
export function mat4Transpose(m: Mat4, result = new Mat4()): Mat4 {
const re = m.clone(result).elements;
let tmp;
// TODO: replace this with just reading from me and setting re, no need for a temporary
tmp = re[1];
re[1] = re[4];
re[4] = tmp;
tmp = re[2];
re[2] = re[8];
re[8] = tmp;
tmp = re[6];
re[6] = re[9];
re[9] = tmp;
tmp = re[3];
re[3] = re[12];
re[12] = tmp;
tmp = re[7];
re[7] = re[13];
re[13] = tmp;
tmp = re[11];
re[11] = re[14];
re[14] = tmp;
return result;
}
export function mat4Inverse(m: Mat4, result = new Mat4()): Mat4 {
// based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
const me = m.elements,
n11 = me[0],
n21 = me[1],
n31 = me[2],
n41 = me[3],
n12 = me[4],
n22 = me[5],
n32 = me[6],
n42 = me[7],
n13 = me[8],
n23 = me[9],
n33 = me[10],
n43 = me[11],
n14 = me[12],
n24 = me[13],
n34 = me[14],
n44 = me[15],
t11 =
n23 * n34 * n42 -
n24 * n33 * n42 +
n24 * n32 * n43 -
n22 * n34 * n43 -
n23 * n32 * n44 +
n22 * n33 * n44,
t12 =
n14 * n33 * n42 -
n13 * n34 * n42 -
n14 * n32 * n43 +
n12 * n34 * n43 +
n13 * n32 * n44 -
n12 * n33 * n44,
t13 =
n13 * n24 * n42 -
n14 * n23 * n42 +
n14 * n22 * n43 -
n12 * n24 * n43 -
n13 * n22 * n44 +
n12 * n23 * n44,
t14 =
n14 * n23 * n32 -
n13 * n24 * n32 -
n14 * n22 * n33 +
n12 * n24 * n33 +
n13 * n22 * n34 -
n12 * n23 * n34;
const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
if (det === 0) {
throw new Error('can not invert degenerate matrix');
}
const detInv = 1 / det;
// TODO: replace with a set
const re = result.elements;
re[0] = t11 * detInv;
re[1] =
(n24 * n33 * n41 -
n23 * n34 * n41 -
n24 * n31 * n43 +
n21 * n34 * n43 +
n23 * n31 * n44 -
n21 * n33 * n44) *
detInv;
re[2] =
(n22 * n34 * n41 -
n24 * n32 * n41 +
n24 * n31 * n42 -
n21 * n34 * n42 -
n22 * n31 * n44 +
n21 * n32 * n44) *
detInv;
re[3] =
(n23 * n32 * n41 -
n22 * n33 * n41 -
n23 * n31 * n42 +
n21 * n33 * n42 +
n22 * n31 * n43 -
n21 * n32 * n43) *
detInv;
re[4] = t12 * detInv;
re[5] =
(n13 * n34 * n41 -
n14 * n33 * n41 +
n14 * n31 * n43 -
n11 * n34 * n43 -
n13 * n31 * n44 +
n11 * n33 * n44) *
detInv;
re[6] =
(n14 * n32 * n41 -
n12 * n34 * n41 -
n14 * n31 * n42 +
n11 * n34 * n42 +
n12 * n31 * n44 -
n11 * n32 * n44) *
detInv;
re[7] =
(n12 * n33 * n41 -
n13 * n32 * n41 +
n13 * n31 * n42 -
n11 * n33 * n42 -
n12 * n31 * n43 +
n11 * n32 * n43) *
detInv;
re[8] = t13 * detInv;
re[9] =
(n14 * n23 * n41 -
n13 * n24 * n41 -
n14 * n21 * n43 +
n11 * n24 * n43 +
n13 * n21 * n44 -
n11 * n23 * n44) *
detInv;
re[10] =
(n12 * n24 * n41 -
n14 * n22 * n41 +
n14 * n21 * n42 -
n11 * n24 * n42 -
n12 * n21 * n44 +
n11 * n22 * n44) *
detInv;
re[11] =
(n13 * n22 * n41 -
n12 * n23 * n41 -
n13 * n21 * n42 +
n11 * n23 * n42 +
n12 * n21 * n43 -
n11 * n22 * n43) *
detInv;
re[12] = t14 * detInv;
re[13] =
(n13 * n24 * n31 -
n14 * n23 * n31 +
n14 * n21 * n33 -
n11 * n24 * n33 -
n13 * n21 * n34 +
n11 * n23 * n34) *
detInv;
re[14] =
(n14 * n22 * n31 -
n12 * n24 * n31 -
n14 * n21 * n32 +
n11 * n24 * n32 +
n12 * n21 * n34 -
n11 * n22 * n34) *
detInv;
re[15] =
(n12 * n23 * n31 -
n13 * n22 * n31 +
n13 * n21 * n32 -
n11 * n23 * n32 -
n12 * n21 * n33 +
n11 * n22 * n33) *
detInv;
return result;
}
export function mat4Mix(
a: Mat4,
b: Mat4,
t: number,
result = new Mat4()
): Mat4 {
const s = 1 - t;
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
result.elements[i] = a.elements[i] * s + b.elements[i] * t;
}
return result;
}
export function arrayToMat4(
array: Float32Array | number[],
offset = 0,
result: Mat4 = new Mat4()
): Mat4 {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
result.elements[i] = array[offset + i];
}
return result;
}
export function mat4ToArray(
a: Mat4,
array: Float32Array | number[],
offset = 0
): void {
for (let i = 0; i < Mat4.NUM_COMPONENTS; i++) {
array[offset + i] = a.elements[i];
}
}
export function mat4ToString(a: Mat4): string {
return toSafeString(a.elements);
}
export function mat4Parse(text: string, result = new Mat4()): Mat4 {
return arrayToMat4(parseSafeFloats(text), 0, result);
}
export function mat3ToMat4(a: Mat3, result = new Mat4()): Mat4 {
const ae = a.elements;
return result.set([
ae[0],
ae[1],
ae[2],
0,
ae[3],
ae[4],
ae[5],
0,
ae[6],
ae[7],
ae[8],
0,
0,
0,
0,
1
]);
}
export function quatToMat4(q: Quat, result = new Mat4()): Mat4 {
return mat3ToMat4(quatToMat3(q), result);
}
export function scale3ToMat4(s: Vec3, result = new Mat4()): Mat4 {
return result.set([s.x, 0, 0, 0, 0, s.y, 0, 0, 0, 0, s.z, 0, 0, 0, 0, 1]);
}
// from gl-matrix
export function mat4ToScale3(m: Mat4, result = new Vec3()): Vec3 {
const mat = m.elements;
const m11 = mat[0];
const m12 = mat[1];
const m13 = mat[2];
const m21 = mat[4];
const m22 = mat[5];
const m23 = mat[6];
const m31 = mat[8];
const m32 = mat[9];
const m33 = mat[10];
return result.set(
Math.sqrt(m11 * m11 + m12 * m12 + m13 * m13),
Math.sqrt(m21 * m21 + m22 * m22 + m23 * m23),
Math.sqrt(m31 * m31 + m32 * m32 + m33 * m33)
);
}
export function mat4ToMaxAxisScale(m: Mat4): number {
const scale = mat4ToScale3(m);
return Math.max(Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z));
}
export function translation3ToMat4(t: Vec3, result = new Mat4()): Mat4 {
return result.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t.x, t.y, t.z, 1]);
}
export function mat4ToTranslation3(m: Mat4, result = new Vec3()): Vec3 {
const me = m.elements;
return result.set(me[12], me[13], me[14]);
}
export function mat4Translate(m: Mat4, t: Vec3, result = new Mat4()): Mat4 {
return mat4Multiply(m, translation3ToMat4(t), result);
}
export function mat4Scale(m: Mat4, s: Vec3, result = new Mat4()): Mat4 {
return mat4Multiply(m, scale3ToMat4(s), result);
}
export function mat4RotateByQuat(m: Mat4, q: Quat, result = new Mat4()): Mat4 {
return mat4Multiply(m, quatToMat4(q), result);
}
export function mat4RotateByEuler(
m: Mat4,
e: Euler3,
result = new Mat4()
): Mat4 {
return mat4Multiply(m, euler3ToMat4(e), result);
}
export function mat4Perspective(
left: number,
right: number,
top: number,
bottom: number,
near: number,
far: number,
result = new Mat4()
): Mat4 {
const x = (2 * near) / (right - left);
const y = (2 * near) / (top - bottom);
const a = (right + left) / (right - left);
const b = (top + bottom) / (top - bottom);
const c = -(far + near) / (far - near);
const d = (-2 * far * near) / (far - near);
return result.set([x, 0, 0, 0, 0, y, 0, 0, a, b, c, -1, 0, 0, d, 0]);
}
export function mat4PerspectiveFov(
verticalFovDegrees: number,
near: number,
far: number,
zoom: number,
aspectRatio: number,
result = new Mat4()
): Mat4 {
const height = (2 * near * Math.tan(degToRad(verticalFovDegrees))) / zoom;
const width = height * aspectRatio;
// NOTE: OpenGL screen coordinates are -bottomt to +top, -left to +right.
const right = width * 0.5;
const left = right - width;
const top = height * 0.5;
const bottom = top - height;
return mat4Perspective(left, right, top, bottom, near, far, result);
}
export function mat4PerspectiveFovSubview(
verticalFovDegrees: number,
near: number,
far: number,
zoom: number,
aspectRatio: number,
relativeOffset: Vec2,
relativeView: Vec2,
result = new Mat4()
): Mat4 {
let height = (2 * near * Math.tan(degToRad(verticalFovDegrees))) / zoom;
let width = height * aspectRatio;
// NOTE: OpenGL screen coordinates are -bottomt to +top, -left to +right.
let left = -width * 0.5;
let top = height * 0.5;
left += relativeOffset.x * relativeView.x;
top -= relativeOffset.y * relativeView.y;
width *= relativeView.x;
height *= relativeView.y;
const right = left + width;
const bottom = top - height;
return mat4Perspective(left, right, top, bottom, near, far, result);
}
export function mat4Trace(m: Mat4): number {
const me = m.elements;
return me[0] + me[5] + me[10] + me[15];
}
// TODO: Replace with a Box3?
export function mat4Orthographic(
left: number,
right: number,
top: number,
bottom: number,
near: number,
far: number,
result = new Mat4()
): Mat4 {
const w = 1 / (right - left);
const h = 1 / (top - bottom);
const p = 1 / (far - near);
const x = (right + left) * w;
const y = (top + bottom) * h;
const z = (far + near) * p;
return result.set([
2 * w,
0,
0,
0,
0,
2 * h,
0,
0,
0,
0,
-2 * p,
0,
-x,
-y,
-z,
1
]);
}
export function mat4OrthographicSimple(
height: number,
center: Vec2,
near: number,
far: number,
zoom: number,
aspectRatio = 1,
result = new Mat4()
): Mat4 {
height /= zoom;
const width = height * aspectRatio;
const left = -width * 0.5 + center.x;
const right = left + width;
const top = -height * 0.5 + center.y;
const bottom = top + height;
return mat4Orthographic(left, right, top, bottom, near, far, result);
}
export function mat4LookAt(
eye: Vec3,
target: Vec3,
up: Vec3,
result = new Mat4()
): Mat4 {
const te = result.elements;
const yUp = up.clone();
const zLook = vec3Subtract(eye, target);
const lookLength = vec3Length(zLook);
if (lookLength === 0) {
zLook.z = 1;
} else {
vec3MultiplyByScalar(zLook, 1 / lookLength, zLook);
}
const xRight = vec3Cross(yUp, zLook);
const rightLength = vec3Length(xRight);
if (rightLength === 0) {
// up and z are parallel
zLook.x += 0.0001;
vec3Cross(yUp, zLook, xRight);
vec3Normalize(xRight, xRight);
} else {
vec3MultiplyByScalar(xRight, 1 / rightLength, xRight);
}
const yUp2 = vec3Cross(zLook, xRight);
te[0] = xRight.x;
te[1] = xRight.y;
te[2] = xRight.z;
//te[3] = 0;
te[4] = yUp2.x;
te[5] = yUp2.y;
te[6] = yUp2.z;
//te[7] = 0;
te[8] = zLook.x;
te[9] = zLook.y;
te[10] = zLook.z;
//te[11] = 0;
//te[12] = 0;
//te[13] = 0;
//te[14] = 0;
//te[15] = 1;
return result;
}
export function angleAxisToMat4(
axis: Vec3,
angle: number,
result = new Mat4()
): Mat4 {
// Based on http://www.gamedev.net/reference/articles/article1199.asp
const c = Math.cos(angle);
const s = Math.sin(angle);
const t = 1 - c;
const { x, y, z } = axis;
const tx = t * x;
const ty = t * y;
return result.set([
tx * x + c,
tx * y - s * z,
tx * z + s * y,
0,
tx * y + s * z,
ty * y + c,
ty * z - s * x,
0,
tx * z - s * y,
ty * z + s * x,
t * z * z + c,
0,
0,
0,
0,
1
]);
}
export function euler3ToMat4(euler: Euler3, result = new Mat4()): Mat4 {
const te = result.elements;
const { x, y, z, order } = euler;
const a = Math.cos(x);
const b = Math.sin(x);
const c = Math.cos(y);
const d = Math.sin(y);
const e = Math.cos(z);
const f = Math.sin(z);
// TODO: Replace smart code that compacts all of these orders into one
switch (order) {
case EulerOrder3.XYZ: {
const ae = a * e;
const af = a * f;
const be = b * e;
const bf = b * f;
te[0] = c * e;
te[4] = -c * f;
te[8] = d;
te[1] = af + be * d;
te[5] = ae - bf * d;
te[9] = -b * c;
te[2] = bf - ae * d;
te[6] = be + af * d;
te[10] = a * c;
break;
}
case EulerOrder3.YXZ: {
const ce = c * e;
const cf = c * f;
const de = d * e;
const df = d * f;
te[0] = ce + df * b;
te[4] = de * b - cf;
te[8] = a * d;
te[1] = a * f;
te[5] = a * e;
te[9] = -b;
te[2] = cf * b - de;
te[6] = df + ce * b;
te[10] = a * c;
break;
}
case EulerOrder3.ZXY: {
const ce = c * e;
const cf = c * f;
const de = d * e;
const df = d * f;
te[0] = ce - df * b;
te[4] = -a * f;
te[8] = de + cf * b;
te[1] = cf + de * b;
te[5] = a * e;
te[9] = df - ce * b;
te[2] = -a * d;
te[6] = b;
te[10] = a * c;
break;
}
case EulerOrder3.ZYX: {
const ae = a * e;
const af = a * f;
const be = b * e;
const bf = b * f;
te[0] = c * e;
te[4] = be * d - af;
te[8] = ae * d + bf;
te[1] = c * f;
te[5] = bf * d + ae;
te[9] = af * d - be;
te[2] = -d;
te[6] = b * c;
te[10] = a * c;
break;
}
case EulerOrder3.YZX: {
const ac = a * c;
const ad = a * d;
const bc = b * c;
const bd = b * d;
te[0] = c * e;
te[4] = bd - ac * f;
te[8] = bc * f + ad;
te[1] = f;
te[5] = a * e;
te[9] = -b * e;
te[2] = -d * e;
te[6] = ad * f + bc;
te[10] = ac - bd * f;
break;
}
case EulerOrder3.XZY: {
const ac = a * c;
const ad = a * d;
const bc = b * c;
const bd = b * d;
te[0] = c * e;
te[4] = -f;
te[8] = d * e;
te[1] = ac * f + bd;
te[5] = a * e;
te[9] = ad * f - bc;
te[2] = bc * f - ad;
te[6] = b * e;
te[10] = bd * f + ac;
break;
}
// No default
}
// bottom row
te[3] = 0;
te[7] = 0;
te[11] = 0;
// last column
te[12] = 0;
te[13] = 0;
te[14] = 0;
te[15] = 1;
return result;
}
export function shearToMat4(
x: number,
y: number,
z: number,
result = new Mat4()
): Mat4 {
return result.set([1, y, z, 0, x, 1, z, 0, x, y, 1, 0, 0, 0, 0, 1]);
}
export function mat4Compose(
translation: Vec3,
rotation: Quat,
scale: Vec3,
result = new Mat4()
): Mat4 {
const { x, y, z, w } = rotation;
const x2 = x + x;
const y2 = y + y;
const z2 = z + z;
const xx = x * x2;
const xy = x * y2;
const xz = x * z2;
const yy = y * y2;
const yz = y * z2;
const zz = z * z2;
const wx = w * x2;
const wy = w * y2;
const wz = w * z2;
const sx = scale.x;
const sy = scale.y;
const sz = scale.z;
return result.set([
(1 - (yy + zz)) * sx,
(xy + wz) * sx,
(xz - wy) * sx,
0,
(xy - wz) * sy,
(1 - (xx + zz)) * sy,
(yz + wx) * sy,
0,
(xz + wy) * sz,
(yz - wx) * sz,
(1 - (xx + yy)) * sz,
0,
translation.x,
translation.y,
translation.z,
1
]);
}
export function mat4Decompose(
m: Mat4,
translation = new Vec3(),
rotation = new Quat(),
scale = new Vec3()
): { translation: Vec3; rotation: Quat; scale: Vec3 } {
const te = m.elements;
let sx = vec3Length(new Vec3(te[0], te[1], te[2]));
const sy = vec3Length(new Vec3(te[4], te[5], te[6]));
const sz = vec3Length(new Vec3(te[8], te[9], te[10]));
// if determine is negative, we need to invert one scale
if (mat4Determinant(m) < 0) {
sx = -sx;
}
translation.x = te[12];
translation.y = te[13];
translation.z = te[14];
// scale the rotation part
const m2 = m.clone();
const invSX = 1 / sx;
const invSY = 1 / sy;
const invSZ = 1 / sz;
// TODO: replace with me
m2.elements[0] *= invSX;
m2.elements[1] *= invSX;
m2.elements[2] *= invSX;
m2.elements[4] *= invSY;
m2.elements[5] *= invSY;
m2.elements[6] *= invSY;
m2.elements[8] *= invSZ;
m2.elements[9] *= invSZ;
m2.elements[10] *= invSZ;
mat4ToQuat(m2, rotation);
scale.x = sx;
scale.y = sy;
scale.z = sz;
return { translation, rotation, scale };
}