@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,341 @@
1
+ import {
2
+ cloneAabb,
3
+ copyAabb,
4
+ createAabb,
5
+ createBoundingSphere,
6
+ createMatrix4,
7
+ createVector3,
8
+ expandAabbByPoint,
9
+ expandAabbBySphere,
10
+ getAabbCenter,
11
+ getAabbContainsPoint,
12
+ getAabbExtents,
13
+ getAabbSize,
14
+ getClosestPointOnAabb,
15
+ intersectAabb,
16
+ isAabbIntersectingAabb,
17
+ setAabb,
18
+ setAabbFromPoints,
19
+ setMatrix4Position,
20
+ transformAabbByMatrix4,
21
+ unionAabb,
22
+ } from '@flighthq/geometry';
23
+
24
+ describe('cloneAabb', () => {
25
+ it('creates an independent copy with independent corner vectors', () => {
26
+ const a = createAabb(-1, -2, -3, 4, 5, 6);
27
+ const c = cloneAabb(a);
28
+ expect(c).not.toBe(a);
29
+ expect(c.min).not.toBe(a.min);
30
+ expect(c.min.x).toBe(-1);
31
+ expect(c.max.z).toBe(6);
32
+ c.min.x = 99;
33
+ expect(a.min.x).toBe(-1);
34
+ });
35
+ });
36
+
37
+ describe('copyAabb', () => {
38
+ it('copies both corners', () => {
39
+ const src = createAabb(-1, -2, -3, 4, 5, 6);
40
+ const out = createAabb();
41
+ copyAabb(out, src);
42
+ expect(out.min.x).toBe(-1);
43
+ expect(out.max.y).toBe(5);
44
+ });
45
+
46
+ it('supports out === source', () => {
47
+ const a = createAabb(-1, -2, -3, 4, 5, 6);
48
+ copyAabb(a, a);
49
+ expect(a.min.x).toBe(-1);
50
+ expect(a.max.z).toBe(6);
51
+ });
52
+ });
53
+
54
+ describe('createAabb', () => {
55
+ it('defaults to an empty box (min > max)', () => {
56
+ const a = createAabb();
57
+ expect(a.min.x).toBe(Number.POSITIVE_INFINITY);
58
+ expect(a.max.x).toBe(Number.NEGATIVE_INFINITY);
59
+ });
60
+
61
+ it('uses provided corners', () => {
62
+ const a = createAabb(1, 2, 3, 4, 5, 6);
63
+ expect(a.min.x).toBe(1);
64
+ expect(a.max.z).toBe(6);
65
+ });
66
+ });
67
+
68
+ describe('expandAabbByPoint', () => {
69
+ it('an empty box snaps exactly to the first point', () => {
70
+ const a = createAabb();
71
+ const out = createAabb();
72
+ expandAabbByPoint(out, a, createVector3(2, 3, 4));
73
+ expect(out.min.x).toBe(2);
74
+ expect(out.max.x).toBe(2);
75
+ expect(out.min.z).toBe(4);
76
+ expect(out.max.z).toBe(4);
77
+ });
78
+
79
+ it('grows to include a point outside the box', () => {
80
+ const a = createAabb(0, 0, 0, 1, 1, 1);
81
+ const out = createAabb();
82
+ expandAabbByPoint(out, a, createVector3(-5, 2, 0.5));
83
+ expect(out.min.x).toBe(-5);
84
+ expect(out.max.y).toBe(2);
85
+ expect(out.max.z).toBe(1);
86
+ });
87
+
88
+ it('supports out === aabb', () => {
89
+ const a = createAabb(0, 0, 0, 1, 1, 1);
90
+ expandAabbByPoint(a, a, createVector3(3, -3, 3));
91
+ expect(a.min.y).toBe(-3);
92
+ expect(a.max.x).toBe(3);
93
+ });
94
+ });
95
+
96
+ describe('expandAabbBySphere', () => {
97
+ it('grows the box to contain the sphere', () => {
98
+ const a = createAabb(0, 0, 0, 1, 1, 1);
99
+ const s = createBoundingSphere(0, 0, 0, 3);
100
+ const out = createAabb();
101
+ expandAabbBySphere(out, a, s);
102
+ expect(out.min.x).toBe(-3);
103
+ expect(out.max.x).toBe(3);
104
+ expect(out.min.y).toBe(-3);
105
+ expect(out.max.y).toBe(3);
106
+ });
107
+
108
+ it('an empty sphere leaves the box unchanged', () => {
109
+ const a = createAabb(0, 0, 0, 1, 1, 1);
110
+ const s = createBoundingSphere(0, 0, 0, -1);
111
+ const out = createAabb();
112
+ expandAabbBySphere(out, a, s);
113
+ expect(out.min.x).toBe(0);
114
+ expect(out.max.x).toBe(1);
115
+ });
116
+
117
+ it('supports out === aabb', () => {
118
+ const a = createAabb(0, 0, 0, 1, 1, 1);
119
+ const s = createBoundingSphere(5, 0, 0, 1);
120
+ expandAabbBySphere(a, a, s);
121
+ expect(a.max.x).toBe(6);
122
+ });
123
+ });
124
+
125
+ describe('getAabbCenter', () => {
126
+ it('writes the midpoint of the corners', () => {
127
+ const a = createAabb(-2, -4, -6, 2, 4, 6);
128
+ const out = createVector3();
129
+ getAabbCenter(out, a);
130
+ expect(out.x).toBe(0);
131
+ expect(out.y).toBe(0);
132
+ expect(out.z).toBe(0);
133
+ });
134
+ });
135
+
136
+ describe('getAabbContainsPoint', () => {
137
+ it('returns true for an interior point and on the boundary', () => {
138
+ const a = createAabb(0, 0, 0, 2, 2, 2);
139
+ expect(getAabbContainsPoint(a, createVector3(1, 1, 1))).toBe(true);
140
+ expect(getAabbContainsPoint(a, createVector3(0, 2, 0))).toBe(true);
141
+ });
142
+
143
+ it('returns false for a point outside', () => {
144
+ const a = createAabb(0, 0, 0, 2, 2, 2);
145
+ expect(getAabbContainsPoint(a, createVector3(3, 1, 1))).toBe(false);
146
+ });
147
+ });
148
+
149
+ describe('getAabbExtents', () => {
150
+ it('writes half-widths of the box', () => {
151
+ const a = createAabb(-2, -4, -6, 2, 4, 6);
152
+ const out = createVector3();
153
+ getAabbExtents(out, a);
154
+ expect(out.x).toBe(2);
155
+ expect(out.y).toBe(4);
156
+ expect(out.z).toBe(6);
157
+ });
158
+ });
159
+
160
+ describe('getAabbSize', () => {
161
+ it('writes the full width/height/depth', () => {
162
+ const a = createAabb(-1, -2, -3, 3, 6, 9);
163
+ const out = createVector3();
164
+ getAabbSize(out, a);
165
+ expect(out.x).toBe(4);
166
+ expect(out.y).toBe(8);
167
+ expect(out.z).toBe(12);
168
+ });
169
+ });
170
+
171
+ describe('getClosestPointOnAabb', () => {
172
+ it('clamps an outside point to the nearest face', () => {
173
+ const a = createAabb(0, 0, 0, 2, 2, 2);
174
+ const out = createVector3();
175
+ getClosestPointOnAabb(out, a, createVector3(5, -1, 1));
176
+ expect(out.x).toBe(2);
177
+ expect(out.y).toBe(0);
178
+ expect(out.z).toBe(1);
179
+ });
180
+
181
+ it('returns the point unchanged when it is inside the box', () => {
182
+ const a = createAabb(0, 0, 0, 4, 4, 4);
183
+ const out = createVector3();
184
+ getClosestPointOnAabb(out, a, createVector3(1, 2, 3));
185
+ expect(out.x).toBe(1);
186
+ expect(out.y).toBe(2);
187
+ expect(out.z).toBe(3);
188
+ });
189
+
190
+ it('supports out === point', () => {
191
+ const a = createAabb(0, 0, 0, 2, 2, 2);
192
+ const p = createVector3(-3, 5, 1);
193
+ getClosestPointOnAabb(p, a, p);
194
+ expect(p.x).toBe(0);
195
+ expect(p.y).toBe(2);
196
+ expect(p.z).toBe(1);
197
+ });
198
+ });
199
+
200
+ describe('intersectAabb', () => {
201
+ it('produces the overlapping sub-box for two overlapping boxes', () => {
202
+ const a = createAabb(0, 0, 0, 4, 4, 4);
203
+ const b = createAabb(2, 2, 2, 6, 6, 6);
204
+ const out = createAabb();
205
+ intersectAabb(out, a, b);
206
+ expect(out.min.x).toBe(2);
207
+ expect(out.max.x).toBe(4);
208
+ expect(out.min.y).toBe(2);
209
+ expect(out.max.y).toBe(4);
210
+ });
211
+
212
+ it('produces an empty box (min > max) for non-overlapping boxes', () => {
213
+ const a = createAabb(0, 0, 0, 1, 1, 1);
214
+ const b = createAabb(5, 5, 5, 6, 6, 6);
215
+ const out = createAabb();
216
+ intersectAabb(out, a, b);
217
+ // min > max in at least one axis
218
+ expect(out.min.x).toBeGreaterThan(out.max.x);
219
+ });
220
+
221
+ it('supports out === a', () => {
222
+ const a = createAabb(0, 0, 0, 4, 4, 4);
223
+ const b = createAabb(2, 2, 2, 6, 6, 6);
224
+ intersectAabb(a, a, b);
225
+ expect(a.min.x).toBe(2);
226
+ expect(a.max.x).toBe(4);
227
+ });
228
+ });
229
+
230
+ describe('isAabbIntersectingAabb', () => {
231
+ it('returns true for overlapping boxes', () => {
232
+ const a = createAabb(0, 0, 0, 2, 2, 2);
233
+ const b = createAabb(1, 1, 1, 3, 3, 3);
234
+ expect(isAabbIntersectingAabb(a, b)).toBe(true);
235
+ });
236
+
237
+ it('returns true for touching boxes (shared face)', () => {
238
+ const a = createAabb(0, 0, 0, 1, 1, 1);
239
+ const b = createAabb(1, 0, 0, 2, 1, 1);
240
+ expect(isAabbIntersectingAabb(a, b)).toBe(true);
241
+ });
242
+
243
+ it('returns false for separated boxes', () => {
244
+ const a = createAabb(0, 0, 0, 1, 1, 1);
245
+ const b = createAabb(5, 5, 5, 6, 6, 6);
246
+ expect(isAabbIntersectingAabb(a, b)).toBe(false);
247
+ });
248
+ });
249
+
250
+ describe('setAabb', () => {
251
+ it('sets both corners', () => {
252
+ const a = createAabb();
253
+ setAabb(a, -1, -1, -1, 1, 1, 1);
254
+ expect(a.min.x).toBe(-1);
255
+ expect(a.max.z).toBe(1);
256
+ });
257
+ });
258
+
259
+ describe('setAabbFromPoints', () => {
260
+ it('computes the tight box over a set of points', () => {
261
+ const a = createAabb();
262
+ setAabbFromPoints(a, [createVector3(1, 5, -2), createVector3(-3, 0, 4), createVector3(2, 2, 2)]);
263
+ expect(a.min.x).toBe(-3);
264
+ expect(a.min.y).toBe(0);
265
+ expect(a.min.z).toBe(-2);
266
+ expect(a.max.x).toBe(2);
267
+ expect(a.max.y).toBe(5);
268
+ expect(a.max.z).toBe(4);
269
+ });
270
+
271
+ it('empty list yields an empty box', () => {
272
+ const a = createAabb(0, 0, 0, 1, 1, 1);
273
+ setAabbFromPoints(a, []);
274
+ expect(a.min.x).toBe(Number.POSITIVE_INFINITY);
275
+ expect(a.max.x).toBe(Number.NEGATIVE_INFINITY);
276
+ });
277
+ });
278
+
279
+ describe('transformAabbByMatrix4', () => {
280
+ it('translates the box', () => {
281
+ const a = createAabb(-1, -1, -1, 1, 1, 1);
282
+ const m = createMatrix4();
283
+ setMatrix4Position(m, createVector3(10, 0, 0));
284
+ const out = createAabb();
285
+ transformAabbByMatrix4(out, a, m);
286
+ expect(out.min.x).toBeCloseTo(9, 6);
287
+ expect(out.max.x).toBeCloseTo(11, 6);
288
+ expect(out.min.y).toBeCloseTo(-1, 6);
289
+ });
290
+
291
+ it('grows the box under a 45-degree rotation about z', () => {
292
+ const a = createAabb(-1, -1, -1, 1, 1, 1);
293
+ // Column-major 45-degree z rotation.
294
+ const c = Math.SQRT1_2;
295
+ const m = createMatrix4(c, c, 0, 0, -c, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
296
+ const out = createAabb();
297
+ transformAabbByMatrix4(out, a, m);
298
+ // Rotated unit box spans +/- sqrt(2) in x and y.
299
+ expect(out.max.x).toBeCloseTo(Math.SQRT2, 5);
300
+ expect(out.min.x).toBeCloseTo(-Math.SQRT2, 5);
301
+ expect(out.max.z).toBeCloseTo(1, 6);
302
+ });
303
+
304
+ it('supports out === aabb', () => {
305
+ const a = createAabb(-1, -1, -1, 1, 1, 1);
306
+ const m = createMatrix4();
307
+ setMatrix4Position(m, createVector3(5, 5, 5));
308
+ transformAabbByMatrix4(a, a, m);
309
+ expect(a.min.x).toBeCloseTo(4, 6);
310
+ expect(a.max.z).toBeCloseTo(6, 6);
311
+ });
312
+ });
313
+
314
+ describe('unionAabb', () => {
315
+ it('encloses both boxes', () => {
316
+ const a = createAabb(0, 0, 0, 1, 1, 1);
317
+ const b = createAabb(-2, 3, -1, -1, 5, 0);
318
+ const out = createAabb();
319
+ unionAabb(out, a, b);
320
+ expect(out.min.x).toBe(-2);
321
+ expect(out.min.z).toBe(-1);
322
+ expect(out.max.y).toBe(5);
323
+ expect(out.max.x).toBe(1);
324
+ });
325
+
326
+ it('supports out === a', () => {
327
+ const a = createAabb(0, 0, 0, 1, 1, 1);
328
+ const b = createAabb(-2, 0, 0, 0, 4, 0);
329
+ unionAabb(a, a, b);
330
+ expect(a.min.x).toBe(-2);
331
+ expect(a.max.y).toBe(4);
332
+ });
333
+
334
+ it('supports out === b', () => {
335
+ const a = createAabb(0, 0, 0, 1, 1, 1);
336
+ const b = createAabb(-2, 0, 0, 0, 4, 0);
337
+ unionAabb(b, a, b);
338
+ expect(b.min.x).toBe(-2);
339
+ expect(b.max.y).toBe(4);
340
+ });
341
+ });
@@ -0,0 +1,247 @@
1
+ import {
2
+ cloneBoundingSphere,
3
+ copyBoundingSphere,
4
+ createAabb,
5
+ createBoundingSphere,
6
+ createMatrix4,
7
+ createVector3,
8
+ getBoundingSphereContainsPoint,
9
+ getClosestPointOnBoundingSphere,
10
+ isBoundingSphereIntersectingBoundingSphere,
11
+ mergeBoundingSphere,
12
+ setBoundingSphere,
13
+ setBoundingSphereFromAabb,
14
+ setMatrix4Position,
15
+ transformBoundingSphereByMatrix4,
16
+ } from '@flighthq/geometry';
17
+
18
+ describe('cloneBoundingSphere', () => {
19
+ it('creates an independent copy with an independent center', () => {
20
+ const s = createBoundingSphere(1, 2, 3, 4);
21
+ const c = cloneBoundingSphere(s);
22
+ expect(c).not.toBe(s);
23
+ expect(c.center).not.toBe(s.center);
24
+ expect(c.center.x).toBe(1);
25
+ expect(c.radius).toBe(4);
26
+ c.center.x = 99;
27
+ expect(s.center.x).toBe(1);
28
+ });
29
+ });
30
+
31
+ describe('copyBoundingSphere', () => {
32
+ it('copies center and radius', () => {
33
+ const src = createBoundingSphere(1, 2, 3, 4);
34
+ const out = createBoundingSphere();
35
+ copyBoundingSphere(out, src);
36
+ expect(out.center.x).toBe(1);
37
+ expect(out.radius).toBe(4);
38
+ });
39
+
40
+ it('supports out === source', () => {
41
+ const s = createBoundingSphere(1, 2, 3, 4);
42
+ copyBoundingSphere(s, s);
43
+ expect(s.center.y).toBe(2);
44
+ expect(s.radius).toBe(4);
45
+ });
46
+ });
47
+
48
+ describe('createBoundingSphere', () => {
49
+ it('defaults to an empty sphere (negative radius)', () => {
50
+ const s = createBoundingSphere();
51
+ expect(s.center.x).toBe(0);
52
+ expect(s.radius).toBe(-1);
53
+ });
54
+
55
+ it('uses provided center and radius', () => {
56
+ const s = createBoundingSphere(1, 2, 3, 5);
57
+ expect(s.center.z).toBe(3);
58
+ expect(s.radius).toBe(5);
59
+ });
60
+ });
61
+
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
+ describe('getClosestPointOnBoundingSphere', () => {
81
+ it('projects an outside point onto the surface', () => {
82
+ const s = createBoundingSphere(0, 0, 0, 2);
83
+ const out = createVector3();
84
+ getClosestPointOnBoundingSphere(out, s, createVector3(10, 0, 0));
85
+ expect(out.x).toBeCloseTo(2, 6);
86
+ expect(out.y).toBeCloseTo(0, 6);
87
+ expect(out.z).toBeCloseTo(0, 6);
88
+ });
89
+
90
+ it('pulls an inside point out to the surface', () => {
91
+ const s = createBoundingSphere(0, 0, 0, 5);
92
+ const out = createVector3();
93
+ getClosestPointOnBoundingSphere(out, s, createVector3(0, 1, 0));
94
+ expect(out.x).toBeCloseTo(0, 6);
95
+ expect(out.y).toBeCloseTo(5, 6);
96
+ expect(out.z).toBeCloseTo(0, 6);
97
+ });
98
+
99
+ it('uses the +X fallback when the point is at the center', () => {
100
+ const s = createBoundingSphere(1, 2, 3, 4);
101
+ const out = createVector3();
102
+ getClosestPointOnBoundingSphere(out, s, createVector3(1, 2, 3));
103
+ expect(out.x).toBeCloseTo(5, 6);
104
+ expect(out.y).toBeCloseTo(2, 6);
105
+ expect(out.z).toBeCloseTo(3, 6);
106
+ });
107
+
108
+ it('writes the center for an empty sphere', () => {
109
+ const s = createBoundingSphere(1, 2, 3, -1);
110
+ const out = createVector3();
111
+ getClosestPointOnBoundingSphere(out, s, createVector3(9, 9, 9));
112
+ expect(out.x).toBe(1);
113
+ expect(out.y).toBe(2);
114
+ expect(out.z).toBe(3);
115
+ });
116
+
117
+ it('supports out === point', () => {
118
+ const s = createBoundingSphere(0, 0, 0, 3);
119
+ const p = createVector3(6, 0, 0);
120
+ getClosestPointOnBoundingSphere(p, s, p);
121
+ expect(p.x).toBeCloseTo(3, 6);
122
+ });
123
+ });
124
+
125
+ describe('isBoundingSphereIntersectingBoundingSphere', () => {
126
+ it('returns true for overlapping spheres', () => {
127
+ const a = createBoundingSphere(0, 0, 0, 2);
128
+ const b = createBoundingSphere(3, 0, 0, 2);
129
+ expect(isBoundingSphereIntersectingBoundingSphere(a, b)).toBe(true);
130
+ });
131
+
132
+ it('returns true for touching spheres', () => {
133
+ const a = createBoundingSphere(0, 0, 0, 1);
134
+ const b = createBoundingSphere(2, 0, 0, 1);
135
+ expect(isBoundingSphereIntersectingBoundingSphere(a, b)).toBe(true);
136
+ });
137
+
138
+ it('returns false for separated spheres', () => {
139
+ const a = createBoundingSphere(0, 0, 0, 1);
140
+ const b = createBoundingSphere(5, 0, 0, 1);
141
+ expect(isBoundingSphereIntersectingBoundingSphere(a, b)).toBe(false);
142
+ });
143
+
144
+ it('returns false if either sphere is empty', () => {
145
+ const a = createBoundingSphere(0, 0, 0, 1);
146
+ const empty = createBoundingSphere(0, 0, 0, -1);
147
+ expect(isBoundingSphereIntersectingBoundingSphere(a, empty)).toBe(false);
148
+ expect(isBoundingSphereIntersectingBoundingSphere(empty, a)).toBe(false);
149
+ });
150
+ });
151
+
152
+ describe('mergeBoundingSphere', () => {
153
+ it('merges two overlapping spheres into a containing sphere', () => {
154
+ const a = createBoundingSphere(-1, 0, 0, 1);
155
+ const b = createBoundingSphere(1, 0, 0, 1);
156
+ const out = createBoundingSphere();
157
+ mergeBoundingSphere(out, a, b);
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);
161
+ });
162
+
163
+ it('if a contains b, result equals a', () => {
164
+ const a = createBoundingSphere(0, 0, 0, 10);
165
+ const b = createBoundingSphere(1, 0, 0, 1);
166
+ const out = createBoundingSphere();
167
+ mergeBoundingSphere(out, a, b);
168
+ expect(out.center.x).toBeCloseTo(0, 5);
169
+ expect(out.radius).toBeCloseTo(10, 5);
170
+ });
171
+
172
+ it('empty source returns the other sphere', () => {
173
+ const a = createBoundingSphere();
174
+ const b = createBoundingSphere(3, 0, 0, 2);
175
+ const out = createBoundingSphere();
176
+ mergeBoundingSphere(out, a, b);
177
+ expect(out.center.x).toBeCloseTo(3, 5);
178
+ expect(out.radius).toBeCloseTo(2, 5);
179
+ });
180
+
181
+ it('supports out === a', () => {
182
+ const a = createBoundingSphere(-1, 0, 0, 1);
183
+ const b = createBoundingSphere(1, 0, 0, 1);
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);
187
+ });
188
+ });
189
+
190
+ describe('setBoundingSphere', () => {
191
+ it('sets center and radius', () => {
192
+ const s = createBoundingSphere();
193
+ setBoundingSphere(s, 5, 6, 7, 8);
194
+ expect(s.center.x).toBe(5);
195
+ expect(s.radius).toBe(8);
196
+ });
197
+ });
198
+
199
+ describe('setBoundingSphereFromAabb', () => {
200
+ it('centers on the box and reaches its corners', () => {
201
+ const aabb = createAabb(-1, -1, -1, 1, 1, 1);
202
+ const s = createBoundingSphere();
203
+ setBoundingSphereFromAabb(s, aabb);
204
+ expect(s.center.x).toBe(0);
205
+ expect(s.center.y).toBe(0);
206
+ expect(s.center.z).toBe(0);
207
+ expect(s.radius).toBeCloseTo(Math.sqrt(3), 6);
208
+ });
209
+
210
+ it('an empty box yields an empty sphere', () => {
211
+ const aabb = createAabb();
212
+ const s = createBoundingSphere(0, 0, 0, 5);
213
+ setBoundingSphereFromAabb(s, aabb);
214
+ expect(s.radius).toBe(-1);
215
+ });
216
+ });
217
+
218
+ describe('transformBoundingSphereByMatrix4', () => {
219
+ it('translates the center and preserves the radius', () => {
220
+ const s = createBoundingSphere(0, 0, 0, 2);
221
+ const m = createMatrix4();
222
+ setMatrix4Position(m, createVector3(10, 0, 0));
223
+ const out = createBoundingSphere();
224
+ transformBoundingSphereByMatrix4(out, s, m);
225
+ expect(out.center.x).toBeCloseTo(10, 6);
226
+ expect(out.radius).toBeCloseTo(2, 6);
227
+ });
228
+
229
+ it('scales the radius by the largest axis scale', () => {
230
+ const s = createBoundingSphere(0, 0, 0, 1);
231
+ // Column-major non-uniform scale diag(2,3,4).
232
+ const m = createMatrix4(2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1);
233
+ const out = createBoundingSphere();
234
+ transformBoundingSphereByMatrix4(out, s, m);
235
+ expect(out.radius).toBeCloseTo(4, 6);
236
+ });
237
+
238
+ it('supports out === sphere', () => {
239
+ const s = createBoundingSphere(1, 0, 0, 2);
240
+ const m = createMatrix4();
241
+ setMatrix4Position(m, createVector3(0, 5, 0));
242
+ transformBoundingSphereByMatrix4(s, s, m);
243
+ expect(s.center.x).toBeCloseTo(1, 6);
244
+ expect(s.center.y).toBeCloseTo(5, 6);
245
+ expect(s.radius).toBeCloseTo(2, 6);
246
+ });
247
+ });