@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.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/package.json +32 -0
  4. package/src/builders/ConvexHullBuilder.ts +437 -0
  5. package/src/builders/ConvexHullBuilder2d.ts +344 -0
  6. package/src/builders/ConvexHullBuilder3d.ts +1689 -0
  7. package/src/builders/HeightMapBuilder.ts +414 -0
  8. package/src/builders/TriangleMeshBuilder.ts +92 -0
  9. package/src/collision/CastShapesModule.ts +184 -0
  10. package/src/collision/CollideShapesModule.ts +152 -0
  11. package/src/collision/HeightMapCaster.ts +38 -0
  12. package/src/collision/HeightMapCollider.ts +33 -0
  13. package/src/collision/TriangleCaster.ts +249 -0
  14. package/src/collision/TriangleCollider.ts +308 -0
  15. package/src/collision/TriangleCollider2.ts +379 -0
  16. package/src/collision/activeEdge.ts +146 -0
  17. package/src/collision/cast/cast.ts +139 -0
  18. package/src/collision/cast/castCompoundVsCompound.ts +59 -0
  19. package/src/collision/cast/castCompoundVsConvex.ts +116 -0
  20. package/src/collision/cast/castConvexVsCompound.ts +123 -0
  21. package/src/collision/cast/castConvexVsConvex.ts +213 -0
  22. package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
  23. package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
  24. package/src/collision/cast/castRayVsCompound.ts +44 -0
  25. package/src/collision/cast/castRayVsConvex.ts +45 -0
  26. package/src/collision/cast/castRayVsHeightMap.ts +58 -0
  27. package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
  28. package/src/collision/closestPoints/closestPoints.ts +23 -0
  29. package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
  30. package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
  31. package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
  32. package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
  33. package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
  34. package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
  35. package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
  36. package/src/collision/collide/collide.ts +146 -0
  37. package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
  38. package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
  39. package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
  40. package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
  41. package/src/collision/collide/collideConvexVsCompound.ts +57 -0
  42. package/src/collision/collide/collideConvexVsConvex.ts +225 -0
  43. package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
  44. package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
  45. package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
  46. package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
  47. package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
  48. package/src/collision/collide/collideSphereVsSphere.ts +81 -0
  49. package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
  50. package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
  51. package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
  52. package/src/collision/epa/StaticArray.ts +154 -0
  53. package/src/collision/epa/TriangleFactory.ts +32 -0
  54. package/src/collision/epa/arrays.ts +99 -0
  55. package/src/collision/epa/binaryHeap.ts +82 -0
  56. package/src/collision/epa/structs.ts +227 -0
  57. package/src/collision/gjk/GjkModule.ts +864 -0
  58. package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
  59. package/src/collision/gjk/SupportPoints.ts +50 -0
  60. package/src/collision/imp/MinkowskiDifference.ts +36 -0
  61. package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
  62. package/src/collision/imp/finalizeImpResult.ts +69 -0
  63. package/src/collision/imp/findContactImp.ts +196 -0
  64. package/src/collision/imp/imp.ts +28 -0
  65. package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
  66. package/src/collision/mpr/findPortal.ts +152 -0
  67. package/src/collision/mpr/mpr.ts +29 -0
  68. package/src/collision/mpr/updatePortal.ts +52 -0
  69. package/src/constraints/BaseConstraint.ts +50 -0
  70. package/src/constraints/ConstraintOptions.ts +22 -0
  71. package/src/constraints/ConstraintSolver.ts +119 -0
  72. package/src/constraints/DistanceConstraint.ts +229 -0
  73. package/src/constraints/FixedConstraint.ts +203 -0
  74. package/src/constraints/HingeConstraint.ts +460 -0
  75. package/src/constraints/PointConstraint.ts +108 -0
  76. package/src/constraints/components/AngleComponent.ts +226 -0
  77. package/src/constraints/components/AxisComponent.ts +263 -0
  78. package/src/constraints/components/HingeComponent.ts +215 -0
  79. package/src/constraints/components/Motor.ts +36 -0
  80. package/src/constraints/components/PointConstraintComponent.ts +179 -0
  81. package/src/constraints/components/RotationEulerComponent.ts +139 -0
  82. package/src/constraints/components/Spring.ts +30 -0
  83. package/src/constraints/components/SpringComponent.ts +71 -0
  84. package/src/constraints/types.ts +6 -0
  85. package/src/helpers.ts +147 -0
  86. package/src/index.ts +50 -0
  87. package/src/math/BasicTransform.ts +19 -0
  88. package/src/math/NumberValue.ts +13 -0
  89. package/src/math/isometry.ts +64 -0
  90. package/src/math/mat3.ts +529 -0
  91. package/src/math/mat4.ts +588 -0
  92. package/src/math/quat.ts +193 -0
  93. package/src/math/scalar.ts +81 -0
  94. package/src/math/tensor.ts +17 -0
  95. package/src/math/vec3.ts +589 -0
  96. package/src/math/vec4.ts +10 -0
  97. package/src/physics/Body.ts +581 -0
  98. package/src/physics/CollisionFilter.ts +52 -0
  99. package/src/physics/SleepModule.ts +163 -0
  100. package/src/physics/broadphase/BodyPairsModule.ts +363 -0
  101. package/src/physics/broadphase/BvhModule.ts +237 -0
  102. package/src/physics/broadphase/BvhTree.ts +803 -0
  103. package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
  104. package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
  105. package/src/physics/manifold/ContactManifold.ts +227 -0
  106. package/src/physics/manifold/ContactManifoldModule.ts +623 -0
  107. package/src/physics/manifold/Face.ts +119 -0
  108. package/src/physics/manifold/ManifoldCache.ts +116 -0
  109. package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
  110. package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
  111. package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
  112. package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
  113. package/src/physics/solver/ContactConstraintModule.ts +659 -0
  114. package/src/physics/solver/ManifoldConstraint.ts +420 -0
  115. package/src/physics/solver/estimateCollisionResponse.ts +146 -0
  116. package/src/shape/Aabb.ts +400 -0
  117. package/src/shape/Box.ts +231 -0
  118. package/src/shape/Capsule.ts +332 -0
  119. package/src/shape/CompoundShape.ts +288 -0
  120. package/src/shape/Convex.ts +130 -0
  121. package/src/shape/ConvexHull.ts +423 -0
  122. package/src/shape/Cylinder.ts +313 -0
  123. package/src/shape/HeightMap.ts +511 -0
  124. package/src/shape/Line.ts +14 -0
  125. package/src/shape/Plane.ts +116 -0
  126. package/src/shape/Ray.ts +81 -0
  127. package/src/shape/Segment.ts +25 -0
  128. package/src/shape/Shape.ts +77 -0
  129. package/src/shape/Sphere.ts +181 -0
  130. package/src/shape/TransformedShape.ts +51 -0
  131. package/src/shape/Triangle.ts +122 -0
  132. package/src/shape/TriangleMesh.ts +186 -0
  133. package/src/types.ts +1 -0
  134. package/src/world.ts +1335 -0
  135. package/tests/BodyPairsModule.test.ts +71 -0
  136. package/tests/BvhTree.test.ts +406 -0
  137. package/tests/test.md +642 -0
  138. package/tests/vec3.test.ts +12 -0
  139. package/tsconfig.json +20 -0
  140. package/vite.config.js +40 -0
@@ -0,0 +1,493 @@
1
+ import { createClass, MonomorphType, NumberType, PropertyDefinitionMap } from "monomorph";
2
+ import { Vec3 } from "../../math/vec3";
3
+ import { GjkCastShapeResult, GjkClosestPoints, GjkModule } from "./GjkModule";
4
+ import { degreesToRadians, squared } from "../../math/scalar";
5
+ import { Isometry } from "../../math/isometry";
6
+ import { ConvexRadiusObject, SupportShape, TransformedConvexObject } from "../../shape/Convex";
7
+ import { EpaConvexHullBuilder, EpaConvexHullBuilderSettings } from "../epa/EpaConvexHullBuilder";
8
+ import { assert } from "../../helpers";
9
+ import { SupportPoints } from "./SupportPoints";
10
+ import { Mat4 } from "../../math/mat4";
11
+ import { NewTriangles } from "../epa/arrays";
12
+ import { Triangle } from "../epa/structs";
13
+
14
+ const d1 = /*@__PURE__*/ Vec3.create({ x: 0, y: 1, z: 0 });
15
+ const d2 = /*@__PURE__*/ Vec3.create({ x: -1, y: -1, z: -1 });
16
+ const d3 = /*@__PURE__*/ Vec3.create({ x: 1, y: -1, z: -1 });
17
+ const d4 = /*@__PURE__*/ Vec3.create({ x: 0, y: -1, z: 1 });
18
+ const axis = /*@__PURE__*/ Vec3.create();
19
+ const rotation = /*@__PURE__*/ Mat4.create();
20
+ const dir1 = /*@__PURE__*/ Vec3.create();
21
+ const dir2 = /*@__PURE__*/ Vec3.create();
22
+ const dir3 = /*@__PURE__*/ Vec3.create();
23
+ const p2 = /*@__PURE__*/ Vec3.create();
24
+ const q2 = /*@__PURE__*/ Vec3.create();
25
+ const w2 = /*@__PURE__*/ Vec3.create();
26
+ const negatedNormal = /*@__PURE__*/ Vec3.create();
27
+ const p01 = /*@__PURE__*/ Vec3.create();
28
+ const p02 = /*@__PURE__*/ Vec3.create();
29
+ const q01 = /*@__PURE__*/ Vec3.create();
30
+ const q02 = /*@__PURE__*/ Vec3.create();
31
+ const p10 = /*@__PURE__*/ Vec3.create();
32
+ const p12 = /*@__PURE__*/ Vec3.create();
33
+ const q10 = /*@__PURE__*/ Vec3.create();
34
+ const q12 = /*@__PURE__*/ Vec3.create();
35
+ export const enum PenetrationDepthStatus {
36
+ NotColliding,
37
+ Colliding,
38
+ Indeterminate,
39
+ }
40
+
41
+ const penetrationDepthProps = {
42
+ status: NumberType(0),
43
+ penetrationAxis: MonomorphType(Vec3),
44
+ pointA: MonomorphType(Vec3),
45
+ pointB: MonomorphType(Vec3),
46
+ } as const satisfies PropertyDefinitionMap;
47
+
48
+ export class PenetrationDepth extends createClass<PenetrationDepth, typeof penetrationDepthProps>(
49
+ penetrationDepthProps
50
+ ) {}
51
+
52
+ const transformed_a = /*@__PURE__*/ new TransformedConvexObject();
53
+ const closest = /*@__PURE__*/ GjkClosestPoints.create();
54
+ const add_convex_a = /*@__PURE__*/ ConvexRadiusObject.create();
55
+ const add_convex_b = /*@__PURE__*/ ConvexRadiusObject.create();
56
+
57
+ export class PenetrationDepthModule {
58
+ gjkModule: GjkModule;
59
+ hull: EpaConvexHullBuilder;
60
+ supportPoints: SupportPoints;
61
+ newTriangles: NewTriangles;
62
+ maxPointsToIncludeOriginInHull: number;
63
+
64
+ constructor(
65
+ gjk: GjkModule,
66
+ hullBuilderSettings: Partial<EpaConvexHullBuilderSettings> = {},
67
+ maxPointsToIncludeOriginInHull: number = 32
68
+ ) {
69
+ this.hull = new EpaConvexHullBuilder(hullBuilderSettings);
70
+ // #v-ifdef DEV
71
+ assert(
72
+ maxPointsToIncludeOriginInHull < this.hull.settings.maxPoints,
73
+ "maxPointsToIncludeOriginInHull must be less than maxPoints in hull builder settings"
74
+ );
75
+ // #v-endif
76
+ this.maxPointsToIncludeOriginInHull = maxPointsToIncludeOriginInHull;
77
+ this.gjkModule = gjk;
78
+ this.supportPoints = new SupportPoints(this.hull.settings.maxPoints);
79
+ this.hull.positions = this.supportPoints.mY;
80
+ this.newTriangles = new NewTriangles(this.hull.settings.maxEdgeLength);
81
+ }
82
+
83
+ getPenetrationDepthStepGjk(
84
+ result: PenetrationDepth,
85
+ inAExcludingConvexRadius: SupportShape,
86
+ inBExcludingConvexRadius: SupportShape,
87
+ inConvexRadiusA: number,
88
+ inConvexRadiusB: number,
89
+ inDirection: Vec3,
90
+ inTolerance: number
91
+ ): void {
92
+ // #v-ifdef DEV
93
+ // Don't supply a zero ioV, we only want to get points on the hull of the Minkowsky sum and not internal points
94
+ if (inDirection.isNearZero()) {
95
+ throw new Error("inDirection cannot be zero");
96
+ }
97
+
98
+ // #v-endif
99
+ // Get closest points
100
+ const combinedRadius = inConvexRadiusA + inConvexRadiusB;
101
+ const combinedRadiusSquared = squared(combinedRadius);
102
+
103
+ this.gjkModule.getClosestPoints(
104
+ closest,
105
+ inAExcludingConvexRadius,
106
+ inBExcludingConvexRadius,
107
+ inTolerance,
108
+ combinedRadiusSquared,
109
+ inDirection
110
+ );
111
+ result.pointA.copy(closest.pointA);
112
+ result.pointB.copy(closest.pointB);
113
+ result.penetrationAxis.copy(closest.penetrationAxis);
114
+
115
+ if (closest.squaredDistance > combinedRadiusSquared) {
116
+ // No collision
117
+ result.status = PenetrationDepthStatus.NotColliding;
118
+ return;
119
+ }
120
+
121
+ if (closest.squaredDistance > 0.0) {
122
+ // Collision within convex radius, adjust points for convex radius
123
+ const vLength = Math.sqrt(closest.squaredDistance); // GetClosestPoints function returns |ioV|^2 when return value < FLT_MAX
124
+ result.pointA.addScaled(result.penetrationAxis, inConvexRadiusA / vLength);
125
+ result.pointB.addScaled(result.penetrationAxis, -inConvexRadiusB / vLength);
126
+ result.status = PenetrationDepthStatus.Colliding;
127
+ return;
128
+ }
129
+
130
+ result.status = PenetrationDepthStatus.Indeterminate;
131
+ }
132
+
133
+ // TODO: fix usage of inPenetrationTolerance
134
+ castShape(
135
+ result: GjkCastShapeResult,
136
+ inStart: Isometry,
137
+ inDirection: Vec3,
138
+ inCollisionTolerance: number,
139
+ inPenetrationTolerance: number,
140
+ inA: SupportShape,
141
+ inB: SupportShape,
142
+ inConvexRadiusA: number,
143
+ inConvexRadiusB: number,
144
+ inReturnDeepestPoint: boolean
145
+ ): void {
146
+ // First determine if there's a collision at all
147
+ this.gjkModule.castShape(
148
+ result,
149
+ inStart,
150
+ inDirection,
151
+ inCollisionTolerance,
152
+ inA,
153
+ inB,
154
+ inConvexRadiusA,
155
+ inConvexRadiusB
156
+ );
157
+ // ioCollector.addGjkCastShapeResult(result);
158
+ if (result.isHitFound === false) {
159
+ return;
160
+ }
161
+
162
+ // When our contact normal is too small, we don't have an accurate result
163
+ const contact_normal_invalid = result.separatingAxis.isNearZero(squared(inCollisionTolerance));
164
+
165
+ if (
166
+ inReturnDeepestPoint &&
167
+ result.lambda === 0.0 && // Only when lambda = 0 we can have the bodies overlap
168
+ (inConvexRadiusA + inConvexRadiusB === 0.0 || // When no convex radius was provided we can never trust contact points at lambda = 0
169
+ contact_normal_invalid)
170
+ ) {
171
+ // If we're initially intersecting, we need to run the EPA algorithm in order to find the deepest contact point
172
+ add_convex_a.setData(inConvexRadiusA, inA);
173
+ add_convex_b.setData(inConvexRadiusB, inB);
174
+ transformed_a.setData(inStart, add_convex_a);
175
+
176
+ // TODO: replace epa for imp
177
+ // if (!GetPenetrationDepthStepEPA(transformed_a, add_convex_b, inPenetrationTolerance, outContactNormal, outPointA, outPointB))
178
+ // return false;
179
+ result.isHitFound = false;
180
+ return;
181
+ } else if (contact_normal_invalid) {
182
+ // If we weren't able to calculate a contact normal, use the cast direction instead
183
+ result.separatingAxis.copy(inDirection);
184
+ }
185
+
186
+ // vec3.normalize(result.separatingAxis, result.separatingAxis);
187
+
188
+ result.isHitFound = true;
189
+ return;
190
+ }
191
+
192
+ getPenetrationDepthStepEPA(
193
+ inAIncludingConvexRadius: SupportShape,
194
+ inBIncludingConvexRadius: SupportShape,
195
+ inTolerance: number,
196
+ outV: Vec3,
197
+ outPointA: Vec3,
198
+ outPointB: Vec3
199
+ ): boolean {
200
+ // #v-ifdef DEV
201
+ assert(inTolerance >= Number.EPSILON, "inTolerance must be greater than or equal to FLT_EPSILON");
202
+ // #v-endif
203
+
204
+ // fetch the simplex from the gjk algorithm
205
+ const support_points = this.supportPoints;
206
+ support_points.clear();
207
+ this.gjkModule.getClosestPointsSimplex(support_points);
208
+
209
+ // Fill up the amount of support points to 4
210
+ switch (support_points.mY.size()) {
211
+ case 1: {
212
+ // #v-ifdef DEV
213
+ // 1 vertex, which must be at the origin, which is useless for our purpose
214
+ assert(
215
+ support_points.mY.values[0].isNearZero(squared(this.gjkModule.tolerance)),
216
+ "only support point Y[0] must not be near zero"
217
+ );
218
+ // #v-endif
219
+ support_points.popBack();
220
+
221
+ const p1 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, d1);
222
+ const p2 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, d2);
223
+ const p3 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, d3);
224
+ const p4 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, d4);
225
+
226
+ // #v-ifdef DEV
227
+ assert(p1 === 0, "support point index 0 must be added first");
228
+ assert(p2 === 1, "support point index 1 must be added second");
229
+ assert(p3 === 2, "support point index 2 must be added third");
230
+ assert(p4 === 3, "support point index 3 must be added fourth");
231
+ // #v-endif
232
+ break;
233
+ }
234
+
235
+ case 2: {
236
+ // Two vertices, create 3 extra by taking perpendicular axis and rotating it around in 120 degree increments
237
+
238
+ axis.subtractVectors(support_points.mY.values[1], support_points.mY.values[0]).normalize();
239
+
240
+ rotation.fromAxisAngle(axis, degreesToRadians(120.0));
241
+
242
+ dir1.computeNormalizedPerpendicular(axis);
243
+ rotation.multiply3x3(dir2, dir1);
244
+ rotation.multiply3x3(dir3, dir2);
245
+
246
+ const p1 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, dir1);
247
+ const p2 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, dir2);
248
+ const p3 = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, dir3);
249
+
250
+ // #v-ifdef DEV
251
+ assert(p1 === 2, "support point index 2 must be added first");
252
+ assert(p2 === 3, "support point index 3 must be added second");
253
+ assert(p3 === 4, "support point index 4 must be added third");
254
+ // #v-endif
255
+ break;
256
+ }
257
+
258
+ case 3:
259
+ case 4: {
260
+ // We already have enough points
261
+ break;
262
+ }
263
+ }
264
+
265
+ // Create hull out of the initial points
266
+ // #v-ifdef DEV
267
+ assert(support_points.mY.size() >= 3, "support points must have at least 3 points");
268
+ // #v-endif
269
+ const hull = this.hull;
270
+ hull.initialize(0, 1, 2);
271
+ for (let i = 3; i < support_points.mY.size(); ++i) {
272
+ const distSq: { value: number } = { value: -1 };
273
+ const t = hull.findFacingTriangle(support_points.mY.values[i], distSq);
274
+ if (t !== null) {
275
+ const newTriangles = this.newTriangles;
276
+ newTriangles.clear();
277
+
278
+ if (!hull.addPoint(t, i, Number.MAX_VALUE, newTriangles)) {
279
+ // We can't recover from a failure to add a point to the hull because the old triangles have been unlinked already.
280
+ // Assume no collision. This can happen if the shapes touch in 1 point (or plane) in which case the hull is degenerate.
281
+ return false;
282
+ }
283
+ }
284
+ }
285
+
286
+ // Loop until we are sure that the origin is inside the hull
287
+ while (true) {
288
+ // Get the next closest triangle
289
+ const t = hull.peekClosestTriangleInQueue();
290
+
291
+ // #v-ifdef DEV
292
+ // assert
293
+ if (!t) {
294
+ throw new Error("t must not be null");
295
+ }
296
+ // #v-endif
297
+
298
+ // Don't process removed triangles, just free them (because they're in a heap we don't remove them earlier since we would have to rebuild the sorted heap)
299
+ if (t.removed) {
300
+ hull.popClosestTriangleFromQueue();
301
+
302
+ // If we run out of triangles, we couldn't include the origin in the hull so there must be very little penetration and we report no collision.
303
+ if (!hull.hasNextTriangle()) {
304
+ return false;
305
+ }
306
+
307
+ hull.freeTriangle(t);
308
+ continue;
309
+ }
310
+
311
+ // If the closest to the triangle is zero or positive, the origin is in the hull and we can proceed to the main algorithm
312
+ if (t.closestLengthSq >= 0.0) {
313
+ break;
314
+ }
315
+
316
+ // Remove the triangle from the queue before we start adding new ones (which may result in a new closest triangle at the front of the queue)
317
+ hull.popClosestTriangleFromQueue();
318
+
319
+ // Add a support point to get the origin inside the hull
320
+ const newIndex = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, t.normal);
321
+ const w = support_points.mY.values[newIndex];
322
+
323
+ // Add the point to the hull, if we fail we terminate and report no collision
324
+ const newTriangles = this.newTriangles;
325
+ newTriangles.clear();
326
+ if (!t.isFacing(w) || !hull.addPoint(t, newIndex, Number.MAX_VALUE, newTriangles)) {
327
+ return false;
328
+ }
329
+
330
+ // The triangle is facing the support point "w" and can now be safely removed
331
+ // #v-ifdef DEV
332
+ assert(t.removed, "triangle must be removed after adding a new point");
333
+ // #v-endif
334
+ hull.freeTriangle(t);
335
+
336
+ // If we run out of triangles or points, we couldn't include the origin in the hull so there must be very little penetration and we report no collision.
337
+ if (!hull.hasNextTriangle() || support_points.mY.size() >= this.maxPointsToIncludeOriginInHull) {
338
+ return false;
339
+ }
340
+ }
341
+
342
+ // Current closest distance to origin
343
+ let closestDistSq = Number.MAX_VALUE;
344
+
345
+ // Remember last good triangle
346
+ let last: Triangle | null = null;
347
+
348
+ // If we want to flip the penetration depth
349
+ let flipVSign = false;
350
+
351
+ // Loop until closest point found
352
+ do {
353
+ // Get closest triangle to the origin
354
+ const t = hull.popClosestTriangleFromQueue();
355
+
356
+ // #v-ifdef DEV
357
+ // assert
358
+ if (!t) {
359
+ throw new Error("t must not be null");
360
+ }
361
+ // #v-endif
362
+
363
+ // Don't process removed triangles, just free them (because they're in a heap we don't remove them earlier since we would have to rebuild the sorted heap)
364
+ if (t.removed) {
365
+ hull.freeTriangle(t);
366
+ continue;
367
+ }
368
+
369
+ // Check if next triangle is further away than closest point, we've found the closest point
370
+ if (t.closestLengthSq >= closestDistSq) {
371
+ break;
372
+ }
373
+
374
+ // Replace last good with this triangle
375
+ if (last !== null) {
376
+ hull.freeTriangle(last);
377
+ }
378
+ last = t;
379
+
380
+ // Add support point in direction of normal of the plane
381
+ // Note that the article uses the closest point between the origin and plane, but this always has the exact same direction as the normal (if the origin is behind the plane)
382
+ // and this way we do less calculations and lose less precision
383
+ const newIndex = support_points.add(inAIncludingConvexRadius, inBIncludingConvexRadius, t.normal);
384
+ const w = support_points.mY.values[newIndex];
385
+
386
+ // Project w onto the triangle normal
387
+ const dot = t.normal.dot(w);
388
+
389
+ // Check if we just found a separating axis. This can happen if the shape shrunk by convex radius and then expanded by
390
+ // convex radius is bigger then the original shape due to inaccuracies in the shrinking process.
391
+ if (dot < 0.0) {
392
+ return false;
393
+ }
394
+
395
+ // Get the distance squared (along normal) to the support point
396
+ const distSq = squared(dot) / t.normal.squaredLength();
397
+
398
+ // If the error became small enough, we've converged
399
+ if (distSq - t.closestLengthSq < t.closestLengthSq * inTolerance) {
400
+ break;
401
+ }
402
+
403
+ // Keep track of the minimum distance
404
+ closestDistSq = Math.min(closestDistSq, distSq);
405
+
406
+ // If the triangle thinks this point is not front facing, we've reached numerical precision and we're done
407
+ if (!t.isFacing(w)) {
408
+ break;
409
+ }
410
+
411
+ // Add point to hull
412
+ const newTriangles = this.newTriangles;
413
+ newTriangles.clear();
414
+ if (!hull.addPoint(t, newIndex, closestDistSq, newTriangles)) {
415
+ break;
416
+ }
417
+
418
+ // If the hull is starting to form defects then we're reaching numerical precision and we have to stop
419
+ let hasDefect = false;
420
+ for (const nt of newTriangles) {
421
+ if (nt && nt.isFacingOrigin()) {
422
+ hasDefect = true;
423
+ break;
424
+ }
425
+ }
426
+
427
+ if (hasDefect) {
428
+ // When the hull has defects it is possible that the origin has been classified on the wrong side of the triangle
429
+ // so we do an additional check to see if the penetration in the -triangle normal direction is smaller than
430
+ // the penetration in the triangle normal direction. If so we must flip the sign of the penetration depth.
431
+
432
+ negatedNormal.negateVector(t.normal);
433
+ inAIncludingConvexRadius.computeSupport(p2, negatedNormal);
434
+ inBIncludingConvexRadius.computeSupport(q2, t.normal);
435
+ w2.subtractVectors(p2, q2);
436
+ const dot2 = negatedNormal.dot(w2);
437
+ if (dot2 < dot) {
438
+ // Flip the sign of the penetration depth
439
+ flipVSign = !flipVSign;
440
+ }
441
+ break;
442
+ }
443
+ } while (hull.hasNextTriangle() && support_points.mY.size() < this.hull.settings.maxPoints);
444
+
445
+ // Determine closest points, if last == null it means the hull was a plane so there's no penetration
446
+ if (last === null) {
447
+ return false;
448
+ }
449
+
450
+ // Calculate penetration by getting the vector from the origin to the closest point on the triangle:
451
+ // distance = (centroid - origin) . normal / |normal|, closest = origin + distance * normal / |normal|
452
+ outV.copy(last.normal).scale(last.centroid.dot(last.normal) / last.normal.squaredLength());
453
+
454
+ // If penetration is near zero, treat this as a non collision since we cannot find a good normal
455
+ if (outV.isNearZero()) {
456
+ return false;
457
+ }
458
+
459
+ // Check if we have to flip the sign of the penetration depth
460
+ if (flipVSign) {
461
+ outV.negate();
462
+ }
463
+
464
+ // Use the barycentric coordinates for the closest point to the origin to find the contact points on A and B
465
+ const xp0 = support_points.mP.values[last.edge[0].startIndex];
466
+ const xp1 = support_points.mP.values[last.edge[1].startIndex];
467
+ const xp2 = support_points.mP.values[last.edge[2].startIndex];
468
+
469
+ const xq0 = support_points.mQ.values[last.edge[0].startIndex];
470
+ const xq1 = support_points.mQ.values[last.edge[1].startIndex];
471
+ const xq2 = support_points.mQ.values[last.edge[2].startIndex];
472
+
473
+ if (last.lambdaRelativeTo0) {
474
+ p01.subtractVectors(xp1, xp0);
475
+ p02.subtractVectors(xp2, xp0);
476
+ q01.subtractVectors(xq1, xq0);
477
+ q02.subtractVectors(xq2, xq0);
478
+
479
+ outPointA.copy(xp0).addScaled(p01, last.lambda[0]).addScaled(p02, last.lambda[1]);
480
+ outPointB.copy(xq0).addScaled(q01, last.lambda[0]).addScaled(q02, last.lambda[1]);
481
+ } else {
482
+ p10.subtractVectors(xp0, xp1);
483
+ p12.subtractVectors(xp2, xp1);
484
+ q10.subtractVectors(xq0, xq1);
485
+ q12.subtractVectors(xq2, xq1);
486
+
487
+ outPointA.copy(xp1).addScaled(p10, last.lambda[0]).addScaled(p12, last.lambda[1]);
488
+ outPointB.copy(xq1).addScaled(q10, last.lambda[0]).addScaled(q12, last.lambda[1]);
489
+ }
490
+
491
+ return true;
492
+ }
493
+ }
@@ -0,0 +1,50 @@
1
+ import { Vec3 } from "../../math/vec3";
2
+ import { SupportShape } from "../../shape/Convex";
3
+ import { Points } from "../epa/arrays";
4
+
5
+ const negatedDirection = /*@__PURE__*/ Vec3.create();
6
+
7
+ const p = /*@__PURE__*/ Vec3.create();
8
+ const q = /*@__PURE__*/ Vec3.create();
9
+ const y = /*@__PURE__*/ Vec3.create();
10
+
11
+ export class SupportPoints {
12
+ mY: Points;
13
+ mP: Points;
14
+ mQ: Points;
15
+
16
+ constructor(capacity: number) {
17
+ this.mY = new Points(capacity);
18
+ this.mP = new Points(capacity);
19
+ this.mQ = new Points(capacity);
20
+ }
21
+
22
+ clear() {
23
+ this.mY.clear();
24
+ this.mP.clear();
25
+ this.mQ.clear();
26
+ }
27
+
28
+ // Calculate and add new support point to the list of points
29
+ add(inA: SupportShape, inB: SupportShape, inDirection: Vec3) {
30
+ negatedDirection.negateVector(inDirection);
31
+
32
+ inA.computeSupport(p, inDirection);
33
+ inB.computeSupport(q, negatedDirection);
34
+ y.subtractVectors(p, q);
35
+
36
+ // Store new point
37
+ this.mY.pushBackCopy(y);
38
+ this.mP.pushBackCopy(p);
39
+ this.mQ.pushBackCopy(q);
40
+
41
+ // return the index of the new point
42
+ return this.mY.size() - 1;
43
+ }
44
+
45
+ popBack() {
46
+ this.mY.popBack();
47
+ this.mP.popBack();
48
+ this.mQ.popBack();
49
+ }
50
+ }
@@ -0,0 +1,36 @@
1
+ import { createClass, MonomorphType, PropertyDefinitionMap } from "monomorph";
2
+ import { Vec3 } from "../../math/vec3";
3
+ import { SupportShape } from "../../shape/Convex";
4
+
5
+ const directionA = /*@__PURE__*/ Vec3.create();
6
+ const directionB = /*@__PURE__*/ Vec3.create();
7
+ const supportPointA = /*@__PURE__*/ Vec3.create();
8
+ const supportPointB = /*@__PURE__*/ Vec3.create();
9
+
10
+ const minkowskiDifferenceProps = {
11
+ direction: MonomorphType(Vec3),
12
+ point: MonomorphType(Vec3),
13
+ pointA: MonomorphType(Vec3),
14
+ pointB: MonomorphType(Vec3),
15
+ } as const satisfies PropertyDefinitionMap;
16
+
17
+ export class MinkowskiDifference extends createClass<MinkowskiDifference, typeof minkowskiDifferenceProps>(
18
+ minkowskiDifferenceProps
19
+ ) {
20
+ get isOriginBeyondSupportPoint(): boolean {
21
+ return this.point.dot(this.direction) < 0;
22
+ }
23
+
24
+ getSupportPointOnMinkowskiDifference(shapeA: SupportShape, shapeB: SupportShape, direction: Vec3): void {
25
+ directionA.copy(direction);
26
+ directionB.negateVector(direction);
27
+
28
+ shapeA.computeSupport(supportPointA, directionA);
29
+ shapeB.computeSupport(supportPointB, directionB);
30
+
31
+ this.direction.copy(direction);
32
+ this.point.subtractVectors(supportPointA, supportPointB);
33
+ this.pointA.copy(supportPointA);
34
+ this.pointB.copy(supportPointB);
35
+ }
36
+ }
@@ -0,0 +1,40 @@
1
+ import { assert } from "../../helpers";
2
+ import { Vec3 } from "../../math/vec3";
3
+
4
+ const d = /*@__PURE__*/ Vec3.create();
5
+
6
+ export function computeExploredDistanceLowerUpperBound(
7
+ result: [number, number],
8
+ ray_direction: Vec3,
9
+ v1: Vec3,
10
+ v2: Vec3,
11
+ v3: Vec3,
12
+ v123_normal: Vec3,
13
+ o_to_v1_dot_v123_normal: number,
14
+ o_to_v4_dot_v_123_normal: number
15
+ ): void {
16
+ d.copy(ray_direction);
17
+ // #v-ifdef DEV
18
+ assert(d.isUnitVector(), "d must be a unit vector");
19
+ assert(v123_normal.isUnitVector(), "v123_normal must be a unit vector");
20
+ // #v-endif
21
+ const v123_normal_dot_d = v123_normal.dot(d);
22
+ if (Math.abs(v123_normal_dot_d) <= 0) {
23
+ const o_to_v1_dot_d = v1.dot(d);
24
+ const o_to_v2_dot_d = v2.dot(d);
25
+ const o_to_v3_dot_d = v3.dot(d);
26
+ result[0] = Math.max(o_to_v1_dot_d, o_to_v2_dot_d, o_to_v3_dot_d);
27
+ result[1] = result[0];
28
+ return;
29
+ }
30
+
31
+ // The case we can make a division
32
+ result[0] = o_to_v1_dot_v123_normal / v123_normal_dot_d;
33
+ result[1] = o_to_v4_dot_v_123_normal / v123_normal_dot_d;
34
+ // #v-ifdef DEV
35
+ // assert(
36
+ // result[1] >= result[0],
37
+ // `upper_bound must be >= lower_bound, got lower bound ${result[0]} and upper bound ${result[1]} instead`
38
+ // );
39
+ // #v-endif
40
+ }
@@ -0,0 +1,69 @@
1
+ import { assert } from "../../helpers";
2
+ import { Vec3 } from "../../math/vec3";
3
+ import { SupportShape } from "../../shape/Convex";
4
+ import { MinkowskiDifference } from "./MinkowskiDifference";
5
+ import { ImpResult } from "./imp";
6
+
7
+ const d = /*@__PURE__*/ Vec3.create();
8
+ const support1 = /*@__PURE__*/ MinkowskiDifference.create();
9
+ const support2 = /*@__PURE__*/ MinkowskiDifference.create();
10
+ const support3 = /*@__PURE__*/ MinkowskiDifference.create();
11
+ const o_projected = /*@__PURE__*/ Vec3.create();
12
+ const v1_to_v2 = /*@__PURE__*/ Vec3.create();
13
+ const v1_to_v3 = /*@__PURE__*/ Vec3.create();
14
+ const s1s2s3_plane_normal = /*@__PURE__*/ Vec3.create();
15
+ const o_projected_to_v1 = /*@__PURE__*/ Vec3.create();
16
+ const cross_v1_to_v3_o_projected_to_v1 = /*@__PURE__*/ Vec3.create();
17
+ const cross_v1_to_v2_o_projected_to_v1 = /*@__PURE__*/ Vec3.create();
18
+
19
+ export function finalizeImpResult(
20
+ penetration_data: ImpResult,
21
+ shapeA: SupportShape,
22
+ shapeB: SupportShape,
23
+ ray_direction: Vec3,
24
+ v1: Vec3,
25
+ v2: Vec3,
26
+ v3: Vec3,
27
+ v1_dir: Vec3,
28
+ v2_dir: Vec3,
29
+ v3_dir: Vec3,
30
+ v123_normal: Vec3,
31
+ [distance_lb, distance_ub]: [number, number]
32
+ ): void {
33
+ support1.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v1_dir);
34
+ support2.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v2_dir);
35
+ support3.getSupportPointOnMinkowskiDifference(shapeA, shapeB, v3_dir);
36
+ v1.copy(support1.point);
37
+ v2.copy(support2.point);
38
+ v3.copy(support3.point);
39
+
40
+ d.copy(ray_direction);
41
+ penetration_data.minimum_penetration = distance_ub;
42
+ penetration_data.penetration_direction.copy(d);
43
+
44
+ // Point in triangle distance is just lb
45
+ // #v-ifdef DEV
46
+ assert(Math.abs(d.dot(v123_normal)) > 0, "d and v123_normal must be in same direction");
47
+ // #v-endif
48
+ o_projected.scaleVector(d, distance_lb);
49
+ v1_to_v2.subtractVectors(v2, v1);
50
+ v1_to_v3.subtractVectors(v3, v1);
51
+ s1s2s3_plane_normal.crossVectors(v1_to_v2, v1_to_v3);
52
+ const area = s1s2s3_plane_normal.length();
53
+ o_projected_to_v1.subtractVectors(v1, o_projected);
54
+ cross_v1_to_v3_o_projected_to_v1.crossVectors(v1_to_v3, o_projected_to_v1);
55
+ cross_v1_to_v2_o_projected_to_v1.crossVectors(v1_to_v2, o_projected_to_v1);
56
+ const s2_weight = cross_v1_to_v3_o_projected_to_v1.length() / area;
57
+ const s3_weight = cross_v1_to_v2_o_projected_to_v1.length() / area;
58
+ const s1_weight = 1 - s2_weight - s3_weight;
59
+
60
+ penetration_data.worldContactPointA.zero();
61
+ penetration_data.worldContactPointA.addScaled(support1.pointA, s1_weight);
62
+ penetration_data.worldContactPointA.addScaled(support2.pointA, s2_weight);
63
+ penetration_data.worldContactPointA.addScaled(support3.pointA, s3_weight);
64
+
65
+ penetration_data.worldContactPointB.zero();
66
+ penetration_data.worldContactPointB.addScaled(support1.pointB, s1_weight);
67
+ penetration_data.worldContactPointB.addScaled(support2.pointB, s2_weight);
68
+ penetration_data.worldContactPointB.addScaled(support3.pointB, s3_weight);
69
+ }