@threeify/math
Version:
Computer graphics oriented, High performance, tree-shakeable, TypeScript 3D vector math library.
459 lines (427 loc) • 16.1 kB
text/typescript
// a set of ts-jest unit tests that ensure the correct functionality of the Mat4 function helpers
import * as THREE from 'three';
import { EPSILON } from './Functions.js';
import { mat3Equals, mat4ToMat3 } from './Mat3.Functions.js';
import { Mat3 } from './Mat3.js';
import { Mat4 } from './Mat4';
import {
basis3ToMat4,
mat4Add,
mat4Compose,
mat4Decompose,
mat4Delta,
mat4Equals,
mat4Identity,
mat4LookAt,
mat4Multiply,
mat4MultiplyByScalar,
mat4Negate,
mat4Orthographic,
mat4Perspective,
mat4Subtract,
mat4ToBasis3,
mat4Transpose,
mat4Zero,
scale3ToMat4,
translation3ToMat4
} from './Mat4.Functions';
import { Quat } from './Quat';
import { quatNormalize } from './Quat.Functions';
import { Vec3 } from './Vec3';
import { mat4TransformPosition3 } from './Vec3.Functions';
describe('Mat4 Functions', () => {
test('mat4Zero', () => {
const a = new Mat4([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]);
const b = mat4Zero(a);
const c = new Mat4([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
expect(mat4Equals(b, c)).toBe(true);
});
test('mat4Identity', () => {
const a = new Mat4([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]);
const b = mat4Identity(a);
const c = new Mat4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
expect(mat4Equals(b, c)).toBe(true);
});
test('mat4ToMat3', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = mat4ToMat3(a);
const c = new Mat3([1, 2, 3, 5, 6, 7, 9, 10, 11]);
expect(mat3Equals(b, c)).toBe(true);
});
test('mat4Equals', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const c = new Mat4([2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
expect(mat4Equals(a, b)).toBe(true);
expect(mat4Equals(a, c)).toBe(false);
});
test('basis3ToMat4/mat4ToBasis3', () => {
const identityBasis = [
new Vec3(1, 0, 0),
new Vec3(0, 1, 0),
new Vec3(0, 0, 1)
];
const a = basis3ToMat4(
identityBasis[0],
identityBasis[1],
identityBasis[2]
);
const identity = new Mat4();
expect(mat4Delta(a, identity)).toBe(0);
const testBases = [
new Vec3(0, 1, 0),
new Vec3(-1, 0, 0),
new Vec3(0, 0, 1)
];
const b = basis3ToMat4(testBases[0], testBases[1], testBases[2]);
const outBasis = [new Vec3(), new Vec3(), new Vec3()];
mat4ToBasis3(b, outBasis[0], outBasis[1], outBasis[2]);
// check what goes in, is what comes out.
for (let j = 0; j < outBasis.length; j++) {
expect(testBases[j].x).toBeCloseTo(outBasis[j].x);
expect(testBases[j].y).toBeCloseTo(outBasis[j].y);
expect(testBases[j].z).toBeCloseTo(outBasis[j].z);
}
// get the basis out the hard war
for (let j = 0; j < identityBasis.length; j++) {
outBasis[j].copy(identityBasis[j]);
mat4TransformPosition3(b, outBasis[j], outBasis[j]);
}
// did the multiply method of basis extraction work?
for (let j = 0; j < outBasis.length; j++) {
expect(testBases[j].x).toBeCloseTo(outBasis[j].x);
expect(testBases[j].y).toBeCloseTo(outBasis[j].y);
expect(testBases[j].z).toBeCloseTo(outBasis[j].z);
}
});
test('mat4Add', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const c = mat4Add(a, b);
expect(c.elements[0]).toBe(2);
expect(c.elements[1]).toBe(4);
expect(c.elements[2]).toBe(6);
expect(c.elements[3]).toBe(8);
expect(c.elements[4]).toBe(10);
expect(c.elements[5]).toBe(12);
expect(c.elements[6]).toBe(14);
expect(c.elements[7]).toBe(16);
expect(c.elements[8]).toBe(18);
expect(c.elements[9]).toBe(20);
expect(c.elements[10]).toBe(22);
expect(c.elements[11]).toBe(24);
expect(c.elements[12]).toBe(26);
expect(c.elements[13]).toBe(28);
expect(c.elements[14]).toBe(30);
expect(c.elements[15]).toBe(32);
});
test('mat4Subtract', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const c = mat4Subtract(a, b);
expect(c.elements[0]).toBe(0);
expect(c.elements[1]).toBe(0);
expect(c.elements[2]).toBe(0);
expect(c.elements[3]).toBe(0);
expect(c.elements[4]).toBe(0);
expect(c.elements[5]).toBe(0);
expect(c.elements[6]).toBe(0);
expect(c.elements[7]).toBe(0);
expect(c.elements[8]).toBe(0);
expect(c.elements[9]).toBe(0);
expect(c.elements[10]).toBe(0);
expect(c.elements[11]).toBe(0);
expect(c.elements[12]).toBe(0);
expect(c.elements[13]).toBe(0);
expect(c.elements[14]).toBe(0);
expect(c.elements[15]).toBe(0);
});
test('mat4MultiplyByScalar', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = mat4MultiplyByScalar(a, 2);
expect(b.elements[0]).toBe(2);
expect(b.elements[1]).toBe(4);
expect(b.elements[2]).toBe(6);
expect(b.elements[3]).toBe(8);
expect(b.elements[4]).toBe(10);
expect(b.elements[5]).toBe(12);
expect(b.elements[6]).toBe(14);
expect(b.elements[7]).toBe(16);
expect(b.elements[8]).toBe(18);
expect(b.elements[9]).toBe(20);
expect(b.elements[10]).toBe(22);
expect(b.elements[11]).toBe(24);
expect(b.elements[12]).toBe(26);
expect(b.elements[13]).toBe(28);
expect(b.elements[14]).toBe(30);
expect(b.elements[15]).toBe(32);
});
test('mat4Negate', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = mat4Negate(a);
expect(b.elements[0]).toBe(-1);
expect(b.elements[1]).toBe(-2);
expect(b.elements[2]).toBe(-3);
expect(b.elements[3]).toBe(-4);
expect(b.elements[4]).toBe(-5);
expect(b.elements[5]).toBe(-6);
expect(b.elements[6]).toBe(-7);
expect(b.elements[7]).toBe(-8);
expect(b.elements[8]).toBe(-9);
expect(b.elements[9]).toBe(-10);
expect(b.elements[10]).toBe(-11);
expect(b.elements[11]).toBe(-12);
expect(b.elements[12]).toBe(-13);
expect(b.elements[13]).toBe(-14);
expect(b.elements[14]).toBe(-15);
expect(b.elements[15]).toBe(-16);
});
test('mat4Multiply', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const c = mat4Multiply(a, b);
expect(c.elements[0]).toBe(90);
expect(c.elements[1]).toBe(100);
expect(c.elements[2]).toBe(110);
expect(c.elements[3]).toBe(120);
expect(c.elements[4]).toBe(202);
expect(c.elements[5]).toBe(228);
expect(c.elements[6]).toBe(254);
expect(c.elements[7]).toBe(280);
expect(c.elements[8]).toBe(314);
expect(c.elements[9]).toBe(356);
expect(c.elements[10]).toBe(398);
expect(c.elements[11]).toBe(440);
expect(c.elements[12]).toBe(426);
expect(c.elements[13]).toBe(484);
expect(c.elements[14]).toBe(542);
expect(c.elements[15]).toBe(600);
});
test('mat4Transpose', () => {
const a = new Mat4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const b = mat4Transpose(a);
expect(b.elements[0]).toBe(1);
expect(b.elements[1]).toBe(5);
expect(b.elements[2]).toBe(9);
expect(b.elements[3]).toBe(13);
expect(b.elements[4]).toBe(2);
expect(b.elements[5]).toBe(6);
expect(b.elements[6]).toBe(10);
expect(b.elements[7]).toBe(14);
expect(b.elements[8]).toBe(3);
expect(b.elements[9]).toBe(7);
expect(b.elements[10]).toBe(11);
expect(b.elements[11]).toBe(15);
expect(b.elements[12]).toBe(4);
expect(b.elements[13]).toBe(8);
expect(b.elements[14]).toBe(12);
expect(b.elements[15]).toBe(16);
});
test('translation3ToMat4', () => {
const a = translation3ToMat4(new Vec3(1, 2, 3));
expect(a.elements[0]).toBe(1);
expect(a.elements[1]).toBe(0);
expect(a.elements[2]).toBe(0);
expect(a.elements[3]).toBe(0);
expect(a.elements[4]).toBe(0);
expect(a.elements[5]).toBe(1);
expect(a.elements[6]).toBe(0);
expect(a.elements[7]).toBe(0);
expect(a.elements[8]).toBe(0);
expect(a.elements[9]).toBe(0);
expect(a.elements[10]).toBe(1);
expect(a.elements[11]).toBe(0);
expect(a.elements[12]).toBe(1);
expect(a.elements[13]).toBe(2);
expect(a.elements[14]).toBe(3);
expect(a.elements[15]).toBe(1);
});
test('scale3ToMat4', () => {
const a = scale3ToMat4(new Vec3(1, 2, 3));
expect(a.elements[0]).toBe(1);
expect(a.elements[1]).toBe(0);
expect(a.elements[2]).toBe(0);
expect(a.elements[3]).toBe(0);
expect(a.elements[4]).toBe(0);
expect(a.elements[5]).toBe(2);
expect(a.elements[6]).toBe(0);
expect(a.elements[7]).toBe(0);
expect(a.elements[8]).toBe(0);
expect(a.elements[9]).toBe(0);
expect(a.elements[10]).toBe(3);
expect(a.elements[11]).toBe(0);
expect(a.elements[12]).toBe(0);
expect(a.elements[13]).toBe(0);
expect(a.elements[14]).toBe(0);
expect(a.elements[15]).toBe(1);
});
// compose the matrix and then decompose the matrix, the results hsould be the same within the EPLISON tolerance
describe('mat4Compose', () => {
const translation = new Vec3(1, 2, 3);
const rotation = quatNormalize(new Quat(1, 2, -3, 4));
const scale = new Vec3(1, 2, 3);
const m = mat4Compose(translation, rotation, scale);
test('explicit compose', () => {
const m2 = new Mat4([
0.13333333333333353, -0.6666666666666666, -0.7333333333333332, 0,
1.8666666666666665, 0.666666666666667, -0.2666666666666667, 0,
0.9999999999999998, -1.9999999999999996, 2, 0, 1, 2, 3, 1
]);
expect(m.elements[0]).toBeCloseTo(m2.elements[0], EPSILON);
expect(m.elements[1]).toBeCloseTo(m2.elements[1], EPSILON);
expect(m.elements[2]).toBeCloseTo(m2.elements[2], EPSILON);
expect(m.elements[3]).toBeCloseTo(m2.elements[3], EPSILON);
expect(m.elements[4]).toBeCloseTo(m2.elements[4], EPSILON);
expect(m.elements[5]).toBeCloseTo(m2.elements[5], EPSILON);
expect(m.elements[6]).toBeCloseTo(m2.elements[6], EPSILON);
expect(m.elements[7]).toBeCloseTo(m2.elements[7], EPSILON);
expect(m.elements[8]).toBeCloseTo(m2.elements[8], EPSILON);
expect(m.elements[9]).toBeCloseTo(m2.elements[9], EPSILON);
expect(m.elements[10]).toBeCloseTo(m2.elements[10], EPSILON);
expect(m.elements[11]).toBeCloseTo(m2.elements[11], EPSILON);
expect(m.elements[12]).toBeCloseTo(m2.elements[12], EPSILON);
expect(m.elements[13]).toBeCloseTo(m2.elements[13], EPSILON);
expect(m.elements[14]).toBeCloseTo(m2.elements[14], EPSILON);
expect(m.elements[15]).toBeCloseTo(m2.elements[15], EPSILON);
});
test('mat4Compose/mat4Decompose', () => {
const {
translation: translation2,
rotation: rotation2,
scale: scale2
} = mat4Decompose(m);
expect(translation2.x).toBeCloseTo(translation.x, EPSILON);
expect(translation2.y).toBeCloseTo(translation.y, EPSILON);
expect(translation2.z).toBeCloseTo(translation.z, EPSILON);
expect(rotation2.x).toBeCloseTo(rotation.x, EPSILON);
expect(rotation2.y).toBeCloseTo(rotation.y, EPSILON);
expect(rotation2.z).toBeCloseTo(rotation.z, EPSILON);
expect(rotation2.w).toBeCloseTo(rotation.w, EPSILON);
expect(scale2.x).toBeCloseTo(scale.x, EPSILON);
expect(scale2.y).toBeCloseTo(scale.y, EPSILON);
expect(scale2.z).toBeCloseTo(scale.z, EPSILON);
});
});
test('mat4Perspective', () => {
const m = mat4Perspective(-1, 1, -1, 1, 1, 100);
const m2 = new Mat4([
1,
0,
0,
0,
0,
-1,
0,
0,
0,
0,
-101 / 99,
-1,
0,
0,
-200 / 99,
0
]);
expect(m.elements[0]).toBeCloseTo(m2.elements[0], EPSILON);
expect(m.elements[1]).toBeCloseTo(m2.elements[1], EPSILON);
expect(m.elements[2]).toBeCloseTo(m2.elements[2], EPSILON);
expect(m.elements[3]).toBeCloseTo(m2.elements[3], EPSILON);
expect(m.elements[4]).toBeCloseTo(m2.elements[4], EPSILON);
expect(m.elements[5]).toBeCloseTo(m2.elements[5], EPSILON);
expect(m.elements[6]).toBeCloseTo(m2.elements[6], EPSILON);
expect(m.elements[7]).toBeCloseTo(m2.elements[7], EPSILON);
expect(m.elements[8]).toBeCloseTo(m2.elements[8], EPSILON);
expect(m.elements[9]).toBeCloseTo(m2.elements[9], EPSILON);
expect(m.elements[10]).toBeCloseTo(m2.elements[10], EPSILON);
expect(m.elements[11]).toBeCloseTo(m2.elements[11], EPSILON);
expect(m.elements[12]).toBeCloseTo(m2.elements[12], EPSILON);
expect(m.elements[13]).toBeCloseTo(m2.elements[13], EPSILON);
expect(m.elements[14]).toBeCloseTo(m2.elements[14], EPSILON);
expect(m.elements[15]).toBeCloseTo(m2.elements[15], EPSILON);
});
test('mat4Orthographic', () => {
const m = mat4Orthographic(-1, 1, -1, 1, 1, 100);
const m2 = new Mat4([
1,
0,
0,
0,
0,
-1,
0,
0,
0,
0,
-2 / 99,
0,
0,
0,
-101 / 99,
1
]);
expect(m.elements[0]).toBeCloseTo(m2.elements[0], EPSILON);
expect(m.elements[1]).toBeCloseTo(m2.elements[1], EPSILON);
expect(m.elements[2]).toBeCloseTo(m2.elements[2], EPSILON);
expect(m.elements[3]).toBeCloseTo(m2.elements[3], EPSILON);
expect(m.elements[4]).toBeCloseTo(m2.elements[4], EPSILON);
expect(m.elements[5]).toBeCloseTo(m2.elements[5], EPSILON);
expect(m.elements[6]).toBeCloseTo(m2.elements[6], EPSILON);
expect(m.elements[7]).toBeCloseTo(m2.elements[7], EPSILON);
expect(m.elements[8]).toBeCloseTo(m2.elements[8], EPSILON);
expect(m.elements[9]).toBeCloseTo(m2.elements[9], EPSILON);
expect(m.elements[10]).toBeCloseTo(m2.elements[10], EPSILON);
expect(m.elements[11]).toBeCloseTo(m2.elements[11], EPSILON);
expect(m.elements[12]).toBeCloseTo(m2.elements[12], EPSILON);
expect(m.elements[13]).toBeCloseTo(m2.elements[13], EPSILON);
expect(m.elements[14]).toBeCloseTo(m2.elements[14], EPSILON);
expect(m.elements[15]).toBeCloseTo(m2.elements[15], EPSILON);
});
test('mat4Multiply-translation-scale', () => {
const t1 = translation3ToMat4(new Vec3(1, 0, 0));
const t2 = translation3ToMat4(new Vec3(0, 1, 0));
const s1 = scale3ToMat4(new Vec3(2, 2, 2));
// s1 happens first, then t1.
const m1 = mat4Multiply(t1, s1);
expect(m1.elements[0]).toBeCloseTo(2, EPSILON);
expect(m1.elements[5]).toBeCloseTo(2, EPSILON);
expect(m1.elements[12]).toBeCloseTo(1, EPSILON);
// t2 happens first, then s1.
const m2 = mat4Multiply(s1, t1);
expect(m2.elements[0]).toBeCloseTo(2, EPSILON);
expect(m2.elements[5]).toBeCloseTo(2, EPSILON);
expect(m2.elements[12]).toBeCloseTo(2, EPSILON);
});
test('mat4LookAt', () => {
const cubeFaceLooks = [
new Vec3(1, 0, 0),
new Vec3(-1, 0, 0),
new Vec3(0, 1, 0),
new Vec3(0, -1, 0), // wrong
new Vec3(0, 0, 1),
new Vec3(0, 0, -1) // wrong
];
// www.khronos.org/opengl/wiki/Cubemap_Texture
const cubeFaceUps = [
new Vec3(0, -1, 0),
new Vec3(0, -1, 0),
new Vec3(0, 0, +1),
new Vec3(0, 0, -1),
new Vec3(0, -1, 0),
new Vec3(0, -1, 0)
];
for (let i = 0; i < 6; i++) {
const forward = cubeFaceLooks[i].clone();
const up = cubeFaceUps[i].clone();
const lookAt = mat4LookAt(new Vec3(0, 0, 0), forward, up);
const tjMat4 = new THREE.Matrix4();
tjMat4.lookAt(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(forward.x, forward.y, forward.z),
new THREE.Vector3(up.x, up.y, up.z)
);
for (let j = 0; j < 16; j++) {
expect(lookAt.elements[j]).toBeCloseTo(tjMat4.elements[j], EPSILON);
}
}
});
});