dacha 0.18.0-alpha.1 → 0.18.0-alpha.2
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/build/contrib/components/collider/index.d.ts +2 -2
- package/build/contrib/systems/physics-system/subsystems/collision-detection/aabb-builders/build-capsule-aabb.d.ts +2 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/aabb-builders/build-capsule-aabb.js +13 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/aabb-builders/build-segment-aabb.js +1 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/aabb-builders/index.js +2 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/constants.d.ts +1 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/constants.js +1 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/dispersion-calculator/index.js +18 -4
- package/build/contrib/systems/physics-system/subsystems/collision-detection/geometry-builders/build-capsule-geometry.d.ts +3 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/geometry-builders/build-capsule-geometry.js +34 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/geometry-builders/index.d.ts +2 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/geometry-builders/index.js +2 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +1 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +1 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +13 -2
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +30 -8
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/check-box-and-capsule-intersection.d.ts +20 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/check-box-and-capsule-intersection.js +32 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/utils.d.ts +15 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/utils.js +135 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-circle/check-box-and-circle-intersection.js +5 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/check-box-and-segment-intersection.js +4 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/utils.d.ts +0 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/utils.js +22 -17
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/capsule-capsule/check-capsules-intersection.d.ts +14 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/capsule-capsule/check-capsules-intersection.js +26 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-capsule/check-circle-and-capsule-intersection.d.ts +11 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-capsule/check-circle-and-capsule-intersection.js +40 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-circle/check-circles-intersection.js +1 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-segment/check-circle-and-segment-intersection.js +5 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/capsule.d.ts +12 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/capsule.js +37 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/normals.d.ts +3 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/normals.js +8 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.d.ts +4 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.js +8 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/projections.d.ts +9 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/projections.js +32 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/segment-distance.d.ts +17 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/segment-distance.js +63 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +30 -3
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/point-box/check-point-and-box-intersection.js +5 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/point-capsule/check-point-and-capsule-intersection.d.ts +10 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/point-capsule/check-point-and-capsule-intersection.js +33 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/point-circle/check-point-and-circle-intersection.js +4 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/point-segment/check-point-and-segment-intersection.js +5 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/ray-box/check-ray-and-box-intersection.js +3 -4
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/ray-capsule/check-ray-and-capsule-intersection.d.ts +11 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/ray-capsule/check-ray-and-capsule-intersection.js +126 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/ray-circle/check-ray-and-circle-intersection.js +3 -4
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/ray-segment/check-ray-and-segment-intersection.js +3 -4
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/segment-capsule/check-segment-and-capsule-intersection.d.ts +12 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/segment-capsule/check-segment-and-capsule-intersection.js +24 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.d.ts +3 -2
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +34 -20
- package/build/contrib/systems/physics-system/subsystems/collision-detection/types.d.ts +4 -1
- package/build/engine/math-lib/vector/ops.js +8 -6
- package/package.json +1 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/utils.d.ts +0 -9
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/utils.js +0 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Component } from '../../../engine/component';
|
|
2
2
|
import type { Point } from '../../../engine/math-lib';
|
|
3
3
|
export interface ColliderConfig {
|
|
4
|
-
type: 'box' | 'circle' | 'segment';
|
|
4
|
+
type: 'box' | 'circle' | 'segment' | 'capsule';
|
|
5
5
|
centerX: number;
|
|
6
6
|
centerY: number;
|
|
7
7
|
sizeX?: number;
|
|
@@ -48,7 +48,7 @@ export interface ColliderConfig {
|
|
|
48
48
|
* @category Components
|
|
49
49
|
*/
|
|
50
50
|
export declare class Collider extends Component {
|
|
51
|
-
type: 'box' | 'circle' | 'segment';
|
|
51
|
+
type: 'box' | 'circle' | 'segment' | 'capsule';
|
|
52
52
|
centerX: number;
|
|
53
53
|
centerY: number;
|
|
54
54
|
sizeX?: number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const buildCapsuleAABB = (geometry) => {
|
|
2
|
+
const { point1, point2, radius } = geometry;
|
|
3
|
+
return {
|
|
4
|
+
min: {
|
|
5
|
+
x: Math.min(point1.x, point2.x) - radius,
|
|
6
|
+
y: Math.min(point1.y, point2.y) - radius,
|
|
7
|
+
},
|
|
8
|
+
max: {
|
|
9
|
+
x: Math.max(point1.x, point2.x) + radius,
|
|
10
|
+
y: Math.max(point1.y, point2.y) + radius,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
};
|
package/build/contrib/systems/physics-system/subsystems/collision-detection/aabb-builders/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { buildBoxAABB } from './build-box-aabb';
|
|
2
|
+
import { buildCapsuleAABB } from './build-capsule-aabb';
|
|
2
3
|
import { buildCircleAABB } from './build-circle-aabb';
|
|
3
4
|
import { buildPointAABB } from './build-point-aabb';
|
|
4
5
|
import { buildRayAABB } from './build-ray-aabb';
|
|
5
6
|
import { buildSegmentAABB } from './build-segment-aabb';
|
|
6
7
|
export const aabbBuilders = {
|
|
7
8
|
box: buildBoxAABB,
|
|
9
|
+
capsule: buildCapsuleAABB,
|
|
8
10
|
circle: buildCircleAABB,
|
|
9
11
|
segment: buildSegmentAABB,
|
|
10
12
|
point: buildPointAABB,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const INTERSECTION_EPSILON = 0.000001;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const INTERSECTION_EPSILON = 1e-6;
|
|
@@ -16,12 +16,26 @@ export class DispersionCalculator {
|
|
|
16
16
|
this.sampleSize += 1;
|
|
17
17
|
}
|
|
18
18
|
removeFromSample(aabb) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (this.sampleSize === 0) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
22
|
this.sampleSize -= 1;
|
|
23
|
+
if (this.sampleSize === 0) {
|
|
24
|
+
this.sum = 0;
|
|
25
|
+
this.squaredSum = 0;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const average = (aabb.min[this.axis] + aabb.max[this.axis]) * 0.5;
|
|
29
|
+
this.sum -= average;
|
|
30
|
+
this.squaredSum -= average ** 2;
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
getDispersion() {
|
|
25
|
-
|
|
34
|
+
if (this.sampleSize <= 1) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
const average = this.sum / this.sampleSize;
|
|
38
|
+
const dispersion = this.squaredSum / this.sampleSize - average ** 2;
|
|
39
|
+
return Math.max(0, dispersion);
|
|
26
40
|
}
|
|
27
41
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { VectorOps } from '../../../../../../engine/math-lib';
|
|
2
|
+
export function buildCapsuleGeometry(collider, transform) {
|
|
3
|
+
const centerX = collider.centerX + transform.world.position.x;
|
|
4
|
+
const centerY = collider.centerY + transform.world.position.y;
|
|
5
|
+
const point1X = collider.point1?.x ?? 0;
|
|
6
|
+
const point1Y = collider.point1?.y ?? 0;
|
|
7
|
+
const point2X = collider.point2?.x ?? 0;
|
|
8
|
+
const point2Y = collider.point2?.y ?? 0;
|
|
9
|
+
const rotation = transform.world.rotation;
|
|
10
|
+
const scaleX = transform.world.scale.x;
|
|
11
|
+
const scaleY = transform.world.scale.y;
|
|
12
|
+
const cos = Math.cos(rotation);
|
|
13
|
+
const sin = Math.sin(rotation);
|
|
14
|
+
const buildPoint = (x, y) => {
|
|
15
|
+
const scaledX = x * scaleX;
|
|
16
|
+
const scaledY = y * scaleY;
|
|
17
|
+
return {
|
|
18
|
+
x: scaledX * cos - scaledY * sin + centerX,
|
|
19
|
+
y: scaledX * sin + scaledY * cos + centerY,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const point1 = buildPoint(point1X, point1Y);
|
|
23
|
+
const point2 = buildPoint(point2X, point2Y);
|
|
24
|
+
return {
|
|
25
|
+
center: {
|
|
26
|
+
x: (point1.x + point2.x) / 2,
|
|
27
|
+
y: (point1.y + point2.y) / 2,
|
|
28
|
+
},
|
|
29
|
+
point1,
|
|
30
|
+
point2,
|
|
31
|
+
normal: VectorOps.getNormal(point1.x, point2.x, point1.y, point2.y),
|
|
32
|
+
radius: (collider.radius ?? 0) * Math.max(scaleX, scaleY),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Collider, Transform } from '../../../../../components';
|
|
2
2
|
import type { Geometry } from '../types';
|
|
3
3
|
import { buildBoxGeometry } from './build-box-geometry';
|
|
4
|
+
import { buildCapsuleGeometry } from './build-capsule-geometry';
|
|
4
5
|
import { buildCircleGeometry } from './build-circle-geometry';
|
|
5
6
|
import { buildPointGeometry } from './build-point-geometry';
|
|
6
7
|
import { buildRayGeometry } from './build-ray-geometry';
|
|
@@ -8,6 +9,7 @@ import { buildSegmentGeometry } from './build-segment-geometry';
|
|
|
8
9
|
export type BuildGeometryFn = (collider: Collider, transform: Transform) => Geometry;
|
|
9
10
|
export declare const geometryBuilders: {
|
|
10
11
|
box: typeof buildBoxGeometry;
|
|
12
|
+
capsule: typeof buildCapsuleGeometry;
|
|
11
13
|
circle: typeof buildCircleGeometry;
|
|
12
14
|
segment: typeof buildSegmentGeometry;
|
|
13
15
|
point: typeof buildPointGeometry;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { buildBoxGeometry } from './build-box-geometry';
|
|
2
|
+
import { buildCapsuleGeometry } from './build-capsule-geometry';
|
|
2
3
|
import { buildCircleGeometry } from './build-circle-geometry';
|
|
3
4
|
import { buildPointGeometry } from './build-point-geometry';
|
|
4
5
|
import { buildRayGeometry } from './build-ray-geometry';
|
|
5
6
|
import { buildSegmentGeometry } from './build-segment-geometry';
|
|
6
7
|
export const geometryBuilders = {
|
|
7
8
|
box: buildBoxGeometry,
|
|
9
|
+
capsule: buildCapsuleGeometry,
|
|
8
10
|
circle: buildCircleGeometry,
|
|
9
11
|
segment: buildSegmentGeometry,
|
|
10
12
|
point: buildPointGeometry,
|
|
@@ -183,7 +183,7 @@ export class CollisionDetectionSubsystem {
|
|
|
183
183
|
testAABB(proxy1, proxy2, axis) {
|
|
184
184
|
const aabb1 = proxy1.aabb;
|
|
185
185
|
const aabb2 = proxy2.aabb;
|
|
186
|
-
return (aabb1.max[axis]
|
|
186
|
+
return (aabb1.max[axis] >= aabb2.min[axis] && aabb1.min[axis] <= aabb2.max[axis]);
|
|
187
187
|
}
|
|
188
188
|
testCollisionLayers(proxy1, proxy2) {
|
|
189
189
|
return this.collisionMatrix[proxy1.layer]?.[proxy2.layer] ?? true;
|
|
@@ -6,12 +6,23 @@ export interface AxisOverlap {
|
|
|
6
6
|
axis: Vector2;
|
|
7
7
|
overlap: number;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Runs the SAT pass for one box's face normals against another box.
|
|
11
|
+
*
|
|
12
|
+
* Each face normal is a potential separating axis. If any projection pair does
|
|
13
|
+
* not overlap, the boxes cannot intersect. Otherwise, the axis with the
|
|
14
|
+
* smallest overlap is the least expensive direction to separate the boxes, so
|
|
15
|
+
* it becomes the candidate collision normal for this polygon.
|
|
16
|
+
*/
|
|
9
17
|
export declare const findMinBoxesOverlap: (geometry1: BoxGeometry, geometry2: BoxGeometry) => AxisOverlap | false;
|
|
10
18
|
/**
|
|
11
19
|
* Builds up to two box-vs-box contact points by clipping the incident edge
|
|
12
20
|
* against the side planes of the reference edge.
|
|
13
21
|
*
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
22
|
+
* The process mirrors the standard contact manifold construction for SAT:
|
|
23
|
+
* choose a reference face, choose the incident face most opposed to it, clip
|
|
24
|
+
* the incident segment by the reference face's tangent bounds, then project
|
|
25
|
+
* surviving points back onto the reference face plane. The projected points are
|
|
26
|
+
* suitable as world-space contacts for impulse resolution.
|
|
16
27
|
*/
|
|
17
28
|
export declare const buildContactPoints: (referencePolygon: BoxGeometry, referenceNormal: Vector2, incidentPolygon: BoxGeometry) => Point[];
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { Vector2, VectorOps } from '../../../../../../../engine/math-lib';
|
|
2
|
-
import {
|
|
2
|
+
import { getProjectionOverlap, projectPolygon } from '../common/projections';
|
|
3
3
|
export const CONTACT_EPSILON = 1e-4;
|
|
4
4
|
export const MAX_CONTACT_POINTS = 2;
|
|
5
|
+
/**
|
|
6
|
+
* Runs the SAT pass for one box's face normals against another box.
|
|
7
|
+
*
|
|
8
|
+
* Each face normal is a potential separating axis. If any projection pair does
|
|
9
|
+
* not overlap, the boxes cannot intersect. Otherwise, the axis with the
|
|
10
|
+
* smallest overlap is the least expensive direction to separate the boxes, so
|
|
11
|
+
* it becomes the candidate collision normal for this polygon.
|
|
12
|
+
*/
|
|
5
13
|
export const findMinBoxesOverlap = (geometry1, geometry2) => {
|
|
6
14
|
let minOverlap = Infinity;
|
|
7
15
|
let bestAxis = geometry1.edges[0].normal;
|
|
8
|
-
for (
|
|
16
|
+
for (let i = 0; i < 2; i += 1) {
|
|
17
|
+
const edge = geometry1.edges[i];
|
|
9
18
|
const axis = edge.normal;
|
|
10
19
|
const projection1 = projectPolygon(geometry1.points, axis);
|
|
11
20
|
const projection2 = projectPolygon(geometry2.points, axis);
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
if (distance1 > INTERSECTION_EPSILON || distance2 > INTERSECTION_EPSILON) {
|
|
21
|
+
const overlap = getProjectionOverlap(projection1.min, projection1.max, projection2.min, projection2.max);
|
|
22
|
+
if (overlap === false) {
|
|
15
23
|
return false;
|
|
16
24
|
}
|
|
17
|
-
const overlap = Math.min(Math.abs(distance1), Math.abs(distance2));
|
|
18
25
|
if (overlap < minOverlap) {
|
|
19
26
|
minOverlap = overlap;
|
|
20
27
|
bestAxis = axis;
|
|
@@ -46,6 +53,12 @@ const clipSegmentToLine = (vertices, normal, offset) => {
|
|
|
46
53
|
}
|
|
47
54
|
return clipped;
|
|
48
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Selects the incident edge for clipping.
|
|
58
|
+
*
|
|
59
|
+
* The incident edge should face into the reference face, so it is the edge
|
|
60
|
+
* whose normal points most strongly opposite the reference collision normal.
|
|
61
|
+
*/
|
|
49
62
|
const getMostAntiParallelEdge = (polygon, normal) => {
|
|
50
63
|
let bestEdge = polygon.edges[0];
|
|
51
64
|
let minDot = VectorOps.dotProduct(bestEdge.normal, normal);
|
|
@@ -58,6 +71,12 @@ const getMostAntiParallelEdge = (polygon, normal) => {
|
|
|
58
71
|
}
|
|
59
72
|
return bestEdge;
|
|
60
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Selects the reference edge that owns the collision plane.
|
|
76
|
+
*
|
|
77
|
+
* The reference edge is the edge whose normal most closely matches the chosen
|
|
78
|
+
* collision normal. Its side planes bound the incident edge during clipping.
|
|
79
|
+
*/
|
|
61
80
|
const getMostParallelEdge = (polygon, normal) => {
|
|
62
81
|
let bestEdge = polygon.edges[0];
|
|
63
82
|
let maxDot = VectorOps.dotProduct(bestEdge.normal, normal);
|
|
@@ -88,8 +107,11 @@ const dedupePoints = (points) => {
|
|
|
88
107
|
* Builds up to two box-vs-box contact points by clipping the incident edge
|
|
89
108
|
* against the side planes of the reference edge.
|
|
90
109
|
*
|
|
91
|
-
* The
|
|
92
|
-
*
|
|
110
|
+
* The process mirrors the standard contact manifold construction for SAT:
|
|
111
|
+
* choose a reference face, choose the incident face most opposed to it, clip
|
|
112
|
+
* the incident segment by the reference face's tangent bounds, then project
|
|
113
|
+
* surviving points back onto the reference face plane. The projected points are
|
|
114
|
+
* suitable as world-space contacts for impulse resolution.
|
|
93
115
|
*/
|
|
94
116
|
export const buildContactPoints = (referencePolygon, referenceNormal, incidentPolygon) => {
|
|
95
117
|
const referenceEdge = getMostParallelEdge(referencePolygon, referenceNormal);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Intersection, Proxy } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Checks a box against a capsule.
|
|
4
|
+
*
|
|
5
|
+
* A capsule is treated as a line segment swept by a radius. The fast rejection
|
|
6
|
+
* path finds the closest distance between each box edge and the capsule axis;
|
|
7
|
+
* if that distance is greater than the capsule radius, the shapes are
|
|
8
|
+
* separated.
|
|
9
|
+
*
|
|
10
|
+
* When the capsule axis is already inside the box, SAT is used with
|
|
11
|
+
* the box face normals plus the capsule axis normal, with the capsule
|
|
12
|
+
* projection expanded by its radius.
|
|
13
|
+
*
|
|
14
|
+
* For deep axis overlaps, the capsule axis
|
|
15
|
+
* is clipped by the box and then shifted by the capsule radius along the
|
|
16
|
+
* collision normal so contacts lie on the capsule surface.
|
|
17
|
+
*
|
|
18
|
+
* Side and corner contacts use the nearest box point.
|
|
19
|
+
*/
|
|
20
|
+
export declare const checkBoxAndCapsuleIntersection: (arg1: Proxy, arg2: Proxy) => Intersection | false;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { orientNormal } from '../common/normals';
|
|
2
|
+
import { buildBoxCapsuleIntersection } from './utils';
|
|
3
|
+
/**
|
|
4
|
+
* Checks a box against a capsule.
|
|
5
|
+
*
|
|
6
|
+
* A capsule is treated as a line segment swept by a radius. The fast rejection
|
|
7
|
+
* path finds the closest distance between each box edge and the capsule axis;
|
|
8
|
+
* if that distance is greater than the capsule radius, the shapes are
|
|
9
|
+
* separated.
|
|
10
|
+
*
|
|
11
|
+
* When the capsule axis is already inside the box, SAT is used with
|
|
12
|
+
* the box face normals plus the capsule axis normal, with the capsule
|
|
13
|
+
* projection expanded by its radius.
|
|
14
|
+
*
|
|
15
|
+
* For deep axis overlaps, the capsule axis
|
|
16
|
+
* is clipped by the box and then shifted by the capsule radius along the
|
|
17
|
+
* collision normal so contacts lie on the capsule surface.
|
|
18
|
+
*
|
|
19
|
+
* Side and corner contacts use the nearest box point.
|
|
20
|
+
*/
|
|
21
|
+
export const checkBoxAndCapsuleIntersection = (arg1, arg2) => {
|
|
22
|
+
const box = arg1.geometry;
|
|
23
|
+
const capsule = arg2.geometry;
|
|
24
|
+
const intersection = buildBoxCapsuleIntersection(box, capsule);
|
|
25
|
+
if (!intersection) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
...intersection,
|
|
30
|
+
normal: orientNormal(intersection.normal, box.center, capsule.center),
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BoxGeometry, CapsuleGeometry, Intersection } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a box-vs-capsule manifold using the cheapest valid path for the
|
|
4
|
+
* contact shape.
|
|
5
|
+
*
|
|
6
|
+
* Edge-to-axis distance handles the common side/corner case directly: if the
|
|
7
|
+
* closest box edge and capsule axis are farther apart than the capsule radius,
|
|
8
|
+
* the shapes are separated; otherwise the closest box point is already a good
|
|
9
|
+
* contact. When the axis penetrates or lies inside the box, distance alone
|
|
10
|
+
* cannot pick a stable separation direction, so the function falls back to SAT
|
|
11
|
+
* over the box face normals plus the capsule axis normal. The chosen SAT axis
|
|
12
|
+
* gives penetration depth, and clipped axis contacts produce one or two
|
|
13
|
+
* surface points for deeper face-like overlaps.
|
|
14
|
+
*/
|
|
15
|
+
export declare const buildBoxCapsuleIntersection: (box: BoxGeometry, capsule: CapsuleGeometry) => Intersection | false;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { MathOps, Vector2, VectorOps, } from '../../../../../../../engine/math-lib';
|
|
2
|
+
import { INTERSECTION_EPSILON } from '../../constants';
|
|
3
|
+
import { orientNormal } from '../common/normals';
|
|
4
|
+
import { arePointsEqual, lerpPoint } from '../common/points';
|
|
5
|
+
import { getProjectionOverlap, projectPolygon, projectSegment, } from '../common/projections';
|
|
6
|
+
import { getClosestPointsBetweenSegments } from '../common/segment-distance';
|
|
7
|
+
/**
|
|
8
|
+
* Clips the capsule axis to the portion that lies inside the box.
|
|
9
|
+
*
|
|
10
|
+
* A capsule is a segment swept by a radius, so when the best collision axis is
|
|
11
|
+
* a face-like axis we first find the interval of the center segment that
|
|
12
|
+
* survives all box half-spaces. The resulting endpoint(s) are then shifted by
|
|
13
|
+
* the capsule radius opposite the contact normal so the manifold lies on the
|
|
14
|
+
* capsule surface rather than on its center line.
|
|
15
|
+
*/
|
|
16
|
+
const buildAxisContactPoints = (box, capsule, normal) => {
|
|
17
|
+
let start = 0;
|
|
18
|
+
let end = 1;
|
|
19
|
+
for (const edge of box.edges) {
|
|
20
|
+
const offset = VectorOps.dotProduct(edge.point1, edge.normal);
|
|
21
|
+
const point1Distance = VectorOps.dotProduct(capsule.point1, edge.normal) - offset;
|
|
22
|
+
const point2Distance = VectorOps.dotProduct(capsule.point2, edge.normal) - offset;
|
|
23
|
+
if (point1Distance > INTERSECTION_EPSILON &&
|
|
24
|
+
point2Distance > INTERSECTION_EPSILON) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
if (point1Distance <= INTERSECTION_EPSILON &&
|
|
28
|
+
point2Distance <= INTERSECTION_EPSILON) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const denominator = point1Distance - point2Distance;
|
|
32
|
+
const t = denominator === 0 ? 0 : point1Distance / denominator;
|
|
33
|
+
if (point1Distance > INTERSECTION_EPSILON) {
|
|
34
|
+
start = Math.max(start, t);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
end = Math.min(end, t);
|
|
38
|
+
}
|
|
39
|
+
if (start > end + INTERSECTION_EPSILON) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const contact1 = lerpPoint(capsule.point1, capsule.point2, MathOps.clamp(start, 0, 1));
|
|
44
|
+
contact1.x -= normal.x * capsule.radius;
|
|
45
|
+
contact1.y -= normal.y * capsule.radius;
|
|
46
|
+
if (Math.abs(end - start) <= INTERSECTION_EPSILON) {
|
|
47
|
+
return [contact1];
|
|
48
|
+
}
|
|
49
|
+
const contact2 = lerpPoint(capsule.point1, capsule.point2, MathOps.clamp(end, 0, 1));
|
|
50
|
+
contact2.x -= normal.x * capsule.radius;
|
|
51
|
+
contact2.y -= normal.y * capsule.radius;
|
|
52
|
+
return arePointsEqual(contact1, contact2) ? [contact1] : [contact1, contact2];
|
|
53
|
+
};
|
|
54
|
+
const getAxisOverlap = (box, capsule, axis) => {
|
|
55
|
+
const boxProjection = projectPolygon(box.points, axis);
|
|
56
|
+
const segmentProjection = projectSegment(capsule.point1, capsule.point2, axis);
|
|
57
|
+
const capsuleMin = segmentProjection.min - capsule.radius;
|
|
58
|
+
const capsuleMax = segmentProjection.max + capsule.radius;
|
|
59
|
+
return getProjectionOverlap(capsuleMin, capsuleMax, boxProjection.min, boxProjection.max);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Builds a box-vs-capsule manifold using the cheapest valid path for the
|
|
63
|
+
* contact shape.
|
|
64
|
+
*
|
|
65
|
+
* Edge-to-axis distance handles the common side/corner case directly: if the
|
|
66
|
+
* closest box edge and capsule axis are farther apart than the capsule radius,
|
|
67
|
+
* the shapes are separated; otherwise the closest box point is already a good
|
|
68
|
+
* contact. When the axis penetrates or lies inside the box, distance alone
|
|
69
|
+
* cannot pick a stable separation direction, so the function falls back to SAT
|
|
70
|
+
* over the box face normals plus the capsule axis normal. The chosen SAT axis
|
|
71
|
+
* gives penetration depth, and clipped axis contacts produce one or two
|
|
72
|
+
* surface points for deeper face-like overlaps.
|
|
73
|
+
*/
|
|
74
|
+
export const buildBoxCapsuleIntersection = (box, capsule) => {
|
|
75
|
+
let closestBoxPoint = box.points[0];
|
|
76
|
+
let closestCapsulePoint = capsule.point1;
|
|
77
|
+
let minDistance = Infinity;
|
|
78
|
+
for (const edge of box.edges) {
|
|
79
|
+
const closest = getClosestPointsBetweenSegments(edge, capsule);
|
|
80
|
+
if (closest.distance < minDistance) {
|
|
81
|
+
minDistance = closest.distance;
|
|
82
|
+
closestBoxPoint = closest.point1;
|
|
83
|
+
closestCapsulePoint = closest.point2;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (VectorOps.isPointInPolygon(capsule.point1, box.edges)) {
|
|
87
|
+
minDistance = 0;
|
|
88
|
+
closestBoxPoint = capsule.point1;
|
|
89
|
+
closestCapsulePoint = capsule.point1;
|
|
90
|
+
}
|
|
91
|
+
else if (VectorOps.isPointInPolygon(capsule.point2, box.edges)) {
|
|
92
|
+
minDistance = 0;
|
|
93
|
+
closestBoxPoint = capsule.point2;
|
|
94
|
+
closestCapsulePoint = capsule.point2;
|
|
95
|
+
}
|
|
96
|
+
if (minDistance > capsule.radius + INTERSECTION_EPSILON) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (minDistance > INTERSECTION_EPSILON) {
|
|
100
|
+
const normal = new Vector2(closestCapsulePoint.x - closestBoxPoint.x, closestCapsulePoint.y - closestBoxPoint.y).normalize();
|
|
101
|
+
return {
|
|
102
|
+
normal,
|
|
103
|
+
penetration: Math.max(0, capsule.radius - minDistance),
|
|
104
|
+
contactPoints: [closestBoxPoint],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
let minOverlap = Infinity;
|
|
108
|
+
let bestAxis = box.edges[0].normal;
|
|
109
|
+
for (let i = 0; i < 2; i += 1) {
|
|
110
|
+
const edge = box.edges[i];
|
|
111
|
+
const overlap = getAxisOverlap(box, capsule, edge.normal);
|
|
112
|
+
if (overlap === false) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (overlap < minOverlap) {
|
|
116
|
+
minOverlap = overlap;
|
|
117
|
+
bestAxis = edge.normal;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const capsuleAxisOverlap = getAxisOverlap(box, capsule, capsule.normal);
|
|
121
|
+
if (capsuleAxisOverlap === false) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (capsuleAxisOverlap < minOverlap) {
|
|
125
|
+
minOverlap = capsuleAxisOverlap;
|
|
126
|
+
bestAxis = capsule.normal;
|
|
127
|
+
}
|
|
128
|
+
const contactNormal = orientNormal(bestAxis.clone(), box.center, capsule.center);
|
|
129
|
+
const contactPoints = buildAxisContactPoints(box, capsule, contactNormal);
|
|
130
|
+
return {
|
|
131
|
+
normal: bestAxis.clone(),
|
|
132
|
+
penetration: minOverlap,
|
|
133
|
+
contactPoints: contactPoints.length > 0 ? contactPoints : [closestBoxPoint],
|
|
134
|
+
};
|
|
135
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MathOps, Vector2, VectorOps, } from '../../../../../../../engine/math-lib';
|
|
2
|
-
import {
|
|
2
|
+
import { INTERSECTION_EPSILON } from '../../constants';
|
|
3
|
+
import { orientNormal } from '../common/normals';
|
|
3
4
|
const buildNormal = (circle, closestEdge, closestPoint) => {
|
|
4
5
|
let normal = new Vector2(circle.center.x - closestPoint.x, circle.center.y - closestPoint.y);
|
|
5
6
|
if (normal.magnitude === 0) {
|
|
@@ -15,9 +16,8 @@ const buildNormal = (circle, closestEdge, closestPoint) => {
|
|
|
15
16
|
* collision direction and penetration is measured to that face.
|
|
16
17
|
*/
|
|
17
18
|
export const checkBoxAndCircleIntersection = (arg1, arg2) => {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const circle = (isBoxFirst ? arg2.geometry : arg1.geometry);
|
|
19
|
+
const box = arg1.geometry;
|
|
20
|
+
const circle = arg2.geometry;
|
|
21
21
|
let closestEdge = box.edges[0];
|
|
22
22
|
let closestPoint = box.points[0];
|
|
23
23
|
let minDistance = Infinity;
|
|
@@ -39,7 +39,7 @@ export const checkBoxAndCircleIntersection = (arg1, arg2) => {
|
|
|
39
39
|
? circle.radius + minDistance
|
|
40
40
|
: Math.max(0, circle.radius - minDistance);
|
|
41
41
|
return {
|
|
42
|
-
normal: orientNormal(buildNormal(circle, closestEdge, closestPoint),
|
|
42
|
+
normal: orientNormal(buildNormal(circle, closestEdge, closestPoint), box.center, circle.center),
|
|
43
43
|
penetration,
|
|
44
44
|
contactPoints: [closestPoint],
|
|
45
45
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildBoxSegmentContactPoints, findMinBoxSegmentOverlap } from './utils';
|
|
2
|
-
import { orientNormal } from '../
|
|
2
|
+
import { orientNormal } from '../common/normals';
|
|
3
3
|
/**
|
|
4
4
|
* Checks a box against a segment.
|
|
5
5
|
*
|
|
@@ -9,9 +9,8 @@ import { orientNormal } from '../utils';
|
|
|
9
9
|
* segment, and explicit edge/segment intersections.
|
|
10
10
|
*/
|
|
11
11
|
export const checkBoxAndSegmentIntersection = (arg1, arg2) => {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const segment = (isBoxFirst ? arg2.geometry : arg1.geometry);
|
|
12
|
+
const box = arg1.geometry;
|
|
13
|
+
const segment = arg2.geometry;
|
|
15
14
|
const overlap = findMinBoxSegmentOverlap(box, segment);
|
|
16
15
|
if (overlap === false) {
|
|
17
16
|
return false;
|
|
@@ -21,7 +20,7 @@ export const checkBoxAndSegmentIntersection = (arg1, arg2) => {
|
|
|
21
20
|
return false;
|
|
22
21
|
}
|
|
23
22
|
return {
|
|
24
|
-
normal: orientNormal(overlap.axis.clone(),
|
|
23
|
+
normal: orientNormal(overlap.axis.clone(), box.center, segment.center),
|
|
25
24
|
penetration: overlap.overlap,
|
|
26
25
|
contactPoints,
|
|
27
26
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type Vector2 } from '../../../../../../../engine/math-lib';
|
|
2
2
|
import type { BoxGeometry, Point, SegmentGeometry } from '../../types';
|
|
3
|
-
export declare const lerpPoint: (point1: Point, point2: Point, value: number) => Point;
|
|
4
3
|
export declare const findMinBoxSegmentOverlap: (box: BoxGeometry, segment: SegmentGeometry) => {
|
|
5
4
|
axis: Vector2;
|
|
6
5
|
overlap: number;
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
import { MathOps, VectorOps, } from '../../../../../../../engine/math-lib';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { arePointsEqual, lerpPoint } from '../common/points';
|
|
3
|
+
import { INTERSECTION_EPSILON } from '../../constants';
|
|
4
|
+
import { getProjectionOverlap, projectPolygon, projectSegment, } from '../common/projections';
|
|
5
|
+
const getBoxSegmentAxisOverlap = (box, segment, axis) => {
|
|
6
|
+
const boxProjection = projectPolygon(box.points, axis);
|
|
7
|
+
const segmentProjection = projectSegment(segment.point1, segment.point2, axis);
|
|
8
|
+
return getProjectionOverlap(segmentProjection.min, segmentProjection.max, boxProjection.min, boxProjection.max);
|
|
9
|
+
};
|
|
9
10
|
export const findMinBoxSegmentOverlap = (box, segment) => {
|
|
10
|
-
const axes = [...box.edges.map((edge) => edge.normal), segment.normal];
|
|
11
11
|
let minOverlap = Infinity;
|
|
12
|
-
let bestAxis =
|
|
13
|
-
for (
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const distance2 = boxProjection.min - segmentProjection.max;
|
|
18
|
-
if (distance1 > INTERSECTION_EPSILON || distance2 > INTERSECTION_EPSILON) {
|
|
12
|
+
let bestAxis = box.edges[0].normal;
|
|
13
|
+
for (let i = 0; i < 2; i += 1) {
|
|
14
|
+
const edge = box.edges[i];
|
|
15
|
+
const overlap = getBoxSegmentAxisOverlap(box, segment, edge.normal);
|
|
16
|
+
if (overlap === false) {
|
|
19
17
|
return false;
|
|
20
18
|
}
|
|
21
|
-
const overlap = Math.min(Math.abs(distance1), Math.abs(distance2));
|
|
22
19
|
if (overlap < minOverlap) {
|
|
23
20
|
minOverlap = overlap;
|
|
24
|
-
bestAxis =
|
|
21
|
+
bestAxis = edge.normal;
|
|
25
22
|
}
|
|
26
23
|
}
|
|
24
|
+
const segmentAxisOverlap = getBoxSegmentAxisOverlap(box, segment, segment.normal);
|
|
25
|
+
if (segmentAxisOverlap === false) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (segmentAxisOverlap < minOverlap) {
|
|
29
|
+
minOverlap = segmentAxisOverlap;
|
|
30
|
+
bestAxis = segment.normal;
|
|
31
|
+
}
|
|
27
32
|
return {
|
|
28
33
|
axis: bestAxis,
|
|
29
34
|
overlap: minOverlap,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Intersection, Proxy } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Checks two capsules for intersection.
|
|
4
|
+
*
|
|
5
|
+
* Each capsule is represented by its center segment swept by a radius. The
|
|
6
|
+
* closest points between the two finite center segments define the collision
|
|
7
|
+
* direction. The capsules overlap when that segment distance is less than or
|
|
8
|
+
* equal to the sum of both radii.
|
|
9
|
+
*
|
|
10
|
+
* The contact point is placed on the surface
|
|
11
|
+
* of the second capsule along the separating direction, matching the manifold
|
|
12
|
+
* orientation used by the other pair checkers.
|
|
13
|
+
*/
|
|
14
|
+
export declare const checkCapsulesIntersection: (arg1: Proxy, arg2: Proxy) => Intersection | false;
|