@flighthq/collision 0.4.0-next.1663.d9c5746 → 0.4.0-next.1694.598ef6f

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 (112) hide show
  1. package/dist/collideContactManifold2D.d.ts +3 -0
  2. package/dist/collideContactManifold2D.d.ts.map +1 -0
  3. package/dist/{collideContactManifold.js → collideContactManifold2D.js} +6 -6
  4. package/dist/collideContactManifold2D.js.map +1 -0
  5. package/dist/collisionShapeValidation.d.ts +3 -3
  6. package/dist/collisionShapeValidation.d.ts.map +1 -1
  7. package/dist/collisionShapeValidation.js +6 -3
  8. package/dist/collisionShapeValidation.js.map +1 -1
  9. package/dist/collisionSupport2D.d.ts +12 -0
  10. package/dist/collisionSupport2D.d.ts.map +1 -0
  11. package/dist/collisionSupport2D.js +111 -0
  12. package/dist/collisionSupport2D.js.map +1 -0
  13. package/dist/contactFeatureId.js +1 -1
  14. package/dist/contactFeatureId.js.map +1 -1
  15. package/dist/contactManifold.d.ts +3 -3
  16. package/dist/contactManifold.d.ts.map +1 -1
  17. package/dist/contactManifold.js +2 -2
  18. package/dist/contactManifold.js.map +1 -1
  19. package/dist/contract.d.ts +8 -5
  20. package/dist/contract.d.ts.map +1 -1
  21. package/dist/contract.js +8 -5
  22. package/dist/contract.js.map +1 -1
  23. package/dist/convexVertices.d.ts +3 -3
  24. package/dist/convexVertices.d.ts.map +1 -1
  25. package/dist/convexVertices.js.map +1 -1
  26. package/dist/enableCollisionGuards.js +30 -11
  27. package/dist/enableCollisionGuards.js.map +1 -1
  28. package/dist/explainCollisionTest2D.d.ts +3 -0
  29. package/dist/explainCollisionTest2D.d.ts.map +1 -0
  30. package/dist/explainCollisionTest2D.js +38 -0
  31. package/dist/explainCollisionTest2D.js.map +1 -0
  32. package/dist/gjk2D.d.ts +4 -0
  33. package/dist/gjk2D.d.ts.map +1 -0
  34. package/dist/gjk2D.js +340 -0
  35. package/dist/gjk2D.js.map +1 -0
  36. package/dist/index.d.ts +4 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/manifold.d.ts +3 -3
  41. package/dist/manifold.d.ts.map +1 -1
  42. package/dist/manifold.js +2 -2
  43. package/dist/manifold.js.map +1 -1
  44. package/dist/pointContainment.d.ts +2 -2
  45. package/dist/pointContainment.d.ts.map +1 -1
  46. package/dist/pointContainment.js +3 -3
  47. package/dist/pointContainment.js.map +1 -1
  48. package/dist/raycastCollisionShape2D.d.ts +4 -0
  49. package/dist/raycastCollisionShape2D.d.ts.map +1 -0
  50. package/dist/{raycastCollisionShape.js → raycastCollisionShape2D.js} +7 -7
  51. package/dist/raycastCollisionShape2D.js.map +1 -0
  52. package/dist/registerBuiltInCollisionPairTests2D.d.ts +2 -0
  53. package/dist/registerBuiltInCollisionPairTests2D.d.ts.map +1 -0
  54. package/dist/registerBuiltInCollisionPairTests2D.js +32 -0
  55. package/dist/registerBuiltInCollisionPairTests2D.js.map +1 -0
  56. package/dist/segmentCollision.d.ts +6 -6
  57. package/dist/segmentCollision.d.ts.map +1 -1
  58. package/dist/segmentCollision.js +2 -2
  59. package/dist/segmentCollision.js.map +1 -1
  60. package/dist/shapeCollision.d.ts +11 -11
  61. package/dist/shapeCollision.d.ts.map +1 -1
  62. package/dist/shapeCollision.js +62 -65
  63. package/dist/shapeCollision.js.map +1 -1
  64. package/dist/shapeContact.d.ts +11 -11
  65. package/dist/shapeContact.d.ts.map +1 -1
  66. package/dist/shapeContact.js +12 -12
  67. package/dist/shapeContact.js.map +1 -1
  68. package/dist/sweepCollisionShape2D.d.ts +4 -0
  69. package/dist/sweepCollisionShape2D.d.ts.map +1 -0
  70. package/dist/{sweepCollisionShape.js → sweepCollisionShape2D.js} +10 -10
  71. package/dist/sweepCollisionShape2D.js.map +1 -0
  72. package/dist/testCollision2D.d.ts +4 -0
  73. package/dist/testCollision2D.d.ts.map +1 -0
  74. package/dist/testCollision2D.js +43 -0
  75. package/dist/testCollision2D.js.map +1 -0
  76. package/package.json +4 -4
  77. package/src/{collideContactManifold.test.ts → collideContactManifold2D.test.ts} +54 -54
  78. package/src/collisionShapeValidation.test.ts +13 -13
  79. package/src/collisionSupport2D.test.ts +166 -0
  80. package/src/contactManifold.test.ts +11 -11
  81. package/src/convexVertices.test.ts +4 -4
  82. package/src/enableCollisionGuards.test.ts +50 -15
  83. package/src/explainCollisionTest2D.test.ts +75 -0
  84. package/src/gjk2D.test.ts +219 -0
  85. package/src/manifold.test.ts +7 -7
  86. package/src/pointContainment.test.ts +45 -41
  87. package/src/{raycastCollisionShape.test.ts → raycastCollisionShape2D.test.ts} +26 -19
  88. package/src/registerBuiltInCollisionPairTests2D.test.ts +62 -0
  89. package/src/segmentCollision.test.ts +10 -10
  90. package/src/shapeCollision.test.ts +103 -103
  91. package/src/shapeContact.test.ts +76 -76
  92. package/src/{sweepCollisionShape.test.ts → sweepCollisionShape2D.test.ts} +26 -26
  93. package/src/testCollision2D.test.ts +175 -0
  94. package/dist/collideContactManifold.d.ts +0 -3
  95. package/dist/collideContactManifold.d.ts.map +0 -1
  96. package/dist/collideContactManifold.js.map +0 -1
  97. package/dist/explainCollisionTest.d.ts +0 -3
  98. package/dist/explainCollisionTest.d.ts.map +0 -1
  99. package/dist/explainCollisionTest.js +0 -21
  100. package/dist/explainCollisionTest.js.map +0 -1
  101. package/dist/raycastCollisionShape.d.ts +0 -4
  102. package/dist/raycastCollisionShape.d.ts.map +0 -1
  103. package/dist/raycastCollisionShape.js.map +0 -1
  104. package/dist/sweepCollisionShape.d.ts +0 -4
  105. package/dist/sweepCollisionShape.d.ts.map +0 -1
  106. package/dist/sweepCollisionShape.js.map +0 -1
  107. package/dist/testCollision.d.ts +0 -4
  108. package/dist/testCollision.d.ts.map +0 -1
  109. package/dist/testCollision.js +0 -97
  110. package/dist/testCollision.js.map +0 -1
  111. package/src/explainCollisionTest.test.ts +0 -52
  112. package/src/testCollision.test.ts +0 -97
@@ -1,7 +1,7 @@
1
- import type { CollisionAabb, CollisionCircle, CollisionObb, CollisionPolygon } from '@flighthq/types/contract';
1
+ import type { CollisionAabb2D, CollisionCircle2D, CollisionObb2D, CollisionPolygon2D } from '@flighthq/types/contract';
2
2
  import { describe, expect, it } from 'vitest';
3
3
 
4
- import { createCollisionManifold } from './manifold';
4
+ import { createCollisionManifold2D } from './manifold';
5
5
  import {
6
6
  testAabbAabbCollision,
7
7
  testAabbObbCollision,
@@ -16,16 +16,16 @@ import {
16
16
  } from './shapeCollision';
17
17
 
18
18
  // A square collider as a flat convex polygon, corners CCW from the min corner.
19
- function square(minX: number, minY: number, size: number): CollisionPolygon {
19
+ function square(minX: number, minY: number, size: number): CollisionPolygon2D {
20
20
  return { points: [minX, minY, minX + size, minY, minX + size, minY + size, minX, minY + size] };
21
21
  }
22
22
 
23
23
  describe('testAabbAabbCollision', () => {
24
24
  it('reports the least-penetration axis: boxes overlapping more on X separate along Y', () => {
25
- const out = createCollisionManifold();
25
+ const out = createCollisionManifold2D();
26
26
  // A = 0..10 square; B spans x[2,8] (deep X overlap) but only y[8,10] of A (shallow Y overlap).
27
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
28
- const b: CollisionAabb = { minX: 2, minY: 8, maxX: 8, maxY: 20 };
27
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
28
+ const b: CollisionAabb2D = { minX: 2, minY: 8, maxX: 8, maxY: 20 };
29
29
  expect(testAabbAabbCollision(a, b, out)).toBe(true);
30
30
  expect(out.overlapping).toBe(true);
31
31
  expect(out.normalX).toBeCloseTo(0);
@@ -34,9 +34,9 @@ describe('testAabbAabbCollision', () => {
34
34
  });
35
35
 
36
36
  it('is separated when the boxes are disjoint', () => {
37
- const out = createCollisionManifold();
38
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 1, maxY: 1 };
39
- const b: CollisionAabb = { minX: 2, minY: 2, maxX: 3, maxY: 3 };
37
+ const out = createCollisionManifold2D();
38
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 1, maxY: 1 };
39
+ const b: CollisionAabb2D = { minX: 2, minY: 2, maxX: 3, maxY: 3 };
40
40
  expect(testAabbAabbCollision(a, b, out)).toBe(false);
41
41
  expect(out.overlapping).toBe(false);
42
42
  expect(out.normalX).toBe(0);
@@ -45,17 +45,17 @@ describe('testAabbAabbCollision', () => {
45
45
  });
46
46
 
47
47
  it('treats edge-touching as not overlapping (exclusive)', () => {
48
- const out = createCollisionManifold();
49
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 1, maxY: 1 };
50
- const b: CollisionAabb = { minX: 1, minY: 0, maxX: 2, maxY: 1 };
48
+ const out = createCollisionManifold2D();
49
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 1, maxY: 1 };
50
+ const b: CollisionAabb2D = { minX: 1, minY: 0, maxX: 2, maxY: 1 };
51
51
  expect(testAabbAabbCollision(a, b, out)).toBe(false);
52
52
  });
53
53
 
54
54
  it('resolves containment with the exit-distance depth, not the intersection length', () => {
55
- const out = createCollisionManifold();
55
+ const out = createCollisionManifold2D();
56
56
  // B fully inside A, closer to A's top face — MTV pushes A up through the nearest face by 5.
57
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
58
- const b: CollisionAabb = { minX: 3, minY: 3, maxX: 6, maxY: 5 };
57
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
58
+ const b: CollisionAabb2D = { minX: 3, minY: 3, maxX: 6, maxY: 5 };
59
59
  expect(testAabbAabbCollision(a, b, out)).toBe(true);
60
60
  expect(out.normalX).toBeCloseTo(0);
61
61
  expect(out.normalY).toBeCloseTo(1);
@@ -63,7 +63,7 @@ describe('testAabbAabbCollision', () => {
63
63
  });
64
64
 
65
65
  it('clears a reused manifold when a following pair misses', () => {
66
- const out = createCollisionManifold();
66
+ const out = createCollisionManifold2D();
67
67
  testAabbAabbCollision({ minX: 0, minY: 0, maxX: 2, maxY: 2 }, { minX: 1, minY: 1, maxX: 3, maxY: 3 }, out);
68
68
  expect(out.overlapping).toBe(true);
69
69
  testAabbAabbCollision({ minX: 0, minY: 0, maxX: 1, maxY: 1 }, { minX: 5, minY: 5, maxX: 6, maxY: 6 }, out);
@@ -74,7 +74,7 @@ describe('testAabbAabbCollision', () => {
74
74
  });
75
75
 
76
76
  it('rejects boxes with a zero-width or zero-height extent', () => {
77
- const out = createCollisionManifold();
77
+ const out = createCollisionManifold2D();
78
78
  out.overlapping = true;
79
79
  out.normalX = 1;
80
80
  out.depth = 4;
@@ -85,7 +85,7 @@ describe('testAabbAabbCollision', () => {
85
85
  });
86
86
 
87
87
  it('prefers +X when both separation axes and directions tie', () => {
88
- const out = createCollisionManifold();
88
+ const out = createCollisionManifold2D();
89
89
  const box = { minX: -1, minY: -1, maxX: 1, maxY: 1 };
90
90
  expect(testAabbAabbCollision(box, box, out)).toBe(true);
91
91
  expect(out.normalX).toBe(1);
@@ -95,7 +95,7 @@ describe('testAabbAabbCollision', () => {
95
95
 
96
96
  describe('testAabbObbCollision', () => {
97
97
  it('keeps SAT state isolated from a nested collision triggered by the output setter', () => {
98
- const out = createCollisionManifold();
98
+ const out = createCollisionManifold2D();
99
99
  let normalX = 0;
100
100
  let nested = false;
101
101
  Object.defineProperty(out, 'normalX', {
@@ -109,7 +109,7 @@ describe('testAabbObbCollision', () => {
109
109
  testAabbObbCollision(
110
110
  { minX: -10, minY: -10, maxX: 10, maxY: 10 },
111
111
  { x: 0, y: 0, halfW: 3, halfH: 3, rotation: 0 },
112
- createCollisionManifold(),
112
+ createCollisionManifold2D(),
113
113
  );
114
114
  },
115
115
  });
@@ -141,18 +141,18 @@ describe('testAabbObbCollision', () => {
141
141
  // than the guards that happen to exist is what reaches the ones nobody thought to cover.
142
142
  // Each degenerate box is placed where it WOULD overlap the AABB if validation wrongly accepted it:
143
143
  // a zero-extent box parked outside the AABB returns false either way and pins nothing.
144
- const out = createCollisionManifold();
144
+ const out = createCollisionManifold2D();
145
145
  out.overlapping = true;
146
146
  out.normalX = 1;
147
147
  out.depth = 4;
148
- expect(testAabbObbCollision({ minX: 0, minY: 0, maxX: 4, maxY: 10 }, b as CollisionObb, out)).toBe(false);
148
+ expect(testAabbObbCollision({ minX: 0, minY: 0, maxX: 4, maxY: 10 }, b as CollisionObb2D, out)).toBe(false);
149
149
  expect(out).toEqual({ depth: 0, normalX: 0, normalY: 0, overlapping: false });
150
150
  });
151
151
 
152
152
  it('overlaps an axis-aligned OBB and pushes the box off it', () => {
153
- const out = createCollisionManifold();
154
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 4, maxY: 10 };
155
- const b: CollisionObb = { x: 6, y: 5, halfW: 3, halfH: 3, rotation: 0 };
153
+ const out = createCollisionManifold2D();
154
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 4, maxY: 10 };
155
+ const b: CollisionObb2D = { x: 6, y: 5, halfW: 3, halfH: 3, rotation: 0 };
156
156
  expect(testAabbObbCollision(a, b, out)).toBe(true);
157
157
  expect(out.normalX).toBeCloseTo(-1);
158
158
  expect(out.normalY).toBeCloseTo(0);
@@ -160,9 +160,9 @@ describe('testAabbObbCollision', () => {
160
160
  });
161
161
 
162
162
  it('is separated when apart', () => {
163
- const out = createCollisionManifold();
164
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 2, maxY: 2 };
165
- const b: CollisionObb = { x: 10, y: 10, halfW: 1, halfH: 1, rotation: 0.5 };
163
+ const out = createCollisionManifold2D();
164
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 2, maxY: 2 };
165
+ const b: CollisionObb2D = { x: 10, y: 10, halfW: 1, halfH: 1, rotation: 0.5 };
166
166
  expect(testAabbObbCollision(a, b, out)).toBe(false);
167
167
  expect(out.overlapping).toBe(false);
168
168
  });
@@ -170,25 +170,25 @@ describe('testAabbObbCollision', () => {
170
170
 
171
171
  describe('testAabbPolygonCollision', () => {
172
172
  it('overlaps a convex polygon', () => {
173
- const out = createCollisionManifold();
174
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 6, maxY: 6 };
173
+ const out = createCollisionManifold2D();
174
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 6, maxY: 6 };
175
175
  expect(testAabbPolygonCollision(a, square(4, 0, 6), out)).toBe(true);
176
176
  expect(out.normalX).toBeCloseTo(-1);
177
177
  expect(out.depth).toBeCloseTo(2); // A right x=6, polygon left x=4 -> penetration 2
178
178
  });
179
179
 
180
180
  it('is separated when apart', () => {
181
- const out = createCollisionManifold();
182
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 2, maxY: 2 };
181
+ const out = createCollisionManifold2D();
182
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 2, maxY: 2 };
183
183
  expect(testAabbPolygonCollision(a, square(10, 10, 4), out)).toBe(false);
184
184
  });
185
185
  });
186
186
 
187
187
  describe('testCircleAabbCollision', () => {
188
188
  it('overlaps with the closest-point normal when the center is outside the box', () => {
189
- const out = createCollisionManifold();
190
- const a: CollisionCircle = { x: 15, y: 5, radius: 7 };
191
- const b: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
189
+ const out = createCollisionManifold2D();
190
+ const a: CollisionCircle2D = { x: 15, y: 5, radius: 7 };
191
+ const b: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
192
192
  expect(testCircleAabbCollision(a, b, out)).toBe(true);
193
193
  expect(out.normalX).toBeCloseTo(1);
194
194
  expect(out.normalY).toBeCloseTo(0);
@@ -196,23 +196,23 @@ describe('testCircleAabbCollision', () => {
196
196
  });
197
197
 
198
198
  it('is separated when the closest point is beyond the radius', () => {
199
- const out = createCollisionManifold();
200
- const a: CollisionCircle = { x: 20, y: 5, radius: 3 };
201
- const b: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
199
+ const out = createCollisionManifold2D();
200
+ const a: CollisionCircle2D = { x: 20, y: 5, radius: 3 };
201
+ const b: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
202
202
  expect(testCircleAabbCollision(a, b, out)).toBe(false);
203
203
  });
204
204
 
205
205
  it('treats exact touching as not overlapping (exclusive)', () => {
206
- const out = createCollisionManifold();
207
- const a: CollisionCircle = { x: 17, y: 5, radius: 7 }; // closest (10,5), dist 7 == radius
208
- const b: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
206
+ const out = createCollisionManifold2D();
207
+ const a: CollisionCircle2D = { x: 17, y: 5, radius: 7 }; // closest (10,5), dist 7 == radius
208
+ const b: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
209
209
  expect(testCircleAabbCollision(a, b, out)).toBe(false);
210
210
  });
211
211
 
212
212
  it('pushes out through the nearest face when the center is inside the box', () => {
213
- const out = createCollisionManifold();
214
- const a: CollisionCircle = { x: 8, y: 5, radius: 1 }; // 2 from the right face
215
- const b: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
213
+ const out = createCollisionManifold2D();
214
+ const a: CollisionCircle2D = { x: 8, y: 5, radius: 1 }; // 2 from the right face
215
+ const b: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
216
216
  expect(testCircleAabbCollision(a, b, out)).toBe(true);
217
217
  expect(out.normalX).toBeCloseTo(1);
218
218
  expect(out.normalY).toBeCloseTo(0);
@@ -222,9 +222,9 @@ describe('testCircleAabbCollision', () => {
222
222
 
223
223
  describe('testCircleCircleCollision', () => {
224
224
  it('gives depth 1 and a unit normal for two radius-1 circles centered 1 apart', () => {
225
- const out = createCollisionManifold();
226
- const a: CollisionCircle = { x: 0, y: 0, radius: 1 };
227
- const b: CollisionCircle = { x: 1, y: 0, radius: 1 };
225
+ const out = createCollisionManifold2D();
226
+ const a: CollisionCircle2D = { x: 0, y: 0, radius: 1 };
227
+ const b: CollisionCircle2D = { x: 1, y: 0, radius: 1 };
228
228
  expect(testCircleCircleCollision(a, b, out)).toBe(true);
229
229
  expect(out.normalX).toBeCloseTo(-1); // pushes A (left) off B (right)
230
230
  expect(out.normalY).toBeCloseTo(0);
@@ -232,33 +232,33 @@ describe('testCircleCircleCollision', () => {
232
232
  });
233
233
 
234
234
  it('is separated when the centers are farther than the radius sum', () => {
235
- const out = createCollisionManifold();
236
- const a: CollisionCircle = { x: 0, y: 0, radius: 1 };
237
- const b: CollisionCircle = { x: 3, y: 0, radius: 1 };
235
+ const out = createCollisionManifold2D();
236
+ const a: CollisionCircle2D = { x: 0, y: 0, radius: 1 };
237
+ const b: CollisionCircle2D = { x: 3, y: 0, radius: 1 };
238
238
  expect(testCircleCircleCollision(a, b, out)).toBe(false);
239
239
  expect(out.depth).toBe(0);
240
240
  });
241
241
 
242
242
  it('treats exact touching (distance == radius sum) as not overlapping', () => {
243
- const out = createCollisionManifold();
244
- const a: CollisionCircle = { x: 0, y: 0, radius: 1 };
245
- const b: CollisionCircle = { x: 2, y: 0, radius: 1 };
243
+ const out = createCollisionManifold2D();
244
+ const a: CollisionCircle2D = { x: 0, y: 0, radius: 1 };
245
+ const b: CollisionCircle2D = { x: 2, y: 0, radius: 1 };
246
246
  expect(testCircleCircleCollision(a, b, out)).toBe(false);
247
247
  });
248
248
 
249
249
  it('handles a fully contained circle', () => {
250
- const out = createCollisionManifold();
251
- const a: CollisionCircle = { x: 0, y: 0, radius: 5 };
252
- const b: CollisionCircle = { x: 1, y: 0, radius: 1 };
250
+ const out = createCollisionManifold2D();
251
+ const a: CollisionCircle2D = { x: 0, y: 0, radius: 5 };
252
+ const b: CollisionCircle2D = { x: 1, y: 0, radius: 1 };
253
253
  expect(testCircleCircleCollision(a, b, out)).toBe(true);
254
254
  expect(out.normalX).toBeCloseTo(-1);
255
255
  expect(out.depth).toBeCloseTo(5); // radiusSum 6 - dist 1
256
256
  });
257
257
 
258
258
  it('falls back to a +X normal at full depth for concentric circles', () => {
259
- const out = createCollisionManifold();
260
- const a: CollisionCircle = { x: 2, y: 2, radius: 3 };
261
- const b: CollisionCircle = { x: 2, y: 2, radius: 1 };
259
+ const out = createCollisionManifold2D();
260
+ const a: CollisionCircle2D = { x: 2, y: 2, radius: 3 };
261
+ const b: CollisionCircle2D = { x: 2, y: 2, radius: 1 };
262
262
  expect(testCircleCircleCollision(a, b, out)).toBe(true);
263
263
  expect(out.normalX).toBe(1);
264
264
  expect(out.normalY).toBe(0);
@@ -266,7 +266,7 @@ describe('testCircleCircleCollision', () => {
266
266
  });
267
267
 
268
268
  it('rejects a zero-radius circle and clears the output', () => {
269
- const out = createCollisionManifold();
269
+ const out = createCollisionManifold2D();
270
270
  out.overlapping = true;
271
271
  out.normalX = -1;
272
272
  out.depth = 2;
@@ -275,7 +275,7 @@ describe('testCircleCircleCollision', () => {
275
275
  });
276
276
 
277
277
  it('keeps a directional normal for proportionally tiny circles', () => {
278
- const out = createCollisionManifold();
278
+ const out = createCollisionManifold2D();
279
279
  expect(testCircleCircleCollision({ x: 0, y: 0, radius: 1e-12 }, { x: 1e-12, y: 0, radius: 1e-12 }, out)).toBe(true);
280
280
  expect(out.normalX).toBe(-1);
281
281
  expect(out.normalY).toBe(0);
@@ -285,9 +285,9 @@ describe('testCircleCircleCollision', () => {
285
285
 
286
286
  describe('testCircleObbCollision', () => {
287
287
  it('matches circle-vs-AABB for a zero-rotation OBB', () => {
288
- const out = createCollisionManifold();
289
- const a: CollisionCircle = { x: 15, y: 5, radius: 7 };
290
- const b: CollisionObb = { x: 5, y: 5, halfW: 5, halfH: 5, rotation: 0 };
288
+ const out = createCollisionManifold2D();
289
+ const a: CollisionCircle2D = { x: 15, y: 5, radius: 7 };
290
+ const b: CollisionObb2D = { x: 5, y: 5, halfW: 5, halfH: 5, rotation: 0 };
291
291
  expect(testCircleObbCollision(a, b, out)).toBe(true);
292
292
  expect(out.normalX).toBeCloseTo(1);
293
293
  expect(out.normalY).toBeCloseTo(0);
@@ -295,10 +295,10 @@ describe('testCircleObbCollision', () => {
295
295
  });
296
296
 
297
297
  it('rotates the manifold normal back into world space for a rotated OBB', () => {
298
- const out = createCollisionManifold();
298
+ const out = createCollisionManifold2D();
299
299
  // OBB rotated 90 degrees: its half-width (5) lies along world Y, half-height (1) along world X.
300
- const a: CollisionCircle = { x: 3, y: 0, radius: 2.5 };
301
- const b: CollisionObb = { x: 0, y: 0, halfW: 5, halfH: 1, rotation: Math.PI / 2 };
300
+ const a: CollisionCircle2D = { x: 3, y: 0, radius: 2.5 };
301
+ const b: CollisionObb2D = { x: 0, y: 0, halfW: 5, halfH: 1, rotation: Math.PI / 2 };
302
302
  expect(testCircleObbCollision(a, b, out)).toBe(true);
303
303
  expect(out.normalX).toBeCloseTo(1); // world +X face at x=1, circle center x=3 r=2.5
304
304
  expect(out.normalY).toBeCloseTo(0);
@@ -306,17 +306,17 @@ describe('testCircleObbCollision', () => {
306
306
  });
307
307
 
308
308
  it('is separated when apart', () => {
309
- const out = createCollisionManifold();
310
- const a: CollisionCircle = { x: 20, y: 0, radius: 1 };
311
- const b: CollisionObb = { x: 0, y: 0, halfW: 2, halfH: 2, rotation: 0.7 };
309
+ const out = createCollisionManifold2D();
310
+ const a: CollisionCircle2D = { x: 20, y: 0, radius: 1 };
311
+ const b: CollisionObb2D = { x: 0, y: 0, halfW: 2, halfH: 2, rotation: 0.7 };
312
312
  expect(testCircleObbCollision(a, b, out)).toBe(false);
313
313
  });
314
314
  });
315
315
 
316
316
  describe('testCirclePolygonCollision', () => {
317
317
  it('overlaps a square polygon on the edge nearest the circle', () => {
318
- const out = createCollisionManifold();
319
- const a: CollisionCircle = { x: 13, y: 5, radius: 5 };
318
+ const out = createCollisionManifold2D();
319
+ const a: CollisionCircle2D = { x: 13, y: 5, radius: 5 };
320
320
  expect(testCirclePolygonCollision(a, square(0, 0, 10), out)).toBe(true);
321
321
  expect(out.normalX).toBeCloseTo(1);
322
322
  expect(out.normalY).toBeCloseTo(0);
@@ -324,47 +324,47 @@ describe('testCirclePolygonCollision', () => {
324
324
  });
325
325
 
326
326
  it('uses the vertex axis to separate a circle just past a corner', () => {
327
- const out = createCollisionManifold();
327
+ const out = createCollisionManifold2D();
328
328
  // Corner (10,10), circle center (13,13): corner distance sqrt(18) ~= 4.243 > radius 4.
329
329
  // Edge normals alone (X and Y) would falsely report overlap; the vertex axis separates them.
330
- const a: CollisionCircle = { x: 13, y: 13, radius: 4 };
330
+ const a: CollisionCircle2D = { x: 13, y: 13, radius: 4 };
331
331
  expect(testCirclePolygonCollision(a, square(0, 0, 10), out)).toBe(false);
332
332
  expect(out.overlapping).toBe(false);
333
333
  });
334
334
 
335
335
  it('reports overlap when the circle reaches past the corner', () => {
336
- const out = createCollisionManifold();
337
- const a: CollisionCircle = { x: 13, y: 13, radius: 5 }; // corner distance 4.243 < radius 5
336
+ const out = createCollisionManifold2D();
337
+ const a: CollisionCircle2D = { x: 13, y: 13, radius: 5 }; // corner distance 4.243 < radius 5
338
338
  expect(testCirclePolygonCollision(a, square(0, 0, 10), out)).toBe(true);
339
339
  });
340
340
 
341
341
  it('handles a circle fully contained in the polygon', () => {
342
- const out = createCollisionManifold();
343
- const a: CollisionCircle = { x: 5, y: 5, radius: 1 };
342
+ const out = createCollisionManifold2D();
343
+ const a: CollisionCircle2D = { x: 5, y: 5, radius: 1 };
344
344
  expect(testCirclePolygonCollision(a, square(0, 0, 10), out)).toBe(true);
345
345
  expect(out.overlapping).toBe(true);
346
346
  expect(out.depth).toBeCloseTo(6); // 5 to the nearest edge + radius 1
347
347
  });
348
348
 
349
349
  it('prefers +X for a centered circle whose edge axes tie', () => {
350
- const out = createCollisionManifold();
350
+ const out = createCollisionManifold2D();
351
351
  expect(testCirclePolygonCollision({ x: 0, y: 0, radius: 1 }, square(-5, -5, 10), out)).toBe(true);
352
352
  expect(out.normalX).toBe(1);
353
353
  expect(out.normalY).toBe(0);
354
354
  });
355
355
 
356
356
  it('is separated when far from the polygon', () => {
357
- const out = createCollisionManifold();
358
- const a: CollisionCircle = { x: 30, y: 5, radius: 3 };
357
+ const out = createCollisionManifold2D();
358
+ const a: CollisionCircle2D = { x: 30, y: 5, radius: 3 };
359
359
  expect(testCirclePolygonCollision(a, square(0, 0, 10), out)).toBe(false);
360
360
  });
361
361
  });
362
362
 
363
363
  describe('testObbObbCollision', () => {
364
364
  it('overlaps an axis-aligned box with a 45-degree diamond and separates along X', () => {
365
- const out = createCollisionManifold();
366
- const a: CollisionObb = { x: 0, y: 0, halfW: 2, halfH: 2, rotation: 0 };
367
- const b: CollisionObb = { x: 3, y: 0, halfW: 2, halfH: 2, rotation: Math.PI / 4 };
365
+ const out = createCollisionManifold2D();
366
+ const a: CollisionObb2D = { x: 0, y: 0, halfW: 2, halfH: 2, rotation: 0 };
367
+ const b: CollisionObb2D = { x: 3, y: 0, halfW: 2, halfH: 2, rotation: Math.PI / 4 };
368
368
  expect(testObbObbCollision(a, b, out)).toBe(true);
369
369
  expect(out.normalX).toBeCloseTo(-1); // pushes A (left) off the diamond
370
370
  expect(out.normalY).toBeCloseTo(0);
@@ -372,35 +372,35 @@ describe('testObbObbCollision', () => {
372
372
  });
373
373
 
374
374
  it('is separated when apart', () => {
375
- const out = createCollisionManifold();
376
- const a: CollisionObb = { x: 0, y: 0, halfW: 1, halfH: 1, rotation: 0.3 };
377
- const b: CollisionObb = { x: 8, y: 0, halfW: 1, halfH: 1, rotation: 1.1 };
375
+ const out = createCollisionManifold2D();
376
+ const a: CollisionObb2D = { x: 0, y: 0, halfW: 1, halfH: 1, rotation: 0.3 };
377
+ const b: CollisionObb2D = { x: 8, y: 0, halfW: 1, halfH: 1, rotation: 1.1 };
378
378
  expect(testObbObbCollision(a, b, out)).toBe(false);
379
379
  });
380
380
  });
381
381
 
382
382
  describe('testObbPolygonCollision', () => {
383
383
  it('overlaps a convex polygon', () => {
384
- const out = createCollisionManifold();
385
- const a: CollisionObb = { x: 0, y: 0, halfW: 3, halfH: 3, rotation: 0 };
384
+ const out = createCollisionManifold2D();
385
+ const a: CollisionObb2D = { x: 0, y: 0, halfW: 3, halfH: 3, rotation: 0 };
386
386
  expect(testObbPolygonCollision(a, square(2, -1, 4), out)).toBe(true);
387
387
  expect(out.normalX).toBeCloseTo(-1);
388
388
  expect(out.depth).toBeCloseTo(1); // OBB right x=3, polygon left x=2 -> penetration 1
389
389
  });
390
390
 
391
391
  it('is separated when apart', () => {
392
- const out = createCollisionManifold();
393
- const a: CollisionObb = { x: 0, y: 0, halfW: 1, halfH: 1, rotation: 0.4 };
392
+ const out = createCollisionManifold2D();
393
+ const a: CollisionObb2D = { x: 0, y: 0, halfW: 1, halfH: 1, rotation: 0.4 };
394
394
  expect(testObbPolygonCollision(a, square(10, 10, 3), out)).toBe(false);
395
395
  });
396
396
  });
397
397
 
398
398
  describe('testPolygonPolygonCollision', () => {
399
399
  it('reports the least-penetration axis for two overlapping squares', () => {
400
- const out = createCollisionManifold();
400
+ const out = createCollisionManifold2D();
401
401
  // A = 0..10 square; B overlaps deeply on X (x[2,8]) but shallowly on Y (y[8,20]).
402
402
  const a = square(0, 0, 10);
403
- const b: CollisionPolygon = { points: [2, 8, 8, 8, 8, 20, 2, 20] };
403
+ const b: CollisionPolygon2D = { points: [2, 8, 8, 8, 8, 20, 2, 20] };
404
404
  expect(testPolygonPolygonCollision(a, b, out)).toBe(true);
405
405
  expect(out.normalX).toBeCloseTo(0);
406
406
  expect(out.normalY).toBeCloseTo(-1);
@@ -408,33 +408,33 @@ describe('testPolygonPolygonCollision', () => {
408
408
  });
409
409
 
410
410
  it('treats a concave-looking-but-convex polygon (a regular hexagon) correctly', () => {
411
- const out = createCollisionManifold();
411
+ const out = createCollisionManifold2D();
412
412
  // A convex hexagon centered near the origin.
413
- const hex: CollisionPolygon = { points: [2, 0, 1, 2, -1, 2, -2, 0, -1, -2, 1, -2] };
413
+ const hex: CollisionPolygon2D = { points: [2, 0, 1, 2, -1, 2, -2, 0, -1, -2, 1, -2] };
414
414
  expect(testPolygonPolygonCollision(hex, square(0, 0, 3), out)).toBe(true);
415
415
  expect(out.overlapping).toBe(true);
416
416
  });
417
417
 
418
418
  it('is separated when the polygons are disjoint', () => {
419
- const out = createCollisionManifold();
419
+ const out = createCollisionManifold2D();
420
420
  expect(testPolygonPolygonCollision(square(0, 0, 2), square(5, 5, 2), out)).toBe(false);
421
421
  });
422
422
 
423
423
  it('treats edge-touching as not overlapping (exclusive)', () => {
424
- const out = createCollisionManifold();
424
+ const out = createCollisionManifold2D();
425
425
  expect(testPolygonPolygonCollision(square(0, 0, 2), square(2, 0, 2), out)).toBe(false);
426
426
  });
427
427
 
428
428
  it('rejects polygons with fewer than three vertices', () => {
429
- const out = createCollisionManifold();
429
+ const out = createCollisionManifold2D();
430
430
  out.overlapping = true;
431
431
  expect(testPolygonPolygonCollision({ points: [0, 0, 1, 1] }, square(0, 0, 2), out)).toBe(false);
432
432
  expect(out).toEqual({ depth: 0, normalX: 0, normalY: 0, overlapping: false });
433
433
  });
434
434
 
435
435
  it('chooses the same +X MTV for coincident squares regardless of start vertex and winding', () => {
436
- const out = createCollisionManifold();
437
- const variants: CollisionPolygon[] = [
436
+ const out = createCollisionManifold2D();
437
+ const variants: CollisionPolygon2D[] = [
438
438
  { points: [-1, -1, 1, -1, 1, 1, -1, 1] },
439
439
  { points: [1, 1, 1, -1, -1, -1, -1, 1] },
440
440
  { points: [-1, 1, 1, 1, 1, -1, -1, -1] },
@@ -448,12 +448,12 @@ describe('testPolygonPolygonCollision', () => {
448
448
  });
449
449
 
450
450
  it('is translation-stable at large world coordinates', () => {
451
- const out = createCollisionManifold();
451
+ const out = createCollisionManifold2D();
452
452
  const origin = 1e15;
453
- const a: CollisionPolygon = {
453
+ const a: CollisionPolygon2D = {
454
454
  points: [origin, origin, origin + 10, origin, origin + 10, origin + 10, origin, origin + 10],
455
455
  };
456
- const b: CollisionPolygon = {
456
+ const b: CollisionPolygon2D = {
457
457
  points: [origin + 8, origin, origin + 18, origin, origin + 18, origin + 10, origin + 8, origin + 10],
458
458
  };
459
459
  expect(testPolygonPolygonCollision(a, b, out)).toBe(true);