@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,982 @@
1
+ import {
2
+ cloneRectangle,
3
+ computeRectangleIntersection,
4
+ containsRectanglePoint,
5
+ containsRectanglePointXY,
6
+ copyRectangle,
7
+ createRectangle,
8
+ createVector2,
9
+ enclosesRectangle,
10
+ equalsRectangle,
11
+ expandRectangleToPoint,
12
+ getRectangleBottom,
13
+ getRectangleBottomRight,
14
+ getRectangleLeft,
15
+ getRectangleMaxX,
16
+ getRectangleMaxY,
17
+ getRectangleMinX,
18
+ getRectangleMinY,
19
+ getRectangleNormalizedBottomRight,
20
+ getRectangleNormalizedTopLeft,
21
+ getRectangleRight,
22
+ getRectangleSize,
23
+ getRectangleTop,
24
+ getRectangleTopLeft,
25
+ inflateRectangle,
26
+ intersectsRectangle,
27
+ isEmptyRectangle,
28
+ isFlippedXRectangle,
29
+ isFlippedYRectangle,
30
+ mergeRectangle,
31
+ normalizeRectangle,
32
+ offsetRectangle,
33
+ offsetRectangleByPoint,
34
+ setEmptyRectangle,
35
+ setRectangle,
36
+ setRectangleBottom,
37
+ setRectangleBottomRight,
38
+ setRectangleLeft,
39
+ setRectangleRight,
40
+ setRectangleSize,
41
+ setRectangleTop,
42
+ setRectangleTopLeft,
43
+ } from '@flighthq/geometry';
44
+ import type { Rectangle } from '@flighthq/types';
45
+
46
+ let r: Rectangle;
47
+ let r2: Rectangle;
48
+
49
+ beforeEach(() => {
50
+ r = createRectangle(0, 0, 10, 20);
51
+ r2 = createRectangle(2, 5, 3, 4);
52
+ });
53
+
54
+ describe('cloneRectangle', () => {
55
+ it('clone creates a copy', () => {
56
+ const c = cloneRectangle(r);
57
+ expect(equalsRectangle(r, c)).toBe(true);
58
+ });
59
+
60
+ it('clone does not affect original rectangle', () => {
61
+ const c = cloneRectangle(r);
62
+ c.x = 100;
63
+ expect(r.x).not.toBe(c.x); // Original should remain unchanged
64
+ });
65
+
66
+ it('allows a rectangle-like object', () => {
67
+ const r = { x: 1, y: 1, width: 100, height: 100 };
68
+ const c = cloneRectangle(r);
69
+ c.x = 100;
70
+ expect(r.x).not.toBe(c.x);
71
+ });
72
+
73
+ it('returns a Rectangle instance', () => {
74
+ const r = { x: 1, y: 1, width: 100, height: 100 };
75
+ const c: Rectangle = cloneRectangle(r);
76
+ expect(c).not.toBeNull();
77
+ });
78
+ });
79
+
80
+ describe('computeRectangleIntersection', () => {
81
+ it('returns intersection rectangle', () => {
82
+ const r3 = createRectangle(5, 10, 10, 10);
83
+ const result = createRectangle();
84
+ computeRectangleIntersection(result, r, r3);
85
+ expect(result.x).toBe(5);
86
+ expect(result.y).toBe(10);
87
+ expect(result.width).toBe(5);
88
+ expect(result.height).toBe(10);
89
+ });
90
+
91
+ it('returns empty rectangle if no intersection', () => {
92
+ const r3 = createRectangle(20, 20, 5, 5);
93
+ const result = createRectangle();
94
+ computeRectangleIntersection(result, r, r3);
95
+ expect(isEmptyRectangle(result)).toBe(true);
96
+ });
97
+
98
+ it('correctly modifies the original rectangle when intersection is empty', () => {
99
+ const r1 = createRectangle(0, 0, 10, 10);
100
+ const r2 = createRectangle(20, 20, 5, 5);
101
+ const result = r1;
102
+ computeRectangleIntersection(r1, r1, r2); // r1 is both source and target
103
+
104
+ // Ensure the result is empty (since no intersection)
105
+ expect(result.width).toBe(0);
106
+ expect(result.height).toBe(0);
107
+
108
+ // Ensure r1 is also modified correctly (should be set to empty)
109
+ expect(r1.width).toBe(0); // r1's width should be 0
110
+ expect(r1.height).toBe(0); // r1's height should be 0
111
+ });
112
+
113
+ it('correctly modifies the target when intersection occurs', () => {
114
+ const r1 = createRectangle(0, 0, 10, 10);
115
+ const r2 = createRectangle(5, 5, 10, 10);
116
+ const result = r1;
117
+ computeRectangleIntersection(r1, r1, r2); // r1 is both source and target
118
+
119
+ // Ensure r1 is correctly modified
120
+ expect(result.width).toBe(5); // Correct intersection width
121
+ expect(result.height).toBe(5); // Correct intersection height
122
+ expect(r1.width).toBe(5); // Ensure r1 got updated
123
+ expect(r1.height).toBe(5); // Ensure r1 got updated
124
+ });
125
+
126
+ it('supports out === b', () => {
127
+ const r1 = createRectangle(0, 0, 10, 10);
128
+ const r2 = createRectangle(5, 5, 10, 10);
129
+ computeRectangleIntersection(r2, r1, r2);
130
+ expect(r2.x).toBe(5);
131
+ expect(r2.y).toBe(5);
132
+ expect(r2.width).toBe(5);
133
+ expect(r2.height).toBe(5);
134
+ });
135
+
136
+ it('allows rectangle-like objects', () => {
137
+ const r3 = { x: 5, y: 10, width: 10, height: 10 };
138
+ const result = { x: 0, y: 0, width: 0, height: 0 };
139
+ computeRectangleIntersection(result, r, r3);
140
+ expect(result.x).toBe(5);
141
+ expect(result.y).toBe(10);
142
+ expect(result.width).toBe(5);
143
+ expect(result.height).toBe(10);
144
+ });
145
+ });
146
+
147
+ describe('containsRectanglePoint', () => {
148
+ it('delegates to contains', () => {
149
+ expect(containsRectanglePoint(r, createVector2(5, 10))).toBe(true);
150
+ });
151
+
152
+ it('allows point-like and rectangle-like objects', () => {
153
+ const r = { x: 0, y: 0, width: 100, height: 100 };
154
+ const p = { x: 5, y: 10 };
155
+ expect(containsRectanglePoint(r, p)).toBe(true);
156
+ });
157
+ });
158
+
159
+ describe('containsRectanglePointXY', () => {
160
+ it('returns true for a point inside', () => {
161
+ expect(containsRectanglePointXY(r, 5, 10)).toBe(true);
162
+ });
163
+
164
+ it('returns false for a point outside', () => {
165
+ expect(containsRectanglePointXY(r, -1, 0)).toBe(false);
166
+ });
167
+
168
+ it('works with flipped rectangle', () => {
169
+ r.width = -10;
170
+ r.height = -20;
171
+ expect(containsRectanglePointXY(r, -5, -10)).toBe(true);
172
+ });
173
+
174
+ it('allows a rectangle-like object', () => {
175
+ const r = { x: 0, y: 0, width: -10, height: -20 };
176
+ expect(containsRectanglePointXY(r, -5, -10)).toBe(true);
177
+ });
178
+ });
179
+
180
+ describe('copyRectangle', () => {
181
+ it('copies values', () => {
182
+ copyRectangle(r, r2);
183
+ expect(equalsRectangle(r, r2)).toBe(true);
184
+ });
185
+
186
+ it('does not change the original values if source and target are the same', () => {
187
+ const r1 = createRectangle(0, 0, 10, 10);
188
+ copyRectangle(r1, r1); // r1 is both source and target
189
+
190
+ // Ensure no changes occur
191
+ expect(r1.x).toBe(0);
192
+ expect(r1.y).toBe(0);
193
+ expect(r1.width).toBe(10);
194
+ expect(r1.height).toBe(10);
195
+ });
196
+
197
+ it('allows rectangle-like objects', () => {
198
+ const r = { x: 0, y: 0, width: 100, height: 100 };
199
+ const r2 = { x: 2, y: 2, width: 2, height: 2 };
200
+ copyRectangle(r2, r);
201
+ expect(equalsRectangle(r, r2)).toBe(true);
202
+ });
203
+ });
204
+
205
+ describe('createRectangle', () => {
206
+ it('initializes with default values', () => {
207
+ const rect = createRectangle();
208
+ expect(rect.x).toBe(0);
209
+ expect(rect.y).toBe(0);
210
+ expect(rect.width).toBe(0);
211
+ expect(rect.height).toBe(0);
212
+ });
213
+
214
+ it('initializes with specified values', () => {
215
+ const rect = createRectangle(1, 2, 3, 4);
216
+ expect(rect.x).toBe(1);
217
+ expect(rect.y).toBe(2);
218
+ expect(rect.width).toBe(3);
219
+ expect(rect.height).toBe(4);
220
+ });
221
+ });
222
+
223
+ describe('enclosesRectangle', () => {
224
+ it('returns true if rectangle is fully inside', () => {
225
+ // r: 0,0,10,10
226
+ // r2: 2,2,5,5 fully inside r
227
+ r2.x = 2;
228
+ r2.y = 2;
229
+ r2.width = 5;
230
+ r2.height = 5;
231
+ expect(enclosesRectangle(r, r2)).toBe(true);
232
+ });
233
+
234
+ it('returns false if rectangle partially outside', () => {
235
+ r2.x = -1; // partially outside on the left
236
+ r2.y = 2;
237
+ r2.width = 5;
238
+ r2.height = 5;
239
+ expect(enclosesRectangle(r, r2)).toBe(false);
240
+ });
241
+
242
+ it('works with flipped rectangle fully inside', () => {
243
+ // Flipped rectangle: width and height negative
244
+ r2.x = 5;
245
+ r2.y = 6;
246
+ r2.width = -3; // left edge at 2
247
+ r2.height = -4; // top edge at 2
248
+ expect(enclosesRectangle(r, r2)).toBe(true);
249
+ });
250
+
251
+ it('returns false if flipped rectangle exceeds bounds', () => {
252
+ // Flipped rectangle exceeding bounds on left/top
253
+ r2.x = 15;
254
+ r2.y = 15;
255
+ r2.width = -20; // left edge at -5
256
+ r2.height = -20; // top edge at -5
257
+ expect(enclosesRectangle(r, r2)).toBe(false);
258
+ });
259
+
260
+ it('works if flipped rectangle exactly fits inside', () => {
261
+ // Flipped rectangle with exact bounds
262
+ r2.x = 10;
263
+ r2.y = 10;
264
+ r2.width = -10; // left edge 0
265
+ r2.height = -10; // top edge 0
266
+ expect(enclosesRectangle(r, r2)).toBe(true);
267
+ });
268
+
269
+ it('returns false if zero-size rectangle outside', () => {
270
+ r2.x = 20;
271
+ r2.y = 20;
272
+ r2.width = 0;
273
+ r2.height = 0;
274
+ expect(enclosesRectangle(r, r2)).toBe(false);
275
+ });
276
+
277
+ it('returns true if zero-size rectangle exactly on a corner', () => {
278
+ r2.x = 0;
279
+ r2.y = 0;
280
+ r2.width = 0;
281
+ r2.height = 0;
282
+ expect(enclosesRectangle(r, r2)).toBe(true);
283
+ });
284
+
285
+ it('allows a rectangle-like object', () => {
286
+ // r: 0,0,10,10
287
+ // r2: 2,2,5,5 fully inside r
288
+ const r2 = { x: 2, y: 2, width: 5, height: 5 };
289
+ expect(enclosesRectangle(r, r2)).toBe(true);
290
+ });
291
+ });
292
+
293
+ describe('equalsRectangle', () => {
294
+ it('returns true for identical rectangles', () => {
295
+ expect(equalsRectangle(r, r)).toBe(true);
296
+ expect(equalsRectangle(r, cloneRectangle(r))).toBe(true);
297
+ });
298
+
299
+ it('returns false for different rectangles', () => {
300
+ r2.x = 1;
301
+ expect(equalsRectangle(r, r2)).toBe(false);
302
+ });
303
+
304
+ it('allows rectangle like objects', () => {
305
+ const r = { x: 1, y: 1, width: 1, height: 1 };
306
+ const r2 = { x: 1, y: 1, width: 1, height: 1 };
307
+ expect(equalsRectangle(r, r)).toBe(true);
308
+ expect(equalsRectangle(r, r2)).toBe(true);
309
+ });
310
+ });
311
+
312
+ describe('expandRectangleToPoint', () => {
313
+ it('inflates rectangle by Vector2', () => {
314
+ const result = createRectangle();
315
+ expandRectangleToPoint(result, r, createVector2(1, 2));
316
+ expect(result).not.toBe(r);
317
+ expect(result.x).toBe(-1);
318
+ expect(result.y).toBe(-2);
319
+ expect(result.width).toBe(12);
320
+ expect(result.height).toBe(24);
321
+ });
322
+
323
+ it('allows rectangle- and point-like objects', () => {
324
+ const r = { x: 0, y: 0, width: 10, height: 20 };
325
+ const p = { x: 1, y: 2 };
326
+ const result = { x: 0, y: 0, width: 0, height: 0 };
327
+ expandRectangleToPoint(result, r, p);
328
+ expect(result.x).toBe(-1);
329
+ expect(result.y).toBe(-2);
330
+ expect(result.width).toBe(12);
331
+ expect(result.height).toBe(24);
332
+ });
333
+ });
334
+
335
+ describe('getRectangleBottom', () => {
336
+ it('returns y + height', () => {
337
+ expect(getRectangleBottom(r)).toBe(20);
338
+ });
339
+
340
+ it('allows a rectangle-like object', () => {
341
+ const r = { x: 0, y: 0, width: 10, height: 20 };
342
+ expect(getRectangleBottom(r)).toBe(20);
343
+ });
344
+ });
345
+
346
+ describe('getRectangleBottomRight', () => {
347
+ it('returns correct Vector2', () => {
348
+ const br = createVector2();
349
+ getRectangleBottomRight(br, r);
350
+ expect(br.x).toBe(10);
351
+ expect(br.y).toBe(20);
352
+ });
353
+ });
354
+
355
+ describe('getRectangleLeft', () => {
356
+ it('returns x', () => {
357
+ expect(getRectangleLeft(r)).toBe(0);
358
+ });
359
+ });
360
+
361
+ describe('getRectangleMaxX', () => {
362
+ it('returns correct maximum X for positive rectangle', () => {
363
+ expect(getRectangleMaxX(r)).toBe(10);
364
+ });
365
+
366
+ it('returns correct maximum X for flipped rectangle', () => {
367
+ r.width = -5;
368
+ expect(getRectangleMaxX(r)).toBe(0);
369
+ });
370
+
371
+ it('allows a rectangle-like object', () => {
372
+ const r = { x: 0, y: 0, width: 10, height: 10 };
373
+ expect(getRectangleMaxX(r)).toBe(10);
374
+ });
375
+ });
376
+
377
+ describe('getRectangleMaxY', () => {
378
+ it('returns correct maximum Y for positive rectangle', () => {
379
+ expect(getRectangleMaxY(r)).toBe(20);
380
+ });
381
+
382
+ it('returns correct maximum Y for flipped rectangle', () => {
383
+ r.height = -10;
384
+ expect(getRectangleMaxY(r)).toBe(0);
385
+ });
386
+
387
+ it('allows a rectangle-like object', () => {
388
+ const r = { x: 0, y: 0, width: 10, height: 10 };
389
+ expect(getRectangleMaxY(r)).toBe(10);
390
+ });
391
+ });
392
+
393
+ describe('getRectangleMinX', () => {
394
+ it('returns correct minimum X for positive rectangle', () => {
395
+ expect(getRectangleMinX(r)).toBe(0);
396
+ });
397
+
398
+ it('returns correct minimum X for flipped rectangle', () => {
399
+ r.width = -5;
400
+ expect(getRectangleMinX(r)).toBe(-5);
401
+ });
402
+
403
+ it('allows a rectangle-like object', () => {
404
+ const r = { x: 0, y: 0, width: 10, height: 10 };
405
+ expect(getRectangleMinX(r)).toBe(0);
406
+ });
407
+ });
408
+
409
+ describe('getRectangleMinY', () => {
410
+ it('returns correct minimum Y for positive rectangle', () => {
411
+ expect(getRectangleMinY(r)).toBe(0);
412
+ });
413
+
414
+ it('returns correct minimum Y for flipped rectangle', () => {
415
+ r.height = -10;
416
+ expect(getRectangleMinY(r)).toBe(-10);
417
+ });
418
+
419
+ it('allows a rectangle-like object', () => {
420
+ const r = { x: 0, y: 0, width: 10, height: 10 };
421
+ expect(getRectangleMinY(r)).toBe(0);
422
+ });
423
+ });
424
+
425
+ describe('getRectangleNormalizedBottomRight', () => {
426
+ it('return proper Vector2s', () => {
427
+ r.x = 10;
428
+ r.y = 20;
429
+ r.width = -5;
430
+ r.height = -15;
431
+ const nbr = createVector2();
432
+ getRectangleNormalizedBottomRight(nbr, r);
433
+ expect(nbr.x).toBe(10);
434
+ expect(nbr.y).toBe(20);
435
+ });
436
+
437
+ it('match min/max for positive rectangles', () => {
438
+ r.x = 2;
439
+ r.y = 3;
440
+ r.width = 10;
441
+ r.height = 15;
442
+ const nbr = createVector2();
443
+ getRectangleNormalizedBottomRight(nbr, r);
444
+ expect(nbr.x).toBe(getRectangleMaxX(r));
445
+ expect(nbr.y).toBe(getRectangleMaxY(r));
446
+ });
447
+
448
+ it('allows Rectangle-like objects', () => {
449
+ const r = { x: 10, y: 20, width: -5, height: -15 };
450
+ const nbr = { x: 0, y: 0 };
451
+ getRectangleNormalizedBottomRight(nbr, r);
452
+ expect(nbr.x).toBe(10);
453
+ expect(nbr.y).toBe(20);
454
+ });
455
+ });
456
+
457
+ describe('getRectangleNormalizedTopLeft', () => {
458
+ it('return proper Vector2s', () => {
459
+ r.x = 10;
460
+ r.y = 20;
461
+ r.width = -5;
462
+ r.height = -15;
463
+ const ntl = createVector2();
464
+ getRectangleNormalizedTopLeft(ntl, r);
465
+ expect(ntl.x).toBe(5);
466
+ expect(ntl.y).toBe(5);
467
+ });
468
+
469
+ it('match min/max for positive rectangles', () => {
470
+ r.x = 2;
471
+ r.y = 3;
472
+ r.width = 10;
473
+ r.height = 15;
474
+ const ntl = createVector2();
475
+ getRectangleNormalizedTopLeft(ntl, r);
476
+ expect(ntl.x).toBe(getRectangleMinX(r));
477
+ expect(ntl.y).toBe(getRectangleMinY(r));
478
+ });
479
+
480
+ it('allows rectangle-like objects', () => {
481
+ const r = { x: 10, y: 20, width: -5, height: -15 };
482
+ let ntl = { x: 0, y: 0 };
483
+ getRectangleNormalizedTopLeft(ntl, r);
484
+ expect(ntl.x).toBe(5);
485
+ expect(ntl.y).toBe(5);
486
+ });
487
+ });
488
+
489
+ describe('getRectangleRight', () => {
490
+ it('getter returns x + width', () => {
491
+ expect(getRectangleRight(r)).toBe(10);
492
+ });
493
+
494
+ it('allows a rectangle-like object', () => {
495
+ const r = { x: 5, y: 0, width: 5, height: 0 };
496
+ expect(getRectangleRight(r)).toBe(10);
497
+ });
498
+ });
499
+
500
+ describe('getRectangleSize', () => {
501
+ it('returns width and height as Vector2', () => {
502
+ const s = createVector2();
503
+ getRectangleSize(s, r);
504
+ expect(s.x).toBe(10);
505
+ expect(s.y).toBe(20);
506
+ });
507
+ });
508
+
509
+ describe('getRectangleTop', () => {
510
+ it('returns y', () => {
511
+ expect(getRectangleTop(r)).toBe(0);
512
+ });
513
+ });
514
+
515
+ describe('getRectangleTopLeft', () => {
516
+ it('getter returns top-left Vector2', () => {
517
+ const tl = createVector2();
518
+ getRectangleTopLeft(tl, r);
519
+ expect(tl.x).toBe(0);
520
+ expect(tl.y).toBe(0);
521
+ });
522
+
523
+ it('allows point-like objects', () => {
524
+ const r = { x: 0, y: 0, height: 0, width: 0 };
525
+ let tl = { x: 0, y: 0 };
526
+ getRectangleTopLeft(tl, r);
527
+ expect(tl.x).toBe(0);
528
+ expect(tl.y).toBe(0);
529
+ });
530
+ });
531
+
532
+ describe('inflateRectangle', () => {
533
+ it('inflates rectangle by dx/dy', () => {
534
+ const result = createRectangle();
535
+ inflateRectangle(result, r, 2, 3);
536
+ expect(result).not.toBe(r);
537
+ expect(result.x).toBe(-2);
538
+ expect(result.y).toBe(-3);
539
+ expect(result.width).toBe(14);
540
+ expect(result.height).toBe(26);
541
+ });
542
+
543
+ it('correctly inflates the rectangle when it is both source and target', () => {
544
+ const r1 = createRectangle(0, 0, 10, 10);
545
+ inflateRectangle(r1, r1, 2, 3); // r1 is both source and target
546
+
547
+ // Ensure r1 is correctly inflated
548
+ expect(r1.x).toBe(-2);
549
+ expect(r1.y).toBe(-3);
550
+ expect(r1.width).toBe(14);
551
+ expect(r1.height).toBe(16);
552
+ });
553
+
554
+ it('allows rectangle-like objects', () => {
555
+ const r = { x: 0, y: 0, width: 10, height: 20 };
556
+ const result = { x: 0, y: 0, width: 0, height: 0 };
557
+ inflateRectangle(result, r, 2, 3);
558
+ expect(result).not.toBe(r);
559
+ expect(result.x).toBe(-2);
560
+ expect(result.y).toBe(-3);
561
+ expect(result.width).toBe(14);
562
+ expect(result.height).toBe(26);
563
+ });
564
+ });
565
+
566
+ describe('intersectsRectangle', () => {
567
+ it('returns true if rectangles overlap', () => {
568
+ const r3 = createRectangle(5, 10, 10, 10);
569
+ expect(intersectsRectangle(r, r3)).toBe(true);
570
+ });
571
+
572
+ it('returns false if rectangles do not overlap', () => {
573
+ const r3 = createRectangle(20, 20, 5, 5);
574
+ expect(intersectsRectangle(r, r3)).toBe(false);
575
+ });
576
+
577
+ it('allows rectangle-like objects', () => {
578
+ const r3 = { x: 5, y: 10, width: 10, height: 10 };
579
+ expect(intersectsRectangle(r, r3)).toBe(true);
580
+ });
581
+ });
582
+
583
+ describe('isEmptyRectangle', () => {
584
+ it('returns true only for zero width or height', () => {
585
+ expect(isEmptyRectangle(r)).toBe(false);
586
+ r.width = 0;
587
+ expect(isEmptyRectangle(r)).toBe(true);
588
+ r.width = 10;
589
+ r.height = 0;
590
+ expect(isEmptyRectangle(r)).toBe(true);
591
+ r.width = -5;
592
+ r.height = -5;
593
+ expect(isEmptyRectangle(r)).toBe(false); // flipped rectangles are valid
594
+ });
595
+
596
+ it('allows rectangle-like objects', () => {
597
+ const r = { x: 0, y: 0, width: 10, height: 10 };
598
+ expect(isEmptyRectangle(r)).toBe(false);
599
+ r.width = 0;
600
+ expect(isEmptyRectangle(r)).toBe(true);
601
+ });
602
+ });
603
+
604
+ describe('isFlippedXRectangle', () => {
605
+ it('returns false if width is positive', () => {
606
+ expect(isFlippedXRectangle(r)).toBe(false);
607
+ });
608
+
609
+ it('returns false if width is negative', () => {
610
+ r.width = -10;
611
+ expect(isFlippedXRectangle(r)).toBe(true);
612
+ });
613
+
614
+ it('allows a rectangle-like object', () => {
615
+ const r = { x: 0, y: 0, width: 100, height: 100 };
616
+ expect(isFlippedXRectangle(r)).toBe(false);
617
+ });
618
+ });
619
+
620
+ describe('isFlippedYRectangle', () => {
621
+ it('returns false if height is positive', () => {
622
+ expect(isFlippedYRectangle(r)).toBe(false);
623
+ });
624
+
625
+ it('returns false if height is negative', () => {
626
+ r.height = -20;
627
+ expect(isFlippedYRectangle(r)).toBe(true);
628
+ });
629
+
630
+ it('allows a rectangle-like object', () => {
631
+ const r = { x: 0, y: 0, width: 100, height: 100 };
632
+ expect(isFlippedYRectangle(r)).toBe(false);
633
+ });
634
+ });
635
+
636
+ describe('mergeRectangle', () => {
637
+ it('returns union of two rectangles', () => {
638
+ const r3 = createRectangle(5, 15, 10, 10);
639
+ const u = createRectangle();
640
+ mergeRectangle(u, r, r3);
641
+ expect(u.x).toBe(0);
642
+ expect(u.y).toBe(0);
643
+ expect(u.width).toBe(15);
644
+ expect(u.height).toBe(25);
645
+ });
646
+
647
+ it('union works with zero-width rectangle', () => {
648
+ const r3 = createRectangle(5, 15, 0, 0);
649
+ const u = createRectangle();
650
+ mergeRectangle(u, r, r3);
651
+ expect(equalsRectangle(u, r)).toBe(true);
652
+ });
653
+
654
+ it('has the values of the right-hand rectangle if the left-hand is degenerate', () => {
655
+ const lh = createRectangle();
656
+ const rh = createRectangle(5, 15, 100, 100);
657
+ const out = createRectangle();
658
+ mergeRectangle(out, lh, rh);
659
+ expect(equalsRectangle(out, rh)).toBe(true);
660
+ });
661
+
662
+ it('has the values of the left-hand rectangle if the right-hand is degenerate', () => {
663
+ const lh = createRectangle(5, 15, 100, 100);
664
+ const rh = createRectangle();
665
+ const out = createRectangle();
666
+ mergeRectangle(out, lh, rh);
667
+ expect(equalsRectangle(out, lh)).toBe(true);
668
+ });
669
+
670
+ it('returns an empty rectangle if both are degenerate', () => {
671
+ const lh = createRectangle(-50, -150, 0, 0);
672
+ const rh = createRectangle(-5, -15, 0, 0);
673
+ const out = createRectangle();
674
+ mergeRectangle(out, lh, rh);
675
+ expect(isEmptyRectangle(out)).toBe(true);
676
+ });
677
+
678
+ it('returns the x/y of the left-hand rectangle if both are degenerate', () => {
679
+ const lh = createRectangle(-50, -150, 0, 0);
680
+ const rh = createRectangle(-5, -15, 0, 0);
681
+ const out = createRectangle();
682
+ mergeRectangle(out, lh, rh);
683
+ expect(equalsRectangle(out, lh)).toBe(true);
684
+ });
685
+
686
+ it('does nothing if right-hand is empty and source===out', () => {
687
+ const lh = createRectangle(-50, -150, 0, 0);
688
+ const rh = createRectangle(-5, -15, 0, 0);
689
+ const cache = createRectangle();
690
+ copyRectangle(cache, lh);
691
+ mergeRectangle(lh, lh, rh);
692
+ expect(equalsRectangle(cache, lh)).toBe(true);
693
+ });
694
+
695
+ it('union works with flipped rectangles', () => {
696
+ const r3 = createRectangle(15, 20, -10, -10);
697
+ const u = createRectangle();
698
+ mergeRectangle(u, r, r3);
699
+ expect(u.x).toBe(0);
700
+ expect(u.y).toBe(0);
701
+ expect(u.width).toBe(15);
702
+ expect(u.height).toBe(20);
703
+ });
704
+
705
+ it('correctly modifies the target when union occurs', () => {
706
+ const r1 = createRectangle(0, 0, 10, 10);
707
+ const r2 = createRectangle(5, 5, 10, 10);
708
+ const result = r1;
709
+ mergeRectangle(r1, r1, r2); // r1 is both source and target
710
+
711
+ // Ensure r1 is correctly updated
712
+ expect(result.width).toBe(15); // Correct union width
713
+ expect(result.height).toBe(15); // Correct union height
714
+ expect(r1.width).toBe(15); // r1 should be updated
715
+ expect(r1.height).toBe(15); // r1 should be updated
716
+ });
717
+
718
+ it('correctly handles union of non-overlapping rectangles', () => {
719
+ const r1 = createRectangle(0, 0, 10, 10);
720
+ const r2 = createRectangle(20, 20, 5, 5);
721
+ const result = r1;
722
+ mergeRectangle(r1, r1, r2); // r1 is both source and target
723
+
724
+ // Ensure r1 is correctly updated
725
+ expect(result.width).toBe(25); // Correct union width
726
+ expect(result.height).toBe(25); // Correct union height
727
+ expect(r1.width).toBe(25); // r1 should be updated
728
+ expect(r1.height).toBe(25); // r1 should be updated
729
+ });
730
+
731
+ it('supports out === other', () => {
732
+ const r1 = createRectangle(0, 0, 10, 10);
733
+ const r2 = createRectangle(5, 5, 10, 10);
734
+ mergeRectangle(r2, r1, r2);
735
+ expect(r2.x).toBe(0);
736
+ expect(r2.y).toBe(0);
737
+ expect(r2.width).toBe(15);
738
+ expect(r2.height).toBe(15);
739
+ });
740
+
741
+ it('allows rectangle-like objects', () => {
742
+ const r = { x: 0, y: 0, width: 10, height: 20 };
743
+ const r3 = { x: 5, y: 15, width: 10, height: 10 };
744
+ const u = { x: 0, y: 0, width: 0, height: 0 };
745
+ mergeRectangle(u, r, r3);
746
+ expect(u.x).toBe(0);
747
+ expect(u.y).toBe(0);
748
+ expect(u.width).toBe(15);
749
+ expect(u.height).toBe(25);
750
+ });
751
+ });
752
+
753
+ describe('normalizeRectangle', () => {
754
+ it('returns rectangle with positive width and height', () => {
755
+ r.x = 10;
756
+ r.y = 20;
757
+ r.width = -5;
758
+ r.height = -15;
759
+ const n = createRectangle();
760
+ normalizeRectangle(n, r);
761
+ expect(n.x).toBe(5);
762
+ expect(n.y).toBe(5);
763
+ expect(n.width).toBe(5);
764
+ expect(n.height).toBe(15);
765
+ });
766
+
767
+ it('supports out === source', () => {
768
+ const rect = createRectangle(10, 20, -5, -15);
769
+ normalizeRectangle(rect, rect);
770
+ expect(rect.x).toBe(5);
771
+ expect(rect.y).toBe(5);
772
+ expect(rect.width).toBe(5);
773
+ expect(rect.height).toBe(15);
774
+ });
775
+
776
+ it('normalized of positive rectangle equals original', () => {
777
+ const n = createRectangle();
778
+ normalizeRectangle(n, r);
779
+ expect(n.x).toBe(r.x);
780
+ expect(n.y).toBe(r.y);
781
+ expect(n.width).toBe(r.width);
782
+ expect(n.height).toBe(r.height);
783
+ });
784
+
785
+ it('allows rectangle-like objects', () => {
786
+ const r = { x: 10, y: 20, width: -5, height: -15 };
787
+ const n = { x: 0, y: 0, width: 0, height: 0 };
788
+ normalizeRectangle(n, r);
789
+ expect(n.x).toBe(5);
790
+ expect(n.y).toBe(5);
791
+ expect(n.width).toBe(5);
792
+ expect(n.height).toBe(15);
793
+ });
794
+ });
795
+
796
+ describe('offsetRectangle', () => {
797
+ it('moves rectangle', () => {
798
+ const result = createRectangle();
799
+ offsetRectangle(result, r, 5, 10);
800
+ expect(result.x).toBe(5);
801
+ expect(result.y).toBe(10);
802
+ });
803
+
804
+ it('correctly offsets the rectangle when it is both source and target', () => {
805
+ const r1 = createRectangle(0, 0, 10, 10);
806
+ offsetRectangle(r1, r1, 5, 10); // r1 is both source and target
807
+
808
+ // Ensure r1 is correctly offset
809
+ expect(r1.x).toBe(5);
810
+ expect(r1.y).toBe(10);
811
+ });
812
+
813
+ it('allows rectangle-like objects', () => {
814
+ const r = { x: 0, y: 0, width: 100, height: 100 };
815
+ const result = { x: 0, y: 0, width: 0, height: 0 };
816
+ offsetRectangle(result, r, 5, 10);
817
+ expect(result.x).toBe(5);
818
+ expect(result.y).toBe(10);
819
+ });
820
+ });
821
+
822
+ describe('offsetRectangleByPoint', () => {
823
+ it('moves rectangle by Vector2', () => {
824
+ const out = createRectangle();
825
+ offsetRectangleByPoint(out, r, createVector2(3, 4));
826
+ expect(out.x).toBe(3);
827
+ expect(out.y).toBe(4);
828
+ });
829
+
830
+ it('allows rectangle- and point-like objects', () => {
831
+ const r = { x: 0, y: 0, width: 100, height: 100 };
832
+ const p = { x: 3, y: 4 };
833
+ const out = { x: 0, y: 0, width: 0, height: 0 };
834
+ offsetRectangleByPoint(out, r, p);
835
+ expect(out.x).toBe(3);
836
+ expect(out.y).toBe(4);
837
+ });
838
+ });
839
+
840
+ describe('setEmptyRectangle', () => {
841
+ it('sets rectangle to zero', () => {
842
+ setEmptyRectangle(r);
843
+ expect(r.x).toBe(0);
844
+ expect(r.y).toBe(0);
845
+ expect(r.width).toBe(0);
846
+ expect(r.height).toBe(0);
847
+ });
848
+
849
+ it('allows rectangle-like objects', () => {
850
+ const r = { x: 10, y: 20, width: -5, height: -15 };
851
+ setEmptyRectangle(r);
852
+ expect(r.x).toBe(0);
853
+ expect(r.y).toBe(0);
854
+ expect(r.width).toBe(0);
855
+ expect(r.height).toBe(0);
856
+ });
857
+ });
858
+
859
+ describe('setRectangle', () => {
860
+ it('sets rectangle to specified values', () => {
861
+ setRectangle(r, 1, 2, 3, 4);
862
+ expect(r.x).toBe(1);
863
+ expect(r.y).toBe(2);
864
+ expect(r.width).toBe(3);
865
+ expect(r.height).toBe(4);
866
+ });
867
+
868
+ it('allows rectangle-like objects', () => {
869
+ const r = { x: 10, y: 20, width: -5, height: -15 };
870
+ setRectangle(r, 1, 2, 3, 4);
871
+ expect(r.x).toBe(1);
872
+ expect(r.y).toBe(2);
873
+ expect(r.width).toBe(3);
874
+ expect(r.height).toBe(4);
875
+ });
876
+ });
877
+
878
+ describe('setRectangleBottom', () => {
879
+ it('adjusts height so that y + height equals value', () => {
880
+ const rect = createRectangle(0, 10, 5, 5);
881
+ setRectangleBottom(rect, 30);
882
+ expect(rect.height).toBe(20);
883
+ });
884
+
885
+ it('can set bottom to equal y (zero height)', () => {
886
+ const rect = createRectangle(0, 5, 10, 10);
887
+ setRectangleBottom(rect, 5);
888
+ expect(rect.height).toBe(0);
889
+ });
890
+ });
891
+
892
+ describe('setRectangleBottomRight', () => {
893
+ it('adjusts width and height', () => {
894
+ setRectangleBottomRight(r, createVector2(15, 25));
895
+ expect(r.width).toBe(15);
896
+ expect(r.height).toBe(25);
897
+ });
898
+ });
899
+
900
+ describe('setRectangleLeft', () => {
901
+ it('adjusts width correctly', () => {
902
+ setRectangleLeft(r, 5);
903
+ expect(r.x).toBe(5);
904
+ expect(r.width).toBe(5);
905
+ });
906
+ });
907
+
908
+ describe('setRectangleRight', () => {
909
+ it('adjusts width', () => {
910
+ setRectangleRight(r, 15);
911
+ expect(r.width).toBe(15);
912
+ });
913
+
914
+ it('accounts for x', () => {
915
+ r.x = 10;
916
+ setRectangleRight(r, 15);
917
+ expect(r.width).toBe(5);
918
+ });
919
+
920
+ it('can create negative width (flipped rectangle)', () => {
921
+ setRectangleRight(r, -5);
922
+ expect(r.width).toBe(-5);
923
+ });
924
+ });
925
+
926
+ describe('setRectangleSize', () => {
927
+ it('setter adjusts width and height', () => {
928
+ setRectangleSize(r, createVector2(5, 6));
929
+ expect(r.width).toBe(5);
930
+ expect(r.height).toBe(6);
931
+ });
932
+ });
933
+
934
+ describe('setRectangleTop', () => {
935
+ it('adjusts height correctly', () => {
936
+ setRectangleTop(r, 5);
937
+ expect(r.y).toBe(5);
938
+ expect(r.height).toBe(15);
939
+ });
940
+
941
+ it('can create negative height (flipped rectangle)', () => {
942
+ setRectangleTop(r, 25);
943
+ expect(r.height).toBe(-5);
944
+ });
945
+ });
946
+
947
+ describe('setRectangleTopLeft', () => {
948
+ it('setter updates x and y', () => {
949
+ const out = createRectangle();
950
+ setRectangleTopLeft(out, createVector2(3, 4));
951
+ expect(out.x).toBe(3);
952
+ expect(out.y).toBe(4);
953
+ });
954
+
955
+ it('allows point-like objects', () => {
956
+ const r = { x: 0, y: 0, height: 0, width: 0 };
957
+ let tl = { x: 10, y: 10 };
958
+ setRectangleTopLeft(r, tl);
959
+ expect(r.x).toBe(10);
960
+ expect(r.y).toBe(10);
961
+ });
962
+ });
963
+
964
+ describe('setTo', () => {
965
+ it('assigns all four properties', () => {
966
+ const rect = createRectangle();
967
+ setRectangle(rect, 1, 2, 3, 4);
968
+ expect(rect.x).toBe(1);
969
+ expect(rect.y).toBe(2);
970
+ expect(rect.width).toBe(3);
971
+ expect(rect.height).toBe(4);
972
+ });
973
+
974
+ it('overwrites existing values', () => {
975
+ const rect = createRectangle(10, 20, 30, 40);
976
+ setRectangle(rect, 0, 0, 0, 0);
977
+ expect(rect.x).toBe(0);
978
+ expect(rect.y).toBe(0);
979
+ expect(rect.width).toBe(0);
980
+ expect(rect.height).toBe(0);
981
+ });
982
+ });