@perplexdotgg/bounce 1.0.0
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/LICENSE +21 -0
- package/README.md +30 -0
- package/package.json +32 -0
- package/src/builders/ConvexHullBuilder.ts +437 -0
- package/src/builders/ConvexHullBuilder2d.ts +344 -0
- package/src/builders/ConvexHullBuilder3d.ts +1689 -0
- package/src/builders/HeightMapBuilder.ts +414 -0
- package/src/builders/TriangleMeshBuilder.ts +92 -0
- package/src/collision/CastShapesModule.ts +184 -0
- package/src/collision/CollideShapesModule.ts +152 -0
- package/src/collision/HeightMapCaster.ts +38 -0
- package/src/collision/HeightMapCollider.ts +33 -0
- package/src/collision/TriangleCaster.ts +249 -0
- package/src/collision/TriangleCollider.ts +308 -0
- package/src/collision/TriangleCollider2.ts +379 -0
- package/src/collision/activeEdge.ts +146 -0
- package/src/collision/cast/cast.ts +139 -0
- package/src/collision/cast/castCompoundVsCompound.ts +59 -0
- package/src/collision/cast/castCompoundVsConvex.ts +116 -0
- package/src/collision/cast/castConvexVsCompound.ts +123 -0
- package/src/collision/cast/castConvexVsConvex.ts +213 -0
- package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
- package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
- package/src/collision/cast/castRayVsCompound.ts +44 -0
- package/src/collision/cast/castRayVsConvex.ts +45 -0
- package/src/collision/cast/castRayVsHeightMap.ts +58 -0
- package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
- package/src/collision/closestPoints/closestPoints.ts +23 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
- package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
- package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
- package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
- package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
- package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
- package/src/collision/collide/collide.ts +146 -0
- package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
- package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
- package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
- package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
- package/src/collision/collide/collideConvexVsCompound.ts +57 -0
- package/src/collision/collide/collideConvexVsConvex.ts +225 -0
- package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
- package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
- package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
- package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
- package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
- package/src/collision/collide/collideSphereVsSphere.ts +81 -0
- package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
- package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
- package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
- package/src/collision/epa/StaticArray.ts +154 -0
- package/src/collision/epa/TriangleFactory.ts +32 -0
- package/src/collision/epa/arrays.ts +99 -0
- package/src/collision/epa/binaryHeap.ts +82 -0
- package/src/collision/epa/structs.ts +227 -0
- package/src/collision/gjk/GjkModule.ts +864 -0
- package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
- package/src/collision/gjk/SupportPoints.ts +50 -0
- package/src/collision/imp/MinkowskiDifference.ts +36 -0
- package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
- package/src/collision/imp/finalizeImpResult.ts +69 -0
- package/src/collision/imp/findContactImp.ts +196 -0
- package/src/collision/imp/imp.ts +28 -0
- package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
- package/src/collision/mpr/findPortal.ts +152 -0
- package/src/collision/mpr/mpr.ts +29 -0
- package/src/collision/mpr/updatePortal.ts +52 -0
- package/src/constraints/BaseConstraint.ts +50 -0
- package/src/constraints/ConstraintOptions.ts +22 -0
- package/src/constraints/ConstraintSolver.ts +119 -0
- package/src/constraints/DistanceConstraint.ts +229 -0
- package/src/constraints/FixedConstraint.ts +203 -0
- package/src/constraints/HingeConstraint.ts +460 -0
- package/src/constraints/PointConstraint.ts +108 -0
- package/src/constraints/components/AngleComponent.ts +226 -0
- package/src/constraints/components/AxisComponent.ts +263 -0
- package/src/constraints/components/HingeComponent.ts +215 -0
- package/src/constraints/components/Motor.ts +36 -0
- package/src/constraints/components/PointConstraintComponent.ts +179 -0
- package/src/constraints/components/RotationEulerComponent.ts +139 -0
- package/src/constraints/components/Spring.ts +30 -0
- package/src/constraints/components/SpringComponent.ts +71 -0
- package/src/constraints/types.ts +6 -0
- package/src/helpers.ts +147 -0
- package/src/index.ts +50 -0
- package/src/math/BasicTransform.ts +19 -0
- package/src/math/NumberValue.ts +13 -0
- package/src/math/isometry.ts +64 -0
- package/src/math/mat3.ts +529 -0
- package/src/math/mat4.ts +588 -0
- package/src/math/quat.ts +193 -0
- package/src/math/scalar.ts +81 -0
- package/src/math/tensor.ts +17 -0
- package/src/math/vec3.ts +589 -0
- package/src/math/vec4.ts +10 -0
- package/src/physics/Body.ts +581 -0
- package/src/physics/CollisionFilter.ts +52 -0
- package/src/physics/SleepModule.ts +163 -0
- package/src/physics/broadphase/BodyPairsModule.ts +363 -0
- package/src/physics/broadphase/BvhModule.ts +237 -0
- package/src/physics/broadphase/BvhTree.ts +803 -0
- package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
- package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
- package/src/physics/manifold/ContactManifold.ts +227 -0
- package/src/physics/manifold/ContactManifoldModule.ts +623 -0
- package/src/physics/manifold/Face.ts +119 -0
- package/src/physics/manifold/ManifoldCache.ts +116 -0
- package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
- package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
- package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
- package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
- package/src/physics/solver/ContactConstraintModule.ts +659 -0
- package/src/physics/solver/ManifoldConstraint.ts +420 -0
- package/src/physics/solver/estimateCollisionResponse.ts +146 -0
- package/src/shape/Aabb.ts +400 -0
- package/src/shape/Box.ts +231 -0
- package/src/shape/Capsule.ts +332 -0
- package/src/shape/CompoundShape.ts +288 -0
- package/src/shape/Convex.ts +130 -0
- package/src/shape/ConvexHull.ts +423 -0
- package/src/shape/Cylinder.ts +313 -0
- package/src/shape/HeightMap.ts +511 -0
- package/src/shape/Line.ts +14 -0
- package/src/shape/Plane.ts +116 -0
- package/src/shape/Ray.ts +81 -0
- package/src/shape/Segment.ts +25 -0
- package/src/shape/Shape.ts +77 -0
- package/src/shape/Sphere.ts +181 -0
- package/src/shape/TransformedShape.ts +51 -0
- package/src/shape/Triangle.ts +122 -0
- package/src/shape/TriangleMesh.ts +186 -0
- package/src/types.ts +1 -0
- package/src/world.ts +1335 -0
- package/tests/BodyPairsModule.test.ts +71 -0
- package/tests/BvhTree.test.ts +406 -0
- package/tests/test.md +642 -0
- package/tests/vec3.test.ts +12 -0
- package/tsconfig.json +20 -0
- package/vite.config.js +40 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { assert } from "../../helpers";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
import { SupportShape } from "../../shape/Convex";
|
|
4
|
+
import { MprResult, MprStatus } from "../mpr/mpr";
|
|
5
|
+
import { MinkowskiDifference } from "./MinkowskiDifference";
|
|
6
|
+
import { finalizeImpResult } from "./finalizeImpResult";
|
|
7
|
+
import { ImpResult, ImpStatus } from "./imp";
|
|
8
|
+
import { incrementalMinimumDistanceExploreDirection } from "./incrementalMinimumDistanceExploreDirection";
|
|
9
|
+
|
|
10
|
+
const support = /*@__PURE__*/ MinkowskiDifference.create();
|
|
11
|
+
const d = /*@__PURE__*/ Vec3.create();
|
|
12
|
+
const v1 = /*@__PURE__*/ Vec3.create();
|
|
13
|
+
const v2 = /*@__PURE__*/ Vec3.create();
|
|
14
|
+
const v3 = /*@__PURE__*/ Vec3.create();
|
|
15
|
+
const v1_dir_in_support = /*@__PURE__*/ Vec3.create();
|
|
16
|
+
const v2_dir_in_support = /*@__PURE__*/ Vec3.create();
|
|
17
|
+
const v3_dir_in_support = /*@__PURE__*/ Vec3.create();
|
|
18
|
+
const new_d = /*@__PURE__*/ Vec3.create();
|
|
19
|
+
const prev_direction = /*@__PURE__*/ Vec3.create();
|
|
20
|
+
const direction_difference = /*@__PURE__*/ Vec3.create();
|
|
21
|
+
const prev_bounds: [number, number] = [0, 0];
|
|
22
|
+
const bounds: [number, number] = [0, 0];
|
|
23
|
+
const mprResult = /*@__PURE__*/ new MprResult();
|
|
24
|
+
|
|
25
|
+
export function findContactImp(
|
|
26
|
+
refine_data: ImpResult,
|
|
27
|
+
shapeA: SupportShape,
|
|
28
|
+
shapeB: SupportShape,
|
|
29
|
+
init_direction: Vec3,
|
|
30
|
+
max_iteration: number,
|
|
31
|
+
tolerance: number,
|
|
32
|
+
return_on_subroutine_converge: boolean
|
|
33
|
+
): void {
|
|
34
|
+
// resetMprResult(mprResult);
|
|
35
|
+
d.copy(init_direction);
|
|
36
|
+
// #v-ifdef DEV
|
|
37
|
+
assert(d.isUnitVector(), "d must be a unit vector");
|
|
38
|
+
// #v-endif
|
|
39
|
+
|
|
40
|
+
// The main loop
|
|
41
|
+
prev_direction.copy(init_direction);
|
|
42
|
+
prev_bounds[0] = -1;
|
|
43
|
+
prev_bounds[1] = -1;
|
|
44
|
+
for (let outer_iteration = 0; outer_iteration < max_iteration; outer_iteration++) {
|
|
45
|
+
// Invoke subroutine
|
|
46
|
+
bounds[0] = 0;
|
|
47
|
+
bounds[1] = 0;
|
|
48
|
+
const portal_valid = outer_iteration >= 1;
|
|
49
|
+
incrementalMinimumDistanceExploreDirection(
|
|
50
|
+
mprResult,
|
|
51
|
+
shapeA,
|
|
52
|
+
shapeB,
|
|
53
|
+
d,
|
|
54
|
+
v1,
|
|
55
|
+
v2,
|
|
56
|
+
v3,
|
|
57
|
+
v1_dir_in_support,
|
|
58
|
+
v2_dir_in_support,
|
|
59
|
+
v3_dir_in_support,
|
|
60
|
+
bounds,
|
|
61
|
+
new_d,
|
|
62
|
+
portal_valid,
|
|
63
|
+
max_iteration,
|
|
64
|
+
tolerance
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Update the output
|
|
68
|
+
switch (mprResult.status) {
|
|
69
|
+
case MprStatus.NewDirection: {
|
|
70
|
+
d.copy(new_d);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case MprStatus.SubroutineConverge: {
|
|
75
|
+
// directly return without further checking
|
|
76
|
+
if (return_on_subroutine_converge) {
|
|
77
|
+
finalizeImpResult(
|
|
78
|
+
refine_data,
|
|
79
|
+
shapeA,
|
|
80
|
+
shapeB,
|
|
81
|
+
d,
|
|
82
|
+
v1,
|
|
83
|
+
v2,
|
|
84
|
+
v3,
|
|
85
|
+
v1_dir_in_support,
|
|
86
|
+
v2_dir_in_support,
|
|
87
|
+
v3_dir_in_support,
|
|
88
|
+
new_d,
|
|
89
|
+
bounds
|
|
90
|
+
);
|
|
91
|
+
refine_data.status = ImpStatus.OK;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Prev-data is valid, use that to check the convergence
|
|
96
|
+
if (outer_iteration >= 1) {
|
|
97
|
+
// #v-ifdef DEV
|
|
98
|
+
assert(prev_bounds[0] >= 0, "prev_bounds[0] must be valid");
|
|
99
|
+
assert(prev_bounds[1] >= 0, "prev_bounds[1] must be valid");
|
|
100
|
+
// #v-endif
|
|
101
|
+
if (Math.abs(bounds[1] - prev_bounds[1]) <= tolerance) {
|
|
102
|
+
// Done as not much improvement
|
|
103
|
+
finalizeImpResult(
|
|
104
|
+
refine_data,
|
|
105
|
+
shapeA,
|
|
106
|
+
shapeB,
|
|
107
|
+
d,
|
|
108
|
+
v1,
|
|
109
|
+
v2,
|
|
110
|
+
v3,
|
|
111
|
+
v1_dir_in_support,
|
|
112
|
+
v2_dir_in_support,
|
|
113
|
+
v3_dir_in_support,
|
|
114
|
+
new_d,
|
|
115
|
+
bounds
|
|
116
|
+
);
|
|
117
|
+
refine_data.status = ImpStatus.OK;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check direction
|
|
123
|
+
const check_direction_termination = true;
|
|
124
|
+
const direction_tolerance = 1e-3;
|
|
125
|
+
direction_difference.subtractVectors(new_d, prev_direction);
|
|
126
|
+
if (check_direction_termination && direction_difference.length() < direction_tolerance) {
|
|
127
|
+
finalizeImpResult(
|
|
128
|
+
refine_data,
|
|
129
|
+
shapeA,
|
|
130
|
+
shapeB,
|
|
131
|
+
d,
|
|
132
|
+
v1,
|
|
133
|
+
v2,
|
|
134
|
+
v3,
|
|
135
|
+
v1_dir_in_support,
|
|
136
|
+
v2_dir_in_support,
|
|
137
|
+
v3_dir_in_support,
|
|
138
|
+
new_d,
|
|
139
|
+
bounds
|
|
140
|
+
);
|
|
141
|
+
refine_data.status = ImpStatus.OK;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Move to next, as the subroutine converge
|
|
146
|
+
// The portal can be rather small here
|
|
147
|
+
// portal_valid = false;
|
|
148
|
+
d.copy(new_d);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case MprStatus.Degenerated: {
|
|
153
|
+
// Done on degenerate
|
|
154
|
+
refine_data.minimum_penetration = bounds[1];
|
|
155
|
+
refine_data.penetration_direction.copy(new_d);
|
|
156
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, new_d);
|
|
157
|
+
refine_data.worldContactPointA.copy(support.pointA);
|
|
158
|
+
refine_data.worldContactPointB.copy(support.pointB);
|
|
159
|
+
refine_data.status = ImpStatus.OK;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case MprStatus.FailedNoIntersect: {
|
|
164
|
+
refine_data.status = ImpStatus.FailedNoIntersect;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
default:
|
|
169
|
+
refine_data.status = ImpStatus.Failed;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Move to next iteration, book keeping
|
|
174
|
+
prev_direction.copy(new_d);
|
|
175
|
+
prev_bounds[0] = bounds[0];
|
|
176
|
+
prev_bounds[1] = bounds[1];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Too many iterations
|
|
180
|
+
finalizeImpResult(
|
|
181
|
+
refine_data,
|
|
182
|
+
shapeA,
|
|
183
|
+
shapeB,
|
|
184
|
+
d,
|
|
185
|
+
v1,
|
|
186
|
+
v2,
|
|
187
|
+
v3,
|
|
188
|
+
v1_dir_in_support,
|
|
189
|
+
v2_dir_in_support,
|
|
190
|
+
v3_dir_in_support,
|
|
191
|
+
prev_direction,
|
|
192
|
+
prev_bounds
|
|
193
|
+
);
|
|
194
|
+
refine_data.status = ImpStatus.IterationLimit;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createClass, MonomorphType, NumberType, PropertyDefinition, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
|
|
4
|
+
export const enum ImpStatus {
|
|
5
|
+
OK,
|
|
6
|
+
Failed,
|
|
7
|
+
FailedNoIntersect,
|
|
8
|
+
DirectedPenetrationFailed,
|
|
9
|
+
IterationLimit,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const impResultProps = {
|
|
13
|
+
status: NumberType(ImpStatus.Failed) as PropertyDefinition<ImpStatus, true>,
|
|
14
|
+
minimum_penetration: NumberType(0.0),
|
|
15
|
+
penetration_direction: MonomorphType(Vec3),
|
|
16
|
+
worldContactPointA: MonomorphType(Vec3),
|
|
17
|
+
worldContactPointB: MonomorphType(Vec3),
|
|
18
|
+
} as const satisfies PropertyDefinitionMap;
|
|
19
|
+
|
|
20
|
+
export class ImpResult extends createClass<ImpResult, typeof impResultProps>(impResultProps) {
|
|
21
|
+
reset() {
|
|
22
|
+
this.status = ImpStatus.Failed;
|
|
23
|
+
this.minimum_penetration = 0;
|
|
24
|
+
this.penetration_direction.zero();
|
|
25
|
+
this.worldContactPointA.zero();
|
|
26
|
+
this.worldContactPointB.zero();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { assert } from "../../helpers";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
import { SupportShape } from "../../shape/Convex";
|
|
4
|
+
import { findPortal } from "../mpr/findPortal";
|
|
5
|
+
import { FindPortalResult, FindPortalStatus, MprResult, MprStatus } from "../mpr/mpr";
|
|
6
|
+
import { updatePortal } from "../mpr/updatePortal";
|
|
7
|
+
import { MinkowskiDifference } from "./MinkowskiDifference";
|
|
8
|
+
import { computeExploredDistanceLowerUpperBound } from "./computeExploredDistanceLowerUpperBound";
|
|
9
|
+
|
|
10
|
+
const support = /*@__PURE__*/ MinkowskiDifference.create();
|
|
11
|
+
const d = /*@__PURE__*/ Vec3.create();
|
|
12
|
+
const v0 = /*@__PURE__*/ Vec3.create();
|
|
13
|
+
const v0_scaled = /*@__PURE__*/ Vec3.create();
|
|
14
|
+
const init_support_dir = /*@__PURE__*/ Vec3.create();
|
|
15
|
+
const init_support = /*@__PURE__*/ Vec3.create();
|
|
16
|
+
const v123_normal = /*@__PURE__*/ Vec3.create();
|
|
17
|
+
const v4 = /*@__PURE__*/ Vec3.create();
|
|
18
|
+
const v12 = /*@__PURE__*/ Vec3.create();
|
|
19
|
+
const v13 = /*@__PURE__*/ Vec3.create();
|
|
20
|
+
|
|
21
|
+
const findPortalResult = /*@__PURE__*/ new FindPortalResult();
|
|
22
|
+
|
|
23
|
+
export function incrementalMinimumDistanceExploreDirection(
|
|
24
|
+
result: MprResult,
|
|
25
|
+
shapeA: SupportShape,
|
|
26
|
+
shapeB: SupportShape,
|
|
27
|
+
direction_to_explore: Vec3,
|
|
28
|
+
v1: Vec3,
|
|
29
|
+
v2: Vec3,
|
|
30
|
+
v3: Vec3,
|
|
31
|
+
v1_dir_in_support: Vec3,
|
|
32
|
+
v2_dir_in_support: Vec3,
|
|
33
|
+
v3_dir_in_support: Vec3,
|
|
34
|
+
bounds: [number, number],
|
|
35
|
+
new_direction: Vec3,
|
|
36
|
+
v123_valid: boolean,
|
|
37
|
+
max_iterations: number,
|
|
38
|
+
tolerance: number
|
|
39
|
+
): void {
|
|
40
|
+
// resetFindPortalResult(findPortalResult);
|
|
41
|
+
|
|
42
|
+
d.normalizeVector(direction_to_explore);
|
|
43
|
+
v0.negateVector(d);
|
|
44
|
+
|
|
45
|
+
// Compute the upper bound
|
|
46
|
+
init_support_dir.copy(d);
|
|
47
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, init_support_dir);
|
|
48
|
+
init_support.copy(support.point);
|
|
49
|
+
|
|
50
|
+
// No intersect case
|
|
51
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
52
|
+
result.status = MprStatus.FailedNoIntersect;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Do we need to re-init v123
|
|
57
|
+
const reinit_v123 = !v123_valid;
|
|
58
|
+
|
|
59
|
+
// Init v1, v2 and v3
|
|
60
|
+
if (reinit_v123) {
|
|
61
|
+
v1_dir_in_support.copy(d);
|
|
62
|
+
v1.copy(init_support);
|
|
63
|
+
|
|
64
|
+
v2_dir_in_support.crossVectors(v0, v1);
|
|
65
|
+
v2_dir_in_support.normalizeVector(v2_dir_in_support);
|
|
66
|
+
// o_to_v0 and o_to_v1 can be co-linear, check it
|
|
67
|
+
// Note that v0 MUST have norm one
|
|
68
|
+
if (v2_dir_in_support.absNorm() <= v1.absNorm() * tolerance) {
|
|
69
|
+
// Refer to the note in RunIntersect
|
|
70
|
+
bounds[0] = v1.dot(d);
|
|
71
|
+
bounds[1] = bounds[0];
|
|
72
|
+
new_direction.copy(d);
|
|
73
|
+
result.status = MprStatus.Degenerated;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v2_dir_in_support);
|
|
78
|
+
v2.copy(support.point);
|
|
79
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
80
|
+
result.status = MprStatus.FailedNoIntersect;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// it is better to form portal faces to be oriented "outside" origin
|
|
85
|
+
// Here O must be an interior point in penetration query
|
|
86
|
+
v3_dir_in_support.crossVectors(v1, v2);
|
|
87
|
+
v3_dir_in_support.normalizeVector(v3_dir_in_support);
|
|
88
|
+
if (v3_dir_in_support.dot(v0) < 0) {
|
|
89
|
+
// swap v1/v2
|
|
90
|
+
v1.swap(v2);
|
|
91
|
+
v1_dir_in_support.swap(v2_dir_in_support);
|
|
92
|
+
v3_dir_in_support.negateVector(v3_dir_in_support);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v3_dir_in_support);
|
|
96
|
+
v3.copy(support.point);
|
|
97
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
98
|
+
result.status = MprStatus.FailedNoIntersect;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Scale the v0 to the max of v1/v2/v3
|
|
104
|
+
// #v-ifdef DEV
|
|
105
|
+
assert(v0.isUnitVector(), "v0 must be a unit vector");
|
|
106
|
+
// #v-endif
|
|
107
|
+
v0_scaled.copy(v0);
|
|
108
|
+
const v1_squared_norm = v1.squaredLength();
|
|
109
|
+
const v2_squared_norm = v2.squaredLength();
|
|
110
|
+
const v3_squared_norm = v3.squaredLength();
|
|
111
|
+
const max_squared_norm = Math.max(v1_squared_norm, v2_squared_norm, v3_squared_norm);
|
|
112
|
+
v0_scaled.scaleVector(v0_scaled, Math.sqrt(max_squared_norm));
|
|
113
|
+
|
|
114
|
+
// the loop to find the portal
|
|
115
|
+
findPortal(
|
|
116
|
+
findPortalResult,
|
|
117
|
+
shapeA,
|
|
118
|
+
shapeB,
|
|
119
|
+
v0_scaled,
|
|
120
|
+
v1,
|
|
121
|
+
v2,
|
|
122
|
+
v3,
|
|
123
|
+
v1_dir_in_support,
|
|
124
|
+
v2_dir_in_support,
|
|
125
|
+
v3_dir_in_support,
|
|
126
|
+
max_iterations
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
switch (findPortalResult.status) {
|
|
130
|
+
case FindPortalStatus.IterationLimit: {
|
|
131
|
+
result.status = MprStatus.Failed;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case FindPortalStatus.DetectSeperated: {
|
|
136
|
+
result.status = MprStatus.FailedNoIntersect;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
default: {
|
|
141
|
+
// #v-ifdef DEV
|
|
142
|
+
assert(findPortalResult.status === FindPortalStatus.PortalFound, "Expecting portal found");
|
|
143
|
+
// #v-endif
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// portal refinement
|
|
148
|
+
let portal_refinement_iteration = 0;
|
|
149
|
+
while (portal_refinement_iteration < max_iterations) {
|
|
150
|
+
// Update iteration data
|
|
151
|
+
portal_refinement_iteration++;
|
|
152
|
+
|
|
153
|
+
// Compute the normal
|
|
154
|
+
// The v123_normal must be oriented in the same side with O
|
|
155
|
+
v12.subtractVectors(v2, v1);
|
|
156
|
+
v13.subtractVectors(v3, v1);
|
|
157
|
+
v123_normal.crossVectors(v12, v13);
|
|
158
|
+
v123_normal.normalizeVector(v123_normal);
|
|
159
|
+
if (v123_normal.dot(d) < 0) {
|
|
160
|
+
v2.swap(v3);
|
|
161
|
+
v2_dir_in_support.swap(v3_dir_in_support);
|
|
162
|
+
v123_normal.negateVector(v123_normal);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// A new point v4 on that direction
|
|
166
|
+
// v123_normal would be normalized in computeShapeSupport
|
|
167
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v123_normal);
|
|
168
|
+
v4.copy(support.point);
|
|
169
|
+
const distance_ub_on_new_supporting_plane = v4.dot(v123_normal);
|
|
170
|
+
// #v-ifdef DEV
|
|
171
|
+
assert(v123_normal.isUnitVector(), "Expecting v123_normal to be unit vector");
|
|
172
|
+
// #v-endif
|
|
173
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
174
|
+
result.status = MprStatus.FailedNoIntersect;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Compute some distance
|
|
179
|
+
const v1_dot_v123_normal = v1.dot(v123_normal);
|
|
180
|
+
const v4_dot_v123_normal = v4.dot(v123_normal);
|
|
181
|
+
const v1_v4_distance_on_n123 = v4_dot_v123_normal - v1_dot_v123_normal;
|
|
182
|
+
computeExploredDistanceLowerUpperBound(bounds, d, v1, v2, v3, v123_normal, v1_dot_v123_normal, v4_dot_v123_normal);
|
|
183
|
+
|
|
184
|
+
// compute the shortcut
|
|
185
|
+
if (bounds[0] > distance_ub_on_new_supporting_plane + tolerance) {
|
|
186
|
+
new_direction.copy(v123_normal);
|
|
187
|
+
result.status = MprStatus.NewDirection;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Separation plane very close to the new (candidate) portal
|
|
192
|
+
// Note that v123_normal is normalized
|
|
193
|
+
// We must should stop the subroutine
|
|
194
|
+
if (Math.abs(v1_v4_distance_on_n123) <= tolerance) {
|
|
195
|
+
new_direction.copy(v123_normal);
|
|
196
|
+
result.status = MprStatus.SubroutineConverge;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// update the portal
|
|
201
|
+
updatePortal(v0_scaled, v4, v123_normal, v1, v2, v3, v1_dir_in_support, v2_dir_in_support, v3_dir_in_support);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Failure
|
|
205
|
+
result.status = MprStatus.Failed;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { assert } from "../../helpers";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
import { SupportShape } from "../../shape/Convex";
|
|
4
|
+
import { MinkowskiDifference } from "../imp/MinkowskiDifference";
|
|
5
|
+
import { FindPortalResult, FindPortalStatus } from "./mpr";
|
|
6
|
+
|
|
7
|
+
const dot_eps_ratio = Number.EPSILON; // TODO: this may not be a 1-to-1 substitute for the c++ code: `static constexpr T dot_eps_ratio = std::numeric_limits<T>::epsilon();`
|
|
8
|
+
|
|
9
|
+
const support = /*@__PURE__*/ MinkowskiDifference.create();
|
|
10
|
+
const v0_to_v1 = /*@__PURE__*/ Vec3.create();
|
|
11
|
+
const v0_to_v2 = /*@__PURE__*/ Vec3.create();
|
|
12
|
+
const v0_to_v3 = /*@__PURE__*/ Vec3.create();
|
|
13
|
+
const v031_normal = /*@__PURE__*/ Vec3.create();
|
|
14
|
+
const v012_normal = /*@__PURE__*/ Vec3.create();
|
|
15
|
+
const v023_normal = /*@__PURE__*/ Vec3.create();
|
|
16
|
+
const search_v2_dir = /*@__PURE__*/ Vec3.create();
|
|
17
|
+
const search_v3_dir = /*@__PURE__*/ Vec3.create();
|
|
18
|
+
const search_v1_dir = /*@__PURE__*/ Vec3.create();
|
|
19
|
+
|
|
20
|
+
export function findPortal(
|
|
21
|
+
result: FindPortalResult,
|
|
22
|
+
shapeA: SupportShape,
|
|
23
|
+
shapeB: SupportShape,
|
|
24
|
+
v0: Vec3,
|
|
25
|
+
v1: Vec3,
|
|
26
|
+
v2: Vec3,
|
|
27
|
+
v3: Vec3,
|
|
28
|
+
v1_dir_in_support: Vec3,
|
|
29
|
+
v2_dir_in_support: Vec3,
|
|
30
|
+
v3_dir_in_support: Vec3,
|
|
31
|
+
max_iterations: number
|
|
32
|
+
): void {
|
|
33
|
+
const v0_abs_norm = v0.absNorm();
|
|
34
|
+
let find_candidate_portal_iteration = 0;
|
|
35
|
+
|
|
36
|
+
// The actual loop
|
|
37
|
+
while (true) {
|
|
38
|
+
if (find_candidate_portal_iteration >= max_iterations) {
|
|
39
|
+
result.status = FindPortalStatus.IterationLimit;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// update iteration data
|
|
44
|
+
find_candidate_portal_iteration++;
|
|
45
|
+
v0_to_v1.subtractVectors(v1, v0);
|
|
46
|
+
v0_to_v2.subtractVectors(v2, v0);
|
|
47
|
+
v0_to_v3.subtractVectors(v3, v0);
|
|
48
|
+
|
|
49
|
+
// update the corresponded vertex
|
|
50
|
+
// these normal are not oriented
|
|
51
|
+
v031_normal.crossVectors(v0_to_v3, v0_to_v1);
|
|
52
|
+
v012_normal.crossVectors(v0_to_v1, v0_to_v2);
|
|
53
|
+
const signed_volume = v0_to_v2.dot(v031_normal);
|
|
54
|
+
|
|
55
|
+
// orient it
|
|
56
|
+
if (signed_volume < 0) {
|
|
57
|
+
v2.swap(v3);
|
|
58
|
+
v2_dir_in_support.swap(v3_dir_in_support);
|
|
59
|
+
v0_to_v2.swap(v0_to_v3);
|
|
60
|
+
|
|
61
|
+
// something tricky here, note the changing of vectors
|
|
62
|
+
v012_normal.swap(v031_normal);
|
|
63
|
+
v031_normal.negateVector(v031_normal);
|
|
64
|
+
v012_normal.negateVector(v012_normal);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// case 031
|
|
68
|
+
// #v-ifdef DEV
|
|
69
|
+
assert(v0_to_v2.dot(v031_normal) >= 0, "v0_to_v2 and v031_normal should be in the same direction");
|
|
70
|
+
// #v-endif
|
|
71
|
+
const v031_seperated_v2_and_o = v0.dot(v031_normal) > dot_eps_ratio * v0_abs_norm * v031_normal.absNorm();
|
|
72
|
+
|
|
73
|
+
if (v031_seperated_v2_and_o) {
|
|
74
|
+
// Orient the normal towards O
|
|
75
|
+
// #v-ifdef DEV
|
|
76
|
+
assert(v0.dot(v031_normal) > 0, "o_to_v0 and v031_normal should be in the same direction");
|
|
77
|
+
// #v-endif
|
|
78
|
+
search_v2_dir.negateVector(v031_normal);
|
|
79
|
+
|
|
80
|
+
// Find a new v2 in that direction
|
|
81
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, search_v2_dir);
|
|
82
|
+
v2.copy(support.point);
|
|
83
|
+
v2_dir_in_support.copy(search_v2_dir);
|
|
84
|
+
|
|
85
|
+
// Miss detection
|
|
86
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
87
|
+
result.status = FindPortalStatus.DetectSeperated;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// case 012
|
|
95
|
+
// #v-ifdef DEV
|
|
96
|
+
assert(v0_to_v3.dot(v012_normal) >= 0, "v0_to_v3 and v012_normal should be in the same direction");
|
|
97
|
+
// #v-endif
|
|
98
|
+
const v012_seperated_v3_and_o = v0.dot(v012_normal) > dot_eps_ratio * v0_abs_norm * v012_normal.absNorm();
|
|
99
|
+
|
|
100
|
+
if (v012_seperated_v3_and_o) {
|
|
101
|
+
// Orient the normal towards O
|
|
102
|
+
// #v-ifdef DEV
|
|
103
|
+
assert(v0.dot(v012_normal) > 0, "o_to_v0 and v012_normal should be in the same direction");
|
|
104
|
+
// #v-endif
|
|
105
|
+
search_v3_dir.negateVector(v012_normal);
|
|
106
|
+
|
|
107
|
+
// Find a new v3 in that direction
|
|
108
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, search_v3_dir);
|
|
109
|
+
v3.copy(support.point);
|
|
110
|
+
v3_dir_in_support.copy(search_v3_dir);
|
|
111
|
+
|
|
112
|
+
// Miss detection
|
|
113
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
114
|
+
result.status = FindPortalStatus.DetectSeperated;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// case 023
|
|
122
|
+
v023_normal.crossVectors(v0_to_v2, v0_to_v3); // this must be done after orienting
|
|
123
|
+
// #v-ifdef DEV
|
|
124
|
+
assert(v0_to_v1.dot(v023_normal) >= 0, "v0_to_v1 and v023_normal should be in the same direction");
|
|
125
|
+
// #v-endif
|
|
126
|
+
const v023_seperated_v1_and_o = v0.dot(v023_normal) > dot_eps_ratio * v0_abs_norm * v023_normal.absNorm();
|
|
127
|
+
if (v023_seperated_v1_and_o) {
|
|
128
|
+
// Orient the normal towards O
|
|
129
|
+
// #v-ifdef DEV
|
|
130
|
+
assert(v0.dot(v023_normal) > 0, "o_to_v0 and v023_normal should be in the same direction");
|
|
131
|
+
// #v-endif
|
|
132
|
+
search_v1_dir.negateVector(v023_normal);
|
|
133
|
+
|
|
134
|
+
// Find a new v1 in that direction
|
|
135
|
+
support.getSupportPointOnMinkowskiDifference(shapeA, shapeB, search_v1_dir);
|
|
136
|
+
v1.copy(support.point);
|
|
137
|
+
v1_dir_in_support.copy(search_v1_dir);
|
|
138
|
+
|
|
139
|
+
// Miss detection
|
|
140
|
+
if (support.isOriginBeyondSupportPoint) {
|
|
141
|
+
result.status = FindPortalStatus.DetectSeperated;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// no seperation found, we're done
|
|
149
|
+
result.status = FindPortalStatus.PortalFound;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const enum FindPortalStatus {
|
|
2
|
+
IterationLimit,
|
|
3
|
+
DetectSeperated,
|
|
4
|
+
PortalFound,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const enum MprStatus {
|
|
8
|
+
NewDirection,
|
|
9
|
+
SubroutineConverge,
|
|
10
|
+
Degenerated,
|
|
11
|
+
Failed,
|
|
12
|
+
FailedNoIntersect,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class MprResult {
|
|
16
|
+
status: MprStatus;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.status = MprStatus.NewDirection;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class FindPortalResult {
|
|
24
|
+
status: FindPortalStatus;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.status = FindPortalStatus.IterationLimit;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Vec3 } from "../../math/vec3";
|
|
2
|
+
|
|
3
|
+
const v0_v4_o_normal = /*@__PURE__*/ Vec3.create();
|
|
4
|
+
|
|
5
|
+
export function updatePortal(
|
|
6
|
+
v0: Vec3,
|
|
7
|
+
v4: Vec3,
|
|
8
|
+
v123_normal: Vec3,
|
|
9
|
+
v1: Vec3,
|
|
10
|
+
v2: Vec3,
|
|
11
|
+
v3: Vec3,
|
|
12
|
+
v1_dir_in_support: Vec3,
|
|
13
|
+
v2_dir_in_support: Vec3,
|
|
14
|
+
v3_dir_in_support: Vec3
|
|
15
|
+
): void {
|
|
16
|
+
// v4 must appear in the next portal
|
|
17
|
+
// select two in v1, v2 and v3
|
|
18
|
+
// First do a separation in with plane v0_v4_o
|
|
19
|
+
v0_v4_o_normal.crossVectors(v4, v0);
|
|
20
|
+
let dot = v1.dot(v0_v4_o_normal);
|
|
21
|
+
if (dot > 0) {
|
|
22
|
+
dot = v2.dot(v0_v4_o_normal);
|
|
23
|
+
if (dot > 0) {
|
|
24
|
+
// Discard v1
|
|
25
|
+
v1.copy(v4);
|
|
26
|
+
if (v1_dir_in_support !== undefined) {
|
|
27
|
+
v1_dir_in_support.copy(v123_normal);
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
// Discard v3
|
|
31
|
+
v3.copy(v4);
|
|
32
|
+
if (v3_dir_in_support !== undefined) {
|
|
33
|
+
v3_dir_in_support.copy(v123_normal);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
dot = v3.dot(v0_v4_o_normal);
|
|
38
|
+
if (dot > 0) {
|
|
39
|
+
// Discard v2
|
|
40
|
+
v2.copy(v4);
|
|
41
|
+
if (v2_dir_in_support !== undefined) {
|
|
42
|
+
v2_dir_in_support.copy(v123_normal);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
// Discard v1
|
|
46
|
+
v1.copy(v4);
|
|
47
|
+
if (v1_dir_in_support !== undefined) {
|
|
48
|
+
v1_dir_in_support.copy(v123_normal);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|