@flighthq/geometry 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aabb.d.ts +88 -0
- package/dist/aabb.d.ts.map +1 -0
- package/dist/aabb.js +230 -0
- package/dist/aabb.js.map +1 -0
- package/dist/boundingSphere.d.ts +62 -0
- package/dist/boundingSphere.d.ts.map +1 -0
- package/dist/boundingSphere.js +188 -0
- package/dist/boundingSphere.js.map +1 -0
- package/dist/capsule.d.ts +38 -0
- package/dist/capsule.d.ts.map +1 -0
- package/dist/capsule.js +215 -0
- package/dist/capsule.js.map +1 -0
- package/dist/frustum.d.ts +49 -0
- package/dist/frustum.d.ts.map +1 -0
- package/dist/frustum.js +149 -0
- package/dist/frustum.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/matrix.d.ts +152 -0
- package/dist/matrix.d.ts.map +1 -0
- package/dist/matrix.js +488 -0
- package/dist/matrix.js.map +1 -0
- package/dist/matrix3.d.ts +63 -0
- package/dist/matrix3.d.ts.map +1 -0
- package/dist/matrix3.js +408 -0
- package/dist/matrix3.js.map +1 -0
- package/dist/matrix3Pool.d.ts +6 -0
- package/dist/matrix3Pool.d.ts.map +1 -0
- package/dist/matrix3Pool.js +26 -0
- package/dist/matrix3Pool.js.map +1 -0
- package/dist/matrix4.d.ts +238 -0
- package/dist/matrix4.d.ts.map +1 -0
- package/dist/matrix4.js +931 -0
- package/dist/matrix4.js.map +1 -0
- package/dist/matrix4Pool.d.ts +6 -0
- package/dist/matrix4Pool.d.ts.map +1 -0
- package/dist/matrix4Pool.js +26 -0
- package/dist/matrix4Pool.js.map +1 -0
- package/dist/matrixPool.d.ts +6 -0
- package/dist/matrixPool.d.ts.map +1 -0
- package/dist/matrixPool.js +26 -0
- package/dist/matrixPool.js.map +1 -0
- package/dist/obb.d.ts +44 -0
- package/dist/obb.d.ts.map +1 -0
- package/dist/obb.js +248 -0
- package/dist/obb.js.map +1 -0
- package/dist/plane.d.ts +66 -0
- package/dist/plane.d.ts.map +1 -0
- package/dist/plane.js +140 -0
- package/dist/plane.js.map +1 -0
- package/dist/quaternion.d.ts +128 -0
- package/dist/quaternion.d.ts.map +1 -0
- package/dist/quaternion.js +508 -0
- package/dist/quaternion.js.map +1 -0
- package/dist/quaternionPool.d.ts +6 -0
- package/dist/quaternionPool.d.ts.map +1 -0
- package/dist/quaternionPool.js +29 -0
- package/dist/quaternionPool.js.map +1 -0
- package/dist/ray3d.d.ts +69 -0
- package/dist/ray3d.d.ts.map +1 -0
- package/dist/ray3d.js +264 -0
- package/dist/ray3d.js.map +1 -0
- package/dist/rectangle.d.ts +56 -0
- package/dist/rectangle.d.ts.map +1 -0
- package/dist/rectangle.js +235 -0
- package/dist/rectangle.js.map +1 -0
- package/dist/rectanglePool.d.ts +6 -0
- package/dist/rectanglePool.d.ts.map +1 -0
- package/dist/rectanglePool.js +29 -0
- package/dist/rectanglePool.js.map +1 -0
- package/dist/typedarray.d.ts +4 -0
- package/dist/typedarray.d.ts.map +1 -0
- package/dist/typedarray.js +25 -0
- package/dist/typedarray.js.map +1 -0
- package/dist/vector2.d.ts +130 -0
- package/dist/vector2.d.ts.map +1 -0
- package/dist/vector2.js +250 -0
- package/dist/vector2.js.map +1 -0
- package/dist/vector2Pool.d.ts +6 -0
- package/dist/vector2Pool.d.ts.map +1 -0
- package/dist/vector2Pool.js +20 -0
- package/dist/vector2Pool.js.map +1 -0
- package/dist/vector3.d.ts +220 -0
- package/dist/vector3.d.ts.map +1 -0
- package/dist/vector3.js +380 -0
- package/dist/vector3.js.map +1 -0
- package/dist/vector3Pool.d.ts +6 -0
- package/dist/vector3Pool.d.ts.map +1 -0
- package/dist/vector3Pool.js +28 -0
- package/dist/vector3Pool.js.map +1 -0
- package/dist/vector4.d.ts +180 -0
- package/dist/vector4.d.ts.map +1 -0
- package/dist/vector4.js +333 -0
- package/dist/vector4.js.map +1 -0
- package/dist/vector4Pool.d.ts +6 -0
- package/dist/vector4Pool.d.ts.map +1 -0
- package/dist/vector4Pool.js +29 -0
- package/dist/vector4Pool.js.map +1 -0
- package/package.json +38 -0
- package/src/aabb.test.ts +341 -0
- package/src/boundingSphere.test.ts +247 -0
- package/src/capsule.test.ts +157 -0
- package/src/frustum.test.ts +160 -0
- package/src/matrix.test.ts +1111 -0
- package/src/matrix3.test.ts +752 -0
- package/src/matrix3Pool.test.ts +73 -0
- package/src/matrix4.test.ts +1627 -0
- package/src/matrix4Pool.test.ts +71 -0
- package/src/matrixPool.test.ts +69 -0
- package/src/obb.test.ts +183 -0
- package/src/plane.test.ts +225 -0
- package/src/quaternion.test.ts +531 -0
- package/src/quaternionPool.test.ts +64 -0
- package/src/ray3d.test.ts +297 -0
- package/src/rectangle.test.ts +982 -0
- package/src/rectanglePool.test.ts +71 -0
- package/src/typedarray.test.ts +118 -0
- package/src/vector2.test.ts +958 -0
- package/src/vector2Pool.test.ts +65 -0
- package/src/vector3.test.ts +749 -0
- package/src/vector3Pool.test.ts +68 -0
- package/src/vector4.test.ts +660 -0
- package/src/vector4Pool.test.ts +71 -0
|
@@ -0,0 +1,1627 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendMatrix4,
|
|
3
|
+
appendRotationMatrix4,
|
|
4
|
+
appendScaleMatrix4,
|
|
5
|
+
appendTranslationMatrix4,
|
|
6
|
+
cloneMatrix4,
|
|
7
|
+
composeMatrix4,
|
|
8
|
+
copyMatrix4,
|
|
9
|
+
copyMatrix4ColumnFromVector4,
|
|
10
|
+
copyMatrix4ColumnToVector4,
|
|
11
|
+
copyMatrix4RowFromVector4,
|
|
12
|
+
copyMatrix4RowToVector4,
|
|
13
|
+
createMatrix,
|
|
14
|
+
createMatrix3,
|
|
15
|
+
createMatrix4,
|
|
16
|
+
createMatrix4From2D,
|
|
17
|
+
createOrthographicMatrix4,
|
|
18
|
+
createPerspectiveMatrix4,
|
|
19
|
+
createQuaternion,
|
|
20
|
+
createVector3,
|
|
21
|
+
decomposeMatrix4,
|
|
22
|
+
equalsMatrix4,
|
|
23
|
+
getMatrix4Determinant,
|
|
24
|
+
getMatrix4Element,
|
|
25
|
+
getMatrix4Position,
|
|
26
|
+
interpolateMatrix4,
|
|
27
|
+
inverseMatrix4,
|
|
28
|
+
isAffineMatrix4,
|
|
29
|
+
matrix4TransformPoint,
|
|
30
|
+
matrix4TransformVector,
|
|
31
|
+
matrix4TransformVectors,
|
|
32
|
+
multiplyMatrix4,
|
|
33
|
+
prependMatrix4,
|
|
34
|
+
prependRotationMatrix4,
|
|
35
|
+
prependScaleMatrix4,
|
|
36
|
+
prependTranslationMatrix4,
|
|
37
|
+
rotateMatrix4,
|
|
38
|
+
scaleMatrix4,
|
|
39
|
+
setMatrix4,
|
|
40
|
+
setMatrix4Element,
|
|
41
|
+
setMatrix4From2D,
|
|
42
|
+
setMatrix4FromFloat32Array,
|
|
43
|
+
setMatrix4FromMatrix,
|
|
44
|
+
setMatrix4FromMatrix3,
|
|
45
|
+
setMatrix4FromQuaternion,
|
|
46
|
+
setMatrix4Identity,
|
|
47
|
+
setMatrix4LookAt,
|
|
48
|
+
setMatrix4Position,
|
|
49
|
+
setOrthographicMatrix4,
|
|
50
|
+
setPerspectiveMatrix4,
|
|
51
|
+
setQuaternionFromAxisAngle,
|
|
52
|
+
translateMatrix4,
|
|
53
|
+
transposeMatrix4,
|
|
54
|
+
writeMatrix4ToFloat32Array,
|
|
55
|
+
} from '@flighthq/geometry';
|
|
56
|
+
import type { Matrix, Matrix4 } from '@flighthq/types';
|
|
57
|
+
|
|
58
|
+
const X_AXIS = { x: 1, y: 0, z: 0, w: 0 };
|
|
59
|
+
const Y_AXIS = { x: 0, y: 1, z: 0, w: 0 };
|
|
60
|
+
const Z_AXIS = { x: 0, y: 0, z: 1, w: 0 };
|
|
61
|
+
|
|
62
|
+
describe('appendMatrix4', () => {
|
|
63
|
+
it('is equivalent to multiply(out, source, other)', () => {
|
|
64
|
+
const a = createMatrix4();
|
|
65
|
+
translateMatrix4(a, a, 5, 0, 0);
|
|
66
|
+
const b = createMatrix4();
|
|
67
|
+
scaleMatrix4(b, b, 2, 2, 2);
|
|
68
|
+
|
|
69
|
+
const out1 = createMatrix4();
|
|
70
|
+
appendMatrix4(out1, a, b);
|
|
71
|
+
|
|
72
|
+
const out2 = createMatrix4();
|
|
73
|
+
multiplyMatrix4(out2, a, b);
|
|
74
|
+
|
|
75
|
+
expect(equalsMatrix4(out1, out2)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('supports out === source', () => {
|
|
79
|
+
const a = createMatrix4();
|
|
80
|
+
translateMatrix4(a, a, 5, 0, 0);
|
|
81
|
+
const b = createMatrix4();
|
|
82
|
+
scaleMatrix4(b, b, 2, 3, 4);
|
|
83
|
+
const expected = createMatrix4();
|
|
84
|
+
appendMatrix4(expected, a, b);
|
|
85
|
+
|
|
86
|
+
appendMatrix4(a, a, b);
|
|
87
|
+
expect(equalsMatrix4(a, expected)).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('appendRotationMatrix4', () => {
|
|
92
|
+
it('rotates identity around Z axis by 90 degrees', () => {
|
|
93
|
+
const m = createMatrix4();
|
|
94
|
+
|
|
95
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
96
|
+
|
|
97
|
+
expect(m.m[0]).toBeCloseTo(0);
|
|
98
|
+
expect(m.m[1]).toBeCloseTo(1);
|
|
99
|
+
expect(m.m[4]).toBeCloseTo(-1);
|
|
100
|
+
expect(m.m[5]).toBeCloseTo(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('does not rotate existing translation when appending rotation', () => {
|
|
104
|
+
const m = createMatrix4();
|
|
105
|
+
translateMatrix4(m, m, 10, 0, 0);
|
|
106
|
+
|
|
107
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
108
|
+
|
|
109
|
+
expect(m.m[12]).toBe(10);
|
|
110
|
+
expect(m.m[13]).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('rotates around pivot point', () => {
|
|
114
|
+
const m = createMatrix4();
|
|
115
|
+
translateMatrix4(m, m, 10, 0, 0);
|
|
116
|
+
|
|
117
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Z_AXIS, { x: 5, y: 0, z: 0, w: 1 });
|
|
118
|
+
|
|
119
|
+
expect(m.m[12]).toBeCloseTo(5);
|
|
120
|
+
expect(m.m[13]).toBeCloseTo(5);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('appendRotation and prependRotation match on identity', () => {
|
|
124
|
+
const a = createMatrix4();
|
|
125
|
+
const b = createMatrix4();
|
|
126
|
+
|
|
127
|
+
appendRotationMatrix4(a, a, Math.PI / 4, Z_AXIS);
|
|
128
|
+
prependRotationMatrix4(b, b, Math.PI / 4, Z_AXIS);
|
|
129
|
+
|
|
130
|
+
expect(equalsMatrix4(a, b)).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('appendScaleMatrix4', () => {
|
|
135
|
+
it('scales an identity matrix', () => {
|
|
136
|
+
const m = createMatrix4();
|
|
137
|
+
|
|
138
|
+
appendScaleMatrix4(m, m, 2, 3, 4);
|
|
139
|
+
|
|
140
|
+
expect(m.m[0]).toBe(2);
|
|
141
|
+
expect(m.m[5]).toBe(3);
|
|
142
|
+
expect(m.m[10]).toBe(4);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('accumulates scale multiplicatively', () => {
|
|
146
|
+
const m = createMatrix4();
|
|
147
|
+
scaleMatrix4(m, m, 2, 2, 2);
|
|
148
|
+
|
|
149
|
+
appendScaleMatrix4(m, m, 3, 4, 5);
|
|
150
|
+
|
|
151
|
+
expect(m.m[0]).toBe(6);
|
|
152
|
+
expect(m.m[5]).toBe(8);
|
|
153
|
+
expect(m.m[10]).toBe(10);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('appendTranslationMatrix4', () => {
|
|
158
|
+
it('adds translation to an identity matrix', () => {
|
|
159
|
+
const m = createMatrix4();
|
|
160
|
+
|
|
161
|
+
appendTranslationMatrix4(m, m, 1, 2, 3);
|
|
162
|
+
|
|
163
|
+
expect(m.m[12]).toBe(1);
|
|
164
|
+
expect(m.m[13]).toBe(2);
|
|
165
|
+
expect(m.m[14]).toBe(3);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('adds to existing translation values', () => {
|
|
169
|
+
const m = createMatrix4();
|
|
170
|
+
m.m[12] = 10;
|
|
171
|
+
m.m[13] = 20;
|
|
172
|
+
m.m[14] = 30;
|
|
173
|
+
|
|
174
|
+
appendTranslationMatrix4(m, m, 1, 2, 3);
|
|
175
|
+
|
|
176
|
+
expect(m.m[12]).toBe(11);
|
|
177
|
+
expect(m.m[13]).toBe(22);
|
|
178
|
+
expect(m.m[14]).toBe(33);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('does not affect rotation or scale components', () => {
|
|
182
|
+
const m = createMatrix4();
|
|
183
|
+
m.m[0] = 2; // scale x
|
|
184
|
+
m.m[5] = 3; // scale y
|
|
185
|
+
m.m[10] = 4; // scale z
|
|
186
|
+
|
|
187
|
+
appendTranslationMatrix4(m, m, 1, 2, 3);
|
|
188
|
+
|
|
189
|
+
expect(m.m[0]).toBe(2);
|
|
190
|
+
expect(m.m[5]).toBe(3);
|
|
191
|
+
expect(m.m[10]).toBe(4);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('cloneMatrix4', () => {
|
|
196
|
+
it('creates a createMatrix4 instance', () => {
|
|
197
|
+
const source = createMatrix4();
|
|
198
|
+
const clone: Matrix4 = cloneMatrix4(source);
|
|
199
|
+
|
|
200
|
+
expect(clone).not.toBeNull();
|
|
201
|
+
expect(clone).not.toBe(source);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('copies all values from the source matrix', () => {
|
|
205
|
+
const source = createMatrix4(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53);
|
|
206
|
+
|
|
207
|
+
const clone = cloneMatrix4(source);
|
|
208
|
+
|
|
209
|
+
expect(Array.from(clone.m)).toEqual(Array.from(source.m));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('does not share internal storage', () => {
|
|
213
|
+
const source = createMatrix4();
|
|
214
|
+
const clone = cloneMatrix4(source);
|
|
215
|
+
|
|
216
|
+
clone.m[5] = 42;
|
|
217
|
+
|
|
218
|
+
expect(source.m[5]).toBe(1);
|
|
219
|
+
expect(clone.m[5]).toBe(42);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('composeMatrix4', () => {
|
|
224
|
+
it('composes translation, identity rotation and scale', () => {
|
|
225
|
+
const out = createMatrix4();
|
|
226
|
+
composeMatrix4(out, createVector3(10, 20, 30), createQuaternion(), createVector3(2, 3, 4));
|
|
227
|
+
|
|
228
|
+
// Column-major: diagonal carries scale, last column carries translation.
|
|
229
|
+
expect(out.m[0]).toBeCloseTo(2, 6);
|
|
230
|
+
expect(out.m[5]).toBeCloseTo(3, 6);
|
|
231
|
+
expect(out.m[10]).toBeCloseTo(4, 6);
|
|
232
|
+
expect(out.m[12]).toBeCloseTo(10, 6);
|
|
233
|
+
expect(out.m[13]).toBeCloseTo(20, 6);
|
|
234
|
+
expect(out.m[14]).toBeCloseTo(30, 6);
|
|
235
|
+
expect(out.m[15]).toBeCloseTo(1, 6);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('round-trips with decomposeMatrix4', () => {
|
|
239
|
+
const position = createVector3(1, -2, 3);
|
|
240
|
+
const rotation = createQuaternion();
|
|
241
|
+
setQuaternionFromAxisAngle(rotation, createVector3(0, 1, 0), Math.PI / 5);
|
|
242
|
+
const scale = createVector3(2, 2, 2);
|
|
243
|
+
|
|
244
|
+
const m = createMatrix4();
|
|
245
|
+
composeMatrix4(m, position, rotation, scale);
|
|
246
|
+
|
|
247
|
+
const p = createVector3();
|
|
248
|
+
const r = createQuaternion();
|
|
249
|
+
const s = createVector3();
|
|
250
|
+
decomposeMatrix4(p, r, s, m);
|
|
251
|
+
|
|
252
|
+
expect(p.x).toBeCloseTo(1, 5);
|
|
253
|
+
expect(p.y).toBeCloseTo(-2, 5);
|
|
254
|
+
expect(p.z).toBeCloseTo(3, 5);
|
|
255
|
+
expect(s.x).toBeCloseTo(2, 5);
|
|
256
|
+
expect(s.y).toBeCloseTo(2, 5);
|
|
257
|
+
expect(s.z).toBeCloseTo(2, 5);
|
|
258
|
+
const dot = r.x * rotation.x + r.y * rotation.y + r.z * rotation.z + r.w * rotation.w;
|
|
259
|
+
expect(Math.abs(dot)).toBeCloseTo(1, 5);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('copyMatrix4', () => {
|
|
264
|
+
it('copies all values from source into out', () => {
|
|
265
|
+
const source = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
266
|
+
|
|
267
|
+
const out = createMatrix4();
|
|
268
|
+
copyMatrix4(out, source);
|
|
269
|
+
|
|
270
|
+
expect(Array.from(out.m)).toEqual(Array.from(source.m));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('does not share the underlying Float32Array', () => {
|
|
274
|
+
const source = createMatrix4();
|
|
275
|
+
const out = createMatrix4();
|
|
276
|
+
|
|
277
|
+
copyMatrix4(out, source);
|
|
278
|
+
|
|
279
|
+
out.m[0] = 99;
|
|
280
|
+
|
|
281
|
+
expect(source.m[0]).toBe(1);
|
|
282
|
+
expect(out.m[0]).toBe(99);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('copyMatrix4ColumnFromVector4', () => {
|
|
287
|
+
it('copies values into column 0', () => {
|
|
288
|
+
const m = createMatrix4();
|
|
289
|
+
const v = { x: 1, y: 2, z: 3, w: 4 };
|
|
290
|
+
|
|
291
|
+
copyMatrix4ColumnFromVector4(m, 0, v);
|
|
292
|
+
|
|
293
|
+
expect(m.m[0]).toBe(1);
|
|
294
|
+
expect(m.m[1]).toBe(2);
|
|
295
|
+
expect(m.m[2]).toBe(3);
|
|
296
|
+
expect(m.m[3]).toBe(4);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('copies values into column 2', () => {
|
|
300
|
+
const m = createMatrix4();
|
|
301
|
+
const v = { x: 5, y: 6, z: 7, w: 8 };
|
|
302
|
+
|
|
303
|
+
copyMatrix4ColumnFromVector4(m, 2, v);
|
|
304
|
+
|
|
305
|
+
expect(m.m[8]).toBe(5);
|
|
306
|
+
expect(m.m[9]).toBe(6);
|
|
307
|
+
expect(m.m[10]).toBe(7);
|
|
308
|
+
expect(m.m[11]).toBe(8);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('throws a RangeError for an invalid column index', () => {
|
|
312
|
+
const m = createMatrix4();
|
|
313
|
+
const v = { x: 0, y: 0, z: 0, w: 0 };
|
|
314
|
+
|
|
315
|
+
expect(() => copyMatrix4ColumnFromVector4(m, -1, v)).toThrow(RangeError);
|
|
316
|
+
expect(() => copyMatrix4ColumnFromVector4(m, 4, v)).toThrow(RangeError);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('copyMatrix4ColumnToVector4', () => {
|
|
321
|
+
it('copies values from column 1 into a vector', () => {
|
|
322
|
+
const m = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
323
|
+
|
|
324
|
+
const out = { x: 0, y: 0, z: 0, w: 0 };
|
|
325
|
+
|
|
326
|
+
copyMatrix4ColumnToVector4(out, 1, m);
|
|
327
|
+
|
|
328
|
+
expect(out).toEqual({
|
|
329
|
+
x: 5,
|
|
330
|
+
y: 6,
|
|
331
|
+
z: 7,
|
|
332
|
+
w: 8,
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('throws a RangeError for an invalid column index', () => {
|
|
337
|
+
const m = createMatrix4();
|
|
338
|
+
const out = { x: 0, y: 0, z: 0, w: 0 };
|
|
339
|
+
|
|
340
|
+
expect(() => copyMatrix4ColumnToVector4(out, 99, m)).toThrow(RangeError);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('copyMatrix4RowFromVector4', () => {
|
|
345
|
+
it('copies values into row 0', () => {
|
|
346
|
+
const m = createMatrix4();
|
|
347
|
+
const v = { x: 1, y: 2, z: 3, w: 4 };
|
|
348
|
+
|
|
349
|
+
copyMatrix4RowFromVector4(m, 0, v);
|
|
350
|
+
|
|
351
|
+
expect(m.m[0]).toBe(1);
|
|
352
|
+
expect(m.m[4]).toBe(2);
|
|
353
|
+
expect(m.m[8]).toBe(3);
|
|
354
|
+
expect(m.m[12]).toBe(4);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('copies values into row 3', () => {
|
|
358
|
+
const m = createMatrix4();
|
|
359
|
+
const v = { x: 9, y: 8, z: 7, w: 6 };
|
|
360
|
+
|
|
361
|
+
copyMatrix4RowFromVector4(m, 3, v);
|
|
362
|
+
|
|
363
|
+
expect(m.m[3]).toBe(9);
|
|
364
|
+
expect(m.m[7]).toBe(8);
|
|
365
|
+
expect(m.m[11]).toBe(7);
|
|
366
|
+
expect(m.m[15]).toBe(6);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('throws a RangeError for an invalid row index', () => {
|
|
370
|
+
const m = createMatrix4();
|
|
371
|
+
const v = { x: 0, y: 0, z: 0, w: 0 };
|
|
372
|
+
|
|
373
|
+
expect(() => copyMatrix4RowFromVector4(m, -1, v)).toThrow(RangeError);
|
|
374
|
+
expect(() => copyMatrix4RowFromVector4(m, 4, v)).toThrow(RangeError);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('copyMatrix4RowToVector4', () => {
|
|
379
|
+
it('copies values from row 2 into a vector', () => {
|
|
380
|
+
const m = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
381
|
+
|
|
382
|
+
const out = { x: 0, y: 0, z: 0, w: 0 };
|
|
383
|
+
|
|
384
|
+
copyMatrix4RowToVector4(out, 2, m);
|
|
385
|
+
|
|
386
|
+
expect(out).toEqual({
|
|
387
|
+
x: 3,
|
|
388
|
+
y: 7,
|
|
389
|
+
z: 11,
|
|
390
|
+
w: 15,
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('throws a RangeError for an invalid row index', () => {
|
|
395
|
+
const m = createMatrix4();
|
|
396
|
+
const out = { x: 0, y: 0, z: 0, w: 0 };
|
|
397
|
+
|
|
398
|
+
expect(() => copyMatrix4RowToVector4(out, 42, m)).toThrow(RangeError);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe('createMatrix4', () => {
|
|
403
|
+
it('creates an identity matrix when called with no arguments', () => {
|
|
404
|
+
const m = createMatrix4();
|
|
405
|
+
|
|
406
|
+
expect(Array.from(m.m)).toEqual([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('creates a Float32Array of length 16', () => {
|
|
410
|
+
const m = createMatrix4();
|
|
411
|
+
|
|
412
|
+
expect(m.m).toBeInstanceOf(Float32Array);
|
|
413
|
+
expect(m.m.length).toBe(16);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('overrides only the provided constructor values', () => {
|
|
417
|
+
const m = createMatrix4(
|
|
418
|
+
2, // m00
|
|
419
|
+
undefined,
|
|
420
|
+
undefined,
|
|
421
|
+
undefined,
|
|
422
|
+
undefined,
|
|
423
|
+
3, // m11
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
expect(Array.from(m.m)).toEqual([2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('maps constructor arguments to correct column-major indices', () => {
|
|
430
|
+
const m = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
431
|
+
|
|
432
|
+
expect(Array.from(m.m)).toEqual([
|
|
433
|
+
// column 0
|
|
434
|
+
1, 2, 3, 4,
|
|
435
|
+
// column 1
|
|
436
|
+
5, 6, 7, 8,
|
|
437
|
+
// column 2
|
|
438
|
+
9, 10, 11, 12,
|
|
439
|
+
// column 3
|
|
440
|
+
13, 14, 15, 16,
|
|
441
|
+
]);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe('createMatrix4From2D', () => {
|
|
446
|
+
it('creates a Matrix4 instance', () => {
|
|
447
|
+
const m: Matrix4 = createMatrix4From2D(1, 0, 0, 1, 10, 20);
|
|
448
|
+
expect(m).not.toBeNull();
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('initializes the matrix using set2D semantics', () => {
|
|
452
|
+
const m = createMatrix4From2D(1, 2, 3, 4, 5, 6);
|
|
453
|
+
|
|
454
|
+
expect(Array.from(m.m)).toEqual([1, 2, 0, 0, 3, 4, 0, 0, 0, 0, 1, 0, 5, 6, 0, 1]);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('does not share internal storage with other matrices', () => {
|
|
458
|
+
const a = createMatrix4From2D(1, 0, 0, 1, 0, 0);
|
|
459
|
+
const b = createMatrix4From2D(1, 0, 0, 1, 0, 0);
|
|
460
|
+
|
|
461
|
+
b.m[0] = 42;
|
|
462
|
+
|
|
463
|
+
expect(a.m[0]).toBe(1);
|
|
464
|
+
expect(b.m[0]).toBe(42);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
describe('createOrthographicMatrix4', () => {
|
|
469
|
+
it('returns a Matrix4 equivalent to setOrtho', () => {
|
|
470
|
+
const m1 = createOrthographicMatrix4(-1, 1, -1, 1, 0.1, 100);
|
|
471
|
+
const m2 = createMatrix4();
|
|
472
|
+
setOrthographicMatrix4(m2, -1, 1, -1, 1, 0.1, 100);
|
|
473
|
+
expect(equalsMatrix4(m1, m2)).toBe(true);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('createPerspectiveMatrix4', () => {
|
|
478
|
+
it('returns a Matrix4 equivalent to setPerspective', () => {
|
|
479
|
+
const m1 = createPerspectiveMatrix4(0.5, 1.6, 0.1, 1000);
|
|
480
|
+
const m2 = createMatrix4();
|
|
481
|
+
setPerspectiveMatrix4(m2, 0.5, 1.6, 0.1, 1000);
|
|
482
|
+
expect(equalsMatrix4(m1, m2)).toBe(true);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('decomposeMatrix4', () => {
|
|
487
|
+
it('extracts translation, rotation and scale from a TRS matrix', () => {
|
|
488
|
+
const m = createMatrix4();
|
|
489
|
+
const rotation = createQuaternion();
|
|
490
|
+
setQuaternionFromAxisAngle(rotation, createVector3(0, 0, 1), Math.PI / 2);
|
|
491
|
+
composeMatrix4(m, createVector3(5, 6, 7), rotation, createVector3(2, 3, 4));
|
|
492
|
+
|
|
493
|
+
const p = createVector3();
|
|
494
|
+
const r = createQuaternion();
|
|
495
|
+
const s = createVector3();
|
|
496
|
+
decomposeMatrix4(p, r, s, m);
|
|
497
|
+
|
|
498
|
+
expect(p.x).toBeCloseTo(5, 5);
|
|
499
|
+
expect(p.y).toBeCloseTo(6, 5);
|
|
500
|
+
expect(p.z).toBeCloseTo(7, 5);
|
|
501
|
+
expect(s.x).toBeCloseTo(2, 5);
|
|
502
|
+
expect(s.y).toBeCloseTo(3, 5);
|
|
503
|
+
expect(s.z).toBeCloseTo(4, 5);
|
|
504
|
+
const dot = r.x * rotation.x + r.y * rotation.y + r.z * rotation.z + r.w * rotation.w;
|
|
505
|
+
expect(Math.abs(dot)).toBeCloseTo(1, 5);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('identity matrix decomposes to origin, identity rotation, unit scale', () => {
|
|
509
|
+
const p = createVector3();
|
|
510
|
+
const r = createQuaternion();
|
|
511
|
+
const s = createVector3();
|
|
512
|
+
decomposeMatrix4(p, r, s, createMatrix4());
|
|
513
|
+
|
|
514
|
+
expect(p.x).toBeCloseTo(0, 6);
|
|
515
|
+
expect(p.y).toBeCloseTo(0, 6);
|
|
516
|
+
expect(p.z).toBeCloseTo(0, 6);
|
|
517
|
+
expect(s.x).toBeCloseTo(1, 6);
|
|
518
|
+
expect(s.y).toBeCloseTo(1, 6);
|
|
519
|
+
expect(s.z).toBeCloseTo(1, 6);
|
|
520
|
+
expect(r.w).toBeCloseTo(1, 6);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
describe('equalsMatrix4', () => {
|
|
525
|
+
it('returns true when comparing the same reference', () => {
|
|
526
|
+
const m = createMatrix4();
|
|
527
|
+
expect(equalsMatrix4(m, m)).toBe(true);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('returns false if either argument is null or undefined', () => {
|
|
531
|
+
const m = createMatrix4();
|
|
532
|
+
|
|
533
|
+
expect(equalsMatrix4(m, null)).toBe(false);
|
|
534
|
+
expect(equalsMatrix4(undefined, m)).toBe(false);
|
|
535
|
+
expect(equalsMatrix4(null, null)).toBe(true); // same reference shortcut
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('returns true for two matrices with identical values', () => {
|
|
539
|
+
const a = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
540
|
+
|
|
541
|
+
const b = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
542
|
+
|
|
543
|
+
expect(equalsMatrix4(a, b)).toBe(true);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('returns false if any value differs', () => {
|
|
547
|
+
const a = createMatrix4();
|
|
548
|
+
const b = createMatrix4();
|
|
549
|
+
|
|
550
|
+
b.m[10] = 2;
|
|
551
|
+
|
|
552
|
+
expect(equalsMatrix4(a, b)).toBe(false);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('getMatrix4Determinant', () => {
|
|
557
|
+
it('returns 1 for the identity matrix', () => {
|
|
558
|
+
const m = createMatrix4();
|
|
559
|
+
expect(getMatrix4Determinant(m)).toBe(1);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
describe('getMatrix4Element', () => {
|
|
564
|
+
it('returns the element at the given row and column', () => {
|
|
565
|
+
const m = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
566
|
+
expect(getMatrix4Element(m, 0, 0)).toBe(1);
|
|
567
|
+
expect(getMatrix4Element(m, 1, 0)).toBe(2);
|
|
568
|
+
expect(getMatrix4Element(m, 0, 1)).toBe(5);
|
|
569
|
+
expect(getMatrix4Element(m, 3, 3)).toBe(16);
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
describe('getMatrix4Position', () => {
|
|
574
|
+
it('extracts translation components from the matrix', () => {
|
|
575
|
+
const m = createMatrix4();
|
|
576
|
+
m.m[12] = 10;
|
|
577
|
+
m.m[13] = 20;
|
|
578
|
+
m.m[14] = 30;
|
|
579
|
+
|
|
580
|
+
const out = { x: 0, y: 0, z: 0 };
|
|
581
|
+
|
|
582
|
+
getMatrix4Position(out, m);
|
|
583
|
+
|
|
584
|
+
expect(out).toEqual({ x: 10, y: 20, z: 30 });
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('does not mutate the source matrix', () => {
|
|
588
|
+
const m = createMatrix4();
|
|
589
|
+
const snapshot = Array.from(m.m);
|
|
590
|
+
|
|
591
|
+
const out = { x: 0, y: 0, z: 0 };
|
|
592
|
+
getMatrix4Position(out, m);
|
|
593
|
+
|
|
594
|
+
expect(Array.from(m.m)).toEqual(snapshot);
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
describe('interpolateMatrix4', () => {
|
|
599
|
+
it('returns a at t=0', () => {
|
|
600
|
+
const a = createMatrix4();
|
|
601
|
+
translateMatrix4(a, a, 0, 0, 0);
|
|
602
|
+
const b = createMatrix4();
|
|
603
|
+
translateMatrix4(b, b, 10, 0, 0);
|
|
604
|
+
const out = createMatrix4();
|
|
605
|
+
interpolateMatrix4(out, a, b, 0);
|
|
606
|
+
expect(equalsMatrix4(out, a)).toBe(true);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('returns b at t=1', () => {
|
|
610
|
+
const a = createMatrix4();
|
|
611
|
+
const b = createMatrix4();
|
|
612
|
+
translateMatrix4(b, b, 10, 0, 0);
|
|
613
|
+
const out = createMatrix4();
|
|
614
|
+
interpolateMatrix4(out, a, b, 1);
|
|
615
|
+
expect(equalsMatrix4(out, b)).toBe(true);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('returns midpoint at t=0.5', () => {
|
|
619
|
+
const a = createMatrix4();
|
|
620
|
+
const b = createMatrix4();
|
|
621
|
+
translateMatrix4(b, b, 10, 0, 0);
|
|
622
|
+
const out = createMatrix4();
|
|
623
|
+
interpolateMatrix4(out, a, b, 0.5);
|
|
624
|
+
expect(out.m[12]).toBeCloseTo(5);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('supports out === a', () => {
|
|
628
|
+
const a = createMatrix4();
|
|
629
|
+
const b = createMatrix4();
|
|
630
|
+
translateMatrix4(b, b, 10, 20, 30);
|
|
631
|
+
interpolateMatrix4(a, a, b, 0.5);
|
|
632
|
+
expect(a.m[12]).toBeCloseTo(5);
|
|
633
|
+
expect(a.m[13]).toBeCloseTo(10);
|
|
634
|
+
expect(a.m[14]).toBeCloseTo(15);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('supports out === b', () => {
|
|
638
|
+
const a = createMatrix4();
|
|
639
|
+
const b = createMatrix4();
|
|
640
|
+
translateMatrix4(b, b, 10, 20, 30);
|
|
641
|
+
interpolateMatrix4(b, a, b, 0.5);
|
|
642
|
+
expect(b.m[12]).toBeCloseTo(5);
|
|
643
|
+
expect(b.m[13]).toBeCloseTo(10);
|
|
644
|
+
expect(b.m[14]).toBeCloseTo(15);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
describe('inverseMatrix4', () => {
|
|
649
|
+
it('inverse of identity is identity', () => {
|
|
650
|
+
const m = createMatrix4();
|
|
651
|
+
const inv = createMatrix4();
|
|
652
|
+
inverseMatrix4(inv, m);
|
|
653
|
+
|
|
654
|
+
expect(equalsMatrix4(inv, createMatrix4())).toBe(true);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('inverse of translation only negates translation', () => {
|
|
658
|
+
const m = createMatrix4();
|
|
659
|
+
translateMatrix4(m, m, 5, -3, 2);
|
|
660
|
+
|
|
661
|
+
const inv = createMatrix4();
|
|
662
|
+
inverseMatrix4(inv, m);
|
|
663
|
+
|
|
664
|
+
expect(inv.m[12]).toBeCloseTo(-5); // Correct negative translation
|
|
665
|
+
expect(inv.m[13]).toBeCloseTo(3); // Correct negative translation
|
|
666
|
+
expect(inv.m[14]).toBeCloseTo(-2); // Correct negative translation
|
|
667
|
+
|
|
668
|
+
// rotation part should stay identity
|
|
669
|
+
expect(inv.m[0]).toBeCloseTo(1);
|
|
670
|
+
expect(inv.m[5]).toBeCloseTo(1);
|
|
671
|
+
expect(inv.m[10]).toBeCloseTo(1);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('inverse of rotation-only matrix is its transpose', () => {
|
|
675
|
+
const m = createMatrix4();
|
|
676
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
677
|
+
|
|
678
|
+
const inv = createMatrix4();
|
|
679
|
+
inverseMatrix4(inv, m);
|
|
680
|
+
|
|
681
|
+
// m * inv = identity
|
|
682
|
+
const check = createMatrix4();
|
|
683
|
+
multiplyMatrix4(check, m, inv);
|
|
684
|
+
expectMatrix4Close(check, createMatrix4());
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('inverse of rotation + translation', () => {
|
|
688
|
+
const m = createMatrix4();
|
|
689
|
+
translateMatrix4(m, m, 5, 0, 0);
|
|
690
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
691
|
+
|
|
692
|
+
const inv = createMatrix4();
|
|
693
|
+
inverseMatrix4(inv, m);
|
|
694
|
+
|
|
695
|
+
// m * inv = identity
|
|
696
|
+
const check = createMatrix4();
|
|
697
|
+
multiplyMatrix4(check, m, inv);
|
|
698
|
+
expectMatrix4Close(check, createMatrix4());
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('supports out === source', () => {
|
|
702
|
+
const matrix = createMatrix4();
|
|
703
|
+
translateMatrix4(matrix, matrix, 5, -3, 2);
|
|
704
|
+
appendRotationMatrix4(matrix, matrix, Math.PI / 6, Z_AXIS);
|
|
705
|
+
scaleMatrix4(matrix, matrix, 2, 3, 4);
|
|
706
|
+
const expected = createMatrix4();
|
|
707
|
+
inverseMatrix4(expected, matrix);
|
|
708
|
+
|
|
709
|
+
inverseMatrix4(matrix, matrix);
|
|
710
|
+
|
|
711
|
+
expectMatrix4Close(matrix, expected);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('inverse of singular matrix should fail or produce NaN', () => {
|
|
715
|
+
const m = createMatrix4();
|
|
716
|
+
m.m[0] = 0; // make determinant zero
|
|
717
|
+
|
|
718
|
+
const inv = createMatrix4();
|
|
719
|
+
const ok = inverseMatrix4(inv, m);
|
|
720
|
+
|
|
721
|
+
const det = getMatrix4Determinant(m);
|
|
722
|
+
expect(det).toBeCloseTo(0);
|
|
723
|
+
|
|
724
|
+
// Documented sentinel for a singular matrix: returns false and fills the output with NaN.
|
|
725
|
+
expect(ok).toBe(false);
|
|
726
|
+
expect(inv.m[0]).toBeNaN();
|
|
727
|
+
expect(inv.m[1]).toBeNaN();
|
|
728
|
+
expect(inv.m[2]).toBeNaN();
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
describe('isAffineMatrix4', () => {
|
|
733
|
+
it('returns true for the identity matrix', () => {
|
|
734
|
+
const m = createMatrix4();
|
|
735
|
+
expect(isAffineMatrix4(m)).toBe(true);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
it('returns true for a 2D transform matrix', () => {
|
|
739
|
+
const m = createMatrix4From2D(1, 0, 0, 1, 10, 20);
|
|
740
|
+
expect(isAffineMatrix4(m)).toBe(true);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('returns false if m[3] is non-zero', () => {
|
|
744
|
+
const m = createMatrix4();
|
|
745
|
+
m.m[3] = 1;
|
|
746
|
+
expect(isAffineMatrix4(m)).toBe(false);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('returns false if m[7] is non-zero', () => {
|
|
750
|
+
const m = createMatrix4();
|
|
751
|
+
m.m[7] = 1;
|
|
752
|
+
expect(isAffineMatrix4(m)).toBe(false);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it('returns false if m[11] is non-zero', () => {
|
|
756
|
+
const m = createMatrix4();
|
|
757
|
+
m.m[11] = 1;
|
|
758
|
+
expect(isAffineMatrix4(m)).toBe(false);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('returns false if m[15] is not 1', () => {
|
|
762
|
+
const m = createMatrix4();
|
|
763
|
+
m.m[15] = 0;
|
|
764
|
+
expect(isAffineMatrix4(m)).toBe(false);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
describe('matrix4TransformPoint', () => {
|
|
769
|
+
it('translates a point by the matrix translation', () => {
|
|
770
|
+
const m = createMatrix4();
|
|
771
|
+
translateMatrix4(m, m, 5, 10, 15);
|
|
772
|
+
const out = { x: 0, y: 0, z: 0 };
|
|
773
|
+
matrix4TransformPoint(out, m, { x: 0, y: 0, z: 0 });
|
|
774
|
+
expect(out.x).toBeCloseTo(5);
|
|
775
|
+
expect(out.y).toBeCloseTo(10);
|
|
776
|
+
expect(out.z).toBeCloseTo(15);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it('scales a point correctly', () => {
|
|
780
|
+
const m = createMatrix4();
|
|
781
|
+
scaleMatrix4(m, m, 2, 3, 4);
|
|
782
|
+
const out = { x: 0, y: 0, z: 0 };
|
|
783
|
+
matrix4TransformPoint(out, m, { x: 1, y: 2, z: 3 });
|
|
784
|
+
expect(out.x).toBe(2);
|
|
785
|
+
expect(out.y).toBe(6);
|
|
786
|
+
expect(out.z).toBe(12);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('supports out === point', () => {
|
|
790
|
+
const m = createMatrix4();
|
|
791
|
+
translateMatrix4(m, m, 5, 10, 15);
|
|
792
|
+
const point = { x: 1, y: 2, z: 3 };
|
|
793
|
+
matrix4TransformPoint(point, m, point);
|
|
794
|
+
expect(point.x).toBeCloseTo(6);
|
|
795
|
+
expect(point.y).toBeCloseTo(12);
|
|
796
|
+
expect(point.z).toBeCloseTo(18);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
describe('matrix4TransformVector', () => {
|
|
801
|
+
it('transforms a Vector4 by the matrix', () => {
|
|
802
|
+
const m = createMatrix4();
|
|
803
|
+
translateMatrix4(m, m, 5, 10, 15);
|
|
804
|
+
const out = { x: 0, y: 0, z: 0, w: 0 };
|
|
805
|
+
matrix4TransformVector(out, m, { x: 1, y: 2, z: 3, w: 1 });
|
|
806
|
+
expect(out.x).toBeCloseTo(6);
|
|
807
|
+
expect(out.y).toBeCloseTo(12);
|
|
808
|
+
expect(out.z).toBeCloseTo(18);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('does not translate a direction vector with w = 0', () => {
|
|
812
|
+
const m = createMatrix4();
|
|
813
|
+
translateMatrix4(m, m, 5, 10, 15);
|
|
814
|
+
const out = { x: 0, y: 0, z: 0, w: 0 };
|
|
815
|
+
matrix4TransformVector(out, m, { x: 1, y: 2, z: 3, w: 0 });
|
|
816
|
+
expect(out.x).toBeCloseTo(1);
|
|
817
|
+
expect(out.y).toBeCloseTo(2);
|
|
818
|
+
expect(out.z).toBeCloseTo(3);
|
|
819
|
+
expect(out.w).toBeCloseTo(0);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('supports out === vector', () => {
|
|
823
|
+
const m = createMatrix4();
|
|
824
|
+
translateMatrix4(m, m, 5, 10, 15);
|
|
825
|
+
const vector = { x: 1, y: 2, z: 3, w: 1 };
|
|
826
|
+
matrix4TransformVector(vector, m, vector);
|
|
827
|
+
expect(vector.x).toBeCloseTo(6);
|
|
828
|
+
expect(vector.y).toBeCloseTo(12);
|
|
829
|
+
expect(vector.z).toBeCloseTo(18);
|
|
830
|
+
expect(vector.w).toBeCloseTo(1);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
describe('matrix4TransformVectors', () => {
|
|
835
|
+
it('transforms a flat array of [x, y, z] triples', () => {
|
|
836
|
+
const m = createMatrix4();
|
|
837
|
+
translateMatrix4(m, m, 1, 2, 3);
|
|
838
|
+
const vectors = new Float32Array([0, 0, 0, 1, 0, 0]);
|
|
839
|
+
const out = new Float32Array(6);
|
|
840
|
+
matrix4TransformVectors(out, m, vectors);
|
|
841
|
+
expect(out[0]).toBeCloseTo(1);
|
|
842
|
+
expect(out[1]).toBeCloseTo(2);
|
|
843
|
+
expect(out[2]).toBeCloseTo(3);
|
|
844
|
+
expect(out[3]).toBeCloseTo(2);
|
|
845
|
+
expect(out[4]).toBeCloseTo(2);
|
|
846
|
+
expect(out[5]).toBeCloseTo(3);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
it('supports out === vectors', () => {
|
|
850
|
+
const m = createMatrix4();
|
|
851
|
+
translateMatrix4(m, m, 1, 2, 3);
|
|
852
|
+
const vectors = new Float32Array([0, 0, 0, 1, 0, 0]);
|
|
853
|
+
matrix4TransformVectors(vectors, m, vectors);
|
|
854
|
+
expect(Array.from(vectors)).toEqual([1, 2, 3, 2, 2, 3]);
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
describe('multiplyMatrix4', () => {
|
|
859
|
+
describe('multiply (identity)', () => {
|
|
860
|
+
it('returns the right-hand operand when left is identity', () => {
|
|
861
|
+
const I = createMatrix4();
|
|
862
|
+
const T = createMatrix4From2D(1, 0, 0, 1, 10, 20);
|
|
863
|
+
|
|
864
|
+
const out = createMatrix4();
|
|
865
|
+
multiplyMatrix4(out, I, T);
|
|
866
|
+
|
|
867
|
+
expect(equalsMatrix4(out, T)).toBe(true);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('returns the left-hand operand when right is identity', () => {
|
|
871
|
+
const I = createMatrix4();
|
|
872
|
+
const S = createMatrix4();
|
|
873
|
+
scaleMatrix4(S, S, 2, 3, 4);
|
|
874
|
+
|
|
875
|
+
const out = createMatrix4();
|
|
876
|
+
multiplyMatrix4(out, S, I);
|
|
877
|
+
|
|
878
|
+
expect(equalsMatrix4(out, S)).toBe(true);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('does not mutate inputs', () => {
|
|
882
|
+
const a = createMatrix4();
|
|
883
|
+
translateMatrix4(a, a, 1, 2, 3);
|
|
884
|
+
const b = createMatrix4();
|
|
885
|
+
scaleMatrix4(b, b, 2, 2, 2);
|
|
886
|
+
|
|
887
|
+
const aBefore = Array.from(a.m);
|
|
888
|
+
const bBefore = Array.from(b.m);
|
|
889
|
+
|
|
890
|
+
multiplyMatrix4(createMatrix4(), a, b);
|
|
891
|
+
|
|
892
|
+
expect(Array.from(a.m)).toEqual(aBefore);
|
|
893
|
+
expect(Array.from(b.m)).toEqual(bBefore);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('supports out === a', () => {
|
|
897
|
+
const a = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
898
|
+
const b = createMatrix4(17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79);
|
|
899
|
+
const expected = createMatrix4();
|
|
900
|
+
multiplyMatrix4(expected, a, b);
|
|
901
|
+
|
|
902
|
+
multiplyMatrix4(a, a, b);
|
|
903
|
+
expect(equalsMatrix4(a, expected)).toBe(true);
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
it('supports out === b', () => {
|
|
907
|
+
const a = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
908
|
+
const b = createMatrix4(17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79);
|
|
909
|
+
const expected = createMatrix4();
|
|
910
|
+
multiplyMatrix4(expected, a, b);
|
|
911
|
+
|
|
912
|
+
multiplyMatrix4(b, a, b);
|
|
913
|
+
expect(equalsMatrix4(b, expected)).toBe(true);
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
describe('multiplyMatrix4', () => {
|
|
918
|
+
it('post-multiplies by the given matrix', () => {
|
|
919
|
+
const m = createMatrix4();
|
|
920
|
+
translateMatrix4(m, m, 1, 0, 0);
|
|
921
|
+
const s = createMatrix4();
|
|
922
|
+
scaleMatrix4(s, s, 2, 2, 2);
|
|
923
|
+
|
|
924
|
+
multiplyMatrix4(m, m, s);
|
|
925
|
+
|
|
926
|
+
// translation then scale: position unaffected
|
|
927
|
+
expect(m.m[12]).toBe(1);
|
|
928
|
+
expect(m.m[0]).toBe(2);
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
describe('multiply (ordering)', () => {
|
|
933
|
+
it('translation × scale ≠ scale × translation', () => {
|
|
934
|
+
const T = createMatrix4();
|
|
935
|
+
translateMatrix4(T, T, 10, 0, 0);
|
|
936
|
+
const S = createMatrix4();
|
|
937
|
+
scaleMatrix4(S, S, 2, 2, 2);
|
|
938
|
+
|
|
939
|
+
const TS = createMatrix4();
|
|
940
|
+
multiplyMatrix4(TS, T, S);
|
|
941
|
+
|
|
942
|
+
const ST = createMatrix4();
|
|
943
|
+
multiplyMatrix4(ST, S, T);
|
|
944
|
+
|
|
945
|
+
// TS: translate, then scale → translation unaffected
|
|
946
|
+
expect(TS.m[12]).toBe(10);
|
|
947
|
+
|
|
948
|
+
// ST: scale, then translate → translation scaled
|
|
949
|
+
expect(ST.m[12]).toBe(20);
|
|
950
|
+
|
|
951
|
+
expect(equalsMatrix4(TS, ST)).toBe(false);
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
describe('multiply equivalence', () => {
|
|
956
|
+
it('appendTranslation equals multiply by translation matrix', () => {
|
|
957
|
+
const m1 = createMatrix4();
|
|
958
|
+
translateMatrix4(m1, m1, 1, 2, 3);
|
|
959
|
+
const m2 = createMatrix4();
|
|
960
|
+
translateMatrix4(m2, m2, 1, 2, 3);
|
|
961
|
+
|
|
962
|
+
const t = createMatrix4();
|
|
963
|
+
translateMatrix4(t, t, 4, 5, 6);
|
|
964
|
+
|
|
965
|
+
appendTranslationMatrix4(m1, m1, 4, 5, 6);
|
|
966
|
+
multiplyMatrix4(m2, m2, t);
|
|
967
|
+
|
|
968
|
+
expect(equalsMatrix4(m1, m2)).toBe(true);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it('prependScale equals multiply scale × matrix', () => {
|
|
972
|
+
const m1 = createMatrix4();
|
|
973
|
+
translateMatrix4(m1, m1, 10, 0, 0);
|
|
974
|
+
const m2 = createMatrix4();
|
|
975
|
+
translateMatrix4(m2, m2, 10, 0, 0);
|
|
976
|
+
|
|
977
|
+
const s = createMatrix4();
|
|
978
|
+
scaleMatrix4(s, s, 2, 2, 2);
|
|
979
|
+
|
|
980
|
+
prependScaleMatrix4(m1, m1, 2, 2, 2);
|
|
981
|
+
multiplyMatrix4(m2, s, m2);
|
|
982
|
+
|
|
983
|
+
expect(equalsMatrix4(m1, m2)).toBe(true);
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
describe('prependMatrix4', () => {
|
|
989
|
+
it('is equivalent to multiply(out, other, source)', () => {
|
|
990
|
+
const a = createMatrix4();
|
|
991
|
+
translateMatrix4(a, a, 5, 0, 0);
|
|
992
|
+
const b = createMatrix4();
|
|
993
|
+
scaleMatrix4(b, b, 2, 2, 2);
|
|
994
|
+
|
|
995
|
+
const out1 = createMatrix4();
|
|
996
|
+
prependMatrix4(out1, a, b);
|
|
997
|
+
|
|
998
|
+
const out2 = createMatrix4();
|
|
999
|
+
multiplyMatrix4(out2, b, a);
|
|
1000
|
+
|
|
1001
|
+
expect(equalsMatrix4(out1, out2)).toBe(true);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
it('supports out === source', () => {
|
|
1005
|
+
const a = createMatrix4();
|
|
1006
|
+
translateMatrix4(a, a, 5, 0, 0);
|
|
1007
|
+
const b = createMatrix4();
|
|
1008
|
+
scaleMatrix4(b, b, 2, 3, 4);
|
|
1009
|
+
const expected = createMatrix4();
|
|
1010
|
+
prependMatrix4(expected, a, b);
|
|
1011
|
+
|
|
1012
|
+
prependMatrix4(a, a, b);
|
|
1013
|
+
expect(equalsMatrix4(a, expected)).toBe(true);
|
|
1014
|
+
});
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
describe('prependRotationMatrix4', () => {
|
|
1018
|
+
it('rotates identity around Z axis', () => {
|
|
1019
|
+
const m = createMatrix4();
|
|
1020
|
+
|
|
1021
|
+
prependRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
1022
|
+
|
|
1023
|
+
expect(m.m[0]).toBeCloseTo(0);
|
|
1024
|
+
expect(m.m[1]).toBeCloseTo(1);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it('rotates translation when prepending rotation', () => {
|
|
1028
|
+
const m = createMatrix4();
|
|
1029
|
+
translateMatrix4(m, m, 10, 0, 0);
|
|
1030
|
+
|
|
1031
|
+
prependRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
1032
|
+
|
|
1033
|
+
// (10, 0, 0) → (0, 10, 0)
|
|
1034
|
+
expect(m.m[12]).toBeCloseTo(0);
|
|
1035
|
+
expect(m.m[13]).toBeCloseTo(10);
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
describe('prependScaleMatrix4', () => {
|
|
1040
|
+
it('scales an identity matrix', () => {
|
|
1041
|
+
const m = createMatrix4();
|
|
1042
|
+
|
|
1043
|
+
prependScaleMatrix4(m, m, 2, 3, 4);
|
|
1044
|
+
|
|
1045
|
+
expect(m.m[0]).toBe(2);
|
|
1046
|
+
expect(m.m[5]).toBe(3);
|
|
1047
|
+
expect(m.m[10]).toBe(4);
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
it('scales translation when prepending scale', () => {
|
|
1051
|
+
const m = createMatrix4();
|
|
1052
|
+
translateMatrix4(m, m, 10, 20, 30);
|
|
1053
|
+
|
|
1054
|
+
prependScaleMatrix4(m, m, 2, 3, 4);
|
|
1055
|
+
|
|
1056
|
+
expect(m.m[12]).toBe(20); // 10 * 2
|
|
1057
|
+
expect(m.m[13]).toBe(60); // 20 * 3
|
|
1058
|
+
expect(m.m[14]).toBe(120); // 30 * 4
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it('scale, appendScale, and prependScale behave the same on identity', () => {
|
|
1062
|
+
const a = createMatrix4();
|
|
1063
|
+
const b = createMatrix4();
|
|
1064
|
+
const c = createMatrix4();
|
|
1065
|
+
|
|
1066
|
+
scaleMatrix4(a, a, 2, 3, 4);
|
|
1067
|
+
appendScaleMatrix4(b, b, 2, 3, 4);
|
|
1068
|
+
prependScaleMatrix4(c, c, 2, 3, 4);
|
|
1069
|
+
|
|
1070
|
+
expect(equalsMatrix4(a, b)).toBe(true);
|
|
1071
|
+
expect(equalsMatrix4(b, c)).toBe(true);
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
describe('prependTranslationMatrix4', () => {
|
|
1076
|
+
it('translates an identity matrix by (x, y, z)', () => {
|
|
1077
|
+
const m = createMatrix4();
|
|
1078
|
+
|
|
1079
|
+
prependTranslationMatrix4(m, m, 1, 2, 3);
|
|
1080
|
+
|
|
1081
|
+
expect(m.m[12]).toBe(1);
|
|
1082
|
+
expect(m.m[13]).toBe(2);
|
|
1083
|
+
expect(m.m[14]).toBe(3);
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
it('prepends translation before existing transforms', () => {
|
|
1087
|
+
const m = createMatrix4();
|
|
1088
|
+
translateMatrix4(m, m, 10, 0, 0);
|
|
1089
|
+
|
|
1090
|
+
prependTranslationMatrix4(m, m, 5, 0, 0);
|
|
1091
|
+
|
|
1092
|
+
// world-space prepend: (5 + 10)
|
|
1093
|
+
expect(m.m[12]).toBe(15);
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
it('translate, appendTranslation, and prependTranslation behave the same on identity', () => {
|
|
1097
|
+
const a = createMatrix4();
|
|
1098
|
+
const b = createMatrix4();
|
|
1099
|
+
const c = createMatrix4();
|
|
1100
|
+
|
|
1101
|
+
translateMatrix4(a, a, 1, 2, 3);
|
|
1102
|
+
appendTranslationMatrix4(b, b, 1, 2, 3);
|
|
1103
|
+
prependTranslationMatrix4(c, c, 1, 2, 3);
|
|
1104
|
+
|
|
1105
|
+
expect(equalsMatrix4(a, b)).toBe(true);
|
|
1106
|
+
expect(equalsMatrix4(b, c)).toBe(true);
|
|
1107
|
+
});
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
describe('rotateMatrix4', () => {
|
|
1111
|
+
it('matches appendRotation on identity', () => {
|
|
1112
|
+
const a = createMatrix4();
|
|
1113
|
+
const b = createMatrix4();
|
|
1114
|
+
|
|
1115
|
+
rotateMatrix4(a, a, Z_AXIS, Math.PI / 2);
|
|
1116
|
+
appendRotationMatrix4(b, b, Math.PI / 2, Z_AXIS);
|
|
1117
|
+
|
|
1118
|
+
expect(equalsMatrix4(a, b)).toBe(true);
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
it('preserves translation when rotating locally', () => {
|
|
1122
|
+
const m = createMatrix4();
|
|
1123
|
+
translateMatrix4(m, m, 5, 0, 0);
|
|
1124
|
+
|
|
1125
|
+
rotateMatrix4(m, m, Z_AXIS, Math.PI / 2);
|
|
1126
|
+
|
|
1127
|
+
expect(m.m[12]).toBe(5);
|
|
1128
|
+
expect(m.m[13]).toBe(0);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it('rotation around X does not affect X axis basis vector', () => {
|
|
1132
|
+
const m = createMatrix4();
|
|
1133
|
+
|
|
1134
|
+
appendRotationMatrix4(m, m, Math.PI / 2, X_AXIS);
|
|
1135
|
+
|
|
1136
|
+
expect(m.m[0]).toBeCloseTo(1);
|
|
1137
|
+
expect(m.m[1]).toBeCloseTo(0);
|
|
1138
|
+
expect(m.m[2]).toBeCloseTo(0);
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('rotation around Y does not affect Y axis basis vector', () => {
|
|
1142
|
+
const m = createMatrix4();
|
|
1143
|
+
|
|
1144
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Y_AXIS);
|
|
1145
|
+
|
|
1146
|
+
expect(m.m[4]).toBeCloseTo(0);
|
|
1147
|
+
expect(m.m[5]).toBeCloseTo(1);
|
|
1148
|
+
expect(m.m[6]).toBeCloseTo(0);
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
describe('scaleMatrix4', () => {
|
|
1153
|
+
it('scales an identity matrix by (x, y, z)', () => {
|
|
1154
|
+
const m = createMatrix4();
|
|
1155
|
+
|
|
1156
|
+
scaleMatrix4(m, m, 2, 3, 4);
|
|
1157
|
+
|
|
1158
|
+
expect(m.m[0]).toBe(2);
|
|
1159
|
+
expect(m.m[5]).toBe(3);
|
|
1160
|
+
expect(m.m[10]).toBe(4);
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
it('accumulates scale multiplicatively', () => {
|
|
1164
|
+
const m = createMatrix4();
|
|
1165
|
+
|
|
1166
|
+
scaleMatrix4(m, m, 2, 3, 4);
|
|
1167
|
+
scaleMatrix4(m, m, 5, 6, 7);
|
|
1168
|
+
|
|
1169
|
+
expect(m.m[0]).toBe(10); // 2 * 5
|
|
1170
|
+
expect(m.m[5]).toBe(18); // 3 * 6
|
|
1171
|
+
expect(m.m[10]).toBe(28); // 4 * 7
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
it('does not modify translation when scaling locally', () => {
|
|
1175
|
+
const m = createMatrix4();
|
|
1176
|
+
translateMatrix4(m, m, 10, 20, 30);
|
|
1177
|
+
|
|
1178
|
+
scaleMatrix4(m, m, 2, 3, 4);
|
|
1179
|
+
|
|
1180
|
+
expect(m.m[12]).toBe(10);
|
|
1181
|
+
expect(m.m[13]).toBe(20);
|
|
1182
|
+
expect(m.m[14]).toBe(30);
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
describe('setMatrix4', () => {
|
|
1187
|
+
it('sets all 16 values in column-major order', () => {
|
|
1188
|
+
const m = createMatrix4();
|
|
1189
|
+
|
|
1190
|
+
setMatrix4(m, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
1191
|
+
|
|
1192
|
+
expect(Array.from(m.m)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
it('overwrites existing matrix values completely', () => {
|
|
1196
|
+
const m = createMatrix4(99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99);
|
|
1197
|
+
|
|
1198
|
+
setMatrix4(m, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
|
1199
|
+
|
|
1200
|
+
expect(Array.from(m.m)).toEqual([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
describe('setMatrix4Element', () => {
|
|
1205
|
+
it('writes the value at the given row and column', () => {
|
|
1206
|
+
const m = createMatrix4();
|
|
1207
|
+
setMatrix4Element(m, 2, 3, 42);
|
|
1208
|
+
expect(getMatrix4Element(m, 2, 3)).toBe(42);
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
it('does not affect other elements', () => {
|
|
1212
|
+
const m = createMatrix4();
|
|
1213
|
+
setMatrix4Element(m, 1, 2, 7);
|
|
1214
|
+
expect(getMatrix4Element(m, 0, 0)).toBe(1);
|
|
1215
|
+
expect(getMatrix4Element(m, 3, 3)).toBe(1);
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
describe('setMatrix4From2D', () => {
|
|
1220
|
+
it('sets a 2D transform with translation', () => {
|
|
1221
|
+
const m = createMatrix4();
|
|
1222
|
+
|
|
1223
|
+
setMatrix4From2D(m, 1, 2, 3, 4, 5, 6);
|
|
1224
|
+
|
|
1225
|
+
expect(Array.from(m.m)).toEqual([
|
|
1226
|
+
// column 0
|
|
1227
|
+
1, 2, 0, 0,
|
|
1228
|
+
// column 1
|
|
1229
|
+
3, 4, 0, 0,
|
|
1230
|
+
// column 2
|
|
1231
|
+
0, 0, 1, 0,
|
|
1232
|
+
// column 3
|
|
1233
|
+
5, 6, 0, 1,
|
|
1234
|
+
]);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
it('defaults tx and ty to 0 when omitted', () => {
|
|
1238
|
+
const m = createMatrix4();
|
|
1239
|
+
|
|
1240
|
+
setMatrix4From2D(m, 7, 8, 9, 10);
|
|
1241
|
+
|
|
1242
|
+
expect(m.m[12]).toBe(0);
|
|
1243
|
+
expect(m.m[13]).toBe(0);
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
it('produces an affine matrix', () => {
|
|
1247
|
+
const m = createMatrix4();
|
|
1248
|
+
|
|
1249
|
+
setMatrix4From2D(m, 1, 0, 0, 1, 0, 0);
|
|
1250
|
+
|
|
1251
|
+
expect(isAffineMatrix4(m)).toBe(true);
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it('sets Z axis to identity', () => {
|
|
1255
|
+
const m = createMatrix4();
|
|
1256
|
+
|
|
1257
|
+
setMatrix4From2D(m, 1, 2, 3, 4);
|
|
1258
|
+
|
|
1259
|
+
expect(m.m[10]).toBe(1); // z-scale
|
|
1260
|
+
expect(m.m[2]).toBe(0);
|
|
1261
|
+
expect(m.m[6]).toBe(0);
|
|
1262
|
+
expect(m.m[14]).toBe(0);
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
describe('setMatrix4FromFloat32Array', () => {
|
|
1267
|
+
it('reads 16 elements at offset 0', () => {
|
|
1268
|
+
const arr = new Float32Array(16);
|
|
1269
|
+
for (let i = 0; i < 16; i++) arr[i] = i + 1;
|
|
1270
|
+
const m = createMatrix4();
|
|
1271
|
+
setMatrix4FromFloat32Array(m, 0, arr);
|
|
1272
|
+
for (let i = 0; i < 16; i++) {
|
|
1273
|
+
expect(m.m[i]).toBe(i + 1);
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
it('reads 16 elements at a non-zero offset', () => {
|
|
1278
|
+
const arr = new Float32Array(20);
|
|
1279
|
+
for (let i = 0; i < 20; i++) arr[i] = i + 1;
|
|
1280
|
+
const m = createMatrix4();
|
|
1281
|
+
setMatrix4FromFloat32Array(m, 4, arr);
|
|
1282
|
+
for (let i = 0; i < 16; i++) {
|
|
1283
|
+
expect(m.m[i]).toBe(i + 5);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
it('round-trips with writeMatrix4ToFloat32Array', () => {
|
|
1288
|
+
const m = createMatrix4();
|
|
1289
|
+
translateMatrix4(m, m, 3, 4, 5);
|
|
1290
|
+
const arr = new Float32Array(16);
|
|
1291
|
+
writeMatrix4ToFloat32Array(arr, 0, m);
|
|
1292
|
+
const m2 = createMatrix4();
|
|
1293
|
+
setMatrix4FromFloat32Array(m2, 0, arr);
|
|
1294
|
+
expect(equalsMatrix4(m, m2)).toBe(true);
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
describe('setMatrix4FromMatrix', () => {
|
|
1299
|
+
it('should convert an Matrix to a Matrix4', () => {
|
|
1300
|
+
const mat2D: Matrix = createMatrix();
|
|
1301
|
+
|
|
1302
|
+
const mat = createMatrix4();
|
|
1303
|
+
setMatrix4FromMatrix(mat, mat2D);
|
|
1304
|
+
|
|
1305
|
+
const expectedMatrix4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1306
|
+
|
|
1307
|
+
expect(mat.m).toEqual(expectedMatrix4);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
it('should handle scaling and translation', () => {
|
|
1311
|
+
// scale(2,3), translate(5,10)
|
|
1312
|
+
const mat2D: Matrix = createMatrix(2, 0, 0, 3, 5, 10);
|
|
1313
|
+
|
|
1314
|
+
const mat = createMatrix4();
|
|
1315
|
+
setMatrix4FromMatrix(mat, mat2D);
|
|
1316
|
+
|
|
1317
|
+
expect(getMatrix4Element(mat, 0, 0)).toEqual(2); // a
|
|
1318
|
+
expect(getMatrix4Element(mat, 0, 1)).toEqual(0); // b
|
|
1319
|
+
expect(getMatrix4Element(mat, 0, 2)).toEqual(0);
|
|
1320
|
+
expect(getMatrix4Element(mat, 0, 3)).toEqual(5); // tx
|
|
1321
|
+
|
|
1322
|
+
expect(getMatrix4Element(mat, 1, 0)).toEqual(0); // c
|
|
1323
|
+
expect(getMatrix4Element(mat, 1, 1)).toEqual(3); // d
|
|
1324
|
+
expect(getMatrix4Element(mat, 1, 2)).toEqual(0);
|
|
1325
|
+
expect(getMatrix4Element(mat, 1, 3)).toEqual(10); // ty
|
|
1326
|
+
|
|
1327
|
+
expect(getMatrix4Element(mat, 2, 0)).toEqual(0);
|
|
1328
|
+
expect(getMatrix4Element(mat, 2, 1)).toEqual(0);
|
|
1329
|
+
expect(getMatrix4Element(mat, 2, 2)).toEqual(1);
|
|
1330
|
+
expect(getMatrix4Element(mat, 2, 3)).toEqual(0);
|
|
1331
|
+
|
|
1332
|
+
expect(getMatrix4Element(mat, 3, 0)).toEqual(0);
|
|
1333
|
+
expect(getMatrix4Element(mat, 3, 1)).toEqual(0);
|
|
1334
|
+
expect(getMatrix4Element(mat, 3, 2)).toEqual(0);
|
|
1335
|
+
expect(getMatrix4Element(mat, 3, 3)).toEqual(1);
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
describe('setMatrix4FromMatrix3', () => {
|
|
1340
|
+
it('should convert a Matrix3x3 to a Matrix4', () => {
|
|
1341
|
+
const mat3 = createMatrix3();
|
|
1342
|
+
|
|
1343
|
+
const mat = createMatrix4();
|
|
1344
|
+
setMatrix4FromMatrix3(mat, mat3);
|
|
1345
|
+
|
|
1346
|
+
const expectedMatrix4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1347
|
+
|
|
1348
|
+
expect(mat.m).toEqual(expectedMatrix4);
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
it('should handle scaling and translation', () => {
|
|
1352
|
+
// scale(2,3), translate(5,10)
|
|
1353
|
+
const mat3 = createMatrix3(2, 0, 5, 0, 3, 10, 0, 0, 1);
|
|
1354
|
+
|
|
1355
|
+
const mat = createMatrix4();
|
|
1356
|
+
setMatrix4FromMatrix3(mat, mat3);
|
|
1357
|
+
|
|
1358
|
+
expect(getMatrix4Element(mat, 0, 0)).toEqual(2); // a
|
|
1359
|
+
expect(getMatrix4Element(mat, 0, 1)).toEqual(0); // b
|
|
1360
|
+
expect(getMatrix4Element(mat, 0, 2)).toEqual(0);
|
|
1361
|
+
expect(getMatrix4Element(mat, 0, 3)).toEqual(5); // tx
|
|
1362
|
+
|
|
1363
|
+
expect(getMatrix4Element(mat, 1, 0)).toEqual(0); // c
|
|
1364
|
+
expect(getMatrix4Element(mat, 1, 1)).toEqual(3); // d
|
|
1365
|
+
expect(getMatrix4Element(mat, 1, 2)).toEqual(0);
|
|
1366
|
+
expect(getMatrix4Element(mat, 1, 3)).toEqual(10); // ty
|
|
1367
|
+
|
|
1368
|
+
expect(getMatrix4Element(mat, 2, 0)).toEqual(0);
|
|
1369
|
+
expect(getMatrix4Element(mat, 2, 1)).toEqual(0);
|
|
1370
|
+
expect(getMatrix4Element(mat, 2, 2)).toEqual(1);
|
|
1371
|
+
expect(getMatrix4Element(mat, 2, 3)).toEqual(0);
|
|
1372
|
+
|
|
1373
|
+
expect(getMatrix4Element(mat, 3, 0)).toEqual(0);
|
|
1374
|
+
expect(getMatrix4Element(mat, 3, 1)).toEqual(0);
|
|
1375
|
+
expect(getMatrix4Element(mat, 3, 2)).toEqual(0);
|
|
1376
|
+
expect(getMatrix4Element(mat, 3, 3)).toEqual(1);
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
describe('setMatrix4FromQuaternion', () => {
|
|
1381
|
+
it('identity quaternion writes the identity matrix', () => {
|
|
1382
|
+
const m = createMatrix4(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2);
|
|
1383
|
+
setMatrix4FromQuaternion(m, createQuaternion());
|
|
1384
|
+
expect(Array.from(m.m)).toEqual([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
it('a 90-degree z rotation maps +x to +y', () => {
|
|
1388
|
+
const q = createQuaternion();
|
|
1389
|
+
setQuaternionFromAxisAngle(q, createVector3(0, 0, 1), Math.PI / 2);
|
|
1390
|
+
const m = createMatrix4();
|
|
1391
|
+
setMatrix4FromQuaternion(m, q);
|
|
1392
|
+
const out = createVector3();
|
|
1393
|
+
matrix4TransformPoint(out, m, createVector3(1, 0, 0));
|
|
1394
|
+
expect(out.x).toBeCloseTo(0, 6);
|
|
1395
|
+
expect(out.y).toBeCloseTo(1, 6);
|
|
1396
|
+
expect(out.z).toBeCloseTo(0, 6);
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
describe('setMatrix4Identity', () => {
|
|
1401
|
+
it('resets a matrix to identity', () => {
|
|
1402
|
+
const m = createMatrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
1403
|
+
|
|
1404
|
+
setMatrix4Identity(m);
|
|
1405
|
+
|
|
1406
|
+
expect(Array.from(m.m)).toEqual([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1407
|
+
});
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
describe('setMatrix4LookAt', () => {
|
|
1411
|
+
it('places the eye at the origin of view space', () => {
|
|
1412
|
+
const m = createMatrix4();
|
|
1413
|
+
const eye = createVector3(0, 0, 5);
|
|
1414
|
+
setMatrix4LookAt(m, eye, createVector3(0, 0, 0), createVector3(0, 1, 0));
|
|
1415
|
+
|
|
1416
|
+
const out = createVector3();
|
|
1417
|
+
matrix4TransformPoint(out, m, eye);
|
|
1418
|
+
expect(out.x).toBeCloseTo(0, 6);
|
|
1419
|
+
expect(out.y).toBeCloseTo(0, 6);
|
|
1420
|
+
expect(out.z).toBeCloseTo(0, 6);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
it('looks down -z so the target lands in front of the camera', () => {
|
|
1424
|
+
const m = createMatrix4();
|
|
1425
|
+
setMatrix4LookAt(m, createVector3(0, 0, 5), createVector3(0, 0, 0), createVector3(0, 1, 0));
|
|
1426
|
+
|
|
1427
|
+
const out = createVector3();
|
|
1428
|
+
matrix4TransformPoint(out, m, createVector3(0, 0, 0));
|
|
1429
|
+
// Target is 5 units in front of the eye => view-space z = -5 (RH).
|
|
1430
|
+
expect(out.z).toBeCloseTo(-5, 6);
|
|
1431
|
+
});
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
describe('setMatrix4Position', () => {
|
|
1435
|
+
it('sets the translation components of the matrix', () => {
|
|
1436
|
+
const m = createMatrix4();
|
|
1437
|
+
|
|
1438
|
+
setMatrix4Position(m, { x: 5, y: 6, z: 7 });
|
|
1439
|
+
|
|
1440
|
+
expect(m.m[12]).toBe(5);
|
|
1441
|
+
expect(m.m[13]).toBe(6);
|
|
1442
|
+
expect(m.m[14]).toBe(7);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
it('does not modify other matrix values', () => {
|
|
1446
|
+
const m = createMatrix4();
|
|
1447
|
+
const before = Array.from(m.m);
|
|
1448
|
+
|
|
1449
|
+
setMatrix4Position(m, { x: 1, y: 2, z: 3 });
|
|
1450
|
+
|
|
1451
|
+
expect(m.m[0]).toBe(before[0]);
|
|
1452
|
+
expect(m.m[5]).toBe(before[5]);
|
|
1453
|
+
expect(m.m[10]).toBe(before[10]);
|
|
1454
|
+
expect(m.m[15]).toBe(before[15]);
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
it('can overwrite an existing translation', () => {
|
|
1458
|
+
const m = createMatrix4();
|
|
1459
|
+
setMatrix4Position(m, { x: 1, y: 2, z: 3 });
|
|
1460
|
+
setMatrix4Position(m, { x: -1, y: -2, z: -3 });
|
|
1461
|
+
|
|
1462
|
+
expect(m.m[12]).toBe(-1);
|
|
1463
|
+
expect(m.m[13]).toBe(-2);
|
|
1464
|
+
expect(m.m[14]).toBe(-3);
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
describe('setOrthographicMatrix4', () => {
|
|
1469
|
+
it('sets m[0] to 2 / (right - left)', () => {
|
|
1470
|
+
const m = createMatrix4();
|
|
1471
|
+
setOrthographicMatrix4(m, -1, 1, -1, 1, 0.1, 100);
|
|
1472
|
+
expect(m.m[0]).toBeCloseTo(1); // 2 / (1 - (-1)) = 1
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
it('sets m[5] to 2 / (top - bottom)', () => {
|
|
1476
|
+
const m = createMatrix4();
|
|
1477
|
+
setOrthographicMatrix4(m, 0, 2, 0, 4, 0.1, 100);
|
|
1478
|
+
expect(m.m[5]).toBeCloseTo(0.5); // 2 / (4 - 0) = 0.5
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
describe('setPerspectiveMatrix4', () => {
|
|
1483
|
+
it('throws when aspect is 0', () => {
|
|
1484
|
+
const m = createMatrix4();
|
|
1485
|
+
expect(() => setPerspectiveMatrix4(m, 0.5, 0, 0.1, 1000)).toThrow();
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
it('sets m[11] to -1', () => {
|
|
1489
|
+
const m = createMatrix4();
|
|
1490
|
+
setPerspectiveMatrix4(m, 0.5, 1.6, 0.1, 1000);
|
|
1491
|
+
expect(m.m[11]).toBe(-1);
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
it('sets m[15] to 0 so clip w is -z, not -z + w', () => {
|
|
1495
|
+
const m = createMatrix4();
|
|
1496
|
+
setPerspectiveMatrix4(m, 0.5, 1.6, 0.1, 1000);
|
|
1497
|
+
expect(m.m[15]).toBe(0);
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
it('projects a point to clip w = -z_eye (the perspective divisor)', () => {
|
|
1501
|
+
const m = createMatrix4();
|
|
1502
|
+
setPerspectiveMatrix4(m, 0.5, 1.6, 0.1, 1000);
|
|
1503
|
+
// w_clip for a point (x, y, z, 1) is row 3 of the matrix: m[3]*x + m[7]*y + m[11]*z + m[15]*1.
|
|
1504
|
+
// With m[3]=m[7]=0, m[11]=-1, m[15]=0, a point 5 units in front of the camera (z_eye = -5) must
|
|
1505
|
+
// divide by +5. A stray m[15]=1 would give 6, shrinking the projection.
|
|
1506
|
+
const zEye = -5;
|
|
1507
|
+
const wClip = m.m[3] * 2 + m.m[7] * 3 + m.m[11] * zEye + m.m[15] * 1;
|
|
1508
|
+
expect(wClip).toBe(5);
|
|
1509
|
+
});
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
describe('translateMatrix4', () => {
|
|
1513
|
+
it('translates an identity matrix by (x, y, z)', () => {
|
|
1514
|
+
const m = createMatrix4();
|
|
1515
|
+
|
|
1516
|
+
translateMatrix4(m, m, 1, 2, 3);
|
|
1517
|
+
|
|
1518
|
+
expect(m.m[12]).toBe(1);
|
|
1519
|
+
expect(m.m[13]).toBe(2);
|
|
1520
|
+
expect(m.m[14]).toBe(3);
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
it('accumulates translation when called multiple times', () => {
|
|
1524
|
+
const m = createMatrix4();
|
|
1525
|
+
|
|
1526
|
+
translateMatrix4(m, m, 1, 2, 3);
|
|
1527
|
+
translateMatrix4(m, m, 4, 5, 6);
|
|
1528
|
+
|
|
1529
|
+
expect(m.m[12]).toBe(5);
|
|
1530
|
+
expect(m.m[13]).toBe(7);
|
|
1531
|
+
expect(m.m[14]).toBe(9);
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
describe('transposeMatrix4', () => {
|
|
1536
|
+
it('transpose of identity is identity', () => {
|
|
1537
|
+
const m = createMatrix4();
|
|
1538
|
+
const t = createMatrix4();
|
|
1539
|
+
transposeMatrix4(t, m);
|
|
1540
|
+
expect(equalsMatrix4(t, m)).toBe(true);
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
it('transpose of diagonal matrix is itself', () => {
|
|
1544
|
+
const m = createMatrix4();
|
|
1545
|
+
scaleMatrix4(m, m, 2, 3, 4); // diagonal elements only
|
|
1546
|
+
const t = createMatrix4();
|
|
1547
|
+
transposeMatrix4(t, m);
|
|
1548
|
+
expect(equalsMatrix4(t, m)).toBe(true);
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it('transpose of rotation matrix equals its inverse', () => {
|
|
1552
|
+
const m = createMatrix4();
|
|
1553
|
+
appendRotationMatrix4(m, m, Math.PI / 2, Z_AXIS);
|
|
1554
|
+
|
|
1555
|
+
const t = createMatrix4();
|
|
1556
|
+
transposeMatrix4(t, m);
|
|
1557
|
+
const inv = createMatrix4();
|
|
1558
|
+
inverseMatrix4(inv, m);
|
|
1559
|
+
|
|
1560
|
+
// element-wise close comparison
|
|
1561
|
+
for (let i = 0; i < 16; i++) {
|
|
1562
|
+
expect(t.m[i]).toBeCloseTo(inv.m[i]);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
it('transpose of arbitrary matrix swaps rows and columns', () => {
|
|
1567
|
+
const m = createMatrix4();
|
|
1568
|
+
setMatrix4(m, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
1569
|
+
const t = createMatrix4();
|
|
1570
|
+
transposeMatrix4(t, m);
|
|
1571
|
+
const values = [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16];
|
|
1572
|
+
for (let i = 0; i < values.length; i++) {
|
|
1573
|
+
expect(t.m[i]).toBe(values[i]);
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
it('transpose twice returns original matrix', () => {
|
|
1578
|
+
const m = createMatrix4();
|
|
1579
|
+
setMatrix4(m, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
1580
|
+
const t = createMatrix4();
|
|
1581
|
+
transposeMatrix4(t, m);
|
|
1582
|
+
const t2 = createMatrix4();
|
|
1583
|
+
transposeMatrix4(t2, t);
|
|
1584
|
+
expect(equalsMatrix4(t2, m)).toBe(true);
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
it('supports out === source', () => {
|
|
1588
|
+
const matrix = createMatrix4();
|
|
1589
|
+
setMatrix4(matrix, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
1590
|
+
const expected = createMatrix4();
|
|
1591
|
+
transposeMatrix4(expected, matrix);
|
|
1592
|
+
|
|
1593
|
+
transposeMatrix4(matrix, matrix);
|
|
1594
|
+
expect(equalsMatrix4(matrix, expected)).toBe(true);
|
|
1595
|
+
});
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
describe('writeMatrix4ToFloat32Array', () => {
|
|
1599
|
+
it('writes 16 elements at offset 0', () => {
|
|
1600
|
+
const m = createMatrix4();
|
|
1601
|
+
setMatrix4(m, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
|
1602
|
+
const arr = new Float32Array(16);
|
|
1603
|
+
writeMatrix4ToFloat32Array(arr, 0, m);
|
|
1604
|
+
for (let i = 0; i < 16; i++) {
|
|
1605
|
+
expect(arr[i]).toBe(m.m[i]);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
it('writes 16 elements at a non-zero offset', () => {
|
|
1610
|
+
const m = createMatrix4();
|
|
1611
|
+
translateMatrix4(m, m, 1, 2, 3);
|
|
1612
|
+
const arr = new Float32Array(20);
|
|
1613
|
+
writeMatrix4ToFloat32Array(arr, 4, m);
|
|
1614
|
+
for (let i = 0; i < 16; i++) {
|
|
1615
|
+
expect(arr[4 + i]).toBe(m.m[i]);
|
|
1616
|
+
}
|
|
1617
|
+
// Bytes before offset untouched
|
|
1618
|
+
expect(arr[0]).toBe(0);
|
|
1619
|
+
expect(arr[3]).toBe(0);
|
|
1620
|
+
});
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
function expectMatrix4Close(actual: Matrix4, expected: Matrix4): void {
|
|
1624
|
+
for (let i = 0; i < 16; i++) {
|
|
1625
|
+
expect(actual.m[i]).toBeCloseTo(expected.m[i]);
|
|
1626
|
+
}
|
|
1627
|
+
}
|