@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.
Files changed (125) hide show
  1. package/dist/aabb.d.ts +88 -0
  2. package/dist/aabb.d.ts.map +1 -0
  3. package/dist/aabb.js +230 -0
  4. package/dist/aabb.js.map +1 -0
  5. package/dist/boundingSphere.d.ts +62 -0
  6. package/dist/boundingSphere.d.ts.map +1 -0
  7. package/dist/boundingSphere.js +188 -0
  8. package/dist/boundingSphere.js.map +1 -0
  9. package/dist/capsule.d.ts +38 -0
  10. package/dist/capsule.d.ts.map +1 -0
  11. package/dist/capsule.js +215 -0
  12. package/dist/capsule.js.map +1 -0
  13. package/dist/frustum.d.ts +49 -0
  14. package/dist/frustum.d.ts.map +1 -0
  15. package/dist/frustum.js +149 -0
  16. package/dist/frustum.js.map +1 -0
  17. package/dist/index.d.ts +25 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +25 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/matrix.d.ts +152 -0
  22. package/dist/matrix.d.ts.map +1 -0
  23. package/dist/matrix.js +488 -0
  24. package/dist/matrix.js.map +1 -0
  25. package/dist/matrix3.d.ts +63 -0
  26. package/dist/matrix3.d.ts.map +1 -0
  27. package/dist/matrix3.js +408 -0
  28. package/dist/matrix3.js.map +1 -0
  29. package/dist/matrix3Pool.d.ts +6 -0
  30. package/dist/matrix3Pool.d.ts.map +1 -0
  31. package/dist/matrix3Pool.js +26 -0
  32. package/dist/matrix3Pool.js.map +1 -0
  33. package/dist/matrix4.d.ts +238 -0
  34. package/dist/matrix4.d.ts.map +1 -0
  35. package/dist/matrix4.js +931 -0
  36. package/dist/matrix4.js.map +1 -0
  37. package/dist/matrix4Pool.d.ts +6 -0
  38. package/dist/matrix4Pool.d.ts.map +1 -0
  39. package/dist/matrix4Pool.js +26 -0
  40. package/dist/matrix4Pool.js.map +1 -0
  41. package/dist/matrixPool.d.ts +6 -0
  42. package/dist/matrixPool.d.ts.map +1 -0
  43. package/dist/matrixPool.js +26 -0
  44. package/dist/matrixPool.js.map +1 -0
  45. package/dist/obb.d.ts +44 -0
  46. package/dist/obb.d.ts.map +1 -0
  47. package/dist/obb.js +248 -0
  48. package/dist/obb.js.map +1 -0
  49. package/dist/plane.d.ts +66 -0
  50. package/dist/plane.d.ts.map +1 -0
  51. package/dist/plane.js +140 -0
  52. package/dist/plane.js.map +1 -0
  53. package/dist/quaternion.d.ts +128 -0
  54. package/dist/quaternion.d.ts.map +1 -0
  55. package/dist/quaternion.js +508 -0
  56. package/dist/quaternion.js.map +1 -0
  57. package/dist/quaternionPool.d.ts +6 -0
  58. package/dist/quaternionPool.d.ts.map +1 -0
  59. package/dist/quaternionPool.js +29 -0
  60. package/dist/quaternionPool.js.map +1 -0
  61. package/dist/ray3d.d.ts +69 -0
  62. package/dist/ray3d.d.ts.map +1 -0
  63. package/dist/ray3d.js +264 -0
  64. package/dist/ray3d.js.map +1 -0
  65. package/dist/rectangle.d.ts +56 -0
  66. package/dist/rectangle.d.ts.map +1 -0
  67. package/dist/rectangle.js +235 -0
  68. package/dist/rectangle.js.map +1 -0
  69. package/dist/rectanglePool.d.ts +6 -0
  70. package/dist/rectanglePool.d.ts.map +1 -0
  71. package/dist/rectanglePool.js +29 -0
  72. package/dist/rectanglePool.js.map +1 -0
  73. package/dist/typedarray.d.ts +4 -0
  74. package/dist/typedarray.d.ts.map +1 -0
  75. package/dist/typedarray.js +25 -0
  76. package/dist/typedarray.js.map +1 -0
  77. package/dist/vector2.d.ts +130 -0
  78. package/dist/vector2.d.ts.map +1 -0
  79. package/dist/vector2.js +250 -0
  80. package/dist/vector2.js.map +1 -0
  81. package/dist/vector2Pool.d.ts +6 -0
  82. package/dist/vector2Pool.d.ts.map +1 -0
  83. package/dist/vector2Pool.js +20 -0
  84. package/dist/vector2Pool.js.map +1 -0
  85. package/dist/vector3.d.ts +220 -0
  86. package/dist/vector3.d.ts.map +1 -0
  87. package/dist/vector3.js +380 -0
  88. package/dist/vector3.js.map +1 -0
  89. package/dist/vector3Pool.d.ts +6 -0
  90. package/dist/vector3Pool.d.ts.map +1 -0
  91. package/dist/vector3Pool.js +28 -0
  92. package/dist/vector3Pool.js.map +1 -0
  93. package/dist/vector4.d.ts +180 -0
  94. package/dist/vector4.d.ts.map +1 -0
  95. package/dist/vector4.js +333 -0
  96. package/dist/vector4.js.map +1 -0
  97. package/dist/vector4Pool.d.ts +6 -0
  98. package/dist/vector4Pool.d.ts.map +1 -0
  99. package/dist/vector4Pool.js +29 -0
  100. package/dist/vector4Pool.js.map +1 -0
  101. package/package.json +38 -0
  102. package/src/aabb.test.ts +341 -0
  103. package/src/boundingSphere.test.ts +247 -0
  104. package/src/capsule.test.ts +157 -0
  105. package/src/frustum.test.ts +160 -0
  106. package/src/matrix.test.ts +1111 -0
  107. package/src/matrix3.test.ts +752 -0
  108. package/src/matrix3Pool.test.ts +73 -0
  109. package/src/matrix4.test.ts +1627 -0
  110. package/src/matrix4Pool.test.ts +71 -0
  111. package/src/matrixPool.test.ts +69 -0
  112. package/src/obb.test.ts +183 -0
  113. package/src/plane.test.ts +225 -0
  114. package/src/quaternion.test.ts +531 -0
  115. package/src/quaternionPool.test.ts +64 -0
  116. package/src/ray3d.test.ts +297 -0
  117. package/src/rectangle.test.ts +982 -0
  118. package/src/rectanglePool.test.ts +71 -0
  119. package/src/typedarray.test.ts +118 -0
  120. package/src/vector2.test.ts +958 -0
  121. package/src/vector2Pool.test.ts +65 -0
  122. package/src/vector3.test.ts +749 -0
  123. package/src/vector3Pool.test.ts +68 -0
  124. package/src/vector4.test.ts +660 -0
  125. package/src/vector4Pool.test.ts +71 -0
@@ -0,0 +1,157 @@
1
+ import {
2
+ createBoundingSphere,
3
+ createCapsule,
4
+ createRay3D,
5
+ createVector3,
6
+ getClosestPointOnCapsule,
7
+ intersectRay3DCapsule,
8
+ isCapsuleIntersectingCapsule,
9
+ isCapsuleIntersectingSphere,
10
+ setCapsule,
11
+ } from '@flighthq/geometry';
12
+
13
+ describe('createCapsule', () => {
14
+ it('stores all fields', () => {
15
+ const c = createCapsule(1, 2, 3, 4, 5, 6, 0.5);
16
+ expect(c.startX).toBe(1);
17
+ expect(c.startY).toBe(2);
18
+ expect(c.startZ).toBe(3);
19
+ expect(c.endX).toBe(4);
20
+ expect(c.endY).toBe(5);
21
+ expect(c.endZ).toBe(6);
22
+ expect(c.radius).toBe(0.5);
23
+ });
24
+ });
25
+
26
+ describe('getClosestPointOnCapsule', () => {
27
+ it('returns the nearest surface point for a point off the side', () => {
28
+ // Vertical capsule along Y axis from (0,-1,0) to (0,1,0) with radius 1.
29
+ const c = createCapsule(0, -1, 0, 0, 1, 0, 1);
30
+ const out = createVector3();
31
+ // Point at (5, 0, 0): closest axis point is (0,0,0), surface is (1,0,0).
32
+ getClosestPointOnCapsule(out, c, createVector3(5, 0, 0));
33
+ expect(out.x).toBeCloseTo(1, 5);
34
+ expect(out.y).toBeCloseTo(0, 5);
35
+ expect(out.z).toBeCloseTo(0, 5);
36
+ });
37
+
38
+ it('returns the start cap surface for a point beyond the start', () => {
39
+ const c = createCapsule(0, 0, 0, 0, 2, 0, 1);
40
+ const out = createVector3();
41
+ // Point at (0,-5,0): closest axis point is (0,0,0) (clamped start), surface is (0,-1,0).
42
+ getClosestPointOnCapsule(out, c, createVector3(0, -5, 0));
43
+ expect(out.x).toBeCloseTo(0, 5);
44
+ expect(out.y).toBeCloseTo(-1, 5);
45
+ expect(out.z).toBeCloseTo(0, 5);
46
+ });
47
+
48
+ it('returns the end cap surface for a point beyond the end', () => {
49
+ const c = createCapsule(0, 0, 0, 0, 2, 0, 1);
50
+ const out = createVector3();
51
+ // Point at (0,10,0): closest axis point is (0,2,0) (clamped end), surface is (0,3,0).
52
+ getClosestPointOnCapsule(out, c, createVector3(0, 10, 0));
53
+ expect(out.x).toBeCloseTo(0, 5);
54
+ expect(out.y).toBeCloseTo(3, 5);
55
+ expect(out.z).toBeCloseTo(0, 5);
56
+ });
57
+
58
+ it('supports out === point', () => {
59
+ const c = createCapsule(0, 0, 0, 0, 0, 0, 1);
60
+ const p = createVector3(5, 0, 0);
61
+ getClosestPointOnCapsule(p, c, p);
62
+ expect(p.x).toBeCloseTo(1, 5);
63
+ expect(p.y).toBeCloseTo(0, 5);
64
+ });
65
+ });
66
+
67
+ describe('intersectRay3DCapsule', () => {
68
+ it('hits a unit capsule from the side', () => {
69
+ // Capsule from (0,-1,0) to (0,1,0) with radius 1.
70
+ const c = createCapsule(0, -1, 0, 0, 1, 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 = intersectRay3DCapsule(ray, c);
74
+ expect(t).toBeCloseTo(4, 5);
75
+ });
76
+
77
+ it('returns -1 for a ray pointing away from the capsule', () => {
78
+ const c = createCapsule(0, -1, 0, 0, 1, 0, 1);
79
+ const ray = createRay3D(5, 0, 0, 1, 0, 0);
80
+ expect(intersectRay3DCapsule(ray, c)).toBe(-1);
81
+ });
82
+
83
+ it('returns 0 for a ray starting inside the capsule', () => {
84
+ const c = createCapsule(0, -1, 0, 0, 1, 0, 1);
85
+ const ray = createRay3D(0, 0, 0, 1, 0, 0);
86
+ expect(intersectRay3DCapsule(ray, c)).toBe(0);
87
+ });
88
+
89
+ it('hits the end cap from above', () => {
90
+ // Capsule from (0,0,0) to (0,0,0) (degenerate sphere) with radius 1, hit from +Y.
91
+ const c = createCapsule(0, 0, 0, 0, 0, 0, 1);
92
+ const ray = createRay3D(0, 5, 0, 0, -1, 0);
93
+ const t = intersectRay3DCapsule(ray, c);
94
+ expect(t).toBeCloseTo(4, 5);
95
+ });
96
+ });
97
+
98
+ describe('isCapsuleIntersectingCapsule', () => {
99
+ it('returns true for two overlapping capsules', () => {
100
+ const a = createCapsule(0, 0, 0, 0, 2, 0, 1);
101
+ const b = createCapsule(1, 0, 0, 1, 2, 0, 1);
102
+ expect(isCapsuleIntersectingCapsule(a, b)).toBe(true);
103
+ });
104
+
105
+ it('returns false for two separated capsules', () => {
106
+ const a = createCapsule(0, 0, 0, 0, 2, 0, 0.5);
107
+ const b = createCapsule(10, 0, 0, 10, 2, 0, 0.5);
108
+ expect(isCapsuleIntersectingCapsule(a, b)).toBe(false);
109
+ });
110
+
111
+ it('returns false if either capsule has a negative radius', () => {
112
+ const a = createCapsule(0, 0, 0, 0, 2, 0, 1);
113
+ const empty = createCapsule(0, 0, 0, 0, 2, 0, -1);
114
+ expect(isCapsuleIntersectingCapsule(a, empty)).toBe(false);
115
+ expect(isCapsuleIntersectingCapsule(empty, a)).toBe(false);
116
+ });
117
+ });
118
+
119
+ describe('isCapsuleIntersectingSphere', () => {
120
+ it('returns true when the sphere overlaps the capsule', () => {
121
+ const c = createCapsule(0, -2, 0, 0, 2, 0, 1);
122
+ const s = createBoundingSphere(2, 0, 0, 1);
123
+ expect(isCapsuleIntersectingSphere(c, s)).toBe(true);
124
+ });
125
+
126
+ it('returns false when the sphere is separated from the capsule', () => {
127
+ const c = createCapsule(0, -2, 0, 0, 2, 0, 1);
128
+ const s = createBoundingSphere(10, 0, 0, 1);
129
+ expect(isCapsuleIntersectingSphere(c, s)).toBe(false);
130
+ });
131
+
132
+ it('returns false if the sphere radius is negative', () => {
133
+ const c = createCapsule(0, -2, 0, 0, 2, 0, 1);
134
+ const empty = createBoundingSphere(0, 0, 0, -1);
135
+ expect(isCapsuleIntersectingSphere(c, empty)).toBe(false);
136
+ });
137
+
138
+ it('returns false if the capsule radius is negative', () => {
139
+ const c = createCapsule(0, -2, 0, 0, 2, 0, -1);
140
+ const s = createBoundingSphere(0, 0, 0, 1);
141
+ expect(isCapsuleIntersectingSphere(c, s)).toBe(false);
142
+ });
143
+ });
144
+
145
+ describe('setCapsule', () => {
146
+ it('updates all fields in place', () => {
147
+ const c = createCapsule(0, 0, 0, 0, 0, 0, 0);
148
+ setCapsule(c, 1, 2, 3, 4, 5, 6, 7);
149
+ expect(c.startX).toBe(1);
150
+ expect(c.startY).toBe(2);
151
+ expect(c.startZ).toBe(3);
152
+ expect(c.endX).toBe(4);
153
+ expect(c.endY).toBe(5);
154
+ expect(c.endZ).toBe(6);
155
+ expect(c.radius).toBe(7);
156
+ });
157
+ });
@@ -0,0 +1,160 @@
1
+ import {
2
+ createAabb,
3
+ createBoundingSphere,
4
+ createFrustum,
5
+ createMatrix4,
6
+ createPerspectiveMatrix4,
7
+ createVector3,
8
+ getFrustumCorners,
9
+ inverseMatrix4,
10
+ isFrustumContainingPoint,
11
+ isFrustumIntersectingAabb,
12
+ isFrustumIntersectingSphere,
13
+ multiplyMatrix4,
14
+ setFrustumFromMatrix4,
15
+ setMatrix4LookAt,
16
+ } from '@flighthq/geometry';
17
+ import type { Matrix4 } from '@flighthq/types';
18
+
19
+ // View-projection for a camera at (0,0,5) looking toward the origin down -z.
20
+ function createTestViewProjection(): Matrix4 {
21
+ const projection = createPerspectiveMatrix4(0.5, 1, 0.1, 100);
22
+ const view = createMatrix4();
23
+ setMatrix4LookAt(view, createVector3(0, 0, 5), createVector3(0, 0, 0), createVector3(0, 1, 0));
24
+ const vp = createMatrix4();
25
+ multiplyMatrix4(vp, projection, view);
26
+ return vp;
27
+ }
28
+
29
+ describe('createFrustum', () => {
30
+ it('creates six independent planes', () => {
31
+ const f = createFrustum();
32
+ expect(f.left).not.toBe(f.right);
33
+ expect(f.near).not.toBe(f.far);
34
+ expect(f.top.a).toBe(0);
35
+ });
36
+ });
37
+
38
+ describe('getFrustumCorners', () => {
39
+ it('writes 8 corners for a perspective frustum', () => {
40
+ const vp = createTestViewProjection();
41
+ const inv = createMatrix4();
42
+ inverseMatrix4(inv, vp);
43
+ const corners = Array.from({ length: 8 }, () => createVector3());
44
+ getFrustumCorners(corners, inv);
45
+ // All 8 corners should be finite and distinct
46
+ const seen = new Set<string>();
47
+ for (const c of corners) {
48
+ expect(isFinite(c.x)).toBe(true);
49
+ expect(isFinite(c.y)).toBe(true);
50
+ expect(isFinite(c.z)).toBe(true);
51
+ seen.add(`${c.x.toFixed(4)},${c.y.toFixed(4)},${c.z.toFixed(4)}`);
52
+ }
53
+ expect(seen.size).toBe(8);
54
+ });
55
+
56
+ it('near corners are closer to the camera than far corners', () => {
57
+ const vp = createTestViewProjection();
58
+ const inv = createMatrix4();
59
+ inverseMatrix4(inv, vp);
60
+ const corners = Array.from({ length: 8 }, () => createVector3());
61
+ getFrustumCorners(corners, inv);
62
+ // Camera at (0,0,5) looking toward origin along -z; near z > far z
63
+ const nearZ = corners.slice(0, 4).map((c) => c.z);
64
+ const farZ = corners.slice(4, 8).map((c) => c.z);
65
+ for (const nz of nearZ) {
66
+ for (const fz of farZ) {
67
+ expect(nz).toBeGreaterThan(fz);
68
+ }
69
+ }
70
+ });
71
+
72
+ it('writes only as many corners as out.length', () => {
73
+ const vp = createTestViewProjection();
74
+ const inv = createMatrix4();
75
+ inverseMatrix4(inv, vp);
76
+ const corners = [createVector3(), createVector3()];
77
+ getFrustumCorners(corners, inv);
78
+ // Only first 2 corners written; rest untouched (default to 0,0,0)
79
+ expect(isFinite(corners[0].x)).toBe(true);
80
+ expect(isFinite(corners[1].x)).toBe(true);
81
+ });
82
+ });
83
+
84
+ describe('isFrustumContainingPoint', () => {
85
+ it('contains a point in front of the camera', () => {
86
+ const f = createFrustum();
87
+ setFrustumFromMatrix4(f, createTestViewProjection());
88
+ expect(isFrustumContainingPoint(f, createVector3(0, 0, 0))).toBe(true);
89
+ });
90
+
91
+ it('rejects a point behind the camera', () => {
92
+ const f = createFrustum();
93
+ setFrustumFromMatrix4(f, createTestViewProjection());
94
+ expect(isFrustumContainingPoint(f, createVector3(0, 0, 20))).toBe(false);
95
+ });
96
+
97
+ it('rejects a point far off to the side', () => {
98
+ const f = createFrustum();
99
+ setFrustumFromMatrix4(f, createTestViewProjection());
100
+ expect(isFrustumContainingPoint(f, createVector3(100, 0, 0))).toBe(false);
101
+ });
102
+ });
103
+
104
+ describe('isFrustumIntersectingAabb', () => {
105
+ it('accepts a box straddling the origin', () => {
106
+ const f = createFrustum();
107
+ setFrustumFromMatrix4(f, createTestViewProjection());
108
+ expect(isFrustumIntersectingAabb(f, createAabb(-1, -1, -1, 1, 1, 1))).toBe(true);
109
+ });
110
+
111
+ it('rejects a box entirely behind the camera', () => {
112
+ const f = createFrustum();
113
+ setFrustumFromMatrix4(f, createTestViewProjection());
114
+ expect(isFrustumIntersectingAabb(f, createAabb(-1, -1, 10, 1, 1, 12))).toBe(false);
115
+ });
116
+
117
+ it('rejects a box far off to the side', () => {
118
+ const f = createFrustum();
119
+ setFrustumFromMatrix4(f, createTestViewProjection());
120
+ expect(isFrustumIntersectingAabb(f, createAabb(50, 50, -1, 60, 60, 1))).toBe(false);
121
+ });
122
+ });
123
+
124
+ describe('isFrustumIntersectingSphere', () => {
125
+ it('accepts a sphere centered in front of the camera', () => {
126
+ const f = createFrustum();
127
+ setFrustumFromMatrix4(f, createTestViewProjection());
128
+ expect(isFrustumIntersectingSphere(f, createBoundingSphere(0, 0, 0, 1))).toBe(true);
129
+ });
130
+
131
+ it('rejects a sphere entirely behind the camera', () => {
132
+ const f = createFrustum();
133
+ setFrustumFromMatrix4(f, createTestViewProjection());
134
+ expect(isFrustumIntersectingSphere(f, createBoundingSphere(0, 0, 20, 1))).toBe(false);
135
+ });
136
+
137
+ it('rejects an empty sphere (negative radius)', () => {
138
+ const f = createFrustum();
139
+ setFrustumFromMatrix4(f, createTestViewProjection());
140
+ expect(isFrustumIntersectingSphere(f, createBoundingSphere(0, 0, 0, -1))).toBe(false);
141
+ });
142
+
143
+ it('accepts a sphere that straddles the near plane', () => {
144
+ const f = createFrustum();
145
+ setFrustumFromMatrix4(f, createTestViewProjection());
146
+ // Near plane is at z ≈ 4.9 (camera at z=5, near=0.1). A sphere centered just behind the
147
+ // near plane with a radius that crosses it should still intersect.
148
+ expect(isFrustumIntersectingSphere(f, createBoundingSphere(0, 0, 5.5, 2))).toBe(true);
149
+ });
150
+ });
151
+
152
+ describe('setFrustumFromMatrix4', () => {
153
+ it('produces unit-length plane normals', () => {
154
+ const f = createFrustum();
155
+ setFrustumFromMatrix4(f, createTestViewProjection());
156
+ const near = f.near;
157
+ const len = Math.sqrt(near.a * near.a + near.b * near.b + near.c * near.c);
158
+ expect(len).toBeCloseTo(1, 5);
159
+ });
160
+ });