@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,958 @@
1
+ import {
2
+ addVector2,
3
+ clampVector2,
4
+ cloneVector2,
5
+ copyVector2,
6
+ createVector2,
7
+ createVector2FromPolar,
8
+ divideVector2,
9
+ equalsVector2,
10
+ getVector2AngleBetween,
11
+ getVector2Distance,
12
+ getVector2DistanceSquared,
13
+ getVector2Dot,
14
+ getVector2Length,
15
+ getVector2LengthSquared,
16
+ interpolateVector2,
17
+ maxVector2,
18
+ minVector2,
19
+ multiplyVector2,
20
+ nearEqualsVector2,
21
+ negateVector2,
22
+ normalizeVector2,
23
+ offsetVector2,
24
+ reflectVector2,
25
+ scaleVector2,
26
+ scaleVector2ToLength,
27
+ setVector2,
28
+ setVector2FromFloat32Array,
29
+ setVector2FromPolar,
30
+ setVector2FromVector3,
31
+ subtractVector2,
32
+ writeVector2ToFloat32Array,
33
+ } from '@flighthq/geometry';
34
+ import type { Vector2 } from '@flighthq/types';
35
+
36
+ let pt: Vector2;
37
+ let pt2: Vector2;
38
+
39
+ beforeEach(() => {
40
+ pt = createVector2();
41
+ pt2 = createVector2();
42
+ });
43
+
44
+ describe('addVector2', () => {
45
+ it('adds two coordinates', () => {
46
+ pt.x = 2;
47
+ pt.y = 10;
48
+ pt2.x = 4;
49
+ pt2.y = 20;
50
+
51
+ const result = createVector2();
52
+ addVector2(result, pt, pt2);
53
+ expect(result.x).toBe(6);
54
+ expect(result.y).toBe(30);
55
+ expect(result).not.toBe(pt);
56
+ expect(result).not.toBe(pt2);
57
+ });
58
+
59
+ it('works correctly if first vector is out', () => {
60
+ pt.x = 3;
61
+ pt.y = 7;
62
+ pt2.x = 5;
63
+ pt2.y = 10;
64
+
65
+ addVector2(pt, pt, pt2);
66
+ expect(pt.x).toBe(8);
67
+ expect(pt.y).toBe(17);
68
+ expect(pt2.x).toBe(5);
69
+ expect(pt2.y).toBe(10);
70
+ });
71
+
72
+ it('works correctly if second vector is out', () => {
73
+ pt.x = 2;
74
+ pt.y = 10;
75
+ pt2.x = 4;
76
+ pt2.y = 20;
77
+
78
+ addVector2(pt2, pt, pt2);
79
+ expect(pt.x).toBe(2);
80
+ expect(pt.y).toBe(10);
81
+ expect(pt2.x).toBe(6);
82
+ expect(pt2.y).toBe(30);
83
+ });
84
+
85
+ it('allows a vector-like object', () => {
86
+ const pt = { x: 2, y: 10 };
87
+ const pt2 = { x: 4, y: 20 };
88
+
89
+ const result = { x: 0, y: 0 };
90
+ addVector2(result, pt, pt2);
91
+ expect(result.x).toBe(6);
92
+ expect(result.y).toBe(30);
93
+ expect(result).not.toBe(pt);
94
+ expect(result).not.toBe(pt2);
95
+ });
96
+ });
97
+
98
+ describe('clampVector2', () => {
99
+ it('clamps each component independently', () => {
100
+ const out = createVector2();
101
+ const value = createVector2(5, -2);
102
+ const min = createVector2(0, 0);
103
+ const max = createVector2(3, 3);
104
+ clampVector2(out, value, min, max);
105
+ expect(out.x).toBe(3);
106
+ expect(out.y).toBe(0);
107
+ });
108
+
109
+ it('leaves values within range unchanged', () => {
110
+ const out = createVector2();
111
+ const value = createVector2(1, 2);
112
+ const min = createVector2(0, 0);
113
+ const max = createVector2(3, 3);
114
+ clampVector2(out, value, min, max);
115
+ expect(out.x).toBe(1);
116
+ expect(out.y).toBe(2);
117
+ });
118
+
119
+ it('supports out === value', () => {
120
+ const v = createVector2(5, -1);
121
+ clampVector2(v, v, createVector2(0, 0), createVector2(3, 3));
122
+ expect(v.x).toBe(3);
123
+ expect(v.y).toBe(0);
124
+ });
125
+ });
126
+
127
+ describe('cloneVector2', () => {
128
+ it('creates a copy of a vector', () => {
129
+ pt.x = 1;
130
+ pt.y = 2;
131
+ const result = cloneVector2(pt);
132
+ expect(result.x).toBe(pt.x);
133
+ expect(result.y).toBe(pt.y);
134
+ });
135
+
136
+ it('returns a Vector2 instance', () => {
137
+ const pt = { x: 1, y: 2 };
138
+ const result: Vector2 = cloneVector2(pt);
139
+ expect(result).not.toBeNull();
140
+ });
141
+ });
142
+
143
+ describe('copyVector2', () => {
144
+ it('copies coordinates from one vector to another', () => {
145
+ pt2.x = 1;
146
+ pt2.y = 2;
147
+ copyVector2(pt, pt2);
148
+ expect(pt.x).toBe(1);
149
+ expect(pt.y).toBe(2);
150
+ });
151
+
152
+ it('supports out === source', () => {
153
+ pt.x = 1;
154
+ pt.y = 2;
155
+ copyVector2(pt, pt);
156
+ expect(pt.x).toBe(1);
157
+ expect(pt.y).toBe(2);
158
+ });
159
+
160
+ it('allows a vector-like object', () => {
161
+ const pt2 = { x: 1, y: 2 };
162
+ copyVector2(pt, pt2);
163
+ expect(pt.x).toBe(1);
164
+ expect(pt.y).toBe(2);
165
+ });
166
+ });
167
+
168
+ describe('createVector2', () => {
169
+ it('returns a Vector2 with default coordinates', () => {
170
+ const p: Vector2 = createVector2();
171
+ expect(p).not.toBeNull();
172
+ expect(p.x).toBe(0);
173
+ expect(p.y).toBe(0);
174
+ });
175
+
176
+ it('sets the specified x and y coordinates', () => {
177
+ const p = createVector2(2, 4);
178
+ expect(p.x).toBe(2);
179
+ expect(p.y).toBe(4);
180
+ });
181
+
182
+ it('allows a vector-like object', () => {
183
+ const p = { x: 2, y: 4 };
184
+ expect(p.x).toBe(2);
185
+ expect(p.y).toBe(4);
186
+ });
187
+ });
188
+
189
+ describe('createVector2FromPolar', () => {
190
+ it('makes a createVector2 and calls polar', () => {
191
+ const pt = createVector2FromPolar(5, 0);
192
+ setVector2FromPolar(pt2, 5, 0);
193
+ expect(equalsVector2(pt, pt2)).toBe(true);
194
+ });
195
+ });
196
+
197
+ describe('divideVector2', () => {
198
+ it('divides component-wise', () => {
199
+ const out = createVector2();
200
+ divideVector2(out, createVector2(6, 8), createVector2(2, 4));
201
+ expect(out.x).toBe(3);
202
+ expect(out.y).toBe(2);
203
+ });
204
+
205
+ it('produces 0 for zero divisor components', () => {
206
+ const out = createVector2();
207
+ divideVector2(out, createVector2(6, 8), createVector2(0, 4));
208
+ expect(out.x).toBe(0);
209
+ expect(out.y).toBe(2);
210
+ });
211
+
212
+ it('supports out === source', () => {
213
+ const v = createVector2(6, 8);
214
+ divideVector2(v, v, createVector2(2, 4));
215
+ expect(v.x).toBe(3);
216
+ expect(v.y).toBe(2);
217
+ });
218
+ });
219
+
220
+ describe('equalsVector2', () => {
221
+ it('returns true if vectors are identical, false otherwise', () => {
222
+ expect(equalsVector2(pt, pt2)).toBe(true);
223
+ pt.x = 1;
224
+ expect(equalsVector2(pt, pt2)).toBe(false);
225
+ pt2.x = 1;
226
+ expect(equalsVector2(pt, pt2)).toBe(true);
227
+ });
228
+
229
+ it('allows a vector-like object', () => {
230
+ const pt2 = { x: 0, y: 0 };
231
+ expect(equalsVector2(pt, pt2)).toBe(true);
232
+ pt.x = 1;
233
+ expect(equalsVector2(pt, pt2)).toBe(false);
234
+ pt2.x = 1;
235
+ expect(equalsVector2(pt, pt2)).toBe(true);
236
+ });
237
+ });
238
+
239
+ describe('getVector2AngleBetween', () => {
240
+ it('returns 0 for identical vectors', () => {
241
+ const a = createVector2(1, 0);
242
+ expect(getVector2AngleBetween(a, a)).toBeCloseTo(0);
243
+ });
244
+
245
+ it('returns PI/2 for perpendicular vectors', () => {
246
+ const a = createVector2(1, 0);
247
+ const b = createVector2(0, 1);
248
+ expect(getVector2AngleBetween(a, b)).toBeCloseTo(Math.PI / 2);
249
+ });
250
+
251
+ it('returns PI for opposite vectors', () => {
252
+ const a = createVector2(1, 0);
253
+ const b = createVector2(-1, 0);
254
+ expect(getVector2AngleBetween(a, b)).toBeCloseTo(Math.PI);
255
+ });
256
+
257
+ it('returns NaN for a zero-length vector', () => {
258
+ const a = createVector2(0, 0);
259
+ const b = createVector2(1, 0);
260
+ expect(getVector2AngleBetween(a, b)).toBeNaN();
261
+ });
262
+ });
263
+
264
+ describe('getVector2Distance', () => {
265
+ const testCases = [
266
+ { a: [100, 0], b: [0, 0], expected: 100 },
267
+ { a: [0, 100], b: [0, 0], expected: 100 },
268
+ { a: [3, 4], b: [0, 0], expected: 5 },
269
+ { a: [5, 12], b: [0, 0], expected: 13 },
270
+ { a: [8, 15], b: [0, 0], expected: 17 },
271
+ ];
272
+
273
+ it('calculates Euclidean distance between vectors', () => {
274
+ for (const { a, b, expected } of testCases) {
275
+ pt.x = a[0];
276
+ pt.y = a[1];
277
+ pt2.x = b[0];
278
+ pt2.y = b[1];
279
+ expect(getVector2Distance(pt, pt2)).toBe(expected);
280
+ }
281
+ });
282
+
283
+ it('allows a vector-like object', () => {
284
+ for (const { a, b, expected } of testCases) {
285
+ const pt = { x: a[0], y: a[1] };
286
+ const pt2 = { x: b[0], y: b[1] };
287
+ expect(getVector2Distance(pt, pt2)).toBe(expected);
288
+ }
289
+ });
290
+ });
291
+
292
+ describe('getVector2DistanceSquared', () => {
293
+ it('returns the squared distance between two vectors', () => {
294
+ const a = createVector2(1, 1);
295
+ const b = createVector2(4, 5);
296
+ expect(getVector2DistanceSquared(a, b)).toBe(25); // 3^2 + 4^2
297
+ });
298
+
299
+ it('allows a vector-like object', () => {
300
+ const a = { x: 1, y: 1 };
301
+ const b = { x: 4, y: 5 };
302
+ expect(getVector2DistanceSquared(a, b)).toBe(25);
303
+ });
304
+ });
305
+
306
+ describe('getVector2Dot', () => {
307
+ it('returns the dot product of two vectors', () => {
308
+ const a = createVector2(1, 2);
309
+ const b = createVector2(4, 5);
310
+ expect(getVector2Dot(a, b)).toBe(14); // 1*4 + 2*5
311
+ });
312
+
313
+ it('allows a vector-like object', () => {
314
+ const a = { x: 1, y: 2 };
315
+ const b = { x: 4, y: 5 };
316
+ expect(getVector2Dot(a, b)).toBe(14);
317
+ });
318
+ });
319
+
320
+ describe('getVector2Length', () => {
321
+ const testCases = [
322
+ { x: 100, y: 0, expected: 100 },
323
+ { x: 0, y: 100, expected: 100 },
324
+ { x: 3, y: 4, expected: 5 },
325
+ { x: 5, y: 12, expected: 13 },
326
+ { x: 8, y: 15, expected: 17 },
327
+ ];
328
+
329
+ it('returns the length of the vector', () => {
330
+ for (const { x, y, expected } of testCases) {
331
+ pt.x = x;
332
+ pt.y = y;
333
+ expect(getVector2Length(pt)).toBe(expected);
334
+ }
335
+ });
336
+
337
+ it('allows a vector-like object', () => {
338
+ for (const { x, y, expected } of testCases) {
339
+ const pt = { x: x, y: y };
340
+ expect(getVector2Length(pt)).toBe(expected);
341
+ }
342
+ });
343
+ });
344
+
345
+ describe('getVector2LengthSquared', () => {
346
+ it('returns the square of the length', () => {
347
+ pt.x = 3;
348
+ pt.y = 4;
349
+ expect(getVector2LengthSquared(pt)).toBe(9 + 16); // 3^2 + 4^2 = 9 + 16 = 25
350
+ });
351
+
352
+ it('returns 0 for the origin (0, 0)', () => {
353
+ expect(getVector2LengthSquared(pt)).toBe(0);
354
+ });
355
+
356
+ it('handles negative values correctly', () => {
357
+ pt.x = -3;
358
+ pt.y = -4;
359
+ expect(getVector2LengthSquared(pt)).toBe(9 + 16); // 9 + 16 = 25
360
+ });
361
+
362
+ it('handles non-integer values', () => {
363
+ pt.x = 2.5;
364
+ pt.y = 4.5;
365
+ expect(getVector2LengthSquared(pt)).toBe(2.5 * 2.5 + 4.5 * 4.5); // 6.25 + 20.25 = 26.5
366
+ });
367
+
368
+ it('allows a vector-like object', () => {
369
+ const pt = { x: 3, y: 4 };
370
+ expect(getVector2LengthSquared(pt)).toBe(9 + 16); // 3^2 + 4^2 = 9 + 16 = 25
371
+ });
372
+ });
373
+
374
+ describe('interpolateVector2', () => {
375
+ const cases = [
376
+ { t: 0, expected: (a: number, _: number) => a },
377
+ { t: 1, expected: (_: number, b: number) => b },
378
+ { t: 0.5, expected: (a: number, b: number) => (a + b) / 2 },
379
+ { t: 10, expected: (a: number, b: number) => a + (b - a) * 10 },
380
+ { t: -1, expected: (a: number, b: number) => a + (b - a) * -1 },
381
+ ];
382
+
383
+ it('interpolates and extrapolates correctly', () => {
384
+ pt.x = 0;
385
+ pt.y = 0;
386
+ pt2.x = 100;
387
+ pt2.y = 100;
388
+
389
+ for (const { t, expected } of cases) {
390
+ const result = createVector2();
391
+ interpolateVector2(result, pt, pt2, t);
392
+ expect(result.x).toBe(expected(pt.x, pt2.x));
393
+ expect(result.y).toBe(expected(pt.y, pt2.y));
394
+ }
395
+ });
396
+
397
+ it('works correctly if out is the first vector', () => {
398
+ pt.x = 10;
399
+ pt.y = 20;
400
+ pt2.x = 30;
401
+ pt2.y = 40;
402
+
403
+ interpolateVector2(pt, pt, pt2, 0.5);
404
+ expect(pt.x).toBe(20);
405
+ expect(pt.y).toBe(30);
406
+ expect(pt2.x).toBe(30);
407
+ expect(pt2.y).toBe(40);
408
+ });
409
+
410
+ it('works correctly if out is the second vector', () => {
411
+ pt.x = 10;
412
+ pt.y = 20;
413
+ pt2.x = 30;
414
+ pt2.y = 40;
415
+
416
+ interpolateVector2(pt2, pt, pt2, 0.5);
417
+ expect(pt.x).toBe(10);
418
+ expect(pt.y).toBe(20);
419
+ expect(pt2.x).toBe(20);
420
+ expect(pt2.y).toBe(30);
421
+ });
422
+
423
+ it('handles extreme extrapolation values for t', () => {
424
+ pt.x = 0;
425
+ pt.y = 0;
426
+ pt2.x = 100;
427
+ pt2.y = 100;
428
+
429
+ const result = createVector2();
430
+ interpolateVector2(result, pt, pt2, 1000);
431
+ expect(result.x).toBe(100000);
432
+ expect(result.y).toBe(100000);
433
+ });
434
+
435
+ it('allows vector-like objects', () => {
436
+ const pt = { x: 0, y: 0 };
437
+ const pt2 = { x: 100, y: 100 };
438
+
439
+ for (const { t, expected } of cases) {
440
+ const result = { x: 0, y: 0 };
441
+ interpolateVector2(result, pt, pt2, t);
442
+ expect(result.x).toBe(expected(pt.x, pt2.x));
443
+ expect(result.y).toBe(expected(pt.y, pt2.y));
444
+ }
445
+ });
446
+ });
447
+
448
+ describe('maxVector2', () => {
449
+ it('returns the component-wise maximum', () => {
450
+ const out = createVector2();
451
+ maxVector2(out, createVector2(1, 5), createVector2(3, 2));
452
+ expect(out.x).toBe(3);
453
+ expect(out.y).toBe(5);
454
+ });
455
+
456
+ it('supports out === a', () => {
457
+ const a = createVector2(1, 5);
458
+ const b = createVector2(3, 2);
459
+ maxVector2(a, a, b);
460
+ expect(a.x).toBe(3);
461
+ expect(a.y).toBe(5);
462
+ });
463
+ });
464
+
465
+ describe('minVector2', () => {
466
+ it('returns the component-wise minimum', () => {
467
+ const out = createVector2();
468
+ minVector2(out, createVector2(1, 5), createVector2(3, 2));
469
+ expect(out.x).toBe(1);
470
+ expect(out.y).toBe(2);
471
+ });
472
+
473
+ it('supports out === a', () => {
474
+ const a = createVector2(1, 5);
475
+ const b = createVector2(3, 2);
476
+ minVector2(a, a, b);
477
+ expect(a.x).toBe(1);
478
+ expect(a.y).toBe(2);
479
+ });
480
+ });
481
+
482
+ describe('multiplyVector2', () => {
483
+ it('multiplies component-wise (Hadamard)', () => {
484
+ const out = createVector2();
485
+ multiplyVector2(out, createVector2(2, 3), createVector2(4, 5));
486
+ expect(out.x).toBe(8);
487
+ expect(out.y).toBe(15);
488
+ });
489
+
490
+ it('supports out === a', () => {
491
+ const a = createVector2(2, 3);
492
+ multiplyVector2(a, a, createVector2(4, 5));
493
+ expect(a.x).toBe(8);
494
+ expect(a.y).toBe(15);
495
+ });
496
+ });
497
+
498
+ describe('nearEqualsVector2', () => {
499
+ it('returns true for identical vectors', () => {
500
+ const a = createVector2(1, 2);
501
+ expect(nearEqualsVector2(a, a)).toBe(true);
502
+ });
503
+
504
+ it('returns true when difference is within default tolerance', () => {
505
+ const a = createVector2(1, 2);
506
+ const b = createVector2(1 + 1e-7, 2);
507
+ expect(nearEqualsVector2(a, b)).toBe(true);
508
+ });
509
+
510
+ it('returns false when difference exceeds default tolerance', () => {
511
+ const a = createVector2(1, 2);
512
+ const b = createVector2(1 + 1e-5, 2);
513
+ expect(nearEqualsVector2(a, b)).toBe(false);
514
+ });
515
+
516
+ it('respects a custom tolerance', () => {
517
+ const a = createVector2(1, 2);
518
+ const b = createVector2(1.05, 2);
519
+ expect(nearEqualsVector2(a, b, 0.1)).toBe(true);
520
+ expect(nearEqualsVector2(a, b, 0.01)).toBe(false);
521
+ });
522
+ });
523
+
524
+ describe('negateVector2', () => {
525
+ it('inverts the values of the vector components', () => {
526
+ const v = createVector2(1, -2);
527
+ const result = createVector2();
528
+ negateVector2(result, v);
529
+ expect(result.x).toBe(-1);
530
+ expect(result.y).toBe(2);
531
+ });
532
+
533
+ it('supports out === source', () => {
534
+ const v = createVector2(1, -2);
535
+ negateVector2(v, v);
536
+ expect(v.x).toBe(-1);
537
+ expect(v.y).toBe(2);
538
+ });
539
+ });
540
+
541
+ describe('normalizeVector2', () => {
542
+ it('produces a unit vector', () => {
543
+ const pt = createVector2(3, 4);
544
+ const result = createVector2();
545
+ normalizeVector2(result, pt);
546
+ expect(pt).not.toBe(result);
547
+ expect(result.x).toBeCloseTo(0.6);
548
+ expect(result.y).toBeCloseTo(0.8);
549
+ expect(getVector2Length(result)).toBeCloseTo(1);
550
+ });
551
+
552
+ it('returns the original length', () => {
553
+ const pt = createVector2(3, 4);
554
+ const result = createVector2();
555
+ const length = normalizeVector2(result, pt);
556
+ expect(length).toBeCloseTo(5);
557
+ });
558
+
559
+ it('returns zero for a zero-length vector', () => {
560
+ const pt = createVector2(0, 0);
561
+ const result = createVector2();
562
+ const length = normalizeVector2(result, pt);
563
+ expect(result).not.toBe(pt);
564
+ expect(result.x).toBe(0);
565
+ expect(result.y).toBe(0);
566
+ expect(length).toBe(0);
567
+ });
568
+
569
+ it('handles very small vectors correctly', () => {
570
+ const pt = createVector2(0.0001, 0.0001);
571
+ const result = createVector2();
572
+ normalizeVector2(result, pt);
573
+ expect(result.x).toBeCloseTo(0.7071, 4);
574
+ expect(result.y).toBeCloseTo(0.7071, 4);
575
+ expect(getVector2Length(result)).toBeCloseTo(1);
576
+ });
577
+
578
+ it('supports out === source', () => {
579
+ const pt = createVector2(3, 4);
580
+ const length = normalizeVector2(pt, pt);
581
+ expect(pt.x).toBeCloseTo(0.6);
582
+ expect(pt.y).toBeCloseTo(0.8);
583
+ expect(length).toBeCloseTo(5);
584
+ });
585
+
586
+ it('allows a vector-like object', () => {
587
+ const pt = { x: 3, y: 4 };
588
+ const result = { x: 0, y: 0 };
589
+ normalizeVector2(result, pt);
590
+ expect(pt).not.toBe(result);
591
+ expect(result.x).toBeCloseTo(0.6);
592
+ expect(result.y).toBeCloseTo(0.8);
593
+ expect(getVector2Length(result)).toBeCloseTo(1);
594
+ });
595
+ });
596
+
597
+ describe('offsetVector2', () => {
598
+ it('adjusts the value of a vector', () => {
599
+ const result = createVector2();
600
+ offsetVector2(result, pt, 10, 100);
601
+ expect(result.x).toBe(10);
602
+ expect(result.y).toBe(100);
603
+ });
604
+
605
+ it('works with negative deltas', () => {
606
+ const result = createVector2();
607
+ offsetVector2(result, pt, -5, -10);
608
+ expect(result.x).toBe(-5);
609
+ expect(result.y).toBe(-10);
610
+ });
611
+
612
+ it('supports out === source', () => {
613
+ const pt = createVector2(1, 2);
614
+ offsetVector2(pt, pt, 3, 4);
615
+ expect(pt.x).toBe(4);
616
+ expect(pt.y).toBe(6);
617
+ });
618
+
619
+ it('allows a vector-like object', () => {
620
+ const result = { x: 0, y: 0 };
621
+ offsetVector2(result, pt, 10, 100);
622
+ expect(result.x).toBe(10);
623
+ expect(result.y).toBe(100);
624
+ });
625
+ });
626
+
627
+ describe('reflectVector2', () => {
628
+ it('reflects incident vector about a normal', () => {
629
+ const out = createVector2();
630
+ // Reflect (1, -1) about the y-axis normal (1, 0)
631
+ reflectVector2(out, createVector2(1, -1), createVector2(1, 0));
632
+ expect(out.x).toBeCloseTo(-1, 6);
633
+ expect(out.y).toBeCloseTo(-1, 6);
634
+ });
635
+
636
+ it('reflects straight down about horizontal normal gives straight up', () => {
637
+ const out = createVector2();
638
+ reflectVector2(out, createVector2(0, -1), createVector2(0, 1));
639
+ expect(out.x).toBeCloseTo(0, 6);
640
+ expect(out.y).toBeCloseTo(1, 6);
641
+ });
642
+
643
+ it('supports out === incident', () => {
644
+ const v = createVector2(1, -1);
645
+ reflectVector2(v, v, createVector2(1, 0));
646
+ expect(v.x).toBeCloseTo(-1, 6);
647
+ expect(v.y).toBeCloseTo(-1, 6);
648
+ });
649
+ });
650
+
651
+ describe('scaleVector2', () => {
652
+ it('scales the vector by a scalar', () => {
653
+ const v = createVector2(1, 2);
654
+ const result = createVector2();
655
+ scaleVector2(result, v, 3);
656
+ expect(result.x).toBe(3);
657
+ expect(result.y).toBe(6);
658
+ });
659
+
660
+ it('supports out === source', () => {
661
+ const v = createVector2(1, 2);
662
+ scaleVector2(v, v, 3);
663
+ expect(v.x).toBe(3);
664
+ expect(v.y).toBe(6);
665
+ });
666
+ });
667
+
668
+ describe('scaleVector2ToLength', () => {
669
+ it('scales a vector to the specified length', () => {
670
+ const pt = createVector2(3, 4);
671
+ const result = createVector2();
672
+ scaleVector2ToLength(result, pt, 10);
673
+ expect(pt).not.toBe(result);
674
+ expect(result.x).toBeCloseTo(6);
675
+ expect(result.y).toBeCloseTo(8);
676
+ expect(getVector2Length(result)).toBeCloseTo(10);
677
+ });
678
+
679
+ it('returns zero for a zero-length vector', () => {
680
+ const pt = createVector2(0, 0);
681
+ const result = createVector2();
682
+ scaleVector2ToLength(result, pt, 5);
683
+ expect(result).not.toBe(pt);
684
+ expect(result.x).toBe(0);
685
+ expect(result.y).toBe(0);
686
+ expect(getVector2Length(result)).toBe(0);
687
+ });
688
+
689
+ it('scales vector to zero length', () => {
690
+ const pt = createVector2(3, 4);
691
+ const result = createVector2();
692
+ scaleVector2ToLength(result, pt, 0);
693
+ expect(result.x).toBe(0);
694
+ expect(result.y).toBe(0);
695
+ expect(getVector2Length(result)).toBe(0);
696
+ });
697
+
698
+ it('scales vector to negative length', () => {
699
+ const pt = createVector2(3, 4);
700
+ const result = createVector2();
701
+ scaleVector2ToLength(result, pt, -10);
702
+ expect(result.x).toBeCloseTo(-6);
703
+ expect(result.y).toBeCloseTo(-8);
704
+ expect(getVector2Length(result)).toBeCloseTo(10);
705
+ });
706
+
707
+ it('supports out === source', () => {
708
+ const pt = createVector2(3, 4);
709
+ scaleVector2ToLength(pt, pt, 10);
710
+ expect(pt.x).toBeCloseTo(6);
711
+ expect(pt.y).toBeCloseTo(8);
712
+ expect(getVector2Length(pt)).toBeCloseTo(10);
713
+ });
714
+
715
+ it('allows a vector-like object', () => {
716
+ const pt = { x: 3, y: 4 };
717
+ const result = { x: 0, y: 0 };
718
+ scaleVector2ToLength(result, pt, 10);
719
+ expect(pt).not.toBe(result);
720
+ expect(result.x).toBeCloseTo(6);
721
+ expect(result.y).toBeCloseTo(8);
722
+ expect(getVector2Length(result)).toBeCloseTo(10);
723
+ });
724
+ });
725
+
726
+ describe('setVector2', () => {
727
+ it('updates coordinates of a vector', () => {
728
+ setVector2(pt, 2, 10);
729
+ expect(pt.x).toBe(2);
730
+ expect(pt.y).toBe(10);
731
+ });
732
+
733
+ it('sets both coordinates to zero correctly', () => {
734
+ pt.x = 1;
735
+ pt.y = 2;
736
+ setVector2(pt, 0, 0);
737
+ expect(pt.x).toBe(0);
738
+ expect(pt.y).toBe(0);
739
+ });
740
+
741
+ it('allows a vector-like object', () => {
742
+ const pt = { x: 0, y: 0 };
743
+ setVector2(pt, 2, 10);
744
+ expect(pt.x).toBe(2);
745
+ expect(pt.y).toBe(10);
746
+ });
747
+ });
748
+
749
+ describe('setVector2FromFloat32Array', () => {
750
+ it('writes the vector from 2 values at the offset', () => {
751
+ const array = new Float32Array(2);
752
+ array[0] = 1;
753
+ array[1] = 2;
754
+ const vector = { x: 100, y: 100 };
755
+ setVector2FromFloat32Array(vector, 0, array);
756
+ expect(vector.x).toBe(1);
757
+ expect(vector.y).toBe(2);
758
+ });
759
+ });
760
+
761
+ describe('setVector2FromPolar', () => {
762
+ it('returns a vector at the given length along the x-axis when angle is 0', () => {
763
+ const p = createVector2();
764
+ setVector2FromPolar(p, 5, 0);
765
+ expect(p.x).toBeCloseTo(5);
766
+ expect(p.y).toBeCloseTo(0);
767
+ expect(getVector2Length(p)).toBeCloseTo(5);
768
+ });
769
+
770
+ it('returns a vector at the given length along the y-axis when angle is π/2', () => {
771
+ const p = createVector2();
772
+ setVector2FromPolar(p, 3, Math.PI / 2);
773
+ expect(p.x).toBeCloseTo(0);
774
+ expect(p.y).toBeCloseTo(3);
775
+ expect(getVector2Length(p)).toBeCloseTo(3);
776
+ });
777
+
778
+ it('returns a vector in the correct quadrant for angle π', () => {
779
+ const p = createVector2();
780
+ setVector2FromPolar(p, 4, Math.PI);
781
+ expect(p.x).toBeCloseTo(-4);
782
+ expect(p.y).toBeCloseTo(0);
783
+ expect(getVector2Length(p)).toBeCloseTo(4);
784
+ });
785
+
786
+ it('returns a vector in the correct quadrant for angle 3π/2', () => {
787
+ const p = createVector2();
788
+ setVector2FromPolar(p, 2, (3 * Math.PI) / 2);
789
+ expect(p.x).toBeCloseTo(0);
790
+ expect(p.y).toBeCloseTo(-2);
791
+ expect(getVector2Length(p)).toBeCloseTo(2);
792
+ });
793
+
794
+ it('handles zero length', () => {
795
+ const p = createVector2();
796
+ setVector2FromPolar(p, 0, Math.PI / 4);
797
+ expect(p.x).toBeCloseTo(0);
798
+ expect(p.y).toBeCloseTo(0);
799
+ expect(getVector2Length(p)).toBeCloseTo(0);
800
+ });
801
+
802
+ it('handles negative length', () => {
803
+ const p = createVector2();
804
+ setVector2FromPolar(p, -5, 0);
805
+ expect(p.x).toBeCloseTo(-5);
806
+ expect(p.y).toBeCloseTo(0);
807
+ expect(getVector2Length(p)).toBeCloseTo(5); // length property is always positive
808
+ });
809
+
810
+ it('handles arbitrary angles', () => {
811
+ const angle = Math.PI / 4; // 45 degrees
812
+ const len = Math.sqrt(2);
813
+ const p = createVector2();
814
+ setVector2FromPolar(p, len, angle);
815
+ expect(p.x).toBeCloseTo(1);
816
+ expect(p.y).toBeCloseTo(1);
817
+ expect(getVector2Length(p)).toBeCloseTo(len);
818
+ });
819
+
820
+ it('allows vector-like objects', () => {
821
+ const p = { x: 0, y: 0 };
822
+ setVector2FromPolar(p, 5, 0);
823
+ expect(p.x).toBeCloseTo(5);
824
+ expect(p.y).toBeCloseTo(0);
825
+ });
826
+ });
827
+
828
+ describe('setVector2FromVector3', () => {
829
+ it('copies x and y from a Vector3Like, dropping z', () => {
830
+ const src = { x: 3, y: 7, z: 99 };
831
+ const out = createVector2();
832
+ setVector2FromVector3(out, src);
833
+ expect(out.x).toBe(3);
834
+ expect(out.y).toBe(7);
835
+ });
836
+
837
+ it('is safe when out shares x/y memory with source (plain object)', () => {
838
+ const out = createVector2(1, 2);
839
+ const src = { x: out.x, y: out.y, z: 5 };
840
+ setVector2FromVector3(out, src);
841
+ expect(out.x).toBe(1);
842
+ expect(out.y).toBe(2);
843
+ });
844
+ });
845
+
846
+ describe('subtractVector2', () => {
847
+ it('subtracts the coordinates of two vectors into a new vector', () => {
848
+ pt.x = 5;
849
+ pt.y = 10;
850
+ pt2.x = 2;
851
+ pt2.y = 4;
852
+
853
+ const result = createVector2();
854
+ subtractVector2(result, pt, pt2);
855
+
856
+ expect(result.x).toBe(3);
857
+ expect(result.y).toBe(6);
858
+ });
859
+
860
+ it('handles negative results correctly', () => {
861
+ pt.x = 2;
862
+ pt.y = 3;
863
+ pt2.x = 5;
864
+ pt2.y = 10;
865
+
866
+ const result = createVector2();
867
+ subtractVector2(result, pt, pt2);
868
+
869
+ expect(result.x).toBe(-3);
870
+ expect(result.y).toBe(-7);
871
+ });
872
+
873
+ it('returns zero when subtracting a vector from itself', () => {
874
+ pt.x = 7;
875
+ pt.y = -3;
876
+
877
+ const result = createVector2();
878
+ subtractVector2(result, pt, pt);
879
+
880
+ expect(result.x).toBe(0);
881
+ expect(result.y).toBe(0);
882
+ });
883
+
884
+ it('supports out === source', () => {
885
+ pt.x = 5;
886
+ pt.y = 10;
887
+ pt2.x = 2;
888
+ pt2.y = 4;
889
+ subtractVector2(pt, pt, pt2);
890
+ expect(pt.x).toBe(3);
891
+ expect(pt.y).toBe(6);
892
+ });
893
+
894
+ it('supports out === other', () => {
895
+ pt.x = 5;
896
+ pt.y = 10;
897
+ pt2.x = 2;
898
+ pt2.y = 4;
899
+ subtractVector2(pt2, pt, pt2);
900
+ expect(pt2.x).toBe(3);
901
+ expect(pt2.y).toBe(6);
902
+ });
903
+
904
+ it('works when source or target vectors are at the origin', () => {
905
+ pt.x = 0;
906
+ pt.y = 0;
907
+ pt2.x = 5;
908
+ pt2.y = 10;
909
+
910
+ const result = createVector2();
911
+ subtractVector2(result, pt, pt2);
912
+ expect(result.x).toBe(-5);
913
+ expect(result.y).toBe(-10);
914
+
915
+ const result2 = createVector2();
916
+ subtractVector2(result2, pt2, pt);
917
+ expect(result2.x).toBe(5);
918
+ expect(result2.y).toBe(10);
919
+ });
920
+
921
+ it('works with very small values and results near zero', () => {
922
+ pt.x = 0.0001;
923
+ pt.y = 0.0001;
924
+ pt2.x = 0.0001;
925
+ pt2.y = 0.0001;
926
+
927
+ const result = createVector2();
928
+ subtractVector2(result, pt, pt2);
929
+ expect(result.x).toBeCloseTo(0);
930
+ expect(result.y).toBeCloseTo(0);
931
+ });
932
+
933
+ it('allows vector-like objects', () => {
934
+ const pt = { x: 5, y: 10 };
935
+ const pt2 = { x: 2, y: 4 };
936
+
937
+ const result = { x: 0, y: 0 };
938
+ subtractVector2(result, pt, pt2);
939
+
940
+ expect(result.x).toBe(3);
941
+ expect(result.y).toBe(6);
942
+
943
+ // Ensure new object is returned
944
+ expect(result).not.toBe(pt);
945
+ expect(result).not.toBe(pt2);
946
+ });
947
+ });
948
+
949
+ describe('writeVector2ToFloat32Array', () => {
950
+ it('writes 2 values at the offset', () => {
951
+ const array = new Float32Array(6);
952
+ const vector = { x: 1, y: 2 };
953
+ writeVector2ToFloat32Array(array, 0, vector);
954
+ for (let i = 0; i < 2; i++) {
955
+ expect(array[i]).toBe(i + 1);
956
+ }
957
+ });
958
+ });