@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,864 @@
|
|
|
1
|
+
import { BooleanType, createClass, MonomorphType, NumberType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
import { computeBarycentricCoordinates2d } from "../closestPoints/computeBarycentricCoordinates2d";
|
|
4
|
+
import { computeBarycentricCoordinates3d } from "../closestPoints/computeBarycentricCoordinates3d";
|
|
5
|
+
import { BarycentricCoordinatesResult, ClosestPointResult } from "../closestPoints/closestPoints";
|
|
6
|
+
import { isClose, squared } from "../../math/scalar";
|
|
7
|
+
import { computeClosestPointOnLine } from "../closestPoints/computeClosestPointOnLine";
|
|
8
|
+
import { computeClosestPointOnTriangle } from "../closestPoints/computeClosestPointOnTriangle";
|
|
9
|
+
import { computeClosestPointOnTetrahedron } from "../closestPoints/computeClosestPointOnTetrahedron";
|
|
10
|
+
import { Isometry } from "../../math/isometry";
|
|
11
|
+
import { SupportShape, TransformedConvexObject } from "../../shape/Convex";
|
|
12
|
+
import { SupportPoints } from "./SupportPoints";
|
|
13
|
+
import { Ray } from "../../shape/Ray";
|
|
14
|
+
|
|
15
|
+
const closestPointToSimplexProps = {
|
|
16
|
+
point: MonomorphType(Vec3),
|
|
17
|
+
squaredDistance: NumberType(0),
|
|
18
|
+
pointSet: NumberType(0),
|
|
19
|
+
closestPointFound: BooleanType(false),
|
|
20
|
+
} as const satisfies PropertyDefinitionMap;
|
|
21
|
+
|
|
22
|
+
export class ClosestPointToSimplex extends createClass<ClosestPointToSimplex, typeof closestPointToSimplexProps>(
|
|
23
|
+
closestPointToSimplexProps
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
const gjkClosestPointsProps = {
|
|
27
|
+
squaredDistance: NumberType(0),
|
|
28
|
+
penetrationAxis: MonomorphType(Vec3),
|
|
29
|
+
pointA: MonomorphType(Vec3),
|
|
30
|
+
pointB: MonomorphType(Vec3),
|
|
31
|
+
} as const satisfies PropertyDefinitionMap;
|
|
32
|
+
|
|
33
|
+
export class GjkClosestPoints extends createClass<GjkClosestPoints, typeof gjkClosestPointsProps>(
|
|
34
|
+
gjkClosestPointsProps
|
|
35
|
+
) {
|
|
36
|
+
reset(): void {
|
|
37
|
+
this.pointA.zero();
|
|
38
|
+
this.pointB.zero();
|
|
39
|
+
this.penetrationAxis.zero();
|
|
40
|
+
this.squaredDistance = 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const gjkCastShapeResultProps = {
|
|
45
|
+
isHitFound: BooleanType(false),
|
|
46
|
+
lambda: NumberType(0),
|
|
47
|
+
separatingAxis: MonomorphType(Vec3),
|
|
48
|
+
pointA: MonomorphType(Vec3),
|
|
49
|
+
pointB: MonomorphType(Vec3),
|
|
50
|
+
} as const satisfies PropertyDefinitionMap;
|
|
51
|
+
|
|
52
|
+
export class GjkCastShapeResult extends createClass<GjkCastShapeResult, typeof gjkCastShapeResultProps>(
|
|
53
|
+
gjkCastShapeResultProps
|
|
54
|
+
) {
|
|
55
|
+
reset(): void {
|
|
56
|
+
this.isHitFound = false;
|
|
57
|
+
this.pointA.zero();
|
|
58
|
+
this.pointB.zero();
|
|
59
|
+
this.separatingAxis.zero();
|
|
60
|
+
this.lambda = 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type Simplex = [Vec3, Vec3, Vec3, Vec3];
|
|
65
|
+
|
|
66
|
+
const transformedConvexObject = /*@__PURE__*/ new TransformedConvexObject();
|
|
67
|
+
|
|
68
|
+
const y0 = /*@__PURE__*/ Vec3.create();
|
|
69
|
+
const y1 = /*@__PURE__*/ Vec3.create();
|
|
70
|
+
const y2 = /*@__PURE__*/ Vec3.create();
|
|
71
|
+
const y3 = /*@__PURE__*/ Vec3.create();
|
|
72
|
+
const ys: Simplex = [y0, y1, y2, y3];
|
|
73
|
+
|
|
74
|
+
const p0 = /*@__PURE__*/ Vec3.create();
|
|
75
|
+
const p1 = /*@__PURE__*/ Vec3.create();
|
|
76
|
+
const p2 = /*@__PURE__*/ Vec3.create();
|
|
77
|
+
const p3 = /*@__PURE__*/ Vec3.create();
|
|
78
|
+
const ps: Simplex = [p0, p1, p2, p3];
|
|
79
|
+
|
|
80
|
+
const q0 = /*@__PURE__*/ Vec3.create();
|
|
81
|
+
const q1 = /*@__PURE__*/ Vec3.create();
|
|
82
|
+
const q2 = /*@__PURE__*/ Vec3.create();
|
|
83
|
+
const q3 = /*@__PURE__*/ Vec3.create();
|
|
84
|
+
const qs: Simplex = [q0, q1, q2, q3];
|
|
85
|
+
|
|
86
|
+
const bary = /*@__PURE__*/ BarycentricCoordinatesResult.create();
|
|
87
|
+
|
|
88
|
+
const closest = /*@__PURE__*/ ClosestPointResult.create();
|
|
89
|
+
|
|
90
|
+
const p = /*@__PURE__*/ Vec3.create();
|
|
91
|
+
const q = /*@__PURE__*/ Vec3.create();
|
|
92
|
+
const w = /*@__PURE__*/ Vec3.create();
|
|
93
|
+
const directionA = /*@__PURE__*/ Vec3.create();
|
|
94
|
+
const directionB = /*@__PURE__*/ Vec3.create();
|
|
95
|
+
const closestPointToSimplex = /*@__PURE__*/ ClosestPointToSimplex.create();
|
|
96
|
+
|
|
97
|
+
const directionA_2 = /*@__PURE__*/ Vec3.create();
|
|
98
|
+
const directionB_2 = /*@__PURE__*/ Vec3.create();
|
|
99
|
+
const x = /*@__PURE__*/ Vec3.create();
|
|
100
|
+
const prev_v = /*@__PURE__*/ Vec3.create();
|
|
101
|
+
const pq = /*@__PURE__*/ Vec3.create();
|
|
102
|
+
const normalized_v = /*@__PURE__*/ Vec3.create();
|
|
103
|
+
const convex_radius_a = /*@__PURE__*/ Vec3.create();
|
|
104
|
+
const convex_radius_b = /*@__PURE__*/ Vec3.create();
|
|
105
|
+
const zero = /*@__PURE__*/ Vec3.create();
|
|
106
|
+
|
|
107
|
+
// Calculate closest points on A and B
|
|
108
|
+
function computePointsAAndB(outPointA: Vec3, outPointB: Vec3, numPoints: number): void {
|
|
109
|
+
outPointA.zero();
|
|
110
|
+
outPointB.zero();
|
|
111
|
+
|
|
112
|
+
switch (numPoints) {
|
|
113
|
+
case 1: {
|
|
114
|
+
outPointA.copy(ps[0]);
|
|
115
|
+
outPointB.copy(qs[0]);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 2: {
|
|
120
|
+
computeBarycentricCoordinates2d(bary, ys[0], ys[1]);
|
|
121
|
+
|
|
122
|
+
outPointA.addScaled(ps[0], bary.u);
|
|
123
|
+
outPointA.addScaled(ps[1], bary.v);
|
|
124
|
+
|
|
125
|
+
outPointB.addScaled(qs[0], bary.u);
|
|
126
|
+
outPointB.addScaled(qs[1], bary.v);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case 3: {
|
|
131
|
+
computeBarycentricCoordinates3d(bary, ys[0], ys[1], ys[2]);
|
|
132
|
+
|
|
133
|
+
outPointA.addScaled(ps[0], bary.u);
|
|
134
|
+
outPointA.addScaled(ps[1], bary.v);
|
|
135
|
+
outPointA.addScaled(ps[2], bary.w);
|
|
136
|
+
|
|
137
|
+
outPointB.addScaled(qs[0], bary.u);
|
|
138
|
+
outPointB.addScaled(qs[1], bary.v);
|
|
139
|
+
outPointB.addScaled(qs[2], bary.w);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
default: {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getMaxYLengthSquared(numPoints: number): number {
|
|
150
|
+
let yLengthSquared = ys[0].squaredLength();
|
|
151
|
+
for (let i = 1; i < numPoints; ++i) {
|
|
152
|
+
yLengthSquared = Math.max(yLengthSquared, ys[i].squaredLength());
|
|
153
|
+
}
|
|
154
|
+
return yLengthSquared;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Remove points that are not in the set, only updates mP
|
|
158
|
+
function updatePointSetP(inSet: number, numPoints: number): number {
|
|
159
|
+
let newNumPoints = 0;
|
|
160
|
+
for (let i = 0; i < numPoints; ++i) {
|
|
161
|
+
if ((inSet & (1 << i)) !== 0) {
|
|
162
|
+
ps[newNumPoints].copy(ps[i]);
|
|
163
|
+
++newNumPoints;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return newNumPoints;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Remove points that are not in the set, only updates mP and mQ
|
|
170
|
+
function updatePointSetPQ(inSet: number, numPoints: number): number {
|
|
171
|
+
let newNumPoints = 0;
|
|
172
|
+
for (let i = 0; i < numPoints; ++i) {
|
|
173
|
+
if ((inSet & (1 << i)) !== 0) {
|
|
174
|
+
ps[newNumPoints].copy(ps[i]);
|
|
175
|
+
qs[newNumPoints].copy(qs[i]);
|
|
176
|
+
++newNumPoints;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return newNumPoints;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Remove points that are not in the set, updates y, p, q
|
|
183
|
+
function updatePointSetYPQ(inSet: number, numPoints: number): number {
|
|
184
|
+
let newNumPoints = 0;
|
|
185
|
+
for (let i = 0; i < numPoints; ++i) {
|
|
186
|
+
if ((inSet & (1 << i)) !== 0) {
|
|
187
|
+
ys[newNumPoints].copy(ys[i]);
|
|
188
|
+
ps[newNumPoints].copy(ps[i]);
|
|
189
|
+
qs[newNumPoints].copy(qs[i]);
|
|
190
|
+
++newNumPoints;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return newNumPoints;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const vvv = /*@__PURE__*/ Vec3.create();
|
|
197
|
+
|
|
198
|
+
function getClosest(
|
|
199
|
+
inPrevVLenSq: number,
|
|
200
|
+
outV: Vec3,
|
|
201
|
+
outVLenSq: { value: number },
|
|
202
|
+
outSet: { value: number },
|
|
203
|
+
lastPointPartOfClosestFeature: boolean,
|
|
204
|
+
numPoints: number,
|
|
205
|
+
simplex: Simplex
|
|
206
|
+
): boolean {
|
|
207
|
+
let set = 0;
|
|
208
|
+
vvv.zero();
|
|
209
|
+
|
|
210
|
+
switch (numPoints) {
|
|
211
|
+
// Single point
|
|
212
|
+
case 1: {
|
|
213
|
+
set = 0b0001;
|
|
214
|
+
vvv.copy(ys[0]);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Line segment
|
|
219
|
+
case 2: {
|
|
220
|
+
computeClosestPointOnLine(closest, simplex[0], simplex[1]);
|
|
221
|
+
set = closest.pointSet;
|
|
222
|
+
vvv.copy(closest.point);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
// Triangle
|
|
226
|
+
case 3: {
|
|
227
|
+
computeClosestPointOnTriangle(closest, simplex[0], simplex[1], simplex[2], lastPointPartOfClosestFeature);
|
|
228
|
+
set = closest.pointSet;
|
|
229
|
+
vvv.copy(closest.point);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Tetrahedron
|
|
234
|
+
case 4: {
|
|
235
|
+
computeClosestPointOnTetrahedron(
|
|
236
|
+
closest,
|
|
237
|
+
simplex[0],
|
|
238
|
+
simplex[1],
|
|
239
|
+
simplex[2],
|
|
240
|
+
simplex[3],
|
|
241
|
+
lastPointPartOfClosestFeature
|
|
242
|
+
);
|
|
243
|
+
set = closest.pointSet;
|
|
244
|
+
vvv.copy(closest.point);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
default: {
|
|
248
|
+
throw new Error("Invalid number of points in simplex");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let v_len_sq = vvv.squaredLength();
|
|
253
|
+
// Note, comparison order important: If v_len_sq is NaN then this expression will be false so we will return false
|
|
254
|
+
if (v_len_sq < inPrevVLenSq) {
|
|
255
|
+
// Return closest point
|
|
256
|
+
outV.copy(vvv);
|
|
257
|
+
outVLenSq.value = v_len_sq;
|
|
258
|
+
outSet.value = set;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// No better match found
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function computeClosestPointToSimplex(
|
|
267
|
+
result: ClosestPointToSimplex,
|
|
268
|
+
inPreviousSquaredDistance: number,
|
|
269
|
+
lastPointPartOfClosestFeature: boolean,
|
|
270
|
+
simplex: Simplex,
|
|
271
|
+
numPoints: number
|
|
272
|
+
): void {
|
|
273
|
+
switch (numPoints) {
|
|
274
|
+
case 1: {
|
|
275
|
+
// Single point
|
|
276
|
+
closest.pointSet = 0b0001;
|
|
277
|
+
closest.point.copy(simplex[0]);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
case 2: {
|
|
282
|
+
// Line segment
|
|
283
|
+
computeClosestPointOnLine(closest, simplex[0], simplex[1]);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case 3: {
|
|
288
|
+
// Triangle
|
|
289
|
+
computeClosestPointOnTriangle(closest, simplex[0], simplex[1], simplex[2], lastPointPartOfClosestFeature);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case 4: {
|
|
294
|
+
// Tetrahedron
|
|
295
|
+
computeClosestPointOnTetrahedron(
|
|
296
|
+
closest,
|
|
297
|
+
simplex[0],
|
|
298
|
+
simplex[1],
|
|
299
|
+
simplex[2],
|
|
300
|
+
simplex[3],
|
|
301
|
+
lastPointPartOfClosestFeature
|
|
302
|
+
);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
default: {
|
|
307
|
+
throw new Error("Invalid number of points");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const squaredDistance = closest.point.squaredLength();
|
|
312
|
+
if (squaredDistance < inPreviousSquaredDistance) {
|
|
313
|
+
// Note, comparison order important: If v_len_sq is NaN then this expression will be false so we will return false
|
|
314
|
+
|
|
315
|
+
// Return closest point
|
|
316
|
+
result.point.copy(closest.point);
|
|
317
|
+
result.squaredDistance = squaredDistance;
|
|
318
|
+
result.pointSet = closest.pointSet;
|
|
319
|
+
result.closestPointFound = true;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// No better match found
|
|
324
|
+
// logDebug("New closer point is further away, failed to converge");
|
|
325
|
+
result.closestPointFound = false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface GjkModuleSettings {
|
|
329
|
+
maxIterations: number;
|
|
330
|
+
tolerance: number;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export class GjkModule {
|
|
334
|
+
numPoints: number;
|
|
335
|
+
settings: GjkModuleSettings;
|
|
336
|
+
maxIterations: number;
|
|
337
|
+
tolerance: number;
|
|
338
|
+
|
|
339
|
+
constructor(settings: Partial<GjkModuleSettings> = {}) {
|
|
340
|
+
this.numPoints = 0;
|
|
341
|
+
this.settings = {
|
|
342
|
+
...GjkModule.createDefaultSettings(),
|
|
343
|
+
...settings,
|
|
344
|
+
};
|
|
345
|
+
this.maxIterations = this.settings.maxIterations;
|
|
346
|
+
this.tolerance = this.settings.tolerance;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static createDefaultSettings(): GjkModuleSettings {
|
|
350
|
+
return {
|
|
351
|
+
maxIterations: 100,
|
|
352
|
+
tolerance: 1e-5,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
getClosestPointsSimplex(supportPoints: SupportPoints) {
|
|
357
|
+
for (let i = 0; i < this.numPoints; i++) {
|
|
358
|
+
supportPoints.mY.pushBackCopy(ys[i]);
|
|
359
|
+
supportPoints.mP.pushBackCopy(ps[i]);
|
|
360
|
+
supportPoints.mQ.pushBackCopy(qs[i]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
getClosestPoints(
|
|
365
|
+
result: GjkClosestPoints,
|
|
366
|
+
inA: SupportShape,
|
|
367
|
+
inB: SupportShape,
|
|
368
|
+
inTolerance: number,
|
|
369
|
+
inMaxDistanceSquared: number,
|
|
370
|
+
inDirection: Vec3
|
|
371
|
+
): void {
|
|
372
|
+
const squaredTolerance = squared(inTolerance);
|
|
373
|
+
|
|
374
|
+
// Reset state
|
|
375
|
+
this.numPoints = 0;
|
|
376
|
+
|
|
377
|
+
// Length^2 of v
|
|
378
|
+
closestPointToSimplex.squaredDistance = inDirection.squaredLength();
|
|
379
|
+
closestPointToSimplex.point.copy(inDirection);
|
|
380
|
+
|
|
381
|
+
// Previous length^2 of v
|
|
382
|
+
// TODO: there was a bug here caused by using Infinity instead of Number.MAX_VALUE due to the value being used in a comparison, is this or similar happening anywhere else?
|
|
383
|
+
// let previousSquaredDistance = Infinity;
|
|
384
|
+
let previousSquaredDistance = Number.MAX_VALUE;
|
|
385
|
+
|
|
386
|
+
let iterations = 0;
|
|
387
|
+
while (iterations++ < this.maxIterations) {
|
|
388
|
+
// #v-ifdef DEV
|
|
389
|
+
if (this.numPoints >= 4) {
|
|
390
|
+
throw new Error("numPoints >= 4");
|
|
391
|
+
}
|
|
392
|
+
// #v-endif
|
|
393
|
+
|
|
394
|
+
// Get support points for shape A and B in the direction of v
|
|
395
|
+
directionA.copy(closestPointToSimplex.point);
|
|
396
|
+
directionB.negateVector(closestPointToSimplex.point);
|
|
397
|
+
inA.computeSupport(p, directionA);
|
|
398
|
+
inB.computeSupport(q, directionB);
|
|
399
|
+
|
|
400
|
+
// Get support point of the minkowski sum A - B of v
|
|
401
|
+
w.subtractVectors(p, q);
|
|
402
|
+
|
|
403
|
+
const dot = closestPointToSimplex.point.dot(w);
|
|
404
|
+
|
|
405
|
+
// Test if we have a separation of more than inMaxDistSq, in which case we terminate early
|
|
406
|
+
if (dot < 0.0 && dot * dot > closestPointToSimplex.squaredDistance * inMaxDistanceSquared) {
|
|
407
|
+
result.squaredDistance = Infinity;
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Store the point for later use
|
|
412
|
+
ys[this.numPoints].copy(w);
|
|
413
|
+
ps[this.numPoints].copy(p);
|
|
414
|
+
qs[this.numPoints].copy(q);
|
|
415
|
+
++this.numPoints;
|
|
416
|
+
|
|
417
|
+
closestPointToSimplex.pointSet = 0;
|
|
418
|
+
computeClosestPointToSimplex(closestPointToSimplex, previousSquaredDistance, true, ys, this.numPoints);
|
|
419
|
+
if (!closestPointToSimplex.closestPointFound) {
|
|
420
|
+
// Undo add last point
|
|
421
|
+
--this.numPoints;
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// If there are 4 points, the origin is inside the tetrahedron and we're done
|
|
426
|
+
if (closestPointToSimplex.pointSet === 0xf) {
|
|
427
|
+
// logDebug("Full simplex");
|
|
428
|
+
closestPointToSimplex.point.zero();
|
|
429
|
+
closestPointToSimplex.squaredDistance = 0.0;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Update the points of the simplex
|
|
434
|
+
this.numPoints = updatePointSetYPQ(closestPointToSimplex.pointSet, this.numPoints);
|
|
435
|
+
|
|
436
|
+
// If v is very close to zero, we consider this a collision
|
|
437
|
+
if (closestPointToSimplex.squaredDistance <= squaredTolerance) {
|
|
438
|
+
// logDebug("Distance zero");
|
|
439
|
+
closestPointToSimplex.point.zero();
|
|
440
|
+
closestPointToSimplex.squaredDistance = 0.0;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// If v is very small compared to the length of y, we also consider this a collision
|
|
445
|
+
if (closestPointToSimplex.squaredDistance <= this.tolerance * getMaxYLengthSquared(this.numPoints)) {
|
|
446
|
+
// logDebug("Machine precision reached");
|
|
447
|
+
closestPointToSimplex.point.zero();
|
|
448
|
+
closestPointToSimplex.squaredDistance = 0.0;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// The next seperation axis to test is the negative of the closest point of the Minkowski sum to the origin
|
|
453
|
+
// Note: This must be done before terminating as converged since the separating axis is -v
|
|
454
|
+
closestPointToSimplex.point.negate();
|
|
455
|
+
|
|
456
|
+
// #v-ifdef DEV
|
|
457
|
+
// If the squared length of v is not changing enough, we've converged and there is no collision
|
|
458
|
+
if (previousSquaredDistance < closestPointToSimplex.squaredDistance) {
|
|
459
|
+
throw new Error("squared distance not decreasing");
|
|
460
|
+
}
|
|
461
|
+
// #v-endif
|
|
462
|
+
|
|
463
|
+
if (previousSquaredDistance - closestPointToSimplex.squaredDistance <= this.tolerance * previousSquaredDistance) {
|
|
464
|
+
// v is a separating axis
|
|
465
|
+
// logDebug("Converged");
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
previousSquaredDistance = closestPointToSimplex.squaredDistance;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Get the closest points
|
|
473
|
+
computePointsAAndB(result.pointA, result.pointB, this.numPoints);
|
|
474
|
+
|
|
475
|
+
// #v-ifdef DEV
|
|
476
|
+
// assert ioV.LengthSq() == v_len_sq;
|
|
477
|
+
if (!isClose(closestPointToSimplex.point.squaredLength(), closestPointToSimplex.squaredDistance, 1e-3)) {
|
|
478
|
+
throw new Error("closest point squared length is not equal to closest squared distance");
|
|
479
|
+
}
|
|
480
|
+
// #v-endif
|
|
481
|
+
|
|
482
|
+
result.squaredDistance = closestPointToSimplex.squaredDistance;
|
|
483
|
+
result.penetrationAxis.copy(closestPointToSimplex.point); //ioV
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
castShape(
|
|
487
|
+
result: GjkCastShapeResult, // contains return_value, ioLambda (lambda), outPointA (pointA), outPointB (pointB), outSeparatingAxis (separatingAxis)
|
|
488
|
+
inStart: Isometry,
|
|
489
|
+
inDirection: Vec3,
|
|
490
|
+
inTolerance: number,
|
|
491
|
+
inA: SupportShape,
|
|
492
|
+
inB: SupportShape,
|
|
493
|
+
inConvexRadiusA: number,
|
|
494
|
+
inConvexRadiusB: number
|
|
495
|
+
): void {
|
|
496
|
+
zero.zero();
|
|
497
|
+
|
|
498
|
+
let squaredTolerance = squared(inTolerance);
|
|
499
|
+
|
|
500
|
+
// Calculate how close A and B (without their convex radius) need to be to each other in order for us to consider this a collision
|
|
501
|
+
const sum_convex_radius = inConvexRadiusA + inConvexRadiusB;
|
|
502
|
+
|
|
503
|
+
// Transform the shape to be cast to the starting position
|
|
504
|
+
const transformed_a = transformedConvexObject;
|
|
505
|
+
transformed_a.setData(inStart, inA);
|
|
506
|
+
|
|
507
|
+
// Reset state
|
|
508
|
+
this.numPoints = 0;
|
|
509
|
+
|
|
510
|
+
let lambda = 0.0;
|
|
511
|
+
|
|
512
|
+
// Since A is already transformed we can start the cast from zero
|
|
513
|
+
x.zero();
|
|
514
|
+
|
|
515
|
+
directionB_2.zero();
|
|
516
|
+
inB.computeSupport(q, directionB_2);
|
|
517
|
+
q.negate();
|
|
518
|
+
|
|
519
|
+
directionA_2.zero();
|
|
520
|
+
transformed_a.computeSupport(p, directionA_2);
|
|
521
|
+
|
|
522
|
+
// SOURCE
|
|
523
|
+
// vec3.add(closest.point, q, p);
|
|
524
|
+
// CHANGE
|
|
525
|
+
closestPointToSimplex.point.subtractVectors(q, p);
|
|
526
|
+
|
|
527
|
+
// let v_len_sq = Number.MAX_VALUE;
|
|
528
|
+
closestPointToSimplex.squaredDistance = Number.MAX_VALUE;
|
|
529
|
+
let allow_restart = false;
|
|
530
|
+
|
|
531
|
+
// Keeps track of separating axis of the previous iteration.
|
|
532
|
+
// Initialized at zero as we don't know if our first v is actually a separating axis.
|
|
533
|
+
prev_v.zero();
|
|
534
|
+
|
|
535
|
+
let iterations = 0;
|
|
536
|
+
while (iterations++ < this.maxIterations) {
|
|
537
|
+
// Calculate the minkowski difference inB - inA
|
|
538
|
+
// inA is moving, so we need to add the back side of inB to the front side of inA
|
|
539
|
+
// Keep the support points on A and B separate so that in the end we can calculate a contact point
|
|
540
|
+
directionA_2.negateVector(closestPointToSimplex.point);
|
|
541
|
+
transformed_a.computeSupport(p, directionA_2);
|
|
542
|
+
|
|
543
|
+
directionB_2.copy(closestPointToSimplex.point);
|
|
544
|
+
inB.computeSupport(q, directionB_2);
|
|
545
|
+
|
|
546
|
+
pq.subtractVectors(q, p);
|
|
547
|
+
w.subtractVectors(x, pq);
|
|
548
|
+
|
|
549
|
+
// Difference from article to this code:
|
|
550
|
+
// We did not include the convex radius in p and q in order to be able to calculate a good separating axis at the end of the algorithm.
|
|
551
|
+
// However when moving forward along inDirection we do need to take this into account so that we keep A and B separated by the sum of their convex radii.
|
|
552
|
+
// From p we have to subtract: inConvexRadiusA * v / |v|
|
|
553
|
+
// To q we have to add: inConvexRadiusB * v / |v|
|
|
554
|
+
// This means that to w we have to add: -(inConvexRadiusA + inConvexRadiusB) * v / |v|
|
|
555
|
+
// So to v . w we have to add: v . (-(inConvexRadiusA + inConvexRadiusB) * v / |v|) = -(inConvexRadiusA + inConvexRadiusB) * |v|
|
|
556
|
+
const v_dot_w = closestPointToSimplex.point.dot(w) - sum_convex_radius * closestPointToSimplex.point.length();
|
|
557
|
+
if (v_dot_w > 0.0) {
|
|
558
|
+
// If ray and normal are in the same direction, we've passed A and there's no collision
|
|
559
|
+
const v_dot_r = closestPointToSimplex.point.dot(inDirection);
|
|
560
|
+
if (v_dot_r >= 0.0) {
|
|
561
|
+
result.isHitFound = false;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Update the lower bound for lambda
|
|
566
|
+
const delta = v_dot_w / v_dot_r;
|
|
567
|
+
const old_lambda = lambda;
|
|
568
|
+
lambda -= delta;
|
|
569
|
+
|
|
570
|
+
// If lambda didn't change, we cannot converge any further and we assume a hit
|
|
571
|
+
if (old_lambda === lambda) {
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// If lambda is bigger or equal than max, we don't have a hit
|
|
576
|
+
if (lambda >= result.lambda) {
|
|
577
|
+
result.isHitFound = false;
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Update x to new closest point on the ray
|
|
582
|
+
x.scaleVector(inDirection, lambda);
|
|
583
|
+
|
|
584
|
+
// We've shifted x, so reset v_len_sq so that it is not used as early out when GetClosest returns false
|
|
585
|
+
closestPointToSimplex.squaredDistance = Number.MAX_VALUE;
|
|
586
|
+
|
|
587
|
+
// Now that we've moved, we know that A and B are not intersecting at lambda = 0, so we can update our tolerance to stop iterating
|
|
588
|
+
// as soon as A and B are inConvexRadiusA + inConvexRadiusB apart
|
|
589
|
+
squaredTolerance = squared(inTolerance + sum_convex_radius);
|
|
590
|
+
|
|
591
|
+
// We allow rebuilding the simplex once after x changes because the simplex was built
|
|
592
|
+
// for another x and numerical round off builds up as you keep adding points to an
|
|
593
|
+
// existing simplex
|
|
594
|
+
allow_restart = true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Add p to set P, q to set Q: P = P U {p}, Q = Q U {q}
|
|
598
|
+
ps[this.numPoints].copy(p);
|
|
599
|
+
qs[this.numPoints].copy(q);
|
|
600
|
+
++this.numPoints;
|
|
601
|
+
|
|
602
|
+
// Calculate Y = {x} - (Q - P)
|
|
603
|
+
for (let i = 0; i < this.numPoints; ++i) {
|
|
604
|
+
pq.subtractVectors(qs[i], ps[i]);
|
|
605
|
+
ys[i].subtractVectors(x, pq);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Determine the new closest point from Y to origin
|
|
609
|
+
closestPointToSimplex.pointSet = 0;
|
|
610
|
+
computeClosestPointToSimplex(
|
|
611
|
+
closestPointToSimplex,
|
|
612
|
+
closestPointToSimplex.squaredDistance,
|
|
613
|
+
false,
|
|
614
|
+
ys,
|
|
615
|
+
this.numPoints
|
|
616
|
+
);
|
|
617
|
+
if (!closestPointToSimplex.closestPointFound) {
|
|
618
|
+
// Only allow 1 restart, if we still can't get a closest point
|
|
619
|
+
// we're so close that we return this as a hit
|
|
620
|
+
if (!allow_restart) {
|
|
621
|
+
// logDebug("Failed to converge");
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// If we fail to converge, we start again with the last point as simplex
|
|
626
|
+
// logDebug("Restarting");
|
|
627
|
+
|
|
628
|
+
allow_restart = false;
|
|
629
|
+
ps[0].copy(p);
|
|
630
|
+
this.numPoints = 1;
|
|
631
|
+
closestPointToSimplex.point.subtractVectors(x, p);
|
|
632
|
+
closestPointToSimplex.squaredDistance = Number.MAX_VALUE;
|
|
633
|
+
continue;
|
|
634
|
+
} else if (closestPointToSimplex.pointSet === 0xf) {
|
|
635
|
+
// logDebug("Full simplex");
|
|
636
|
+
|
|
637
|
+
// #v-ifdef DEV
|
|
638
|
+
// We're inside the tetrahedron, we have a hit (verify that length of v is 0)
|
|
639
|
+
if (closestPointToSimplex.squaredDistance !== 0.0) {
|
|
640
|
+
throw new Error("v length must be 0");
|
|
641
|
+
}
|
|
642
|
+
// #v-endif
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Update the points P and Q to form the new simplex
|
|
646
|
+
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
|
|
647
|
+
this.numPoints = updatePointSetPQ(closestPointToSimplex.pointSet, this.numPoints);
|
|
648
|
+
|
|
649
|
+
// Check if A and B are touching according to our tolerance
|
|
650
|
+
if (closestPointToSimplex.squaredDistance <= squaredTolerance) {
|
|
651
|
+
// logDebug("Converged");
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Store our v to return as separating axis
|
|
656
|
+
prev_v.copy(closestPointToSimplex.point);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Calculate Y = {x} - (Q - P) again so we can calculate the contact points
|
|
660
|
+
for (let i = 0; i < this.numPoints; ++i) {
|
|
661
|
+
pq.subtractVectors(qs[i], ps[i]);
|
|
662
|
+
ys[i].subtractVectors(x, pq);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Calculate the offset we need to apply to A and B to correct for the convex radius
|
|
666
|
+
closestPointToSimplex.point.normalizedOr(normalized_v, zero);
|
|
667
|
+
convex_radius_a.scaleVector(normalized_v, inConvexRadiusA);
|
|
668
|
+
convex_radius_b.scaleVector(normalized_v, inConvexRadiusB);
|
|
669
|
+
|
|
670
|
+
const bary = /*@__PURE__*/ BarycentricCoordinatesResult.create();
|
|
671
|
+
result.pointA.zero();
|
|
672
|
+
result.pointB.zero();
|
|
673
|
+
|
|
674
|
+
// Get the contact point
|
|
675
|
+
// Note that A and B will coincide when lambda > 0. In this case we calculate only B as it is more accurate as it contains less terms.
|
|
676
|
+
switch (this.numPoints) {
|
|
677
|
+
case 1: {
|
|
678
|
+
result.pointB.addVectors(qs[0], convex_radius_b);
|
|
679
|
+
if (lambda > 0.0) {
|
|
680
|
+
result.pointA.copy(result.pointB);
|
|
681
|
+
} else {
|
|
682
|
+
result.pointA.subtractVectors(ps[0], convex_radius_a);
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case 2: {
|
|
687
|
+
computeBarycentricCoordinates2d(bary, ys[0], ys[1]);
|
|
688
|
+
|
|
689
|
+
result.pointB.addScaled(qs[0], bary.u);
|
|
690
|
+
result.pointB.addScaled(qs[1], bary.v);
|
|
691
|
+
result.pointB.addVectors(result.pointB, convex_radius_b);
|
|
692
|
+
|
|
693
|
+
if (lambda > 0.0) {
|
|
694
|
+
result.pointA.copy(result.pointB);
|
|
695
|
+
} else {
|
|
696
|
+
result.pointA.addScaled(ps[0], bary.u);
|
|
697
|
+
result.pointA.addScaled(ps[1], bary.v);
|
|
698
|
+
result.pointA.subtractVectors(result.pointA, convex_radius_a);
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
case 3:
|
|
703
|
+
case 4: {
|
|
704
|
+
// A full simplex, we can't properly determine a contact point! As contact point we take the closest point of the previous iteration.
|
|
705
|
+
computeBarycentricCoordinates3d(bary, ys[0], ys[1], ys[2]);
|
|
706
|
+
|
|
707
|
+
result.pointB.addScaled(qs[0], bary.u);
|
|
708
|
+
result.pointB.addScaled(qs[1], bary.v);
|
|
709
|
+
result.pointB.addScaled(qs[2], bary.w);
|
|
710
|
+
result.pointB.addVectors(result.pointB, convex_radius_b);
|
|
711
|
+
|
|
712
|
+
if (lambda > 0.0) {
|
|
713
|
+
result.pointA.copy(result.pointB);
|
|
714
|
+
} else {
|
|
715
|
+
result.pointA.addScaled(ps[0], bary.u);
|
|
716
|
+
result.pointA.addScaled(ps[1], bary.v);
|
|
717
|
+
result.pointA.addScaled(ps[2], bary.w);
|
|
718
|
+
result.pointA.subtractVectors(result.pointA, convex_radius_a);
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
default: {
|
|
723
|
+
throw new Error("Invalid number of points");
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Store separating axis, in case we have a convex radius we can just return v,
|
|
728
|
+
// otherwise v will be very small and we resort to returning previous v as an approximation.
|
|
729
|
+
if (sum_convex_radius > 0.0) {
|
|
730
|
+
result.separatingAxis.negateVector(closestPointToSimplex.point);
|
|
731
|
+
} else {
|
|
732
|
+
result.separatingAxis.negateVector(prev_v);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Store hit fraction
|
|
736
|
+
result.lambda = lambda;
|
|
737
|
+
result.isHitFound = true;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
castRay(
|
|
741
|
+
inRayOrigin: Vec3,
|
|
742
|
+
inRayDirection: Vec3,
|
|
743
|
+
inTolerance: number,
|
|
744
|
+
inA: SupportShape,
|
|
745
|
+
ioLambda: { fraction: number }
|
|
746
|
+
): boolean {
|
|
747
|
+
const squaredTolerance = squared(inTolerance);
|
|
748
|
+
|
|
749
|
+
// Reset state
|
|
750
|
+
this.numPoints = 0;
|
|
751
|
+
|
|
752
|
+
let lambda = 0.0;
|
|
753
|
+
x.copy(inRayOrigin);
|
|
754
|
+
directionB_2.zero();
|
|
755
|
+
inA.computeSupport(p, directionB_2);
|
|
756
|
+
v.subtractVectors(x, p);
|
|
757
|
+
let v_len_sq = { value: Number.MAX_VALUE };
|
|
758
|
+
let allowRestart = false;
|
|
759
|
+
|
|
760
|
+
let iterations = 0;
|
|
761
|
+
while (true) {
|
|
762
|
+
if (iterations >= this.maxIterations) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Get new support point
|
|
767
|
+
inA.computeSupport(p, v);
|
|
768
|
+
w.subtractVectors(x, p);
|
|
769
|
+
|
|
770
|
+
const vDotW = v.dot(w);
|
|
771
|
+
|
|
772
|
+
if (vDotW > 0.0) {
|
|
773
|
+
// If ray and normal are in the same direction, we've passed A and there's no collision
|
|
774
|
+
const vDotR = v.dot(inRayDirection);
|
|
775
|
+
|
|
776
|
+
// Instead of checking >= 0, check with epsilon as we don't want the division below to overflow to infinity as it can cause a float exception
|
|
777
|
+
if (vDotR >= -1.0e-18) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Update the lower bound for lambda
|
|
782
|
+
const delta = vDotW / vDotR;
|
|
783
|
+
const oldLambda = lambda;
|
|
784
|
+
lambda -= delta;
|
|
785
|
+
|
|
786
|
+
// If lambda didn't change, we cannot converge any further and we assume a hit
|
|
787
|
+
if (oldLambda === lambda) {
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// If lambda is bigger or equal than max, we don't have a hit
|
|
792
|
+
if (lambda >= ioLambda.fraction) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Update x to new closest point on the ray
|
|
797
|
+
x.addScaledToVector(inRayOrigin, inRayDirection, lambda);
|
|
798
|
+
|
|
799
|
+
// We've shifted x, so reset vLengthSquared so that it is not used as early out for GetClosest
|
|
800
|
+
v_len_sq.value = Number.MAX_VALUE;
|
|
801
|
+
|
|
802
|
+
// We allow rebuilding the simplex once after x changes because the simplex was built
|
|
803
|
+
// for another x and numerical round off builds up as you keep adding points to an
|
|
804
|
+
// existing simplex
|
|
805
|
+
allowRestart = true;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Add p to set P: P = P U {p}
|
|
809
|
+
ps[this.numPoints].copy(p);
|
|
810
|
+
++this.numPoints;
|
|
811
|
+
|
|
812
|
+
// Calculate Y = {x} - P
|
|
813
|
+
for (let i = 0; i < this.numPoints; ++i) {
|
|
814
|
+
ys[i].subtractVectors(x, ps[i]);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Determine the new closest point from Y to origin
|
|
818
|
+
const set = { value: 0 };
|
|
819
|
+
const found = getClosest(v_len_sq.value, v, v_len_sq, set, false, this.numPoints, ys);
|
|
820
|
+
|
|
821
|
+
if (!found) {
|
|
822
|
+
// Only allow 1 restart, if we still can't get a closest point
|
|
823
|
+
// we're so close that we return this as a hit
|
|
824
|
+
if (!allowRestart) {
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// If we fail to converge, we start again with the last point as simplex
|
|
829
|
+
allowRestart = false;
|
|
830
|
+
ps[0].copy(p);
|
|
831
|
+
this.numPoints = 1;
|
|
832
|
+
v.subtractVectors(x, p);
|
|
833
|
+
v_len_sq.value = Number.MAX_VALUE;
|
|
834
|
+
continue;
|
|
835
|
+
} else if (set.value === 0xf) {
|
|
836
|
+
// #v-ifdef DEV
|
|
837
|
+
// We're inside the tetrahedron, we have a hit (verify that length of v is 0)
|
|
838
|
+
if (v_len_sq.value !== 0.0) {
|
|
839
|
+
throw new Error("v length must be 0");
|
|
840
|
+
}
|
|
841
|
+
// #v-endif
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Update the points P to form the new simplex
|
|
846
|
+
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
|
|
847
|
+
this.numPoints = updatePointSetP(set.value, this.numPoints);
|
|
848
|
+
|
|
849
|
+
// Check if A and B are touching according to our tolerance
|
|
850
|
+
if (v_len_sq.value <= squaredTolerance) {
|
|
851
|
+
// logDebug("Converged");
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
iterations++;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Store hit fraction
|
|
859
|
+
ioLambda.fraction = lambda;
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const v = /*@__PURE__*/ Vec3.create();
|