@flighthq/collision 0.4.0-next.1663.d9c5746 → 0.4.0-next.1695.181dea5

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,8 +1,8 @@
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 { createCollisionContactManifold } from './contactManifold';
5
- import { createCollisionManifold } from './manifold';
4
+ import { createCollisionContactManifold2D } from './contactManifold';
5
+ import { createCollisionManifold2D } from './manifold';
6
6
  import {
7
7
  testAabbAabbCollision,
8
8
  testCircleCircleCollision,
@@ -23,24 +23,24 @@ import {
23
23
  } from './shapeContact';
24
24
 
25
25
  // A wide, shallow ground slab every resting-contact case sits on.
26
- const ground: CollisionAabb = { minX: -5, minY: -1, maxX: 5, maxY: 0 };
26
+ const ground: CollisionAabb2D = { minX: -5, minY: -1, maxX: 5, maxY: 0 };
27
27
 
28
28
  // A square collider as a flat convex polygon, corners counter-clockwise from the min corner.
29
- function square(minX: number, minY: number, size: number): CollisionPolygon {
29
+ function square(minX: number, minY: number, size: number): CollisionPolygon2D {
30
30
  return { points: [minX, minY, minX + size, minY, minX + size, minY + size, minX, minY + size] };
31
31
  }
32
32
 
33
33
  // The contact points that are actually valid this call, in order. Reading past `pointCount` is
34
34
  // outside the contract, so every assertion goes through this.
35
- function livePoints(manifold: ReturnType<typeof createCollisionContactManifold>) {
35
+ function livePoints(manifold: ReturnType<typeof createCollisionContactManifold2D>) {
36
36
  return manifold.points.slice(0, manifold.pointCount);
37
37
  }
38
38
 
39
39
  describe('collideAabbAabbContactManifold', () => {
40
40
  it('resolves a box resting on the ground to two points spanning the overlap', () => {
41
- const out = createCollisionContactManifold();
41
+ const out = createCollisionContactManifold2D();
42
42
  // The box's underside dips 0.1 below the ground's top face across x[0,2].
43
- const box: CollisionAabb = { minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 };
43
+ const box: CollisionAabb2D = { minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 };
44
44
  expect(collideAabbAabbContactManifold(box, ground, out)).toBe(true);
45
45
  expect(out.overlapping).toBe(true);
46
46
  expect(out.normalX).toBeCloseTo(0);
@@ -62,8 +62,8 @@ describe('collideAabbAabbContactManifold', () => {
62
62
  });
63
63
 
64
64
  it('gives the two points distinct feature ids that survive the box sliding along the ground', () => {
65
- const first = createCollisionContactManifold();
66
- const second = createCollisionContactManifold();
65
+ const first = createCollisionContactManifold2D();
66
+ const second = createCollisionContactManifold2D();
67
67
  expect(collideAabbAabbContactManifold({ minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 }, ground, first)).toBe(true);
68
68
  expect(collideAabbAabbContactManifold({ minX: 0.3, minY: -0.12, maxX: 2.3, maxY: 1.88 }, ground, second)).toBe(
69
69
  true,
@@ -76,10 +76,10 @@ describe('collideAabbAabbContactManifold', () => {
76
76
  });
77
77
 
78
78
  it('agrees with the lean overlap test on normal and depth', () => {
79
- const contact = createCollisionContactManifold();
80
- const lean = createCollisionManifold();
81
- const a: CollisionAabb = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
82
- const b: CollisionAabb = { minX: 2, minY: 8, maxX: 8, maxY: 20 };
79
+ const contact = createCollisionContactManifold2D();
80
+ const lean = createCollisionManifold2D();
81
+ const a: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
82
+ const b: CollisionAabb2D = { minX: 2, minY: 8, maxX: 8, maxY: 20 };
83
83
  expect(collideAabbAabbContactManifold(a, b, contact)).toBe(true);
84
84
  expect(testAabbAabbCollision(a, b, lean)).toBe(true);
85
85
  expect(contact.normalX).toBeCloseTo(lean.normalX);
@@ -93,9 +93,9 @@ describe('collideAabbAabbContactManifold', () => {
93
93
  // (same xs) and the pair-level answer (normal negated, depth equal) while moving the points one
94
94
  // penetration depth down, onto the other body's face. Both are correct contacts; which one you get
95
95
  // is the caller's argument order, and this pins that rather than averting its eyes from it.
96
- const forward = createCollisionContactManifold();
97
- const reversed = createCollisionContactManifold();
98
- const box: CollisionAabb = { minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 };
96
+ const forward = createCollisionContactManifold2D();
97
+ const reversed = createCollisionContactManifold2D();
98
+ const box: CollisionAabb2D = { minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 };
99
99
  expect(collideAabbAabbContactManifold(box, ground, forward)).toBe(true);
100
100
  expect(collideAabbAabbContactManifold(ground, box, reversed)).toBe(true);
101
101
 
@@ -116,7 +116,7 @@ describe('collideAabbAabbContactManifold', () => {
116
116
  });
117
117
 
118
118
  it('clears the manifold on a miss, so no stale point is left readable', () => {
119
- const out = createCollisionContactManifold();
119
+ const out = createCollisionContactManifold2D();
120
120
  expect(collideAabbAabbContactManifold({ minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 }, ground, out)).toBe(true);
121
121
  expect(out.pointCount).toBe(2);
122
122
 
@@ -127,7 +127,7 @@ describe('collideAabbAabbContactManifold', () => {
127
127
  });
128
128
 
129
129
  it('treats exactly touching boxes as not overlapping, matching the lean test', () => {
130
- const out = createCollisionContactManifold();
130
+ const out = createCollisionContactManifold2D();
131
131
  expect(collideAabbAabbContactManifold({ minX: 0, minY: 0, maxX: 2, maxY: 2 }, ground, out)).toBe(false);
132
132
  expect(out.pointCount).toBe(0);
133
133
  });
@@ -135,9 +135,9 @@ describe('collideAabbAabbContactManifold', () => {
135
135
 
136
136
  describe('collideAabbObbContactManifold', () => {
137
137
  it('resolves a flat-lying oriented box under a box to a two-point face contact', () => {
138
- const out = createCollisionContactManifold();
139
- const box: CollisionAabb = { minX: -1, minY: -0.1, maxX: 1, maxY: 2 };
140
- const platform: CollisionObb = { x: 0, y: -1, halfW: 3, halfH: 1, rotation: 0 };
138
+ const out = createCollisionContactManifold2D();
139
+ const box: CollisionAabb2D = { minX: -1, minY: -0.1, maxX: 1, maxY: 2 };
140
+ const platform: CollisionObb2D = { x: 0, y: -1, halfW: 3, halfH: 1, rotation: 0 };
141
141
  expect(collideAabbObbContactManifold(box, platform, out)).toBe(true);
142
142
  expect(out.normalY).toBeCloseTo(1);
143
143
  expect(out.depth).toBeCloseTo(0.1);
@@ -148,11 +148,11 @@ describe('collideAabbObbContactManifold', () => {
148
148
  });
149
149
 
150
150
  it('resolves a corner-first oriented box to a single deepest point', () => {
151
- const out = createCollisionContactManifold();
151
+ const out = createCollisionContactManifold2D();
152
152
  const half = Math.SQRT2 / 2;
153
153
  // A unit-ish diamond whose lowest corner just dips under the box's top face.
154
- const diamond: CollisionObb = { x: 0, y: 1 + half - 0.05, halfW: 0.5, halfH: 0.5, rotation: Math.PI / 4 };
155
- const box: CollisionAabb = { minX: -3, minY: -1, maxX: 3, maxY: 1 };
154
+ const diamond: CollisionObb2D = { x: 0, y: 1 + half - 0.05, halfW: 0.5, halfH: 0.5, rotation: Math.PI / 4 };
155
+ const box: CollisionAabb2D = { minX: -3, minY: -1, maxX: 3, maxY: 1 };
156
156
  expect(collideAabbObbContactManifold(box, diamond, out)).toBe(true);
157
157
  expect(out.pointCount).toBe(1);
158
158
  expect(out.points[0].x).toBeCloseTo(0);
@@ -161,7 +161,7 @@ describe('collideAabbObbContactManifold', () => {
161
161
  });
162
162
 
163
163
  it('is separated when the boxes are disjoint', () => {
164
- const out = createCollisionContactManifold();
164
+ const out = createCollisionContactManifold2D();
165
165
  expect(
166
166
  collideAabbObbContactManifold(
167
167
  { minX: 0, minY: 0, maxX: 1, maxY: 1 },
@@ -175,11 +175,11 @@ describe('collideAabbObbContactManifold', () => {
175
175
 
176
176
  describe('collideAabbPolygonContactManifold', () => {
177
177
  it('matches the all-polygon result for the same geometry', () => {
178
- const viaAabb = createCollisionContactManifold();
179
- const viaPolygon = createCollisionContactManifold();
180
- const box: CollisionAabb = { minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 };
178
+ const viaAabb = createCollisionContactManifold2D();
179
+ const viaPolygon = createCollisionContactManifold2D();
180
+ const box: CollisionAabb2D = { minX: 0, minY: -0.1, maxX: 2, maxY: 1.9 };
181
181
  // The ground slab restated as a polygon, so both routes see identical geometry.
182
- const slab: CollisionPolygon = { points: [-5, -1, 5, -1, 5, 0, -5, 0] };
182
+ const slab: CollisionPolygon2D = { points: [-5, -1, 5, -1, 5, 0, -5, 0] };
183
183
  expect(collideAabbPolygonContactManifold(box, slab, viaAabb)).toBe(true);
184
184
  expect(collidePolygonPolygonContactManifold(square(0, -0.1, 2), slab, viaPolygon)).toBe(true);
185
185
 
@@ -190,8 +190,8 @@ describe('collideAabbPolygonContactManifold', () => {
190
190
  });
191
191
 
192
192
  it('reports a clean miss rather than NaN for a degenerate polygon', () => {
193
- const out = createCollisionContactManifold();
194
- const degenerate: CollisionPolygon = { points: [0, 0, 1, 1] };
193
+ const out = createCollisionContactManifold2D();
194
+ const degenerate: CollisionPolygon2D = { points: [0, 0, 1, 1] };
195
195
  expect(collideAabbPolygonContactManifold({ minX: -1, minY: -1, maxX: 2, maxY: 2 }, degenerate, out)).toBe(false);
196
196
  expect(out.overlapping).toBe(false);
197
197
  expect(out.pointCount).toBe(0);
@@ -202,8 +202,8 @@ describe('collideAabbPolygonContactManifold', () => {
202
202
 
203
203
  describe('collideCircleAabbContactManifold', () => {
204
204
  it('places the single point on the circle surface at its deepest penetration', () => {
205
- const out = createCollisionContactManifold();
206
- const circle: CollisionCircle = { x: 0, y: 0.9, radius: 1 };
205
+ const out = createCollisionContactManifold2D();
206
+ const circle: CollisionCircle2D = { x: 0, y: 0.9, radius: 1 };
207
207
  expect(collideCircleAabbContactManifold(circle, ground, out)).toBe(true);
208
208
  expect(out.normalY).toBeCloseTo(1);
209
209
  expect(out.depth).toBeCloseTo(0.1);
@@ -214,7 +214,7 @@ describe('collideCircleAabbContactManifold', () => {
214
214
  });
215
215
 
216
216
  it('is separated when the circle clears the box', () => {
217
- const out = createCollisionContactManifold();
217
+ const out = createCollisionContactManifold2D();
218
218
  expect(collideCircleAabbContactManifold({ x: 0, y: 4, radius: 1 }, ground, out)).toBe(false);
219
219
  expect(out.pointCount).toBe(0);
220
220
  });
@@ -224,7 +224,7 @@ describe('collideCircleCircleContactManifold', () => {
224
224
  it('keeps the lean manifold isolated from a nested contact triggered after the lean test', () => {
225
225
  let xReads = 0;
226
226
  let nestedCalls = 0;
227
- const a: CollisionCircle = {
227
+ const a: CollisionCircle2D = {
228
228
  get x() {
229
229
  xReads++;
230
230
  if (xReads === 3) {
@@ -232,7 +232,7 @@ describe('collideCircleCircleContactManifold', () => {
232
232
  collideCircleCircleContactManifold(
233
233
  { x: 0, y: 0, radius: 1 },
234
234
  { x: 0.5, y: 0, radius: 1 },
235
- createCollisionContactManifold(),
235
+ createCollisionContactManifold2D(),
236
236
  );
237
237
  }
238
238
  return 0;
@@ -240,7 +240,7 @@ describe('collideCircleCircleContactManifold', () => {
240
240
  y: 0,
241
241
  radius: 1,
242
242
  };
243
- const out = createCollisionContactManifold();
243
+ const out = createCollisionContactManifold2D();
244
244
 
245
245
  expect(collideCircleCircleContactManifold(a, { x: 1.5, y: 0, radius: 1 }, out)).toBe(true);
246
246
  expect(nestedCalls).toBe(1);
@@ -250,9 +250,9 @@ describe('collideCircleCircleContactManifold', () => {
250
250
  });
251
251
 
252
252
  it('places the point on A surface along the contact normal', () => {
253
- const out = createCollisionContactManifold();
254
- const a: CollisionCircle = { x: 0, y: 0, radius: 1 };
255
- const b: CollisionCircle = { x: 1.5, y: 0, radius: 1 };
253
+ const out = createCollisionContactManifold2D();
254
+ const a: CollisionCircle2D = { x: 0, y: 0, radius: 1 };
255
+ const b: CollisionCircle2D = { x: 1.5, y: 0, radius: 1 };
256
256
  expect(collideCircleCircleContactManifold(a, b, out)).toBe(true);
257
257
  expect(out.normalX).toBeCloseTo(-1);
258
258
  expect(out.depth).toBeCloseTo(0.5);
@@ -263,10 +263,10 @@ describe('collideCircleCircleContactManifold', () => {
263
263
  });
264
264
 
265
265
  it('agrees with the lean overlap test on normal and depth', () => {
266
- const contact = createCollisionContactManifold();
267
- const lean = createCollisionManifold();
268
- const a: CollisionCircle = { x: 1, y: 2, radius: 3 };
269
- const b: CollisionCircle = { x: 4, y: 6, radius: 4 };
266
+ const contact = createCollisionContactManifold2D();
267
+ const lean = createCollisionManifold2D();
268
+ const a: CollisionCircle2D = { x: 1, y: 2, radius: 3 };
269
+ const b: CollisionCircle2D = { x: 4, y: 6, radius: 4 };
270
270
  expect(collideCircleCircleContactManifold(a, b, contact)).toBe(true);
271
271
  expect(testCircleCircleCollision(a, b, lean)).toBe(true);
272
272
  expect(contact.normalX).toBeCloseTo(lean.normalX);
@@ -275,7 +275,7 @@ describe('collideCircleCircleContactManifold', () => {
275
275
  });
276
276
 
277
277
  it('clears a previously populated manifold on a miss', () => {
278
- const out = createCollisionContactManifold();
278
+ const out = createCollisionContactManifold2D();
279
279
  expect(collideCircleCircleContactManifold({ x: 0, y: 0, radius: 1 }, { x: 1.5, y: 0, radius: 1 }, out)).toBe(true);
280
280
  expect(collideCircleCircleContactManifold({ x: 0, y: 0, radius: 1 }, { x: 9, y: 0, radius: 1 }, out)).toBe(false);
281
281
  expect(out.pointCount).toBe(0);
@@ -285,10 +285,10 @@ describe('collideCircleCircleContactManifold', () => {
285
285
 
286
286
  describe('collideCircleObbContactManifold', () => {
287
287
  it('places the point on the circle surface in the rotated box frame', () => {
288
- const out = createCollisionContactManifold();
288
+ const out = createCollisionContactManifold2D();
289
289
  // The box is rotated a quarter turn, so its half-extents swap: it spans y[-1,1] about the origin.
290
- const box: CollisionObb = { x: 0, y: 0, halfW: 1, halfH: 4, rotation: Math.PI / 2 };
291
- const circle: CollisionCircle = { x: 0, y: 1.9, radius: 1 };
290
+ const box: CollisionObb2D = { x: 0, y: 0, halfW: 1, halfH: 4, rotation: Math.PI / 2 };
291
+ const circle: CollisionCircle2D = { x: 0, y: 1.9, radius: 1 };
292
292
  expect(collideCircleObbContactManifold(circle, box, out)).toBe(true);
293
293
  expect(out.normalY).toBeCloseTo(1);
294
294
  expect(out.depth).toBeCloseTo(0.1);
@@ -297,7 +297,7 @@ describe('collideCircleObbContactManifold', () => {
297
297
  });
298
298
 
299
299
  it('is separated when the circle clears the rotated box', () => {
300
- const out = createCollisionContactManifold();
300
+ const out = createCollisionContactManifold2D();
301
301
  expect(
302
302
  collideCircleObbContactManifold(
303
303
  { x: 0, y: 20, radius: 1 },
@@ -311,8 +311,8 @@ describe('collideCircleObbContactManifold', () => {
311
311
 
312
312
  describe('collideCirclePolygonContactManifold', () => {
313
313
  it('places the point on the circle surface below the polygon face', () => {
314
- const out = createCollisionContactManifold();
315
- const circle: CollisionCircle = { x: 2.5, y: 5.9, radius: 1 };
314
+ const out = createCollisionContactManifold2D();
315
+ const circle: CollisionCircle2D = { x: 2.5, y: 5.9, radius: 1 };
316
316
  expect(collideCirclePolygonContactManifold(circle, square(0, 0, 5), out)).toBe(true);
317
317
  expect(out.normalY).toBeCloseTo(1);
318
318
  expect(out.depth).toBeCloseTo(0.1);
@@ -322,9 +322,9 @@ describe('collideCirclePolygonContactManifold', () => {
322
322
  });
323
323
 
324
324
  it('resolves a corner contact to one point without reporting a face', () => {
325
- const out = createCollisionContactManifold();
325
+ const out = createCollisionContactManifold2D();
326
326
  // Sitting diagonally off the square's top-right corner, just inside the radius.
327
- const circle: CollisionCircle = { x: 5.6, y: 5.6, radius: 1 };
327
+ const circle: CollisionCircle2D = { x: 5.6, y: 5.6, radius: 1 };
328
328
  expect(collideCirclePolygonContactManifold(circle, square(0, 0, 5), out)).toBe(true);
329
329
  expect(out.pointCount).toBe(1);
330
330
  expect(out.normalX).toBeCloseTo(Math.SQRT1_2);
@@ -334,9 +334,9 @@ describe('collideCirclePolygonContactManifold', () => {
334
334
 
335
335
  describe('collideObbObbContactManifold', () => {
336
336
  it('resolves two aligned oriented boxes to a two-point face contact', () => {
337
- const out = createCollisionContactManifold();
338
- const upper: CollisionObb = { x: 0, y: 0.9, halfW: 1, halfH: 1, rotation: 0 };
339
- const lower: CollisionObb = { x: 0, y: -1, halfW: 1, halfH: 1, rotation: 0 };
337
+ const out = createCollisionContactManifold2D();
338
+ const upper: CollisionObb2D = { x: 0, y: 0.9, halfW: 1, halfH: 1, rotation: 0 };
339
+ const lower: CollisionObb2D = { x: 0, y: -1, halfW: 1, halfH: 1, rotation: 0 };
340
340
  expect(collideObbObbContactManifold(upper, lower, out)).toBe(true);
341
341
  expect(out.normalY).toBeCloseTo(1);
342
342
  expect(out.depth).toBeCloseTo(0.1);
@@ -349,10 +349,10 @@ describe('collideObbObbContactManifold', () => {
349
349
  });
350
350
 
351
351
  it('agrees with the lean overlap test on normal and depth for a rotated pair', () => {
352
- const contact = createCollisionContactManifold();
353
- const lean = createCollisionManifold();
354
- const a: CollisionObb = { x: 0, y: 0, halfW: 2, halfH: 1, rotation: 0.4 };
355
- const b: CollisionObb = { x: 2.5, y: 0.5, halfW: 1, halfH: 1.5, rotation: -0.2 };
352
+ const contact = createCollisionContactManifold2D();
353
+ const lean = createCollisionManifold2D();
354
+ const a: CollisionObb2D = { x: 0, y: 0, halfW: 2, halfH: 1, rotation: 0.4 };
355
+ const b: CollisionObb2D = { x: 2.5, y: 0.5, halfW: 1, halfH: 1.5, rotation: -0.2 };
356
356
  expect(collideObbObbContactManifold(a, b, contact)).toBe(true);
357
357
  expect(testObbObbCollision(a, b, lean)).toBe(true);
358
358
  expect(contact.normalX).toBeCloseTo(lean.normalX);
@@ -361,9 +361,9 @@ describe('collideObbObbContactManifold', () => {
361
361
  });
362
362
 
363
363
  it('keeps every contact point at or behind the contact plane', () => {
364
- const out = createCollisionContactManifold();
365
- const a: CollisionObb = { x: 0, y: 0, halfW: 2, halfH: 1, rotation: 0.4 };
366
- const b: CollisionObb = { x: 2.5, y: 0.5, halfW: 1, halfH: 1.5, rotation: -0.2 };
364
+ const out = createCollisionContactManifold2D();
365
+ const a: CollisionObb2D = { x: 0, y: 0, halfW: 2, halfH: 1, rotation: 0.4 };
366
+ const b: CollisionObb2D = { x: 2.5, y: 0.5, halfW: 1, halfH: 1.5, rotation: -0.2 };
367
367
  expect(collideObbObbContactManifold(a, b, out)).toBe(true);
368
368
  expect(out.pointCount).toBeGreaterThan(0);
369
369
  for (const point of livePoints(out)) {
@@ -375,8 +375,8 @@ describe('collideObbObbContactManifold', () => {
375
375
 
376
376
  describe('collideObbPolygonContactManifold', () => {
377
377
  it('resolves an oriented box resting on a polygon slab to two points', () => {
378
- const out = createCollisionContactManifold();
379
- const box: CollisionObb = { x: 2.5, y: 5.9, halfW: 1, halfH: 1, rotation: 0 };
378
+ const out = createCollisionContactManifold2D();
379
+ const box: CollisionObb2D = { x: 2.5, y: 5.9, halfW: 1, halfH: 1, rotation: 0 };
380
380
  expect(collideObbPolygonContactManifold(box, square(0, 0, 5), out)).toBe(true);
381
381
  expect(out.normalY).toBeCloseTo(1);
382
382
  expect(out.depth).toBeCloseTo(0.1);
@@ -384,7 +384,7 @@ describe('collideObbPolygonContactManifold', () => {
384
384
  });
385
385
 
386
386
  it('is separated when the box clears the polygon', () => {
387
- const out = createCollisionContactManifold();
387
+ const out = createCollisionContactManifold2D();
388
388
  expect(
389
389
  collideObbPolygonContactManifold({ x: 50, y: 50, halfW: 1, halfH: 1, rotation: 0 }, square(0, 0, 5), out),
390
390
  ).toBe(false);
@@ -394,7 +394,7 @@ describe('collideObbPolygonContactManifold', () => {
394
394
 
395
395
  describe('collidePolygonPolygonContactManifold', () => {
396
396
  it('resolves a square resting on a slab to two points spanning the overlap', () => {
397
- const out = createCollisionContactManifold();
397
+ const out = createCollisionContactManifold2D();
398
398
  expect(collidePolygonPolygonContactManifold(square(0, 4.9, 2), square(0, 0, 5), out)).toBe(true);
399
399
  expect(out.normalY).toBeCloseTo(1);
400
400
  expect(out.depth).toBeCloseTo(0.1);
@@ -407,8 +407,8 @@ describe('collidePolygonPolygonContactManifold', () => {
407
407
  });
408
408
 
409
409
  it('agrees with the lean overlap test on normal and depth', () => {
410
- const contact = createCollisionContactManifold();
411
- const lean = createCollisionManifold();
410
+ const contact = createCollisionContactManifold2D();
411
+ const lean = createCollisionManifold2D();
412
412
  const a = square(0, 4.9, 2);
413
413
  const b = square(0, 0, 5);
414
414
  expect(collidePolygonPolygonContactManifold(a, b, contact)).toBe(true);
@@ -419,8 +419,8 @@ describe('collidePolygonPolygonContactManifold', () => {
419
419
  });
420
420
 
421
421
  it('resolves a triangle tip pressed into a face to a single point', () => {
422
- const out = createCollisionContactManifold();
423
- const triangle: CollisionPolygon = { points: [2, 4.9, 3, 7, 1, 7] };
422
+ const out = createCollisionContactManifold2D();
423
+ const triangle: CollisionPolygon2D = { points: [2, 4.9, 3, 7, 1, 7] };
424
424
  expect(collidePolygonPolygonContactManifold(triangle, square(0, 0, 5), out)).toBe(true);
425
425
  expect(out.pointCount).toBe(1);
426
426
  expect(out.points[0].x).toBeCloseTo(2);
@@ -429,10 +429,10 @@ describe('collidePolygonPolygonContactManifold', () => {
429
429
  });
430
430
 
431
431
  it('resolves the same contact whichever winding the polygons are given in', () => {
432
- const counterClockwise = createCollisionContactManifold();
433
- const clockwise = createCollisionContactManifold();
432
+ const counterClockwise = createCollisionContactManifold2D();
433
+ const clockwise = createCollisionContactManifold2D();
434
434
  const slab = square(0, 0, 5);
435
- const reversedSlab: CollisionPolygon = { points: [0, 0, 0, 5, 5, 5, 5, 0] };
435
+ const reversedSlab: CollisionPolygon2D = { points: [0, 0, 0, 5, 5, 5, 5, 0] };
436
436
  expect(collidePolygonPolygonContactManifold(square(0, 4.9, 2), slab, counterClockwise)).toBe(true);
437
437
  expect(collidePolygonPolygonContactManifold(square(0, 4.9, 2), reversedSlab, clockwise)).toBe(true);
438
438
 
@@ -442,7 +442,7 @@ describe('collidePolygonPolygonContactManifold', () => {
442
442
  });
443
443
 
444
444
  it('reports a clean miss rather than NaN for a polygon with too few vertices', () => {
445
- const out = createCollisionContactManifold();
445
+ const out = createCollisionContactManifold2D();
446
446
  expect(collidePolygonPolygonContactManifold({ points: [0, 0] }, square(0, 0, 5), out)).toBe(false);
447
447
  expect(out.pointCount).toBe(0);
448
448
  expect(Number.isNaN(out.depth)).toBe(false);
@@ -1,16 +1,16 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { createCollisionTimeOfImpact, sweepCollisionShape } from './sweepCollisionShape';
3
+ import { createCollisionTimeOfImpact2D, sweepCollisionShape2D } from './sweepCollisionShape2D';
4
4
 
5
- describe('createCollisionTimeOfImpact', () => {
5
+ describe('createCollisionTimeOfImpact2D', () => {
6
6
  it('creates a cleared reusable output record', () => {
7
- expect(createCollisionTimeOfImpact()).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
7
+ expect(createCollisionTimeOfImpact2D()).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
8
8
  });
9
9
  });
10
10
 
11
- describe('sweepCollisionShape', () => {
11
+ describe('sweepCollisionShape2D', () => {
12
12
  it('keeps continuous-SAT state isolated from a nested sweep triggered by the output setter', () => {
13
- const out = createCollisionTimeOfImpact();
13
+ const out = createCollisionTimeOfImpact2D();
14
14
  let fraction = 0;
15
15
  let fractionWrites = 0;
16
16
  let nestedCalls = 0;
@@ -23,20 +23,20 @@ describe('sweepCollisionShape', () => {
23
23
  fractionWrites++;
24
24
  if (fractionWrites !== 2) return;
25
25
  nestedCalls++;
26
- sweepCollisionShape(
26
+ sweepCollisionShape2D(
27
27
  { kind: 'aabb', minX: -1, minY: 0, maxX: 1, maxY: 2 },
28
28
  0,
29
29
  10,
30
30
  { kind: 'aabb', minX: -1, minY: 5, maxX: 1, maxY: 7 },
31
31
  0,
32
32
  0,
33
- createCollisionTimeOfImpact(),
33
+ createCollisionTimeOfImpact2D(),
34
34
  );
35
35
  },
36
36
  });
37
37
 
38
38
  expect(
39
- sweepCollisionShape(
39
+ sweepCollisionShape2D(
40
40
  { kind: 'obb', x: 0, y: 0, halfW: 1, halfH: 1, rotation: 0 },
41
41
  10,
42
42
  0,
@@ -53,9 +53,9 @@ describe('sweepCollisionShape', () => {
53
53
  });
54
54
 
55
55
  it('finds the exact circle-circle root under relative motion', () => {
56
- const out = createCollisionTimeOfImpact();
56
+ const out = createCollisionTimeOfImpact2D();
57
57
  expect(
58
- sweepCollisionShape(
58
+ sweepCollisionShape2D(
59
59
  { kind: 'circle', x: 0, y: 0, radius: 1 },
60
60
  10,
61
61
  0,
@@ -72,22 +72,22 @@ describe('sweepCollisionShape', () => {
72
72
 
73
73
  it('sweeps a circle exactly against polygon faces and rounded corners', () => {
74
74
  const box = { kind: 'aabb', minX: 5, minY: 5, maxX: 7, maxY: 7 } as const;
75
- const face = createCollisionTimeOfImpact();
76
- expect(sweepCollisionShape({ kind: 'circle', x: 0, y: 6, radius: 1 }, 10, 0, box, 0, 0, face)).toBe(true);
75
+ const face = createCollisionTimeOfImpact2D();
76
+ expect(sweepCollisionShape2D({ kind: 'circle', x: 0, y: 6, radius: 1 }, 10, 0, box, 0, 0, face)).toBe(true);
77
77
  expect(face.fraction).toBeCloseTo(0.4);
78
78
  expect([face.normalX, face.normalY]).toEqual([-1, 0]);
79
79
 
80
- const corner = createCollisionTimeOfImpact();
81
- expect(sweepCollisionShape({ kind: 'circle', x: 0, y: 0, radius: 1 }, 10, 10, box, 0, 0, corner)).toBe(true);
80
+ const corner = createCollisionTimeOfImpact2D();
81
+ expect(sweepCollisionShape2D({ kind: 'circle', x: 0, y: 0, radius: 1 }, 10, 10, box, 0, 0, corner)).toBe(true);
82
82
  expect(corner.fraction).toBeCloseTo((5 - Math.SQRT1_2) / 10);
83
83
  expect(corner.normalX).toBeCloseTo(-Math.SQRT1_2);
84
84
  expect(corner.normalY).toBeCloseTo(-Math.SQRT1_2);
85
85
  });
86
86
 
87
87
  it('reverses the normal when the polygon is shape A and still writes a point on A', () => {
88
- const out = createCollisionTimeOfImpact();
88
+ const out = createCollisionTimeOfImpact2D();
89
89
  expect(
90
- sweepCollisionShape(
90
+ sweepCollisionShape2D(
91
91
  { kind: 'aabb', minX: 5, minY: -1, maxX: 7, maxY: 1 },
92
92
  0,
93
93
  0,
@@ -105,20 +105,20 @@ describe('sweepCollisionShape', () => {
105
105
  it('uses continuous SAT for polygon pairs and honours maxFraction', () => {
106
106
  const moving = { kind: 'obb', x: 0, y: 0, halfW: 1, halfH: 1, rotation: 0 } as const;
107
107
  const fixed = { kind: 'aabb', minX: 5, minY: -1, maxX: 7, maxY: 1 } as const;
108
- const out = createCollisionTimeOfImpact();
108
+ const out = createCollisionTimeOfImpact2D();
109
109
 
110
- expect(sweepCollisionShape(moving, 10, 0, fixed, 0, 0, out, 0.3)).toBe(false);
110
+ expect(sweepCollisionShape2D(moving, 10, 0, fixed, 0, 0, out, 0.3)).toBe(false);
111
111
  expect(out).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
112
- expect(sweepCollisionShape(moving, 10, 0, fixed, 0, 0, out)).toBe(true);
112
+ expect(sweepCollisionShape2D(moving, 10, 0, fixed, 0, 0, out)).toBe(true);
113
113
  expect(out.fraction).toBeCloseTo(0.4);
114
114
  expect(out.normalX).toBe(-1);
115
115
  expect(out.x).toBeCloseTo(5);
116
116
  });
117
117
 
118
118
  it('reports an initial overlap at zero and ignores a touching pair moving apart', () => {
119
- const out = createCollisionTimeOfImpact();
119
+ const out = createCollisionTimeOfImpact2D();
120
120
  expect(
121
- sweepCollisionShape(
121
+ sweepCollisionShape2D(
122
122
  { kind: 'circle', x: 0, y: 0, radius: 1 },
123
123
  1,
124
124
  0,
@@ -132,7 +132,7 @@ describe('sweepCollisionShape', () => {
132
132
  expect(out.normalX).toBe(-1);
133
133
 
134
134
  expect(
135
- sweepCollisionShape(
135
+ sweepCollisionShape2D(
136
136
  { kind: 'circle', x: 0, y: 0, radius: 1 },
137
137
  -1,
138
138
  0,
@@ -145,8 +145,8 @@ describe('sweepCollisionShape', () => {
145
145
  });
146
146
 
147
147
  it('fails closed and clears reused output for unsupported or invalid input', () => {
148
- const out = createCollisionTimeOfImpact();
149
- sweepCollisionShape(
148
+ const out = createCollisionTimeOfImpact2D();
149
+ sweepCollisionShape2D(
150
150
  { kind: 'circle', x: 0, y: 0, radius: 1 },
151
151
  10,
152
152
  0,
@@ -156,11 +156,11 @@ describe('sweepCollisionShape', () => {
156
156
  out,
157
157
  );
158
158
  expect(
159
- sweepCollisionShape({ kind: 'point', x: 0, y: 0 }, 1, 0, { kind: 'circle', x: 2, y: 0, radius: 1 }, 0, 0, out),
159
+ sweepCollisionShape2D({ kind: 'point', x: 0, y: 0 }, 1, 0, { kind: 'circle', x: 2, y: 0, radius: 1 }, 0, 0, out),
160
160
  ).toBe(false);
161
161
  expect(out).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
162
162
  expect(
163
- sweepCollisionShape(
163
+ sweepCollisionShape2D(
164
164
  { kind: 'circle', x: 0, y: 0, radius: 1 },
165
165
  Number.NaN,
166
166
  0,