@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.
- package/dist/collideContactManifold2D.d.ts +3 -0
- package/dist/collideContactManifold2D.d.ts.map +1 -0
- package/dist/{collideContactManifold.js → collideContactManifold2D.js} +6 -6
- package/dist/collideContactManifold2D.js.map +1 -0
- package/dist/collisionShapeValidation.d.ts +3 -3
- package/dist/collisionShapeValidation.d.ts.map +1 -1
- package/dist/collisionShapeValidation.js +6 -3
- package/dist/collisionShapeValidation.js.map +1 -1
- package/dist/collisionSupport2D.d.ts +12 -0
- package/dist/collisionSupport2D.d.ts.map +1 -0
- package/dist/collisionSupport2D.js +111 -0
- package/dist/collisionSupport2D.js.map +1 -0
- package/dist/contactFeatureId.js +1 -1
- package/dist/contactFeatureId.js.map +1 -1
- package/dist/contactManifold.d.ts +3 -3
- package/dist/contactManifold.d.ts.map +1 -1
- package/dist/contactManifold.js +2 -2
- package/dist/contactManifold.js.map +1 -1
- package/dist/contract.d.ts +8 -5
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +8 -5
- package/dist/contract.js.map +1 -1
- package/dist/convexVertices.d.ts +3 -3
- package/dist/convexVertices.d.ts.map +1 -1
- package/dist/convexVertices.js.map +1 -1
- package/dist/enableCollisionGuards.js +30 -11
- package/dist/enableCollisionGuards.js.map +1 -1
- package/dist/explainCollisionTest2D.d.ts +3 -0
- package/dist/explainCollisionTest2D.d.ts.map +1 -0
- package/dist/explainCollisionTest2D.js +38 -0
- package/dist/explainCollisionTest2D.js.map +1 -0
- package/dist/gjk2D.d.ts +4 -0
- package/dist/gjk2D.d.ts.map +1 -0
- package/dist/gjk2D.js +340 -0
- package/dist/gjk2D.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/manifold.d.ts +3 -3
- package/dist/manifold.d.ts.map +1 -1
- package/dist/manifold.js +2 -2
- package/dist/manifold.js.map +1 -1
- package/dist/pointContainment.d.ts +2 -2
- package/dist/pointContainment.d.ts.map +1 -1
- package/dist/pointContainment.js +3 -3
- package/dist/pointContainment.js.map +1 -1
- package/dist/raycastCollisionShape2D.d.ts +4 -0
- package/dist/raycastCollisionShape2D.d.ts.map +1 -0
- package/dist/{raycastCollisionShape.js → raycastCollisionShape2D.js} +7 -7
- package/dist/raycastCollisionShape2D.js.map +1 -0
- package/dist/registerBuiltInCollisionPairTests2D.d.ts +2 -0
- package/dist/registerBuiltInCollisionPairTests2D.d.ts.map +1 -0
- package/dist/registerBuiltInCollisionPairTests2D.js +32 -0
- package/dist/registerBuiltInCollisionPairTests2D.js.map +1 -0
- package/dist/segmentCollision.d.ts +6 -6
- package/dist/segmentCollision.d.ts.map +1 -1
- package/dist/segmentCollision.js +2 -2
- package/dist/segmentCollision.js.map +1 -1
- package/dist/shapeCollision.d.ts +11 -11
- package/dist/shapeCollision.d.ts.map +1 -1
- package/dist/shapeCollision.js +62 -65
- package/dist/shapeCollision.js.map +1 -1
- package/dist/shapeContact.d.ts +11 -11
- package/dist/shapeContact.d.ts.map +1 -1
- package/dist/shapeContact.js +12 -12
- package/dist/shapeContact.js.map +1 -1
- package/dist/sweepCollisionShape2D.d.ts +4 -0
- package/dist/sweepCollisionShape2D.d.ts.map +1 -0
- package/dist/{sweepCollisionShape.js → sweepCollisionShape2D.js} +10 -10
- package/dist/sweepCollisionShape2D.js.map +1 -0
- package/dist/testCollision2D.d.ts +4 -0
- package/dist/testCollision2D.d.ts.map +1 -0
- package/dist/testCollision2D.js +43 -0
- package/dist/testCollision2D.js.map +1 -0
- package/package.json +4 -4
- package/src/{collideContactManifold.test.ts → collideContactManifold2D.test.ts} +54 -54
- package/src/collisionShapeValidation.test.ts +13 -13
- package/src/collisionSupport2D.test.ts +166 -0
- package/src/contactManifold.test.ts +11 -11
- package/src/convexVertices.test.ts +4 -4
- package/src/enableCollisionGuards.test.ts +50 -15
- package/src/explainCollisionTest2D.test.ts +75 -0
- package/src/gjk2D.test.ts +219 -0
- package/src/manifold.test.ts +7 -7
- package/src/pointContainment.test.ts +45 -41
- package/src/{raycastCollisionShape.test.ts → raycastCollisionShape2D.test.ts} +26 -19
- package/src/registerBuiltInCollisionPairTests2D.test.ts +62 -0
- package/src/segmentCollision.test.ts +10 -10
- package/src/shapeCollision.test.ts +103 -103
- package/src/shapeContact.test.ts +76 -76
- package/src/{sweepCollisionShape.test.ts → sweepCollisionShape2D.test.ts} +26 -26
- package/src/testCollision2D.test.ts +175 -0
- package/dist/collideContactManifold.d.ts +0 -3
- package/dist/collideContactManifold.d.ts.map +0 -1
- package/dist/collideContactManifold.js.map +0 -1
- package/dist/explainCollisionTest.d.ts +0 -3
- package/dist/explainCollisionTest.d.ts.map +0 -1
- package/dist/explainCollisionTest.js +0 -21
- package/dist/explainCollisionTest.js.map +0 -1
- package/dist/raycastCollisionShape.d.ts +0 -4
- package/dist/raycastCollisionShape.d.ts.map +0 -1
- package/dist/raycastCollisionShape.js.map +0 -1
- package/dist/sweepCollisionShape.d.ts +0 -4
- package/dist/sweepCollisionShape.d.ts.map +0 -1
- package/dist/sweepCollisionShape.js.map +0 -1
- package/dist/testCollision.d.ts +0 -4
- package/dist/testCollision.d.ts.map +0 -1
- package/dist/testCollision.js +0 -97
- package/dist/testCollision.js.map +0 -1
- package/src/explainCollisionTest.test.ts +0 -52
- package/src/testCollision.test.ts +0 -97
package/src/shapeContact.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CollisionAabb2D, CollisionCircle2D, CollisionObb2D, CollisionPolygon2D } from '@flighthq/types/contract';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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:
|
|
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):
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
66
|
-
const second =
|
|
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 =
|
|
80
|
-
const lean =
|
|
81
|
-
const a:
|
|
82
|
-
const b:
|
|
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 =
|
|
97
|
-
const reversed =
|
|
98
|
-
const box:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
139
|
-
const box:
|
|
140
|
-
const platform:
|
|
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 =
|
|
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:
|
|
155
|
-
const box:
|
|
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 =
|
|
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 =
|
|
179
|
-
const viaPolygon =
|
|
180
|
-
const box:
|
|
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:
|
|
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 =
|
|
194
|
-
const degenerate:
|
|
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 =
|
|
206
|
-
const circle:
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
254
|
-
const a:
|
|
255
|
-
const b:
|
|
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 =
|
|
267
|
-
const lean =
|
|
268
|
-
const a:
|
|
269
|
-
const b:
|
|
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 =
|
|
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 =
|
|
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:
|
|
291
|
-
const circle:
|
|
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 =
|
|
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 =
|
|
315
|
-
const circle:
|
|
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 =
|
|
325
|
+
const out = createCollisionContactManifold2D();
|
|
326
326
|
// Sitting diagonally off the square's top-right corner, just inside the radius.
|
|
327
|
-
const circle:
|
|
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 =
|
|
338
|
-
const upper:
|
|
339
|
-
const lower:
|
|
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 =
|
|
353
|
-
const lean =
|
|
354
|
-
const a:
|
|
355
|
-
const b:
|
|
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 =
|
|
365
|
-
const a:
|
|
366
|
-
const b:
|
|
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 =
|
|
379
|
-
const box:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
411
|
-
const lean =
|
|
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 =
|
|
423
|
-
const triangle:
|
|
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 =
|
|
433
|
-
const clockwise =
|
|
432
|
+
const counterClockwise = createCollisionContactManifold2D();
|
|
433
|
+
const clockwise = createCollisionContactManifold2D();
|
|
434
434
|
const slab = square(0, 0, 5);
|
|
435
|
-
const reversedSlab:
|
|
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 =
|
|
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 {
|
|
3
|
+
import { createCollisionTimeOfImpact2D, sweepCollisionShape2D } from './sweepCollisionShape2D';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('createCollisionTimeOfImpact2D', () => {
|
|
6
6
|
it('creates a cleared reusable output record', () => {
|
|
7
|
-
expect(
|
|
7
|
+
expect(createCollisionTimeOfImpact2D()).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
|
|
8
8
|
});
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
describe('
|
|
11
|
+
describe('sweepCollisionShape2D', () => {
|
|
12
12
|
it('keeps continuous-SAT state isolated from a nested sweep triggered by the output setter', () => {
|
|
13
|
-
const out =
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
createCollisionTimeOfImpact2D(),
|
|
34
34
|
);
|
|
35
35
|
},
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
expect(
|
|
39
|
-
|
|
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 =
|
|
56
|
+
const out = createCollisionTimeOfImpact2D();
|
|
57
57
|
expect(
|
|
58
|
-
|
|
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 =
|
|
76
|
-
expect(
|
|
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 =
|
|
81
|
-
expect(
|
|
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 =
|
|
88
|
+
const out = createCollisionTimeOfImpact2D();
|
|
89
89
|
expect(
|
|
90
|
-
|
|
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 =
|
|
108
|
+
const out = createCollisionTimeOfImpact2D();
|
|
109
109
|
|
|
110
|
-
expect(
|
|
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(
|
|
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 =
|
|
119
|
+
const out = createCollisionTimeOfImpact2D();
|
|
120
120
|
expect(
|
|
121
|
-
|
|
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
|
-
|
|
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 =
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
+
sweepCollisionShape2D(
|
|
164
164
|
{ kind: 'circle', x: 0, y: 0, radius: 1 },
|
|
165
165
|
Number.NaN,
|
|
166
166
|
0,
|