@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,71 @@
|
|
|
1
|
+
import { getMatrix4Element } from '@flighthq/geometry';
|
|
2
|
+
import type { Matrix4 } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { acquireIdentityMatrix4, acquireMatrix4, clearMatrix4Pool, releaseMatrix4 } from './matrix4Pool';
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
clearMatrix4Pool();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('acquireIdentityMatrix4', () => {
|
|
11
|
+
it('returns a matrix set to identity', () => {
|
|
12
|
+
const m = acquireIdentityMatrix4();
|
|
13
|
+
expect(getMatrix4Element(m, 0, 0)).toBe(1);
|
|
14
|
+
expect(getMatrix4Element(m, 0, 1)).toBe(0);
|
|
15
|
+
expect(getMatrix4Element(m, 1, 1)).toBe(1);
|
|
16
|
+
expect(getMatrix4Element(m, 2, 2)).toBe(1);
|
|
17
|
+
expect(getMatrix4Element(m, 3, 3)).toBe(1);
|
|
18
|
+
expect(getMatrix4Element(m, 0, 3)).toBe(0);
|
|
19
|
+
expect(getMatrix4Element(m, 3, 0)).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('resets a released matrix to identity', () => {
|
|
23
|
+
const m1 = acquireMatrix4();
|
|
24
|
+
m1.m[0] = 5;
|
|
25
|
+
m1.m[12] = 10;
|
|
26
|
+
releaseMatrix4(m1);
|
|
27
|
+
const m2 = acquireIdentityMatrix4();
|
|
28
|
+
expect(m2).toBe(m1);
|
|
29
|
+
expect(getMatrix4Element(m2, 0, 0)).toBe(1);
|
|
30
|
+
expect(getMatrix4Element(m2, 3, 0)).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('acquireMatrix4', () => {
|
|
35
|
+
it('returns a new Matrix4 when pool is empty', () => {
|
|
36
|
+
const m: Matrix4 = acquireMatrix4();
|
|
37
|
+
expect(m).not.toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('reuses released matrices', () => {
|
|
41
|
+
const m1 = acquireMatrix4();
|
|
42
|
+
releaseMatrix4(m1);
|
|
43
|
+
const m2 = acquireMatrix4();
|
|
44
|
+
expect(m2).toBe(m1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('reuses released matrices in last-in-first-out order', () => {
|
|
48
|
+
const m1 = acquireMatrix4();
|
|
49
|
+
const m2 = acquireMatrix4();
|
|
50
|
+
releaseMatrix4(m1);
|
|
51
|
+
releaseMatrix4(m2);
|
|
52
|
+
expect(acquireMatrix4()).toBe(m2);
|
|
53
|
+
expect(acquireMatrix4()).toBe(m1);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('clearMatrix4Pool', () => {
|
|
58
|
+
it('empties the pool so the next get allocates fresh', () => {
|
|
59
|
+
const m = acquireMatrix4();
|
|
60
|
+
releaseMatrix4(m);
|
|
61
|
+
clearMatrix4Pool();
|
|
62
|
+
const m2 = acquireMatrix4();
|
|
63
|
+
expect(m2).not.toBe(m);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('releaseMatrix4', () => {
|
|
68
|
+
it('handles null safely', () => {
|
|
69
|
+
expect(() => releaseMatrix4(null as unknown as Matrix4)).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Matrix } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import { acquireIdentityMatrix, acquireMatrix, clearMatrixPool, releaseMatrix } from './matrixPool';
|
|
4
|
+
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
clearMatrixPool();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('acquireIdentityMatrix', () => {
|
|
10
|
+
it('returns a new Matrix when pool is empty', () => {
|
|
11
|
+
const m: Matrix = acquireMatrix();
|
|
12
|
+
expect(m).not.toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('reuses released matrices', () => {
|
|
16
|
+
const m1 = acquireMatrix();
|
|
17
|
+
releaseMatrix(m1);
|
|
18
|
+
const m2 = acquireMatrix();
|
|
19
|
+
expect(m2).toBe(m1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('reuses released matrices in last-in-first-out order', () => {
|
|
23
|
+
const m1 = acquireMatrix();
|
|
24
|
+
const m2 = acquireMatrix();
|
|
25
|
+
releaseMatrix(m1);
|
|
26
|
+
releaseMatrix(m2);
|
|
27
|
+
expect(acquireMatrix()).toBe(m2);
|
|
28
|
+
expect(acquireMatrix()).toBe(m1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('acquireMatrix', () => {
|
|
33
|
+
it('returns a matrix set to identity', () => {
|
|
34
|
+
const m = acquireIdentityMatrix();
|
|
35
|
+
expect(m.a).toBe(1);
|
|
36
|
+
expect(m.b).toBe(0);
|
|
37
|
+
expect(m.c).toBe(0);
|
|
38
|
+
expect(m.d).toBe(1);
|
|
39
|
+
expect(m.tx).toBe(0);
|
|
40
|
+
expect(m.ty).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('resets a released matrix to identity', () => {
|
|
44
|
+
const m1 = acquireMatrix();
|
|
45
|
+
m1.a = 5;
|
|
46
|
+
m1.tx = 10;
|
|
47
|
+
releaseMatrix(m1);
|
|
48
|
+
const m2 = acquireIdentityMatrix();
|
|
49
|
+
expect(m2).toBe(m1);
|
|
50
|
+
expect(m2.a).toBe(1);
|
|
51
|
+
expect(m2.tx).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('clearMatrixPool', () => {
|
|
56
|
+
it('empties the pool so the next get allocates fresh', () => {
|
|
57
|
+
const m = acquireMatrix();
|
|
58
|
+
releaseMatrix(m);
|
|
59
|
+
clearMatrixPool();
|
|
60
|
+
const m2 = acquireMatrix();
|
|
61
|
+
expect(m2).not.toBe(m);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('releaseMatrix', () => {
|
|
66
|
+
it('handles null safely', () => {
|
|
67
|
+
expect(() => releaseMatrix(null as unknown as Matrix)).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/obb.test.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAabb,
|
|
3
|
+
createMatrix4,
|
|
4
|
+
createObb,
|
|
5
|
+
createRay3D,
|
|
6
|
+
createVector3,
|
|
7
|
+
getClosestPointOnObb,
|
|
8
|
+
intersectRay3DObb,
|
|
9
|
+
isObbIntersectingAabb,
|
|
10
|
+
isObbIntersectingObb,
|
|
11
|
+
setMatrix4Position,
|
|
12
|
+
setObb,
|
|
13
|
+
transformObbByMatrix4,
|
|
14
|
+
} from '@flighthq/geometry';
|
|
15
|
+
|
|
16
|
+
describe('createObb', () => {
|
|
17
|
+
it('stores center, half-extents, and orientation', () => {
|
|
18
|
+
const o = createObb(1, 2, 3, 4, 5, 6, 0, 0, 0, 1);
|
|
19
|
+
expect(o.centerX).toBe(1);
|
|
20
|
+
expect(o.centerY).toBe(2);
|
|
21
|
+
expect(o.centerZ).toBe(3);
|
|
22
|
+
expect(o.halfExtentX).toBe(4);
|
|
23
|
+
expect(o.halfExtentY).toBe(5);
|
|
24
|
+
expect(o.halfExtentZ).toBe(6);
|
|
25
|
+
expect(o.orientationX).toBe(0);
|
|
26
|
+
expect(o.orientationY).toBe(0);
|
|
27
|
+
expect(o.orientationZ).toBe(0);
|
|
28
|
+
expect(o.orientationW).toBe(1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('getClosestPointOnObb', () => {
|
|
33
|
+
it('clamps an outside point to the nearest face of an axis-aligned OBB', () => {
|
|
34
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
35
|
+
const out = createVector3();
|
|
36
|
+
getClosestPointOnObb(out, o, createVector3(5, 0, 0));
|
|
37
|
+
expect(out.x).toBeCloseTo(1, 6);
|
|
38
|
+
expect(out.y).toBeCloseTo(0, 6);
|
|
39
|
+
expect(out.z).toBeCloseTo(0, 6);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns a point inside the OBB unchanged', () => {
|
|
43
|
+
const o = createObb(0, 0, 0, 2, 2, 2, 0, 0, 0, 1);
|
|
44
|
+
const out = createVector3();
|
|
45
|
+
getClosestPointOnObb(out, o, createVector3(1, 0.5, -0.5));
|
|
46
|
+
expect(out.x).toBeCloseTo(1, 6);
|
|
47
|
+
expect(out.y).toBeCloseTo(0.5, 6);
|
|
48
|
+
expect(out.z).toBeCloseTo(-0.5, 6);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('handles off-center OBB with identity orientation', () => {
|
|
52
|
+
const o = createObb(10, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
53
|
+
const out = createVector3();
|
|
54
|
+
getClosestPointOnObb(out, o, createVector3(0, 0, 0));
|
|
55
|
+
expect(out.x).toBeCloseTo(9, 6);
|
|
56
|
+
expect(out.y).toBeCloseTo(0, 6);
|
|
57
|
+
expect(out.z).toBeCloseTo(0, 6);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('supports out === point', () => {
|
|
61
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
62
|
+
const p = createVector3(5, 0, 0);
|
|
63
|
+
getClosestPointOnObb(p, o, p);
|
|
64
|
+
expect(p.x).toBeCloseTo(1, 6);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('intersectRay3DObb', () => {
|
|
69
|
+
it('hits a unit OBB at the expected t from outside', () => {
|
|
70
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
71
|
+
// Ray from (5,0,0) pointing in -X: should enter at x=1, t=4.
|
|
72
|
+
const ray = createRay3D(5, 0, 0, -1, 0, 0);
|
|
73
|
+
const t = intersectRay3DObb(ray, o);
|
|
74
|
+
expect(t).toBeCloseTo(4, 6);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns -1 for a ray pointing away from the OBB', () => {
|
|
78
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
79
|
+
const ray = createRay3D(5, 0, 0, 1, 0, 0);
|
|
80
|
+
expect(intersectRay3DObb(ray, o)).toBe(-1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns 0 for a ray starting inside the OBB', () => {
|
|
84
|
+
const o = createObb(0, 0, 0, 2, 2, 2, 0, 0, 0, 1);
|
|
85
|
+
const ray = createRay3D(0, 0, 0, 0, 0, 1);
|
|
86
|
+
expect(intersectRay3DObb(ray, o)).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns -1 for a ray that misses an off-center OBB', () => {
|
|
90
|
+
const o = createObb(10, 10, 10, 1, 1, 1, 0, 0, 0, 1);
|
|
91
|
+
const ray = createRay3D(0, 0, 0, 1, 0, 0);
|
|
92
|
+
expect(intersectRay3DObb(ray, o)).toBe(-1);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('isObbIntersectingAabb', () => {
|
|
97
|
+
it('returns true for coincident OBB and AABB', () => {
|
|
98
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
99
|
+
const a = createAabb(-1, -1, -1, 1, 1, 1);
|
|
100
|
+
expect(isObbIntersectingAabb(o, a)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns true for overlapping OBB and AABB', () => {
|
|
104
|
+
const o = createObb(0.5, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
105
|
+
const a = createAabb(-1, -1, -1, 1, 1, 1);
|
|
106
|
+
expect(isObbIntersectingAabb(o, a)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns false for separated OBB and AABB', () => {
|
|
110
|
+
const o = createObb(10, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
111
|
+
const a = createAabb(-1, -1, -1, 1, 1, 1);
|
|
112
|
+
expect(isObbIntersectingAabb(o, a)).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('isObbIntersectingObb', () => {
|
|
117
|
+
it('returns true for two coincident OBBs', () => {
|
|
118
|
+
const a = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
119
|
+
const b = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
120
|
+
expect(isObbIntersectingObb(a, b)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns true for two overlapping OBBs', () => {
|
|
124
|
+
const a = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
125
|
+
const b = createObb(1.5, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
126
|
+
expect(isObbIntersectingObb(a, b)).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('returns false for two separated OBBs', () => {
|
|
130
|
+
const a = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
131
|
+
const b = createObb(10, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
132
|
+
expect(isObbIntersectingObb(a, b)).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('setObb', () => {
|
|
137
|
+
it('updates all fields in place', () => {
|
|
138
|
+
const o = createObb(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
|
|
139
|
+
setObb(o, 1, 2, 3, 4, 5, 6, 0.1, 0.2, 0.3, 0.9);
|
|
140
|
+
expect(o.centerX).toBe(1);
|
|
141
|
+
expect(o.centerY).toBe(2);
|
|
142
|
+
expect(o.centerZ).toBe(3);
|
|
143
|
+
expect(o.halfExtentX).toBe(4);
|
|
144
|
+
expect(o.halfExtentY).toBe(5);
|
|
145
|
+
expect(o.halfExtentZ).toBe(6);
|
|
146
|
+
expect(o.orientationX).toBe(0.1);
|
|
147
|
+
expect(o.orientationW).toBe(0.9);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('transformObbByMatrix4', () => {
|
|
152
|
+
it('translates the center by a translation matrix', () => {
|
|
153
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
154
|
+
const m = createMatrix4();
|
|
155
|
+
setMatrix4Position(m, createVector3(5, 0, 0));
|
|
156
|
+
const out = createObb(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
|
|
157
|
+
transformObbByMatrix4(out, o, m);
|
|
158
|
+
expect(out.centerX).toBeCloseTo(5, 6);
|
|
159
|
+
expect(out.centerY).toBeCloseTo(0, 6);
|
|
160
|
+
expect(out.halfExtentX).toBeCloseTo(1, 6);
|
|
161
|
+
expect(out.orientationW).toBeCloseTo(1, 5);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('scales half-extents by a uniform scale matrix', () => {
|
|
165
|
+
const o = createObb(0, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
166
|
+
const m = createMatrix4(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1);
|
|
167
|
+
const out = createObb(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
|
|
168
|
+
transformObbByMatrix4(out, o, m);
|
|
169
|
+
expect(out.halfExtentX).toBeCloseTo(2, 6);
|
|
170
|
+
expect(out.halfExtentY).toBeCloseTo(2, 6);
|
|
171
|
+
expect(out.halfExtentZ).toBeCloseTo(2, 6);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('supports out === obb', () => {
|
|
175
|
+
const o = createObb(1, 0, 0, 1, 1, 1, 0, 0, 0, 1);
|
|
176
|
+
const m = createMatrix4();
|
|
177
|
+
setMatrix4Position(m, createVector3(0, 3, 0));
|
|
178
|
+
transformObbByMatrix4(o, o, m);
|
|
179
|
+
expect(o.centerX).toBeCloseTo(1, 6);
|
|
180
|
+
expect(o.centerY).toBeCloseTo(3, 6);
|
|
181
|
+
expect(o.halfExtentX).toBeCloseTo(1, 6);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clonePlane,
|
|
3
|
+
copyPlane,
|
|
4
|
+
createPlane,
|
|
5
|
+
createVector3,
|
|
6
|
+
getClosestPointOnPlane,
|
|
7
|
+
getPlaneCoplanarPoint,
|
|
8
|
+
getPlaneSignedDistanceToPoint,
|
|
9
|
+
normalizePlane,
|
|
10
|
+
projectVector3OntoPlane,
|
|
11
|
+
setPlane,
|
|
12
|
+
setPlaneFromNormalAndPoint,
|
|
13
|
+
setPlaneFromPoints,
|
|
14
|
+
} from '@flighthq/geometry';
|
|
15
|
+
|
|
16
|
+
describe('clonePlane', () => {
|
|
17
|
+
it('creates an independent copy', () => {
|
|
18
|
+
const p = createPlane(1, 0, 0, -5);
|
|
19
|
+
const c = clonePlane(p);
|
|
20
|
+
expect(c).not.toBe(p);
|
|
21
|
+
expect(c.a).toBe(1);
|
|
22
|
+
expect(c.d).toBe(-5);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('copyPlane', () => {
|
|
27
|
+
it('copies all coefficients', () => {
|
|
28
|
+
const src = createPlane(0, 1, 0, 3);
|
|
29
|
+
const out = createPlane();
|
|
30
|
+
copyPlane(out, src);
|
|
31
|
+
expect(out.a).toBe(0);
|
|
32
|
+
expect(out.b).toBe(1);
|
|
33
|
+
expect(out.c).toBe(0);
|
|
34
|
+
expect(out.d).toBe(3);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('supports out === source', () => {
|
|
38
|
+
const p = createPlane(2, 3, 4, 5);
|
|
39
|
+
copyPlane(p, p);
|
|
40
|
+
expect(p.a).toBe(2);
|
|
41
|
+
expect(p.d).toBe(5);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('createPlane', () => {
|
|
46
|
+
it('defaults to zeros', () => {
|
|
47
|
+
const p = createPlane();
|
|
48
|
+
expect(p.a).toBe(0);
|
|
49
|
+
expect(p.b).toBe(0);
|
|
50
|
+
expect(p.c).toBe(0);
|
|
51
|
+
expect(p.d).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('uses provided coefficients', () => {
|
|
55
|
+
const p = createPlane(1, 2, 3, 4);
|
|
56
|
+
expect(p.a).toBe(1);
|
|
57
|
+
expect(p.b).toBe(2);
|
|
58
|
+
expect(p.c).toBe(3);
|
|
59
|
+
expect(p.d).toBe(4);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('getClosestPointOnPlane', () => {
|
|
64
|
+
it('projects a point onto the plane along the normal', () => {
|
|
65
|
+
// Plane z = 0 => normal (0,0,1), d = 0.
|
|
66
|
+
const p = createPlane(0, 0, 1, 0);
|
|
67
|
+
const out = createVector3();
|
|
68
|
+
getClosestPointOnPlane(out, p, createVector3(2, 3, 5));
|
|
69
|
+
expect(out.x).toBeCloseTo(2, 6);
|
|
70
|
+
expect(out.y).toBeCloseTo(3, 6);
|
|
71
|
+
expect(out.z).toBeCloseTo(0, 6);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('leaves a point already on the plane in place', () => {
|
|
75
|
+
const p = createPlane(1, 0, 0, -5);
|
|
76
|
+
const out = createVector3();
|
|
77
|
+
getClosestPointOnPlane(out, p, createVector3(5, 1, 2));
|
|
78
|
+
expect(out.x).toBeCloseTo(5, 6);
|
|
79
|
+
expect(out.y).toBeCloseTo(1, 6);
|
|
80
|
+
expect(out.z).toBeCloseTo(2, 6);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('agrees with projectVector3OntoPlane', () => {
|
|
84
|
+
const p = createPlane(0, 1, 0, -3);
|
|
85
|
+
const closest = createVector3();
|
|
86
|
+
const projected = createVector3();
|
|
87
|
+
getClosestPointOnPlane(closest, p, createVector3(4, 7, -2));
|
|
88
|
+
projectVector3OntoPlane(projected, createVector3(4, 7, -2), p);
|
|
89
|
+
expect(closest.x).toBeCloseTo(projected.x, 6);
|
|
90
|
+
expect(closest.y).toBeCloseTo(projected.y, 6);
|
|
91
|
+
expect(closest.z).toBeCloseTo(projected.z, 6);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('supports out === point', () => {
|
|
95
|
+
const p = createPlane(0, 0, 1, 0);
|
|
96
|
+
const v = createVector3(1, 2, 9);
|
|
97
|
+
getClosestPointOnPlane(v, p, v);
|
|
98
|
+
expect(v.z).toBeCloseTo(0, 6);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('getPlaneCoplanarPoint', () => {
|
|
103
|
+
it('point on the plane has signed distance zero', () => {
|
|
104
|
+
// Plane x = 5 => normal (+1,0,0), d = -5.
|
|
105
|
+
const p = createPlane(1, 0, 0, -5);
|
|
106
|
+
const out = createVector3();
|
|
107
|
+
getPlaneCoplanarPoint(out, p);
|
|
108
|
+
expect(getPlaneSignedDistanceToPoint(p, out)).toBeCloseTo(0, 6);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('getPlaneSignedDistanceToPoint', () => {
|
|
113
|
+
it('is positive on the normal side and negative behind', () => {
|
|
114
|
+
// Plane x = 0 with normal +x.
|
|
115
|
+
const p = createPlane(1, 0, 0, 0);
|
|
116
|
+
expect(getPlaneSignedDistanceToPoint(p, createVector3(3, 0, 0))).toBeCloseTo(3, 6);
|
|
117
|
+
expect(getPlaneSignedDistanceToPoint(p, createVector3(-2, 5, 9))).toBeCloseTo(-2, 6);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('accounts for the plane offset', () => {
|
|
121
|
+
// Plane x = 4 with normal +x => a*x + d = x - 4.
|
|
122
|
+
const p = createPlane(1, 0, 0, -4);
|
|
123
|
+
expect(getPlaneSignedDistanceToPoint(p, createVector3(4, 0, 0))).toBeCloseTo(0, 6);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('normalizePlane', () => {
|
|
128
|
+
it('normalizes a plane with non-unit normal', () => {
|
|
129
|
+
// Normal (0,2,0), d=4 → normalized (0,1,0), d=2.
|
|
130
|
+
const p = createPlane(0, 2, 0, 4);
|
|
131
|
+
const out = createPlane();
|
|
132
|
+
normalizePlane(out, p);
|
|
133
|
+
expect(out.b).toBeCloseTo(1, 6);
|
|
134
|
+
expect(out.d).toBeCloseTo(2, 6);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('supports out === source', () => {
|
|
138
|
+
const p = createPlane(0, 3, 0, 6);
|
|
139
|
+
normalizePlane(p, p);
|
|
140
|
+
expect(p.b).toBeCloseTo(1, 6);
|
|
141
|
+
expect(p.d).toBeCloseTo(2, 6);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('leaves a degenerate plane unchanged', () => {
|
|
145
|
+
const p = createPlane(0, 0, 0, 5);
|
|
146
|
+
const out = createPlane();
|
|
147
|
+
normalizePlane(out, p);
|
|
148
|
+
expect(out.d).toBe(5);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('projectVector3OntoPlane', () => {
|
|
153
|
+
it('projects a point above the XZ plane down to y=0', () => {
|
|
154
|
+
// Plane y=0: normal (0,1,0), d=0.
|
|
155
|
+
const plane = createPlane(0, 1, 0, 0);
|
|
156
|
+
const out = createVector3();
|
|
157
|
+
projectVector3OntoPlane(out, createVector3(3, 5, 2), plane);
|
|
158
|
+
expect(out.x).toBeCloseTo(3, 6);
|
|
159
|
+
expect(out.y).toBeCloseTo(0, 6);
|
|
160
|
+
expect(out.z).toBeCloseTo(2, 6);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('supports out === point', () => {
|
|
164
|
+
const plane = createPlane(1, 0, 0, 0);
|
|
165
|
+
const pt = createVector3(4, 2, 1);
|
|
166
|
+
projectVector3OntoPlane(pt, pt, plane);
|
|
167
|
+
expect(pt.x).toBeCloseTo(0, 6);
|
|
168
|
+
expect(pt.y).toBeCloseTo(2, 6);
|
|
169
|
+
expect(pt.z).toBeCloseTo(1, 6);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('setPlane', () => {
|
|
174
|
+
it('sets all coefficients', () => {
|
|
175
|
+
const p = createPlane();
|
|
176
|
+
setPlane(p, 9, 8, 7, 6);
|
|
177
|
+
expect(p.a).toBe(9);
|
|
178
|
+
expect(p.b).toBe(8);
|
|
179
|
+
expect(p.c).toBe(7);
|
|
180
|
+
expect(p.d).toBe(6);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('setPlaneFromNormalAndPoint', () => {
|
|
185
|
+
it('builds a plane that contains the given point', () => {
|
|
186
|
+
const normal = createVector3(0, 1, 0);
|
|
187
|
+
const point = createVector3(0, 5, 0);
|
|
188
|
+
const p = createPlane();
|
|
189
|
+
setPlaneFromNormalAndPoint(p, normal, point);
|
|
190
|
+
expect(getPlaneSignedDistanceToPoint(p, point)).toBeCloseTo(0, 6);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('stores the normal in (a, b, c)', () => {
|
|
194
|
+
const normal = createVector3(1, 0, 0);
|
|
195
|
+
const point = createVector3(3, 0, 0);
|
|
196
|
+
const p = createPlane();
|
|
197
|
+
setPlaneFromNormalAndPoint(p, normal, point);
|
|
198
|
+
expect(p.a).toBeCloseTo(1, 6);
|
|
199
|
+
expect(p.b).toBeCloseTo(0, 6);
|
|
200
|
+
expect(p.c).toBeCloseTo(0, 6);
|
|
201
|
+
expect(p.d).toBeCloseTo(-3, 6);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('setPlaneFromPoints', () => {
|
|
206
|
+
it('builds the XZ plane from three points in XZ', () => {
|
|
207
|
+
const p = createPlane();
|
|
208
|
+
setPlaneFromPoints(p, createVector3(0, 0, 0), createVector3(1, 0, 0), createVector3(0, 0, 1));
|
|
209
|
+
// Normal should be +y (right-hand rule CCW from top)
|
|
210
|
+
expect(Math.abs(p.b)).toBeCloseTo(1, 5);
|
|
211
|
+
expect(p.a).toBeCloseTo(0, 5);
|
|
212
|
+
expect(p.c).toBeCloseTo(0, 5);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('all three points lie on the resulting plane', () => {
|
|
216
|
+
const a = createVector3(1, 2, 3);
|
|
217
|
+
const b = createVector3(4, 0, 1);
|
|
218
|
+
const c = createVector3(-1, 3, 5);
|
|
219
|
+
const p = createPlane();
|
|
220
|
+
setPlaneFromPoints(p, a, b, c);
|
|
221
|
+
expect(getPlaneSignedDistanceToPoint(p, a)).toBeCloseTo(0, 5);
|
|
222
|
+
expect(getPlaneSignedDistanceToPoint(p, b)).toBeCloseTo(0, 5);
|
|
223
|
+
expect(getPlaneSignedDistanceToPoint(p, c)).toBeCloseTo(0, 5);
|
|
224
|
+
});
|
|
225
|
+
});
|