@flighthq/geometry 0.2.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/matrix.d.ts.map +1 -1
- package/dist/matrix.js +2 -1
- package/dist/matrix.js.map +1 -1
- package/dist/matrix3.d.ts +9 -5
- package/dist/matrix3.d.ts.map +1 -1
- package/dist/matrix3.js +149 -186
- package/dist/matrix3.js.map +1 -1
- package/dist/matrix4.d.ts.map +1 -1
- package/dist/matrix4.js +5 -3
- package/dist/matrix4.js.map +1 -1
- package/dist/quaternion.d.ts +1 -0
- package/dist/quaternion.d.ts.map +1 -1
- package/dist/quaternion.js +8 -0
- package/dist/quaternion.js.map +1 -1
- package/dist/transform2d.d.ts +4 -0
- package/dist/transform2d.d.ts.map +1 -0
- package/dist/transform2d.js +53 -0
- package/dist/transform2d.js.map +1 -0
- package/dist/transform3d.d.ts +5 -0
- package/dist/transform3d.d.ts.map +1 -0
- package/dist/transform3d.js +23 -0
- package/dist/transform3d.js.map +1 -0
- package/dist/vector3.d.ts +2 -2
- package/dist/vector3.js +5 -5
- package/package.json +3 -3
- package/src/matrix.test.ts +2 -1
- package/src/matrix3.test.ts +123 -88
- package/src/matrix4.test.ts +13 -0
- package/src/quaternion.test.ts +9 -0
- package/src/transform2d.test.ts +84 -0
- package/src/transform3d.test.ts +50 -0
package/src/matrix3.test.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
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[
|
|
196
|
-
expect(m.m[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
342
|
-
let out = createMatrix3();
|
|
355
|
+
const out = createMatrix3();
|
|
343
356
|
inverseMatrix3(out, m);
|
|
344
357
|
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
expect(out.m[
|
|
351
|
-
expect(out.m[
|
|
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
|
|
368
|
-
expect(result
|
|
369
|
-
expect(result
|
|
370
|
-
expect(result
|
|
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
|
-
|
|
375
|
-
|
|
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); //
|
|
378
|
-
expect(out.m[1]).toBeCloseTo(0); //
|
|
379
|
-
expect(out.m[3]).toBeCloseTo(0); //
|
|
380
|
-
expect(out.m[4]).toBeCloseTo(0.5); //
|
|
381
|
-
expect(out.m[
|
|
382
|
-
expect(out.m[
|
|
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', () => {
|
|
@@ -476,12 +486,22 @@ describe('multiplyMatrix3', () => {
|
|
|
476
486
|
expect(equalsMatrix3(out, b)).toBe(true);
|
|
477
487
|
});
|
|
478
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
|
+
|
|
479
498
|
it('should allow matrix-like objects', () => {
|
|
499
|
+
// Column-major: a = diag(2,2,1), b = translate(5,5).
|
|
480
500
|
const a = { m: new Float32Array([2, 0, 0, 0, 2, 0, 0, 0, 1]) };
|
|
481
|
-
const b = { m: new Float32Array([1, 0,
|
|
501
|
+
const b = { m: new Float32Array([1, 0, 0, 0, 1, 0, 5, 5, 1]) };
|
|
482
502
|
multiplyMatrix3(a, a, b);
|
|
483
|
-
expect(a.m[
|
|
484
|
-
expect(a.m[
|
|
503
|
+
expect(a.m[6]).toBe(10); // (0,2)
|
|
504
|
+
expect(a.m[7]).toBe(10); // (1,2)
|
|
485
505
|
});
|
|
486
506
|
});
|
|
487
507
|
|
|
@@ -490,24 +510,24 @@ describe('rotateMatrix3', () => {
|
|
|
490
510
|
const m = createMatrix3(1, 0, 0, 0, 1, 0, 0, 0, 1);
|
|
491
511
|
const out = createMatrix3();
|
|
492
512
|
rotateMatrix3(out, m, Math.PI / 2); // 90 degrees
|
|
493
|
-
|
|
494
|
-
expect(out
|
|
495
|
-
expect(out
|
|
496
|
-
expect(out
|
|
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);
|
|
497
518
|
});
|
|
498
519
|
|
|
499
520
|
it('should allow a matrix-like object', () => {
|
|
500
521
|
const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
|
|
501
522
|
const out = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
|
|
502
523
|
|
|
503
|
-
// Apply 90 degrees rotation (π/2 radians)
|
|
504
524
|
rotateMatrix3(out, m, Math.PI / 2);
|
|
505
525
|
|
|
506
|
-
//
|
|
507
|
-
expect(out.m[0]).toBeCloseTo(0); //
|
|
508
|
-
expect(out.m[1]).toBeCloseTo(
|
|
509
|
-
expect(out.m[3]).toBeCloseTo(1); //
|
|
510
|
-
expect(out.m[4]).toBeCloseTo(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)
|
|
511
531
|
});
|
|
512
532
|
|
|
513
533
|
it('supports out === source', () => {
|
|
@@ -525,21 +545,21 @@ describe('scaleMatrix3', () => {
|
|
|
525
545
|
const m = createMatrix3();
|
|
526
546
|
const out = createMatrix3();
|
|
527
547
|
scaleMatrix3(out, m, 2, 3);
|
|
528
|
-
expect(out
|
|
529
|
-
expect(out
|
|
548
|
+
expect(getMatrix3Element(out, 0, 0)).toBe(2);
|
|
549
|
+
expect(getMatrix3Element(out, 1, 1)).toBe(3);
|
|
530
550
|
});
|
|
531
551
|
|
|
532
552
|
it('should allow a matrix-like object', () => {
|
|
533
553
|
const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
|
|
534
554
|
const out = createMatrix3();
|
|
535
555
|
scaleMatrix3(out, m, 2, 3);
|
|
536
|
-
expect(out.m[0]).toBe(2); //
|
|
537
|
-
expect(out.m[4]).toBe(3); //
|
|
556
|
+
expect(out.m[0]).toBe(2); // (0,0)
|
|
557
|
+
expect(out.m[4]).toBe(3); // (1,1)
|
|
538
558
|
});
|
|
539
559
|
});
|
|
540
560
|
|
|
541
561
|
describe('setMatrix3', () => {
|
|
542
|
-
it('sets all 9 elements
|
|
562
|
+
it('sets all 9 elements by (row, column)', () => {
|
|
543
563
|
const m = createMatrix3();
|
|
544
564
|
setMatrix3(m, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
545
565
|
for (let row = 0; row < 3; row++) {
|
|
@@ -548,6 +568,12 @@ describe('setMatrix3', () => {
|
|
|
548
568
|
}
|
|
549
569
|
}
|
|
550
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
|
+
});
|
|
551
577
|
});
|
|
552
578
|
|
|
553
579
|
describe('setMatrix3Element', () => {
|
|
@@ -583,36 +609,22 @@ describe('setMatrix3FromFloat32Array', () => {
|
|
|
583
609
|
|
|
584
610
|
describe('setMatrix3FromMatrix', () => {
|
|
585
611
|
it('should convert a Matrix3x2 to a Matrix3', () => {
|
|
586
|
-
// Define a matrix (6 values, row-major)
|
|
587
612
|
const mat2D = createMatrix();
|
|
588
|
-
|
|
589
|
-
// Define an empty Matrix3 to store the result
|
|
590
613
|
const mat = createMatrix3();
|
|
591
|
-
|
|
592
|
-
// Call the fromMatrix3x2 function
|
|
593
614
|
setMatrix3FromMatrix(mat, mat2D);
|
|
594
615
|
|
|
595
|
-
//
|
|
616
|
+
// Identity, column-major.
|
|
596
617
|
const expectedMatrix3 = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
|
|
597
|
-
|
|
598
|
-
// Assert that the result matches the expected outcome
|
|
599
618
|
expect(mat.m).toEqual(expectedMatrix3);
|
|
600
619
|
});
|
|
601
620
|
|
|
602
621
|
it('should handle scaling and translation', () => {
|
|
603
|
-
// Define a matrix with scaling and translation
|
|
604
622
|
const mat2D = createMatrix(2, 0, 0, 3, 5, 10); // Scaling by 2,3 and translation by (5,10)
|
|
605
|
-
|
|
606
|
-
// Define an empty Matrix3 to store the result
|
|
607
623
|
const mat = createMatrix3();
|
|
608
|
-
|
|
609
|
-
// Call the fromMatrix3x2 function
|
|
610
624
|
setMatrix3FromMatrix(mat, mat2D);
|
|
611
625
|
|
|
612
|
-
//
|
|
613
|
-
const expectedMatrix3 = new Float32Array([2, 0,
|
|
614
|
-
|
|
615
|
-
// 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]);
|
|
616
628
|
expect(mat.m).toEqual(expectedMatrix3);
|
|
617
629
|
});
|
|
618
630
|
});
|
|
@@ -643,9 +655,7 @@ describe('setMatrix3FromMatrix4', () => {
|
|
|
643
655
|
const mat = createMatrix3();
|
|
644
656
|
setMatrix3FromMatrix4(mat, Matrix4x4);
|
|
645
657
|
|
|
646
|
-
// Expected result: Identity matrix for Matrix3
|
|
647
658
|
const expectedMatrix3 = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
|
|
648
|
-
|
|
649
659
|
expect(mat.m).toEqual(expectedMatrix3);
|
|
650
660
|
});
|
|
651
661
|
|
|
@@ -674,11 +684,20 @@ describe('setMatrix3FromMatrix4', () => {
|
|
|
674
684
|
const mat = createMatrix3();
|
|
675
685
|
setMatrix3FromMatrix4(mat, Matrix4x4);
|
|
676
686
|
|
|
677
|
-
// Expected result: Upper-left 3x3 part of Matrix4x4
|
|
678
687
|
const expectedMatrix3 = new Float32Array([2, 0, 0, 0, 3, 0, 0, 0, 4]);
|
|
679
|
-
|
|
680
688
|
expect(mat.m).toEqual(expectedMatrix3);
|
|
681
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
|
+
});
|
|
682
701
|
});
|
|
683
702
|
|
|
684
703
|
describe('setMatrix3Identity', () => {
|
|
@@ -699,13 +718,24 @@ describe('setMatrix3NormalFromMatrix4', () => {
|
|
|
699
718
|
const m4 = createMatrix4(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
|
700
719
|
const out = createMatrix3();
|
|
701
720
|
setMatrix3NormalFromMatrix4(out, m4);
|
|
702
|
-
// For a pure rotation, normal matrix == rotation
|
|
703
|
-
|
|
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);
|
|
704
735
|
expect(out.m[0]).toBeCloseTo(0, 5);
|
|
705
|
-
expect(out.m[1]).toBeCloseTo(
|
|
706
|
-
expect(out.m[3]).toBeCloseTo(1, 5);
|
|
736
|
+
expect(out.m[1]).toBeCloseTo(1, 5);
|
|
737
|
+
expect(out.m[3]).toBeCloseTo(-1, 5);
|
|
707
738
|
expect(out.m[4]).toBeCloseTo(0, 5);
|
|
708
|
-
expect(out.m[8]).toBeCloseTo(1, 5);
|
|
709
739
|
});
|
|
710
740
|
|
|
711
741
|
it('keeps a normal perpendicular under non-uniform scale', () => {
|
|
@@ -714,9 +744,9 @@ describe('setMatrix3NormalFromMatrix4', () => {
|
|
|
714
744
|
const normalMatrix = createMatrix3();
|
|
715
745
|
setMatrix3NormalFromMatrix4(normalMatrix, m4);
|
|
716
746
|
// Inverse-transpose of diag(2,1,1) = diag(0.5,1,1).
|
|
717
|
-
expect(normalMatrix
|
|
718
|
-
expect(normalMatrix
|
|
719
|
-
expect(normalMatrix
|
|
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);
|
|
720
750
|
});
|
|
721
751
|
});
|
|
722
752
|
|
|
@@ -725,16 +755,16 @@ describe('translateMatrix3', () => {
|
|
|
725
755
|
const m = createMatrix3();
|
|
726
756
|
const out = createMatrix3();
|
|
727
757
|
translateMatrix3(out, m, 2, 3);
|
|
728
|
-
expect(out
|
|
729
|
-
expect(out
|
|
758
|
+
expect(getMatrix3Element(out, 0, 2)).toBe(2);
|
|
759
|
+
expect(getMatrix3Element(out, 1, 2)).toBe(3);
|
|
730
760
|
});
|
|
731
761
|
|
|
732
762
|
it('should allow a matrix-like object', () => {
|
|
733
763
|
const m = { m: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) };
|
|
734
764
|
const out = createMatrix3();
|
|
735
765
|
translateMatrix3(out, m, 2, 3);
|
|
736
|
-
expect(out.m[
|
|
737
|
-
expect(out.m[
|
|
766
|
+
expect(out.m[6]).toBe(2); // (0,2) tx
|
|
767
|
+
expect(out.m[7]).toBe(3); // (1,2) ty
|
|
738
768
|
});
|
|
739
769
|
});
|
|
740
770
|
|
|
@@ -743,13 +773,17 @@ describe('transposeMatrix3', () => {
|
|
|
743
773
|
const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
744
774
|
const out = createMatrix3();
|
|
745
775
|
transposeMatrix3(out, m);
|
|
746
|
-
|
|
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);
|
|
747
781
|
});
|
|
748
782
|
|
|
749
783
|
it('supports out === source', () => {
|
|
750
784
|
const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
751
785
|
transposeMatrix3(m, m);
|
|
752
|
-
expect(Array.from(m.m)).toEqual([1,
|
|
786
|
+
expect(Array.from(m.m)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
753
787
|
});
|
|
754
788
|
|
|
755
789
|
it('transpose of identity is identity', () => {
|
|
@@ -765,7 +799,8 @@ describe('writeMatrix3ToFloat32Array', () => {
|
|
|
765
799
|
const m = createMatrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
766
800
|
const data = new Float32Array(10);
|
|
767
801
|
writeMatrix3ToFloat32Array(data, 1, m);
|
|
768
|
-
|
|
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]);
|
|
769
804
|
});
|
|
770
805
|
|
|
771
806
|
it('round-trips through setMatrix3FromFloat32Array', () => {
|
package/src/matrix4.test.ts
CHANGED
|
@@ -1603,6 +1603,19 @@ describe('transposeMatrix4', () => {
|
|
|
1603
1603
|
transposeMatrix4(matrix, matrix);
|
|
1604
1604
|
expect(equalsMatrix4(matrix, expected)).toBe(true);
|
|
1605
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
|
+
});
|
|
1606
1619
|
});
|
|
1607
1620
|
|
|
1608
1621
|
describe('writeMatrix4ToFloat32Array', () => {
|
package/src/quaternion.test.ts
CHANGED
|
@@ -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();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createMatrix } from './matrix';
|
|
4
|
+
import { createTransform2D, decomposeMatrixToTransform2D } from './transform2d';
|
|
5
|
+
|
|
6
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
7
|
+
|
|
8
|
+
// Mirrors the display object's forward transform build (recomputeLocalTransform2D) so round-trip
|
|
9
|
+
// tests exercise the exact convention decomposeMatrixToTransform2D inverts.
|
|
10
|
+
function buildMatrix(x: number, y: number, rotation: number, scaleX: number, scaleY: number, skewX = 0, skewY = 0) {
|
|
11
|
+
const radY = (rotation + skewY) * DEG_TO_RAD;
|
|
12
|
+
const radX = (rotation + skewX) * DEG_TO_RAD;
|
|
13
|
+
return createMatrix(
|
|
14
|
+
Math.cos(radY) * scaleX,
|
|
15
|
+
Math.sin(radY) * scaleX,
|
|
16
|
+
-Math.sin(radX) * scaleY,
|
|
17
|
+
Math.cos(radX) * scaleY,
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('createTransform2D', () => {
|
|
24
|
+
it('defaults to the identity transform', () => {
|
|
25
|
+
const t = createTransform2D();
|
|
26
|
+
expect(t).toMatchObject({
|
|
27
|
+
pivotX: 0,
|
|
28
|
+
pivotY: 0,
|
|
29
|
+
rotation: 0,
|
|
30
|
+
scaleX: 1,
|
|
31
|
+
scaleY: 1,
|
|
32
|
+
skewX: 0,
|
|
33
|
+
skewY: 0,
|
|
34
|
+
x: 0,
|
|
35
|
+
y: 0,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('accepts positional fields', () => {
|
|
40
|
+
const t = createTransform2D(10, 20, 45, 2, 3);
|
|
41
|
+
expect(t).toMatchObject({ rotation: 45, scaleX: 2, scaleY: 3, x: 10, y: 20 });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('decomposeMatrixToTransform2D', () => {
|
|
46
|
+
it('decomposes the identity matrix', () => {
|
|
47
|
+
const out = createTransform2D(99, 99, 99, 99, 99, 99, 99);
|
|
48
|
+
decomposeMatrixToTransform2D(out, createMatrix());
|
|
49
|
+
expect(out).toMatchObject({ rotation: 0, scaleX: 1, scaleY: 1, skewX: 0, skewY: 0, x: 0, y: 0 });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('recovers translation and scale', () => {
|
|
53
|
+
const out = createTransform2D();
|
|
54
|
+
decomposeMatrixToTransform2D(out, buildMatrix(5, 7, 0, 2, 3));
|
|
55
|
+
expect(out.x).toBeCloseTo(5);
|
|
56
|
+
expect(out.y).toBeCloseTo(7);
|
|
57
|
+
expect(out.scaleX).toBeCloseTo(2);
|
|
58
|
+
expect(out.scaleY).toBeCloseTo(3);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('recovers a pure rotation with no skew', () => {
|
|
62
|
+
const out = createTransform2D();
|
|
63
|
+
decomposeMatrixToTransform2D(out, buildMatrix(0, 0, 30, 1, 1));
|
|
64
|
+
expect(out.rotation).toBeCloseTo(30);
|
|
65
|
+
expect(out.skewX).toBe(0);
|
|
66
|
+
expect(out.skewY).toBe(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('round-trips a skewed transform, folding the angles into skew', () => {
|
|
70
|
+
const out = createTransform2D();
|
|
71
|
+
decomposeMatrixToTransform2D(out, buildMatrix(0, 0, 0, 1, 1, 10, 20));
|
|
72
|
+
expect(out.rotation).toBe(0);
|
|
73
|
+
expect(out.skewX).toBeCloseTo(10);
|
|
74
|
+
expect(out.skewY).toBeCloseTo(20);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('carries a reflection on scaleY', () => {
|
|
78
|
+
const out = createTransform2D();
|
|
79
|
+
// A mirror across X: determinant negative.
|
|
80
|
+
decomposeMatrixToTransform2D(out, createMatrix(1, 0, 0, -1, 0, 0));
|
|
81
|
+
expect(out.scaleX).toBeCloseTo(1);
|
|
82
|
+
expect(out.scaleY).toBeCloseTo(-1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createMatrix4 } from './matrix4';
|
|
4
|
+
import { createQuaternion } from './quaternion';
|
|
5
|
+
import { composeMatrix4FromTransform3D, createTransform3D, decomposeMatrix4ToTransform3D } from './transform3d';
|
|
6
|
+
import { createVector3 } from './vector3';
|
|
7
|
+
|
|
8
|
+
describe('composeMatrix4FromTransform3D', () => {
|
|
9
|
+
it('composes translation, rotation, and scale into a matrix', () => {
|
|
10
|
+
const t = createTransform3D();
|
|
11
|
+
t.position = createVector3(4, 5, 6);
|
|
12
|
+
t.scale = createVector3(2, 2, 2);
|
|
13
|
+
const out = createMatrix4();
|
|
14
|
+
composeMatrix4FromTransform3D(out, t);
|
|
15
|
+
// Identity rotation + uniform scale 2 + translation (4,5,6): diagonal scale, last column translation.
|
|
16
|
+
expect(out.m[0]).toBeCloseTo(2);
|
|
17
|
+
expect(out.m[5]).toBeCloseTo(2);
|
|
18
|
+
expect(out.m[10]).toBeCloseTo(2);
|
|
19
|
+
expect(out.m[12]).toBeCloseTo(4);
|
|
20
|
+
expect(out.m[13]).toBeCloseTo(5);
|
|
21
|
+
expect(out.m[14]).toBeCloseTo(6);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('createTransform3D', () => {
|
|
26
|
+
it('defaults to identity: zero translation, identity rotation, unit scale', () => {
|
|
27
|
+
const t = createTransform3D();
|
|
28
|
+
expect(t.position).toMatchObject({ x: 0, y: 0, z: 0 });
|
|
29
|
+
expect(t.rotation).toMatchObject({ x: 0, y: 0, z: 0, w: 1 });
|
|
30
|
+
expect(t.scale).toMatchObject({ x: 1, y: 1, z: 1 });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('decomposeMatrix4ToTransform3D', () => {
|
|
35
|
+
it('round-trips a shear-free compose', () => {
|
|
36
|
+
const source = createTransform3D();
|
|
37
|
+
source.position = createVector3(1, 2, 3);
|
|
38
|
+
source.rotation = createQuaternion(0, 0, 0, 1);
|
|
39
|
+
source.scale = createVector3(2, 3, 4);
|
|
40
|
+
const m = createMatrix4();
|
|
41
|
+
composeMatrix4FromTransform3D(m, source);
|
|
42
|
+
|
|
43
|
+
const out = createTransform3D();
|
|
44
|
+
decomposeMatrix4ToTransform3D(out, m);
|
|
45
|
+
expect(out.position).toMatchObject({ x: 1, y: 2, z: 3 });
|
|
46
|
+
expect(out.scale.x).toBeCloseTo(2);
|
|
47
|
+
expect(out.scale.y).toBeCloseTo(3);
|
|
48
|
+
expect(out.scale.z).toBeCloseTo(4);
|
|
49
|
+
});
|
|
50
|
+
});
|