@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/manifold.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { clearCollisionManifold2D, createCollisionManifold2D } from './manifold';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('clearCollisionManifold2D', () => {
|
|
6
6
|
it('resets every field to the non-overlapping state', () => {
|
|
7
7
|
const manifold = { overlapping: true, normalX: 0.5, normalY: -0.5, depth: 3 };
|
|
8
|
-
|
|
8
|
+
clearCollisionManifold2D(manifold);
|
|
9
9
|
expect(manifold.overlapping).toBe(false);
|
|
10
10
|
expect(manifold.normalX).toBe(0);
|
|
11
11
|
expect(manifold.normalY).toBe(0);
|
|
@@ -13,9 +13,9 @@ describe('clearCollisionManifold', () => {
|
|
|
13
13
|
});
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
describe('
|
|
16
|
+
describe('createCollisionManifold2D', () => {
|
|
17
17
|
it('allocates a fresh manifold in the non-overlapping state', () => {
|
|
18
|
-
const manifold =
|
|
18
|
+
const manifold = createCollisionManifold2D();
|
|
19
19
|
expect(manifold.overlapping).toBe(false);
|
|
20
20
|
expect(manifold.normalX).toBe(0);
|
|
21
21
|
expect(manifold.normalY).toBe(0);
|
|
@@ -23,8 +23,8 @@ describe('createCollisionManifold', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('returns an independent object each call', () => {
|
|
26
|
-
const a =
|
|
27
|
-
const b =
|
|
26
|
+
const a = createCollisionManifold2D();
|
|
27
|
+
const b = createCollisionManifold2D();
|
|
28
28
|
a.overlapping = true;
|
|
29
29
|
expect(b.overlapping).toBe(false);
|
|
30
30
|
});
|
|
@@ -1,79 +1,83 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CollisionBuiltInShape2D } from '@flighthq/types/contract';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { getCollisionShapeContainsPoint2D } from './pointContainment';
|
|
5
5
|
|
|
6
|
-
describe('
|
|
6
|
+
describe('getCollisionShapeContainsPoint2D', () => {
|
|
7
7
|
it('tests a circle inside, outside, and on the boundary (inclusive)', () => {
|
|
8
|
-
const circle:
|
|
9
|
-
expect(
|
|
10
|
-
expect(
|
|
11
|
-
expect(
|
|
8
|
+
const circle: CollisionBuiltInShape2D = { kind: 'circle', x: 0, y: 0, radius: 5 };
|
|
9
|
+
expect(getCollisionShapeContainsPoint2D(circle, 1, 1)).toBe(true);
|
|
10
|
+
expect(getCollisionShapeContainsPoint2D(circle, 5, 0)).toBe(true); // on the boundary
|
|
11
|
+
expect(getCollisionShapeContainsPoint2D(circle, 6, 0)).toBe(false);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('tests an axis-aligned box, boundary-inclusive', () => {
|
|
15
|
-
const box:
|
|
16
|
-
expect(
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
15
|
+
const box: CollisionBuiltInShape2D = { kind: 'aabb', minX: 0, minY: 0, maxX: 10, maxY: 10 };
|
|
16
|
+
expect(getCollisionShapeContainsPoint2D(box, 5, 5)).toBe(true);
|
|
17
|
+
expect(getCollisionShapeContainsPoint2D(box, 0, 10)).toBe(true); // corner
|
|
18
|
+
expect(getCollisionShapeContainsPoint2D(box, -1, 5)).toBe(false);
|
|
19
|
+
expect(getCollisionShapeContainsPoint2D(box, 11, 5)).toBe(false);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it('tests a rotated oriented box in its own frame', () => {
|
|
23
23
|
// 45-degree box, half-extents 2x2. A point on the world axis at distance 2.8 is inside
|
|
24
24
|
// (the box reaches ~2.83 along its diagonal) but the same distance is outside a smaller reach.
|
|
25
|
-
const obb:
|
|
26
|
-
expect(
|
|
27
|
-
expect(
|
|
28
|
-
expect(
|
|
25
|
+
const obb: CollisionBuiltInShape2D = { kind: 'obb', x: 0, y: 0, halfW: 2, halfH: 2, rotation: Math.PI / 4 };
|
|
26
|
+
expect(getCollisionShapeContainsPoint2D(obb, 0, 0)).toBe(true);
|
|
27
|
+
expect(getCollisionShapeContainsPoint2D(obb, 2.8, 0)).toBe(true); // toward a rotated corner
|
|
28
|
+
expect(getCollisionShapeContainsPoint2D(obb, 2.1, 2.1)).toBe(false); // just past a flat edge
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('tests a convex hexagon inside and outside', () => {
|
|
32
|
-
const hex:
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
35
|
-
expect(
|
|
36
|
-
expect(
|
|
32
|
+
const hex: CollisionBuiltInShape2D = { kind: 'polygon', points: [2, 0, 1, 2, -1, 2, -2, 0, -1, -2, 1, -2] };
|
|
33
|
+
expect(getCollisionShapeContainsPoint2D(hex, 0, 0)).toBe(true);
|
|
34
|
+
expect(getCollisionShapeContainsPoint2D(hex, 1.9, 0)).toBe(true);
|
|
35
|
+
expect(getCollisionShapeContainsPoint2D(hex, 3, 0)).toBe(false);
|
|
36
|
+
expect(getCollisionShapeContainsPoint2D(hex, 0, 3)).toBe(false);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('rejects polygons with fewer than three vertices', () => {
|
|
40
|
-
const polygon:
|
|
41
|
-
expect(
|
|
40
|
+
const polygon: CollisionBuiltInShape2D = { kind: 'polygon', points: [0, 0, 1, 1] };
|
|
41
|
+
expect(getCollisionShapeContainsPoint2D(polygon, 0.5, 0.5)).toBe(false);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it('keeps containment proportional for a tiny polygon', () => {
|
|
45
|
-
const polygon:
|
|
45
|
+
const polygon: CollisionBuiltInShape2D = {
|
|
46
46
|
kind: 'polygon',
|
|
47
47
|
points: [0, 0, 1e-6, 0, 1e-6, 1e-6, 0, 1e-6],
|
|
48
48
|
};
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
49
|
+
expect(getCollisionShapeContainsPoint2D(polygon, 0.5e-6, 0.5e-6)).toBe(true);
|
|
50
|
+
expect(getCollisionShapeContainsPoint2D(polygon, 2e-6, 0.5e-6)).toBe(false);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('tests a point lying on a segment', () => {
|
|
54
|
-
const segment:
|
|
55
|
-
expect(
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
54
|
+
const segment: CollisionBuiltInShape2D = { kind: 'segment', x0: 0, y0: 0, x1: 10, y1: 0 };
|
|
55
|
+
expect(getCollisionShapeContainsPoint2D(segment, 5, 0)).toBe(true);
|
|
56
|
+
expect(getCollisionShapeContainsPoint2D(segment, 5, 0.1)).toBe(false);
|
|
57
|
+
expect(getCollisionShapeContainsPoint2D(segment, 11, 0)).toBe(false);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it('tests coincidence with a point collider', () => {
|
|
61
|
-
const point:
|
|
62
|
-
expect(
|
|
63
|
-
expect(
|
|
61
|
+
const point: CollisionBuiltInShape2D = { kind: 'point', x: 3, y: 4 };
|
|
62
|
+
expect(getCollisionShapeContainsPoint2D(point, 3, 4)).toBe(true);
|
|
63
|
+
expect(getCollisionShapeContainsPoint2D(point, 3, 5)).toBe(false);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('treats zero-radius circles and zero-length segments as point-like', () => {
|
|
67
|
-
const circle:
|
|
68
|
-
const segment:
|
|
69
|
-
expect(
|
|
70
|
-
expect(
|
|
71
|
-
expect(
|
|
72
|
-
expect(
|
|
67
|
+
const circle: CollisionBuiltInShape2D = { kind: 'circle', radius: 0, x: 3, y: 4 };
|
|
68
|
+
const segment: CollisionBuiltInShape2D = { kind: 'segment', x0: 3, x1: 3, y0: 4, y1: 4 };
|
|
69
|
+
expect(getCollisionShapeContainsPoint2D(circle, 3, 4)).toBe(true);
|
|
70
|
+
expect(getCollisionShapeContainsPoint2D(circle, 3, 4.001)).toBe(false);
|
|
71
|
+
expect(getCollisionShapeContainsPoint2D(segment, 3, 4)).toBe(true);
|
|
72
|
+
expect(getCollisionShapeContainsPoint2D(segment, 3, 4.001)).toBe(false);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
it('returns false for an unknown kind', () => {
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// The double cast is the test: a vendor kind is a COMPILE error at this signature now, because
|
|
77
|
+
// containment has no registry to answer one with. What is asserted here is the residual runtime
|
|
78
|
+
// behaviour for callers who reach it anyway — untyped JavaScript, or a cast like this one — which
|
|
79
|
+
// must still be the `false` sentinel rather than a crash reading fields that are not there.
|
|
80
|
+
const shape = { kind: 'acme.capsule', x: 0, y: 0 } as unknown as CollisionBuiltInShape2D;
|
|
81
|
+
expect(getCollisionShapeContainsPoint2D(shape, 0, 0)).toBe(false);
|
|
78
82
|
});
|
|
79
83
|
});
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CollisionRaycastHit2D, CollisionBuiltInShape2D } from '@flighthq/types/contract';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createCollisionRaycastHit2D, raycastCollisionShape2D } from './raycastCollisionShape2D';
|
|
5
5
|
|
|
6
|
-
function hit():
|
|
7
|
-
return
|
|
6
|
+
function hit(): CollisionRaycastHit2D {
|
|
7
|
+
return createCollisionRaycastHit2D();
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
describe('
|
|
10
|
+
describe('createCollisionRaycastHit2D', () => {
|
|
11
11
|
it('starts in the reusable miss state', () => {
|
|
12
|
-
expect(
|
|
12
|
+
expect(createCollisionRaycastHit2D()).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
|
|
13
13
|
});
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
describe('
|
|
16
|
+
describe('raycastCollisionShape2D', () => {
|
|
17
17
|
it('keeps polygon center state isolated from a nested raycast triggered by a point getter', () => {
|
|
18
18
|
const points = [2, -1, 4, -1, 4, 1, 2, 1];
|
|
19
19
|
let armed = false;
|
|
@@ -24,7 +24,14 @@ describe('raycastCollisionShape', () => {
|
|
|
24
24
|
if (armed) {
|
|
25
25
|
armed = false;
|
|
26
26
|
nestedCalls++;
|
|
27
|
-
|
|
27
|
+
raycastCollisionShape2D(
|
|
28
|
+
{ kind: 'polygon', points: [-102, -1, -100, -1, -100, 1, -102, 1] },
|
|
29
|
+
0,
|
|
30
|
+
0,
|
|
31
|
+
1,
|
|
32
|
+
0,
|
|
33
|
+
hit(),
|
|
34
|
+
);
|
|
28
35
|
}
|
|
29
36
|
return 2;
|
|
30
37
|
},
|
|
@@ -38,7 +45,7 @@ describe('raycastCollisionShape', () => {
|
|
|
38
45
|
});
|
|
39
46
|
|
|
40
47
|
const out = hit();
|
|
41
|
-
expect(
|
|
48
|
+
expect(raycastCollisionShape2D({ kind: 'polygon', points }, 0, 0, 1, 0, out)).toBe(true);
|
|
42
49
|
expect(nestedCalls).toBeGreaterThan(0);
|
|
43
50
|
expect(out.fraction).toBeCloseTo(2);
|
|
44
51
|
expect(out.normalX).toBeCloseTo(-1);
|
|
@@ -54,7 +61,7 @@ describe('raycastCollisionShape', () => {
|
|
|
54
61
|
['point', { kind: 'point', x: 2, y: 0 }, 2, 0, 0],
|
|
55
62
|
] as const)('hits a %s at its first exact fraction', (_name, shape, fraction, normalX, normalY) => {
|
|
56
63
|
const out = hit();
|
|
57
|
-
expect(
|
|
64
|
+
expect(raycastCollisionShape2D(shape as CollisionBuiltInShape2D, 0, 0, 1, 0, out)).toBe(true);
|
|
58
65
|
expect(out.fraction).toBeCloseTo(fraction);
|
|
59
66
|
expect(out.x).toBeCloseTo(fraction);
|
|
60
67
|
expect(out.y).toBeCloseTo(0);
|
|
@@ -64,33 +71,33 @@ describe('raycastCollisionShape', () => {
|
|
|
64
71
|
|
|
65
72
|
it('rotates an oriented-box hit normal back to world space', () => {
|
|
66
73
|
const out = hit();
|
|
67
|
-
const shape:
|
|
68
|
-
expect(
|
|
74
|
+
const shape: CollisionBuiltInShape2D = { kind: 'obb', x: 3, y: 0, halfW: 1, halfH: 0.5, rotation: Math.PI / 4 };
|
|
75
|
+
expect(raycastCollisionShape2D(shape, 0, 0, 1, 0, out)).toBe(true);
|
|
69
76
|
expect(out.normalX).toBeCloseTo(-Math.SQRT1_2);
|
|
70
77
|
expect(out.normalY).toBeCloseTo(Math.SQRT1_2);
|
|
71
78
|
});
|
|
72
79
|
|
|
73
80
|
it('reports an origin inside a shape at fraction zero with no entry normal', () => {
|
|
74
81
|
const out = hit();
|
|
75
|
-
expect(
|
|
82
|
+
expect(raycastCollisionShape2D({ kind: 'circle', x: 0, y: 0, radius: 2 }, 0, 0, 1, 0, out)).toBe(true);
|
|
76
83
|
expect(out).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
|
|
77
84
|
});
|
|
78
85
|
|
|
79
86
|
it('honours maxFraction and clears output on a miss', () => {
|
|
80
87
|
const out = { fraction: 9, x: 9, y: 9, normalX: 9, normalY: 9 };
|
|
81
|
-
expect(
|
|
88
|
+
expect(raycastCollisionShape2D({ kind: 'circle', x: 3, y: 0, radius: 1 }, 0, 0, 1, 0, out, 1)).toBe(false);
|
|
82
89
|
expect(out).toEqual({ fraction: 0, x: 0, y: 0, normalX: 0, normalY: 0 });
|
|
83
90
|
});
|
|
84
91
|
|
|
85
92
|
it('treats a zero direction as an exact point query', () => {
|
|
86
93
|
const out = hit();
|
|
87
|
-
expect(
|
|
88
|
-
expect(
|
|
94
|
+
expect(raycastCollisionShape2D({ kind: 'aabb', minX: -1, minY: -1, maxX: 1, maxY: 1 }, 0, 0, 0, 0, out)).toBe(true);
|
|
95
|
+
expect(raycastCollisionShape2D({ kind: 'aabb', minX: 2, minY: 2, maxX: 3, maxY: 3 }, 0, 0, 0, 0, out)).toBe(false);
|
|
89
96
|
});
|
|
90
97
|
|
|
91
98
|
it('keeps point-ray tolerance invariant when direction is rescaled', () => {
|
|
92
|
-
const shape:
|
|
93
|
-
expect(
|
|
94
|
-
expect(
|
|
99
|
+
const shape: CollisionBuiltInShape2D = { kind: 'point', x: 2, y: 1e-6 };
|
|
100
|
+
expect(raycastCollisionShape2D(shape, 0, 0, 1, 0, hit())).toBe(false);
|
|
101
|
+
expect(raycastCollisionShape2D(shape, 0, 0, 1e6, 0, hit())).toBe(false);
|
|
95
102
|
});
|
|
96
103
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CollisionShape2D } from '@flighthq/types/contract';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { getCollisionPairTest2D } from './collisionSupport2D';
|
|
5
|
+
import { createCollisionManifold2D } from './manifold';
|
|
6
|
+
import { registerBuiltInCollisionPairTests2D } from './registerBuiltInCollisionPairTests2D';
|
|
7
|
+
import { testCircleAabbCollision } from './shapeCollision';
|
|
8
|
+
|
|
9
|
+
registerBuiltInCollisionPairTests2D();
|
|
10
|
+
|
|
11
|
+
describe('registerBuiltInCollisionPairTests2D', () => {
|
|
12
|
+
it('registers exactly the ten canonical orders, and not their reverses', () => {
|
|
13
|
+
const kinds = ['circle', 'aabb', 'obb', 'polygon'];
|
|
14
|
+
const registered: string[] = [];
|
|
15
|
+
for (const kindA of kinds) {
|
|
16
|
+
for (const kindB of kinds) {
|
|
17
|
+
if (getCollisionPairTest2D(kindA, kindB) !== null) registered.push(`${kindA}-${kindB}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Ten, not sixteen: the four self-pairs plus one order of each of the six mixed pairs. The reverse
|
|
22
|
+
// orders are absent on purpose — `testCollision2D` reaches them by trying the reverse key and
|
|
23
|
+
// negating, so registering both would double the table for no new geometry.
|
|
24
|
+
expect(registered).toEqual([
|
|
25
|
+
'circle-circle',
|
|
26
|
+
'circle-aabb',
|
|
27
|
+
'circle-obb',
|
|
28
|
+
'circle-polygon',
|
|
29
|
+
'aabb-aabb',
|
|
30
|
+
'aabb-obb',
|
|
31
|
+
'aabb-polygon',
|
|
32
|
+
'obb-obb',
|
|
33
|
+
'obb-polygon',
|
|
34
|
+
'polygon-polygon',
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('leaves the area-less kinds out of the table entirely', () => {
|
|
39
|
+
for (const kind of ['segment', 'point']) {
|
|
40
|
+
expect(getCollisionPairTest2D('circle', kind)).toBeNull();
|
|
41
|
+
expect(getCollisionPairTest2D(kind, 'circle')).toBeNull();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('routes each entry to the direct typed test it specializes', () => {
|
|
46
|
+
const throughRegistry = createCollisionManifold2D();
|
|
47
|
+
const direct = createCollisionManifold2D();
|
|
48
|
+
const circle: CollisionShape2D = { kind: 'circle', x: 15, y: 5, radius: 7 };
|
|
49
|
+
const box: CollisionShape2D = { kind: 'aabb', minX: 0, minY: 0, maxX: 10, maxY: 10 };
|
|
50
|
+
|
|
51
|
+
const entry = getCollisionPairTest2D('circle', 'aabb');
|
|
52
|
+
expect(entry).not.toBeNull();
|
|
53
|
+
expect(entry?.(circle, box, throughRegistry)).toBe(true);
|
|
54
|
+
expect(testCircleAabbCollision({ x: 15, y: 5, radius: 7 }, { minX: 0, minY: 0, maxX: 10, maxY: 10 }, direct)).toBe(
|
|
55
|
+
true,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// The adapter casts rather than re-dispatching, so this is what proves the cast lands on the right
|
|
59
|
+
// function: an adapter wired to the wrong pair would still typecheck and still return a manifold.
|
|
60
|
+
expect(throughRegistry).toEqual(direct);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
CollisionAabb2D,
|
|
3
|
+
CollisionCircle2D,
|
|
4
|
+
CollisionObb2D,
|
|
5
|
+
CollisionPolygon2D,
|
|
6
|
+
CollisionSegment2D,
|
|
7
7
|
} from '@flighthq/types/contract';
|
|
8
8
|
import { describe, expect, it } from 'vitest';
|
|
9
9
|
|
|
@@ -15,12 +15,12 @@ import {
|
|
|
15
15
|
testSegmentSegmentCollision,
|
|
16
16
|
} from './segmentCollision';
|
|
17
17
|
|
|
18
|
-
function segment(x0: number, y0: number, x1: number, y1: number):
|
|
18
|
+
function segment(x0: number, y0: number, x1: number, y1: number): CollisionSegment2D {
|
|
19
19
|
return { x0, y0, x1, y1 };
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
describe('testSegmentAabbCollision', () => {
|
|
23
|
-
const box:
|
|
23
|
+
const box: CollisionAabb2D = { minX: 0, minY: 0, maxX: 10, maxY: 10 };
|
|
24
24
|
|
|
25
25
|
it('is true for a segment crossing the box', () => {
|
|
26
26
|
expect(testSegmentAabbCollision(segment(-5, 5, 15, 5), box)).toBe(true);
|
|
@@ -45,7 +45,7 @@ describe('testSegmentAabbCollision', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
describe('testSegmentCircleCollision', () => {
|
|
48
|
-
const circle:
|
|
48
|
+
const circle: CollisionCircle2D = { x: 0, y: 0, radius: 2 };
|
|
49
49
|
|
|
50
50
|
it('is true for a segment passing through the circle', () => {
|
|
51
51
|
expect(testSegmentCircleCollision(segment(-10, 0, 10, 0), circle)).toBe(true);
|
|
@@ -65,7 +65,7 @@ describe('testSegmentCircleCollision', () => {
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
describe('testSegmentObbCollision', () => {
|
|
68
|
-
const obb:
|
|
68
|
+
const obb: CollisionObb2D = { x: 0, y: 0, halfW: 3, halfH: 3, rotation: Math.PI / 4 };
|
|
69
69
|
|
|
70
70
|
it('is true for a segment crossing the rotated box', () => {
|
|
71
71
|
expect(testSegmentObbCollision(segment(-10, 0, 10, 0), obb)).toBe(true);
|
|
@@ -77,7 +77,7 @@ describe('testSegmentObbCollision', () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
describe('testSegmentPolygonCollision', () => {
|
|
80
|
-
const square:
|
|
80
|
+
const square: CollisionPolygon2D = { points: [0, 0, 10, 0, 10, 10, 0, 10] };
|
|
81
81
|
|
|
82
82
|
it('is true for a segment crossing the polygon', () => {
|
|
83
83
|
expect(testSegmentPolygonCollision(segment(-5, 5, 15, 5), square)).toBe(true);
|