@flighthq/geometry 0.1.0 → 0.2.1-next.410.a23f189

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.
Files changed (46) hide show
  1. package/dist/aabb.d.ts +4 -4
  2. package/dist/aabb.d.ts.map +1 -1
  3. package/dist/aabb.js +11 -11
  4. package/dist/aabb.js.map +1 -1
  5. package/dist/boundingSphere.d.ts +5 -5
  6. package/dist/boundingSphere.d.ts.map +1 -1
  7. package/dist/boundingSphere.js +12 -12
  8. package/dist/boundingSphere.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/matrix.d.ts +5 -2
  14. package/dist/matrix.d.ts.map +1 -1
  15. package/dist/matrix.js +19 -15
  16. package/dist/matrix.js.map +1 -1
  17. package/dist/matrix3.d.ts +13 -7
  18. package/dist/matrix3.d.ts.map +1 -1
  19. package/dist/matrix3.js +158 -195
  20. package/dist/matrix3.js.map +1 -1
  21. package/dist/matrix4.d.ts.map +1 -1
  22. package/dist/matrix4.js +5 -3
  23. package/dist/matrix4.js.map +1 -1
  24. package/dist/quaternion.d.ts +1 -0
  25. package/dist/quaternion.d.ts.map +1 -1
  26. package/dist/quaternion.js +8 -0
  27. package/dist/quaternion.js.map +1 -1
  28. package/dist/transform2d.d.ts +4 -0
  29. package/dist/transform2d.d.ts.map +1 -0
  30. package/dist/transform2d.js +53 -0
  31. package/dist/transform2d.js.map +1 -0
  32. package/dist/transform3d.d.ts +5 -0
  33. package/dist/transform3d.d.ts.map +1 -0
  34. package/dist/transform3d.js +23 -0
  35. package/dist/transform3d.js.map +1 -0
  36. package/dist/vector3.d.ts +2 -2
  37. package/dist/vector3.js +5 -5
  38. package/package.json +8 -3
  39. package/src/aabb.test.ts +14 -14
  40. package/src/boundingSphere.test.ts +23 -23
  41. package/src/matrix.test.ts +28 -1
  42. package/src/matrix3.test.ts +150 -88
  43. package/src/matrix4.test.ts +23 -0
  44. package/src/quaternion.test.ts +9 -0
  45. package/src/transform2d.test.ts +84 -0
  46. package/src/transform3d.test.ts +50 -0
@@ -5,7 +5,7 @@ import {
5
5
  createBoundingSphere,
6
6
  createMatrix4,
7
7
  createVector3,
8
- getBoundingSphereContainsPoint,
8
+ containsBoundingSpherePoint,
9
9
  getClosestPointOnBoundingSphere,
10
10
  isBoundingSphereIntersectingBoundingSphere,
11
11
  mergeBoundingSphere,
@@ -28,6 +28,24 @@ describe('cloneBoundingSphere', () => {
28
28
  });
29
29
  });
30
30
 
31
+ describe('containsBoundingSpherePoint', () => {
32
+ it('returns true inside and on the surface', () => {
33
+ const s = createBoundingSphere(0, 0, 0, 2);
34
+ expect(containsBoundingSpherePoint(s, createVector3(1, 0, 0))).toBe(true);
35
+ expect(containsBoundingSpherePoint(s, createVector3(2, 0, 0))).toBe(true);
36
+ });
37
+
38
+ it('returns false outside', () => {
39
+ const s = createBoundingSphere(0, 0, 0, 2);
40
+ expect(containsBoundingSpherePoint(s, createVector3(3, 0, 0))).toBe(false);
41
+ });
42
+
43
+ it('an empty sphere contains no points', () => {
44
+ const s = createBoundingSphere(0, 0, 0, -1);
45
+ expect(containsBoundingSpherePoint(s, createVector3(0, 0, 0))).toBe(false);
46
+ });
47
+ });
48
+
31
49
  describe('copyBoundingSphere', () => {
32
50
  it('copies center and radius', () => {
33
51
  const src = createBoundingSphere(1, 2, 3, 4);
@@ -59,24 +77,6 @@ describe('createBoundingSphere', () => {
59
77
  });
60
78
  });
61
79
 
62
- describe('getBoundingSphereContainsPoint', () => {
63
- it('returns true inside and on the surface', () => {
64
- const s = createBoundingSphere(0, 0, 0, 2);
65
- expect(getBoundingSphereContainsPoint(s, createVector3(1, 0, 0))).toBe(true);
66
- expect(getBoundingSphereContainsPoint(s, createVector3(2, 0, 0))).toBe(true);
67
- });
68
-
69
- it('returns false outside', () => {
70
- const s = createBoundingSphere(0, 0, 0, 2);
71
- expect(getBoundingSphereContainsPoint(s, createVector3(3, 0, 0))).toBe(false);
72
- });
73
-
74
- it('an empty sphere contains no points', () => {
75
- const s = createBoundingSphere(0, 0, 0, -1);
76
- expect(getBoundingSphereContainsPoint(s, createVector3(0, 0, 0))).toBe(false);
77
- });
78
- });
79
-
80
80
  describe('getClosestPointOnBoundingSphere', () => {
81
81
  it('projects an outside point onto the surface', () => {
82
82
  const s = createBoundingSphere(0, 0, 0, 2);
@@ -156,8 +156,8 @@ describe('mergeBoundingSphere', () => {
156
156
  const out = createBoundingSphere();
157
157
  mergeBoundingSphere(out, a, b);
158
158
  // Both original spheres must be contained.
159
- expect(getBoundingSphereContainsPoint(out, createVector3(-2, 0, 0))).toBe(true);
160
- expect(getBoundingSphereContainsPoint(out, createVector3(2, 0, 0))).toBe(true);
159
+ expect(containsBoundingSpherePoint(out, createVector3(-2, 0, 0))).toBe(true);
160
+ expect(containsBoundingSpherePoint(out, createVector3(2, 0, 0))).toBe(true);
161
161
  });
162
162
 
163
163
  it('if a contains b, result equals a', () => {
@@ -182,8 +182,8 @@ describe('mergeBoundingSphere', () => {
182
182
  const a = createBoundingSphere(-1, 0, 0, 1);
183
183
  const b = createBoundingSphere(1, 0, 0, 1);
184
184
  mergeBoundingSphere(a, a, b);
185
- expect(getBoundingSphereContainsPoint(a, createVector3(-2, 0, 0))).toBe(true);
186
- expect(getBoundingSphereContainsPoint(a, createVector3(2, 0, 0))).toBe(true);
185
+ expect(containsBoundingSpherePoint(a, createVector3(-2, 0, 0))).toBe(true);
186
+ expect(containsBoundingSpherePoint(a, createVector3(2, 0, 0))).toBe(true);
187
187
  });
188
188
  });
189
189
 
@@ -331,6 +331,32 @@ describe('inverseMatrix', () => {
331
331
 
332
332
  expect(equalsMatrix(matrix, expected)).toBe(true);
333
333
  });
334
+
335
+ it('returns true on success', () => {
336
+ const m = createMatrix(2, 0, 0, 2, 5, 3);
337
+ const out = createMatrix();
338
+ expect(inverseMatrix(out, m)).toBe(true);
339
+ });
340
+
341
+ it('returns false and writes a degenerate fill for a singular matrix', () => {
342
+ const m = createMatrix(0, 0, 0, 0, 5, 3); // det = a*d - c*b = 0
343
+ const out = createMatrix();
344
+ expect(inverseMatrix(out, m)).toBe(false);
345
+ expect(out.a).toBe(0);
346
+ expect(out.b).toBe(0);
347
+ expect(out.c).toBe(0);
348
+ expect(out.d).toBe(0);
349
+ expect(out.tx).toBe(-5);
350
+ expect(out.ty).toBe(-3);
351
+ });
352
+
353
+ it('returns false for a singular matrix with out === source', () => {
354
+ const m = createMatrix(0, 0, 0, 0, 5, 3);
355
+ expect(inverseMatrix(m, m)).toBe(false);
356
+ expect(m.a).toBe(0);
357
+ expect(m.tx).toBe(-5);
358
+ expect(m.ty).toBe(-3);
359
+ });
334
360
  });
335
361
 
336
362
  describe('inverseMatrixTransformPoint', () => {
@@ -918,7 +944,8 @@ describe('setMatrixFromMatrix3', () => {
918
944
  beforeEach(() => {
919
945
  // Setup a basic Matrix3x3 instance for testing
920
946
  mat3 = {
921
- m: new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9]), // 3x3 matrix3x2 (row-major)
947
+ // Column-major storage of rows [1,2,3],[4,5,6],[7,8,9].
948
+ m: new Float32Array([1, 4, 7, 2, 5, 8, 3, 6, 9]),
922
949
  };
923
950
  mat2D = createMatrix(); // Create a createMatrix instance
924
951
  });
@@ -30,6 +30,11 @@ import {
30
30
  } from '@flighthq/geometry';
31
31
  import type { Matrix3 } from '@flighthq/types';
32
32
 
33
+ // Matrix3 storage is column-major: element (row r, column c) lives at m[3 * c + r], matching
34
+ // Matrix4 and the GL/GLSL/WGSL uniform ABI. A matrix-like {m} built from a raw Float32Array below
35
+ // therefore lists its values column-by-column. getMatrix3Element(r, c) / column / row accessors are
36
+ // layout-agnostic, so those assertions double as storage-independent behavior locks.
37
+
33
38
  describe('cloneMatrix3', () => {
34
39
  it('should clone the matrix correctly', () => {
35
40
  const m1 = createMatrix3(2, 3, 4, 5, 6, 7, 8, 9, 10);
@@ -46,7 +51,8 @@ describe('cloneMatrix3', () => {
46
51
  });
47
52
 
48
53
  it('should also clone matrix-like objects', () => {
49
- const obj = { m: new Float32Array([2, 3, 4, 5, 6, 7, 8, 9, 10]) };
54
+ // Column-major storage of rows [2,3,4],[5,6,7],[8,9,10].
55
+ const obj = { m: new Float32Array([2, 5, 8, 3, 6, 9, 4, 7, 10]) };
50
56
  const m2 = cloneMatrix3(obj);
51
57
  expect(getMatrix3Element(m2, 0, 0)).toBe(2);
52
58
  expect(getMatrix3Element(m2, 0, 1)).toBe(3);
@@ -60,7 +66,7 @@ describe('cloneMatrix3', () => {
60
66
  });
61
67
 
62
68
  it('should return a matrix instance', () => {
63
- const obj = { m: new Float32Array([2, 3, 4, 5, 6, 7, 8, 9, 10]) };
69
+ const obj = { m: new Float32Array([2, 5, 8, 3, 6, 9, 4, 7, 10]) };
64
70
  const m2: Matrix3 = cloneMatrix3(obj);
65
71
  expect(m2).not.toBeNull();
66
72
  });
@@ -153,7 +159,8 @@ describe('copyMatrix3ColumnToVector3', () => {
153
159
  });
154
160
 
155
161
  it('should allow matrix-like and vector-like objects', () => {
156
- const m = { m: new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) };
162
+ // Column-major storage of rows [1,2,3],[4,5,6],[7,8,9]; column 0 = (1,4,7).
163
+ const m = { m: new Float32Array([1, 4, 7, 2, 5, 8, 3, 6, 9]) };
157
164
  const v = { x: 0, y: 0, z: 0 };
158
165
  copyMatrix3ColumnToVector3(v, 0, m); // column 0
159
166
  expect(v.x).toBe(1);
@@ -190,10 +197,10 @@ describe('copyMatrix3RowFromVector3', () => {
190
197
  it('should allow matrix-like and vector-like objects', () => {
191
198
  const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
192
199
  const v = { x: 1, y: 2, z: 3 };
193
- copyMatrix3RowFromVector3(m, 0, v); // row 0
200
+ copyMatrix3RowFromVector3(m, 0, v); // row 0 -> column-major indices 0, 3, 6
194
201
  expect(m.m[0]).toBe(1);
195
- expect(m.m[1]).toBe(2);
196
- expect(m.m[2]).toBe(3);
202
+ expect(m.m[3]).toBe(2);
203
+ expect(m.m[6]).toBe(3);
197
204
  });
198
205
  });
199
206
 
@@ -226,7 +233,8 @@ describe('copyMatrix3RowToVector3', () => {
226
233
  });
227
234
 
228
235
  it('should allow matrix-like and vector-like objects', () => {
229
- const m = { m: new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) };
236
+ // Column-major storage of rows [1,2,3],[4,5,6],[7,8,9]; row 0 = (1,2,3).
237
+ const m = { m: new Float32Array([1, 4, 7, 2, 5, 8, 3, 6, 9]) };
230
238
  const v = { x: 0, y: 0, z: 0 };
231
239
  copyMatrix3RowToVector3(v, 0, m); // row 0
232
240
  expect(v.x).toBe(1); // m.a
@@ -249,6 +257,11 @@ describe('createMatrix3', () => {
249
257
  expect(getMatrix3Element(m, 2, 2)).toBe(10);
250
258
  });
251
259
 
260
+ it('stores its (row, column) arguments column-major', () => {
261
+ const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
262
+ expect(Array.from(m.m)).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]);
263
+ });
264
+
252
265
  it('should default to identity matrix when no values are provided', () => {
253
266
  const m = createMatrix3();
254
267
  expect(getMatrix3Element(m, 0, 0)).toBe(1);
@@ -295,7 +308,8 @@ describe('equalsMatrix3', () => {
295
308
 
296
309
  it('should return true if one object is matrix-like and one is not', () => {
297
310
  const mat1 = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
298
- const mat2 = { m: new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9]) };
311
+ // Column-major storage of the same matrix.
312
+ const mat2 = { m: new Float32Array([1, 4, 7, 2, 5, 8, 3, 6, 9]) };
299
313
  expect(equalsMatrix3(mat1, mat2)).toBe(true);
300
314
  });
301
315
 
@@ -335,24 +349,19 @@ describe('getMatrix3Element', () => {
335
349
 
336
350
  describe('inverseMatrix3', () => {
337
351
  it('should invert the matrix correctly', () => {
338
- // Create a matrix with scaling of 2 and translation of (5, 3)
352
+ // A scale of 2 with translation of (5, 3): rows [2,0,5],[0,2,3],[0,0,1].
339
353
  const m = createMatrix3(2, 0, 5, 0, 2, 3, 0, 0, 1);
340
354
 
341
- // Apply inversion
342
- let out = createMatrix3();
355
+ const out = createMatrix3();
343
356
  inverseMatrix3(out, m);
344
357
 
345
- // Expected inverse matrix:
346
- // Scaling should be 0.5 (inverse of 2)
347
- // Translation should be -2.5 (inverse of 5 scaled by 0.5) and -1.5 (inverse of 3 scaled by 0.5)
348
-
349
- // Assert the inverse matrix values
350
- expect(out.m[0]).toBeCloseTo(0.5); // Inverse scaling on x
351
- expect(out.m[1]).toBeCloseTo(0); // No shear on x
352
- expect(out.m[3]).toBeCloseTo(0); // No shear on y
353
- expect(out.m[4]).toBeCloseTo(0.5); // Inverse scaling on y
354
- expect(out.m[2]).toBeCloseTo(-2.5); // Inverse translation on x
355
- expect(out.m[5]).toBeCloseTo(-1.5); // Inverse translation on y
358
+ // Inverse: scale 0.5, translation (-2.5, -1.5). Column-major storage.
359
+ expect(out.m[0]).toBeCloseTo(0.5); // (0,0) inverse scale x
360
+ expect(out.m[1]).toBeCloseTo(0); // (1,0) no shear
361
+ expect(out.m[3]).toBeCloseTo(0); // (0,1) no shear
362
+ expect(out.m[4]).toBeCloseTo(0.5); // (1,1) inverse scale y
363
+ expect(out.m[6]).toBeCloseTo(-2.5); // (0,2) inverse translation x
364
+ expect(out.m[7]).toBeCloseTo(-1.5); // (1,2) inverse translation y
356
365
  });
357
366
 
358
367
  it('should not depend on initial out matrix values', () => {
@@ -364,22 +373,23 @@ describe('inverseMatrix3', () => {
364
373
  const result = createMatrix3();
365
374
  multiplyMatrix3(result, source, out);
366
375
 
367
- expect(result.m[0]).toBeCloseTo(1);
368
- expect(result.m[1]).toBeCloseTo(0);
369
- expect(result.m[3]).toBeCloseTo(0);
370
- expect(result.m[4]).toBeCloseTo(1);
376
+ expect(getMatrix3Element(result, 0, 0)).toBeCloseTo(1);
377
+ expect(getMatrix3Element(result, 0, 1)).toBeCloseTo(0);
378
+ expect(getMatrix3Element(result, 1, 0)).toBeCloseTo(0);
379
+ expect(getMatrix3Element(result, 1, 1)).toBeCloseTo(1);
371
380
  });
372
381
 
373
382
  it('should should allow matrix-like objects', () => {
374
- const m = { m: new Float32Array([2, 0, 5, 0, 2, 3, 0, 0, 1]) };
375
- let out = { m: new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]) };
383
+ // Column-major storage of the affine scale-2 translate-(5,3).
384
+ const m = { m: new Float32Array([2, 0, 0, 0, 2, 0, 5, 3, 1]) };
385
+ const out = { m: new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]) };
376
386
  inverseMatrix3(out, m);
377
- expect(out.m[0]).toBeCloseTo(0.5); // Inverse scaling on x
378
- expect(out.m[1]).toBeCloseTo(0); // No shear on x
379
- expect(out.m[3]).toBeCloseTo(0); // No shear on y
380
- expect(out.m[4]).toBeCloseTo(0.5); // Inverse scaling on y
381
- expect(out.m[2]).toBeCloseTo(-2.5); // Inverse translation on x
382
- expect(out.m[5]).toBeCloseTo(-1.5); // Inverse translation on y
387
+ expect(out.m[0]).toBeCloseTo(0.5); // (0,0) inverse scale x
388
+ expect(out.m[1]).toBeCloseTo(0); // (1,0) no shear
389
+ expect(out.m[3]).toBeCloseTo(0); // (0,1) no shear
390
+ expect(out.m[4]).toBeCloseTo(0.5); // (1,1) inverse scale y
391
+ expect(out.m[6]).toBeCloseTo(-2.5); // (0,2) inverse translation x
392
+ expect(out.m[7]).toBeCloseTo(-1.5); // (1,2) inverse translation y
383
393
  });
384
394
 
385
395
  it('supports out === source for affine matrices', () => {
@@ -401,6 +411,33 @@ describe('inverseMatrix3', () => {
401
411
 
402
412
  expect(equalsMatrix3(matrix, expected)).toBe(true);
403
413
  });
414
+
415
+ it('returns true on success', () => {
416
+ const m = createMatrix3(2, 0, 5, 0, 2, 3, 0, 0, 1);
417
+ const out = createMatrix3();
418
+ expect(inverseMatrix3(out, m)).toBe(true);
419
+ });
420
+
421
+ it('returns false and fills NaN for a singular affine matrix', () => {
422
+ const m = createMatrix3(0, 0, 5, 0, 0, 3, 0, 0, 1); // zero linear part -> det 0
423
+ const out = createMatrix3();
424
+ expect(inverseMatrix3(out, m)).toBe(false);
425
+ expect(out.m[0]).toBeNaN();
426
+ expect(out.m[4]).toBeNaN();
427
+ });
428
+
429
+ it('returns false and fills NaN for a singular non-affine matrix', () => {
430
+ const m = createMatrix3(1, 2, 3, 2, 4, 6, 1, 1, 1); // rows 0,1 linearly dependent -> det 0
431
+ const out = createMatrix3();
432
+ expect(inverseMatrix3(out, m)).toBe(false);
433
+ expect(out.m[0]).toBeNaN();
434
+ });
435
+
436
+ it('returns false for a singular matrix with out === source', () => {
437
+ const m = createMatrix3(0, 0, 5, 0, 0, 3, 0, 0, 1);
438
+ expect(inverseMatrix3(m, m)).toBe(false);
439
+ expect(m.m[0]).toBeNaN();
440
+ });
404
441
  });
405
442
 
406
443
  describe('isAffineMatrix3', () => {
@@ -449,12 +486,22 @@ describe('multiplyMatrix3', () => {
449
486
  expect(equalsMatrix3(out, b)).toBe(true);
450
487
  });
451
488
 
489
+ it('composes an affine scale then translate (row/column semantics)', () => {
490
+ // a = scale(2,2), b = translate(5,5). a * b applies b then a to a column vector.
491
+ const a = createMatrix3(2, 0, 0, 0, 2, 0, 0, 0, 1);
492
+ const b = createMatrix3(1, 0, 5, 0, 1, 5, 0, 0, 1);
493
+ multiplyMatrix3(a, a, b);
494
+ expect(getMatrix3Element(a, 0, 2)).toBe(10);
495
+ expect(getMatrix3Element(a, 1, 2)).toBe(10);
496
+ });
497
+
452
498
  it('should allow matrix-like objects', () => {
499
+ // Column-major: a = diag(2,2,1), b = translate(5,5).
453
500
  const a = { m: new Float32Array([2, 0, 0, 0, 2, 0, 0, 0, 1]) };
454
- const b = { m: new Float32Array([1, 0, 5, 0, 1, 5, 0, 0, 1]) };
501
+ const b = { m: new Float32Array([1, 0, 0, 0, 1, 0, 5, 5, 1]) };
455
502
  multiplyMatrix3(a, a, b);
456
- expect(a.m[2]).toBe(10);
457
- expect(a.m[5]).toBe(10);
503
+ expect(a.m[6]).toBe(10); // (0,2)
504
+ expect(a.m[7]).toBe(10); // (1,2)
458
505
  });
459
506
  });
460
507
 
@@ -463,24 +510,24 @@ describe('rotateMatrix3', () => {
463
510
  const m = createMatrix3(1, 0, 0, 0, 1, 0, 0, 0, 1);
464
511
  const out = createMatrix3();
465
512
  rotateMatrix3(out, m, Math.PI / 2); // 90 degrees
466
- expect(out.m[0]).toBeCloseTo(0);
467
- expect(out.m[1]).toBeCloseTo(-1);
468
- expect(out.m[3]).toBeCloseTo(1);
469
- expect(out.m[4]).toBeCloseTo(0);
513
+ // Rotation (0,1)-plane: (0,0)=0, (0,1)=-1, (1,0)=1, (1,1)=0.
514
+ expect(getMatrix3Element(out, 0, 0)).toBeCloseTo(0);
515
+ expect(getMatrix3Element(out, 0, 1)).toBeCloseTo(-1);
516
+ expect(getMatrix3Element(out, 1, 0)).toBeCloseTo(1);
517
+ expect(getMatrix3Element(out, 1, 1)).toBeCloseTo(0);
470
518
  });
471
519
 
472
520
  it('should allow a matrix-like object', () => {
473
521
  const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
474
522
  const out = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
475
523
 
476
- // Apply 90 degrees rotation (π/2 radians)
477
524
  rotateMatrix3(out, m, Math.PI / 2);
478
525
 
479
- // Check that the resulting matrix corresponds to a 90-degree rotation matrix
480
- expect(out.m[0]).toBeCloseTo(0); // a = cos(π/2) = 0
481
- expect(out.m[1]).toBeCloseTo(-1); // b = -sin(π/2) = -1
482
- expect(out.m[3]).toBeCloseTo(1); // c = sin(π/2) = 1
483
- expect(out.m[4]).toBeCloseTo(0); // d = cos(π/2) = 0
526
+ // 90-degree rotation, column-major storage: (0,0)=0,(0,1)=-1,(1,0)=1,(1,1)=0.
527
+ expect(out.m[0]).toBeCloseTo(0); // (0,0)
528
+ expect(out.m[1]).toBeCloseTo(1); // (1,0)
529
+ expect(out.m[3]).toBeCloseTo(-1); // (0,1)
530
+ expect(out.m[4]).toBeCloseTo(0); // (1,1)
484
531
  });
485
532
 
486
533
  it('supports out === source', () => {
@@ -498,21 +545,21 @@ describe('scaleMatrix3', () => {
498
545
  const m = createMatrix3();
499
546
  const out = createMatrix3();
500
547
  scaleMatrix3(out, m, 2, 3);
501
- expect(out.m[0]).toBe(2);
502
- expect(out.m[4]).toBe(3);
548
+ expect(getMatrix3Element(out, 0, 0)).toBe(2);
549
+ expect(getMatrix3Element(out, 1, 1)).toBe(3);
503
550
  });
504
551
 
505
552
  it('should allow a matrix-like object', () => {
506
553
  const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
507
554
  const out = createMatrix3();
508
555
  scaleMatrix3(out, m, 2, 3);
509
- expect(out.m[0]).toBe(2); // a
510
- expect(out.m[4]).toBe(3); // d
556
+ expect(out.m[0]).toBe(2); // (0,0)
557
+ expect(out.m[4]).toBe(3); // (1,1)
511
558
  });
512
559
  });
513
560
 
514
561
  describe('setMatrix3', () => {
515
- it('sets all 9 elements in row-major order', () => {
562
+ it('sets all 9 elements by (row, column)', () => {
516
563
  const m = createMatrix3();
517
564
  setMatrix3(m, 1, 2, 3, 4, 5, 6, 7, 8, 9);
518
565
  for (let row = 0; row < 3; row++) {
@@ -521,6 +568,12 @@ describe('setMatrix3', () => {
521
568
  }
522
569
  }
523
570
  });
571
+
572
+ it('stores its arguments column-major', () => {
573
+ const m = createMatrix3();
574
+ setMatrix3(m, 1, 2, 3, 4, 5, 6, 7, 8, 9);
575
+ expect(Array.from(m.m)).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]);
576
+ });
524
577
  });
525
578
 
526
579
  describe('setMatrix3Element', () => {
@@ -556,36 +609,22 @@ describe('setMatrix3FromFloat32Array', () => {
556
609
 
557
610
  describe('setMatrix3FromMatrix', () => {
558
611
  it('should convert a Matrix3x2 to a Matrix3', () => {
559
- // Define a matrix (6 values, row-major)
560
612
  const mat2D = createMatrix();
561
-
562
- // Define an empty Matrix3 to store the result
563
613
  const mat = createMatrix3();
564
-
565
- // Call the fromMatrix3x2 function
566
614
  setMatrix3FromMatrix(mat, mat2D);
567
615
 
568
- // Expected result: Identity matrix for Matrix3
616
+ // Identity, column-major.
569
617
  const expectedMatrix3 = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
570
-
571
- // Assert that the result matches the expected outcome
572
618
  expect(mat.m).toEqual(expectedMatrix3);
573
619
  });
574
620
 
575
621
  it('should handle scaling and translation', () => {
576
- // Define a matrix with scaling and translation
577
622
  const mat2D = createMatrix(2, 0, 0, 3, 5, 10); // Scaling by 2,3 and translation by (5,10)
578
-
579
- // Define an empty Matrix3 to store the result
580
623
  const mat = createMatrix3();
581
-
582
- // Call the fromMatrix3x2 function
583
624
  setMatrix3FromMatrix(mat, mat2D);
584
625
 
585
- // Expected result: Scaling and translation in Matrix3
586
- const expectedMatrix3 = new Float32Array([2, 0, 5, 0, 3, 10, 0, 0, 1]);
587
-
588
- // Assert that the result matches the expected outcome
626
+ // Column-major storage of the affine scale-(2,3) translate-(5,10).
627
+ const expectedMatrix3 = new Float32Array([2, 0, 0, 0, 3, 0, 5, 10, 1]);
589
628
  expect(mat.m).toEqual(expectedMatrix3);
590
629
  });
591
630
  });
@@ -616,9 +655,7 @@ describe('setMatrix3FromMatrix4', () => {
616
655
  const mat = createMatrix3();
617
656
  setMatrix3FromMatrix4(mat, Matrix4x4);
618
657
 
619
- // Expected result: Identity matrix for Matrix3
620
658
  const expectedMatrix3 = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
621
-
622
659
  expect(mat.m).toEqual(expectedMatrix3);
623
660
  });
624
661
 
@@ -647,11 +684,20 @@ describe('setMatrix3FromMatrix4', () => {
647
684
  const mat = createMatrix3();
648
685
  setMatrix3FromMatrix4(mat, Matrix4x4);
649
686
 
650
- // Expected result: Upper-left 3x3 part of Matrix4x4
651
687
  const expectedMatrix3 = new Float32Array([2, 0, 0, 0, 3, 0, 0, 0, 4]);
652
-
653
688
  expect(mat.m).toEqual(expectedMatrix3);
654
689
  });
690
+
691
+ it('copies the upper-left 3x3 column-major (no transpose)', () => {
692
+ // Matrix4 columns [1,2,3,0],[4,5,6,0],[7,8,9,0],[0,0,0,1]; upper-left 3x3 is column-major
693
+ // [1,2,3, 4,5,6, 7,8,9] and copies straight through.
694
+ const m4 = { m: new Float32Array([1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1]) };
695
+ const mat = createMatrix3();
696
+ setMatrix3FromMatrix4(mat, m4);
697
+ expect(Array.from(mat.m)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
698
+ expect(getMatrix3Element(mat, 0, 1)).toBe(4);
699
+ expect(getMatrix3Element(mat, 1, 0)).toBe(2);
700
+ });
655
701
  });
656
702
 
657
703
  describe('setMatrix3Identity', () => {
@@ -672,13 +718,24 @@ describe('setMatrix3NormalFromMatrix4', () => {
672
718
  const m4 = createMatrix4(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
673
719
  const out = createMatrix3();
674
720
  setMatrix3NormalFromMatrix4(out, m4);
675
- // For a pure rotation, normal matrix == rotation. Row-major matrix3 of R (+x -> +y):
676
- // [[0,-1,0],[1,0,0],[0,0,1]].
721
+ // For a pure rotation, normal matrix == rotation R (+x -> +y): (0,0)=0,(0,1)=-1,(1,0)=1,(1,1)=0.
722
+ expect(getMatrix3Element(out, 0, 0)).toBeCloseTo(0, 5);
723
+ expect(getMatrix3Element(out, 0, 1)).toBeCloseTo(-1, 5);
724
+ expect(getMatrix3Element(out, 1, 0)).toBeCloseTo(1, 5);
725
+ expect(getMatrix3Element(out, 1, 1)).toBeCloseTo(0, 5);
726
+ expect(getMatrix3Element(out, 2, 2)).toBeCloseTo(1, 5);
727
+ });
728
+
729
+ it('uploads column-major so a GL upload needs no transpose', () => {
730
+ // The whole point of the migration: the normal matrix' storage matches uniformMatrix3fv(false).
731
+ // 90-degree rotation about z, column-major storage: R = [[0,-1,0],[1,0,0],[0,0,1]] -> [0,1,0,-1,0,0,0,0,1].
732
+ const m4 = createMatrix4(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
733
+ const out = createMatrix3();
734
+ setMatrix3NormalFromMatrix4(out, m4);
677
735
  expect(out.m[0]).toBeCloseTo(0, 5);
678
- expect(out.m[1]).toBeCloseTo(-1, 5);
679
- expect(out.m[3]).toBeCloseTo(1, 5);
736
+ expect(out.m[1]).toBeCloseTo(1, 5);
737
+ expect(out.m[3]).toBeCloseTo(-1, 5);
680
738
  expect(out.m[4]).toBeCloseTo(0, 5);
681
- expect(out.m[8]).toBeCloseTo(1, 5);
682
739
  });
683
740
 
684
741
  it('keeps a normal perpendicular under non-uniform scale', () => {
@@ -687,9 +744,9 @@ describe('setMatrix3NormalFromMatrix4', () => {
687
744
  const normalMatrix = createMatrix3();
688
745
  setMatrix3NormalFromMatrix4(normalMatrix, m4);
689
746
  // Inverse-transpose of diag(2,1,1) = diag(0.5,1,1).
690
- expect(normalMatrix.m[0]).toBeCloseTo(0.5, 5);
691
- expect(normalMatrix.m[4]).toBeCloseTo(1, 5);
692
- expect(normalMatrix.m[8]).toBeCloseTo(1, 5);
747
+ expect(getMatrix3Element(normalMatrix, 0, 0)).toBeCloseTo(0.5, 5);
748
+ expect(getMatrix3Element(normalMatrix, 1, 1)).toBeCloseTo(1, 5);
749
+ expect(getMatrix3Element(normalMatrix, 2, 2)).toBeCloseTo(1, 5);
693
750
  });
694
751
  });
695
752
 
@@ -698,16 +755,16 @@ describe('translateMatrix3', () => {
698
755
  const m = createMatrix3();
699
756
  const out = createMatrix3();
700
757
  translateMatrix3(out, m, 2, 3);
701
- expect(out.m[2]).toBe(2);
702
- expect(out.m[5]).toBe(3);
758
+ expect(getMatrix3Element(out, 0, 2)).toBe(2);
759
+ expect(getMatrix3Element(out, 1, 2)).toBe(3);
703
760
  });
704
761
 
705
762
  it('should allow a matrix-like object', () => {
706
763
  const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
707
764
  const out = createMatrix3();
708
765
  translateMatrix3(out, m, 2, 3);
709
- expect(out.m[2]).toBe(2); // tx
710
- expect(out.m[5]).toBe(3); // ty
766
+ expect(out.m[6]).toBe(2); // (0,2) tx
767
+ expect(out.m[7]).toBe(3); // (1,2) ty
711
768
  });
712
769
  });
713
770
 
@@ -716,13 +773,17 @@ describe('transposeMatrix3', () => {
716
773
  const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
717
774
  const out = createMatrix3();
718
775
  transposeMatrix3(out, m);
719
- expect(Array.from(out.m)).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]);
776
+ // Transpose of rows [1,2,3],[4,5,6],[7,8,9] is rows [1,4,7],[2,5,8],[3,6,9];
777
+ // column-major storage of that is [1,2,3,4,5,6,7,8,9].
778
+ expect(Array.from(out.m)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
779
+ expect(getMatrix3Element(out, 0, 1)).toBe(4);
780
+ expect(getMatrix3Element(out, 1, 0)).toBe(2);
720
781
  });
721
782
 
722
783
  it('supports out === source', () => {
723
784
  const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
724
785
  transposeMatrix3(m, m);
725
- expect(Array.from(m.m)).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]);
786
+ expect(Array.from(m.m)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
726
787
  });
727
788
 
728
789
  it('transpose of identity is identity', () => {
@@ -738,7 +799,8 @@ describe('writeMatrix3ToFloat32Array', () => {
738
799
  const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
739
800
  const data = new Float32Array(10);
740
801
  writeMatrix3ToFloat32Array(data, 1, m);
741
- expect(Array.from(data)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
802
+ // Column-major storage of the matrix, written verbatim.
803
+ expect(Array.from(data)).toEqual([0, 1, 4, 7, 2, 5, 8, 3, 6, 9]);
742
804
  });
743
805
 
744
806
  it('round-trips through setMatrix3FromFloat32Array', () => {
@@ -727,6 +727,16 @@ describe('inverseMatrix4', () => {
727
727
  expect(inv.m[1]).toBeNaN();
728
728
  expect(inv.m[2]).toBeNaN();
729
729
  });
730
+
731
+ it('returns false for a singular matrix with out === source', () => {
732
+ const m = createMatrix4();
733
+ m.m[0] = 0; // make determinant zero
734
+
735
+ const ok = inverseMatrix4(m, m);
736
+
737
+ expect(ok).toBe(false);
738
+ expect(m.m[0]).toBeNaN();
739
+ });
730
740
  });
731
741
 
732
742
  describe('isAffineMatrix4', () => {
@@ -1593,6 +1603,19 @@ describe('transposeMatrix4', () => {
1593
1603
  transposeMatrix4(matrix, matrix);
1594
1604
  expect(equalsMatrix4(matrix, expected)).toBe(true);
1595
1605
  });
1606
+
1607
+ it('supports out === source with non-symmetric values', () => {
1608
+ // A deliberately non-symmetric matrix where aliasing would corrupt results
1609
+ // if __swap read from out after a prior __swap already wrote to it.
1610
+ const m = createMatrix4();
1611
+ setMatrix4(m, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53);
1612
+ transposeMatrix4(m, m);
1613
+ // Column-major transposed: rows become columns.
1614
+ const expected = [2, 11, 23, 41, 3, 13, 29, 43, 5, 17, 31, 47, 7, 19, 37, 53];
1615
+ for (let i = 0; i < 16; i++) {
1616
+ expect(m.m[i]).toBe(expected[i]);
1617
+ }
1618
+ });
1596
1619
  });
1597
1620
 
1598
1621
  describe('writeMatrix4ToFloat32Array', () => {
@@ -15,6 +15,7 @@ import {
15
15
  normalizeQuaternion,
16
16
  rotateVector3ByQuaternion,
17
17
  setMatrix4FromQuaternion,
18
+ setQuaternion,
18
19
  setQuaternionFromAxisAngle,
19
20
  setQuaternionFromEuler,
20
21
  setQuaternionFromMatrix4,
@@ -359,6 +360,14 @@ describe('setMatrix4FromQuaternion', () => {
359
360
  });
360
361
  });
361
362
 
363
+ describe('setQuaternion', () => {
364
+ it('sets the components directly', () => {
365
+ const out = createQuaternion();
366
+ setQuaternion(out, 0.1, 0.2, 0.3, 0.4);
367
+ expect(out).toMatchObject({ w: 0.4, x: 0.1, y: 0.2, z: 0.3 });
368
+ });
369
+ });
370
+
362
371
  describe('setQuaternionFromAxisAngle', () => {
363
372
  it('builds a half-angle quaternion', () => {
364
373
  const q = createQuaternion();