@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,755 @@
1
+ import { destroyAllInstancesInPool } from "../../helpers";
2
+ import { degreesToRadians, squared } from "../../math/scalar";
3
+ import { Vec3 } from "../../math/vec3";
4
+ import { Body, BodyType } from "../Body";
5
+ import {
6
+ ActiveEdgeMode,
7
+ CollectFacesMode,
8
+ CollisionCollector,
9
+ CollisionResult,
10
+ CollisionSettings,
11
+ } from "../../collision/collide/collide";
12
+ import { CollideShapesModule } from "../../collision/CollideShapesModule";
13
+ import { ContactConstraintModule } from "../../physics/solver/ContactConstraintModule";
14
+ import { ContactManifold, ContactManifoldPool, createContactPairKey } from "../manifold/ContactManifold";
15
+ import { ContactManifoldModule } from "../manifold/ContactManifoldModule";
16
+ import { SleepModule } from "../../physics/SleepModule";
17
+ import { Shape, ShapeType } from "../../shape/Shape";
18
+ import { Isometry } from "../../math/isometry";
19
+ import { HeightMap } from "../../shape/HeightMap";
20
+ import { Aabb } from "../../shape/Aabb";
21
+ import type { World } from "../../world";
22
+ import { CastCollector, CastResult, CastSettings, RayCastSettings } from "../../collision/cast/cast";
23
+ import { CastShapesModule } from "../../collision/CastShapesModule";
24
+ import { AllFlag } from "../CollisionFilter";
25
+ import { Quat } from "../../math/quat";
26
+ import { Ray } from "../../shape/Ray";
27
+ import { estimateCollisionResponse, EstimateCollisionResponseResult } from "../solver/estimateCollisionResponse";
28
+
29
+ const manifolds = /*@__PURE__*/ new ContactManifoldPool(32, 64);
30
+
31
+ const inverseOrientationA = /*@__PURE__*/ Quat.create();
32
+
33
+ export interface CollideBodiesSettings {
34
+ speculativeContactDistance: number;
35
+ }
36
+
37
+ const worldSpaceNormal = /*@__PURE__*/ Vec3.create();
38
+ const negatedWorldSpaceNormal = /*@__PURE__*/ Vec3.create();
39
+ const zeroVector = /*@__PURE__*/ Vec3.create({ x: 0, y: 0, z: 0 });
40
+
41
+ const centerOfMassAInWorldSpace = /*@__PURE__*/ Vec3.create();
42
+ const isometryWorldToB = /*@__PURE__*/ Isometry.create();
43
+ const projectedPointInB = /*@__PURE__*/ Vec3.create();
44
+ const projectedPointOnHeightMap = /*@__PURE__*/ Vec3.create();
45
+
46
+ const isometryA = /*@__PURE__*/ Isometry.create();
47
+ const isometryB = /*@__PURE__*/ Isometry.create();
48
+ const offset = /*@__PURE__*/ Vec3.create();
49
+ const negatedOffset = /*@__PURE__*/ Vec3.create();
50
+ const projectedPosition = /*@__PURE__*/ Vec3.create();
51
+ const centerOfMassAInB = /*@__PURE__*/ Vec3.create();
52
+ const linearVelocityBA = /*@__PURE__*/ Vec3.create();
53
+
54
+ class CollideBodiesCollector extends CollisionCollector {
55
+ numHits: number;
56
+ settings: CollideBodiesSettings;
57
+ contactManifoldModule: ContactManifoldModule;
58
+ bodyA: Body | null;
59
+ bodyB: Body | null;
60
+
61
+ constructor(contactManifoldModule: ContactManifoldModule) {
62
+ super();
63
+ this.numHits = 0;
64
+ this.settings = { speculativeContactDistance: 0.02 };
65
+ this.contactManifoldModule = contactManifoldModule;
66
+ this.bodyA = null;
67
+ this.bodyB = null;
68
+ }
69
+
70
+ init(settings: CollideBodiesSettings, bodyA: Body, bodyB: Body): this {
71
+ this.reset();
72
+ this.numHits = 0;
73
+ this.settings = settings;
74
+ this.bodyA = bodyA;
75
+ this.bodyB = bodyB;
76
+
77
+ destroyAllInstancesInPool(manifolds.contactManifoldPool);
78
+ return this;
79
+ }
80
+
81
+ addHit(result: CollisionResult): void {
82
+ this.numHits++;
83
+
84
+ const bodyA = this.bodyA!;
85
+ const bodyB = this.bodyB!;
86
+
87
+ // get the normal
88
+ worldSpaceNormal.normalizeVector(result.normalA); // TODO: or B?
89
+
90
+ // check if we can add the contact point
91
+ const maxDeltaCosAngle = Math.cos(degreesToRadians(5));
92
+
93
+ // iterate over all manifolds
94
+ let reachedEndWithoutAdding = true;
95
+ let newManifold: ContactManifold | null = null;
96
+ for (const manifold of manifolds.contactManifoldPool) {
97
+ if (worldSpaceNormal.dot(manifold.firstWorldSpaceNormal) >= maxDeltaCosAngle) {
98
+ // we can add it
99
+ manifold.worldSpaceNormal.addVector(worldSpaceNormal);
100
+ manifold.penetrationDepth = Math.max(manifold.penetrationDepth, result.penetration);
101
+ reachedEndWithoutAdding = false;
102
+ newManifold = manifold;
103
+ break;
104
+ }
105
+ }
106
+
107
+ if (reachedEndWithoutAdding) {
108
+ // TODO handle trimesh
109
+
110
+ // case: full manifolds, replace the one with the least penetration depth if possible
111
+ if (manifolds.contactManifoldPool.length === manifolds.contactManifoldPool.maxLength) {
112
+ let minPenetrationDepth = Infinity;
113
+ // let minPenetrationDepthId = -1;
114
+ let minPenetrationDepthManifold: ContactManifold | null = null;
115
+
116
+ // iterate over all manifolds
117
+ for (const manifold of manifolds.contactManifoldPool) {
118
+ if (manifold.penetrationDepth < minPenetrationDepth) {
119
+ minPenetrationDepth = manifold.penetrationDepth;
120
+ minPenetrationDepthManifold = manifold;
121
+ }
122
+ }
123
+
124
+ // get the manifold with the least penetration depth
125
+ if (!minPenetrationDepthManifold) {
126
+ throw new Error("minPenetrationDepthManifold is null");
127
+ }
128
+
129
+ // case: result penetration is less than the minimum penetration depth, skip this result
130
+ if (result.penetration > minPenetrationDepth) {
131
+ return;
132
+ }
133
+
134
+ // case: replace the manifold with the least penetration depth
135
+ minPenetrationDepthManifold.destroy();
136
+ newManifold = manifolds.createContactManifold({
137
+ bodyA: bodyA,
138
+ bodyB: bodyB,
139
+ subShapeIdA: result.subShapeIdA,
140
+ subShapeIdB: result.subShapeIdB,
141
+ nextContactManifold: null,
142
+
143
+ baseTranslation: { x: 0, y: 0, z: 0 },
144
+ firstWorldSpaceNormal: worldSpaceNormal,
145
+ worldSpaceNormal: worldSpaceNormal,
146
+ penetrationDepth: result.penetration,
147
+ numContacts: 0,
148
+ });
149
+ }
150
+ // case: room to add a new manifold
151
+ else {
152
+ newManifold = manifolds.createContactManifold({
153
+ bodyA: bodyA,
154
+ bodyB: bodyB,
155
+ subShapeIdA: result.subShapeIdA,
156
+ subShapeIdB: result.subShapeIdB,
157
+ nextContactManifold: null,
158
+
159
+ baseTranslation: { x: 0, y: 0, z: 0 },
160
+ firstWorldSpaceNormal: worldSpaceNormal,
161
+ worldSpaceNormal: worldSpaceNormal,
162
+ penetrationDepth: result.penetration,
163
+ numContacts: 0,
164
+ });
165
+ }
166
+ }
167
+
168
+ if (!newManifold) {
169
+ throw new Error("newManifold is null");
170
+ }
171
+
172
+ // add contact point
173
+ // get the supporting faces
174
+ negatedWorldSpaceNormal.negateVector(worldSpaceNormal);
175
+ zeroVector.zero();
176
+
177
+ if (result.faceA.numVertices === 0) {
178
+ bodyA!.computeSupportingFace(result.faceA, result.subShapeIdA, negatedWorldSpaceNormal, zeroVector);
179
+ }
180
+
181
+ if (result.faceB.numVertices === 0) {
182
+ bodyB!.computeSupportingFace(result.faceB, result.subShapeIdB, worldSpaceNormal, zeroVector);
183
+ }
184
+
185
+ const penetrationTolerance = 1e-3;
186
+ // const penetrationToleranceSquared = squared(penetrationTolerance);
187
+ // const speculativeContactDistanceSquared = squared(this.settings.speculativeContactDistance);
188
+ // const maxContactDistancedSquared = speculativeContactDistanceSquared + penetrationToleranceSquared;
189
+
190
+ result.contactPointA.addVector(result.bodyA!.computedCenterOfMassPosition);
191
+ result.contactPointB.addVector(result.bodyA!.computedCenterOfMassPosition);
192
+ // newManifold.baseTranslation.copy(bodyA.computedCenterOfMassPosition);
193
+
194
+ this.contactManifoldModule.manifoldBetweenTwoFaces(
195
+ result.contactPointA,
196
+ result.contactPointB,
197
+ worldSpaceNormal, // TODO: A or B?
198
+ // maxContactDistancedSquared,
199
+ this.settings.speculativeContactDistance + 1e-3,
200
+ result.faceA,
201
+ result.faceB,
202
+ newManifold
203
+ );
204
+
205
+ // prune if more than 32 contact points
206
+ if (newManifold.numContacts > 32) {
207
+ this.contactManifoldModule.pruneContactPoints(newManifold.firstWorldSpaceNormal, newManifold);
208
+ }
209
+ }
210
+ }
211
+
212
+ export class CollideBodiesModule {
213
+ world: World;
214
+ collideShapesModule: CollideShapesModule;
215
+ castShapesModule: CastShapesModule;
216
+ contactManifoldModule: ContactManifoldModule;
217
+ contactConstraintModule: ContactConstraintModule;
218
+ sleepModule: SleepModule;
219
+
220
+ collector: CollideBodiesCollector;
221
+
222
+ constructor(
223
+ world: World,
224
+ collideShapesModule: CollideShapesModule,
225
+ castShapesModule: CastShapesModule,
226
+ contactManifoldModule: ContactManifoldModule,
227
+ contactConstraintModule: ContactConstraintModule,
228
+ sleepModule: SleepModule
229
+ ) {
230
+ this.world = world;
231
+ this.collideShapesModule = collideShapesModule;
232
+ this.castShapesModule = castShapesModule;
233
+ this.contactManifoldModule = contactManifoldModule;
234
+ this.contactConstraintModule = contactConstraintModule;
235
+ this.sleepModule = sleepModule;
236
+
237
+ this.collector = new CollideBodiesCollector(contactManifoldModule);
238
+ }
239
+
240
+ /**
241
+ * checks for collisions between two bodies. assumes bodyA is always dynamic. bodyB can be any type
242
+ */
243
+ collideBodies(
244
+ // onCollide: (manifold: ContactManifold) => void,
245
+ bodyA: Body,
246
+ bodyB: Body,
247
+ collideShapesSettings: CollisionSettings,
248
+ collideBodiesSettings: CollideBodiesSettings,
249
+ timeStepSizeSeconds: number,
250
+ isWarmStartingEnabled: boolean,
251
+ minVelocityForElasticContact: number
252
+ ) {
253
+ // TODO: pass use persistent contacts as a parameter?
254
+ let usePersistentContacts: boolean;
255
+ usePersistentContacts = true;
256
+ usePersistentContacts = false;
257
+ let pairHandled = false;
258
+ let constraintCreated = false;
259
+
260
+ if (usePersistentContacts) {
261
+ [pairHandled, constraintCreated] = this.contactManifoldModule.getContactsFromCache(
262
+ bodyA,
263
+ bodyB,
264
+ this.contactConstraintModule,
265
+ this.sleepModule,
266
+ isWarmStartingEnabled,
267
+ timeStepSizeSeconds,
268
+ minVelocityForElasticContact
269
+ );
270
+ }
271
+
272
+ // exit if pair handled by persistent contacts
273
+ if (pairHandled) {
274
+ // TODO: add bodies-in-contact for contact constraint contact baumgarte multiplier
275
+ return;
276
+ }
277
+
278
+ linearVelocityBA.subtractVectors(bodyA.linearVelocity, bodyB.linearVelocity);
279
+ collideShapesSettings.activeEdgeMovementDirectionX = linearVelocityBA.x;
280
+ collideShapesSettings.activeEdgeMovementDirectionY = linearVelocityBA.y;
281
+ collideShapesSettings.activeEdgeMovementDirectionZ = linearVelocityBA.z;
282
+ collideShapesSettings.maxSeparation = collideBodiesSettings.speculativeContactDistance;
283
+ // collideShapesSettings.collectFacesMode = CollectFacesMode.CollectFaces;
284
+ collideShapesSettings.activeEdgeMode = ActiveEdgeMode.CollideOnlyWithActive;
285
+
286
+ offset.copy(bodyA.computedCenterOfMassPosition);
287
+ negatedOffset.negateVector(offset);
288
+ isometryA.matrix.fromQuat(bodyA.orientation);
289
+ isometryB.fromRotationAndTranslation(bodyB.orientation, bodyB.computedCenterOfMassPosition);
290
+ isometryB.matrix.postTranslated(negatedOffset);
291
+
292
+ // TODO: handle trimesh
293
+ this.collector.init(collideBodiesSettings, bodyA, bodyB);
294
+ this.collideShapesModule.collideShapes(
295
+ this.collector,
296
+ bodyA.shape!,
297
+ isometryA,
298
+ bodyB.shape!,
299
+ isometryB,
300
+ collideShapesSettings,
301
+ bodyA,
302
+ bodyB,
303
+ 0,
304
+ 0
305
+ );
306
+
307
+ if (this.collector.numHits < 1) {
308
+ return;
309
+
310
+ // code below was an attempt to fix tunneling through heightmaps, but it caused some problems so it's disabled for now
311
+
312
+ // case: no contact found between shape and non-heightmap, we're really done
313
+ if (bodyB.shape!.type !== ShapeType.heightMap) {
314
+ // this.world.tracker.timeEnd("compute contact manifolds");
315
+ return;
316
+ }
317
+
318
+ if (bodyA.shape!.type === ShapeType.triangleMesh) {
319
+ // this.world.tracker.timeEnd("compute contact manifolds");
320
+ return;
321
+ }
322
+
323
+ // case: no contact found between shape and heightmap triangles
324
+ // check if bodyA center of mass is above the heightmap
325
+
326
+ projectedPosition.addScaledToVector(
327
+ bodyA.computedCenterOfMassPosition,
328
+ bodyA.linearVelocity,
329
+ timeStepSizeSeconds
330
+ );
331
+ centerOfMassAInWorldSpace.copy(projectedPosition);
332
+ isometryWorldToB.matrix.fromInverseRotationAndTranslation(bodyB.orientation, bodyB.computedCenterOfMassPosition);
333
+ projectedPointInB.transformVectorFromMat4(centerOfMassAInWorldSpace, isometryWorldToB.matrix);
334
+ centerOfMassAInB.transformVectorFromMat4(bodyA.computedCenterOfMassPosition, isometryWorldToB.matrix);
335
+
336
+ const interpolatedHeight = (bodyB.shape as unknown as HeightMap).computeInterpolatedHeight(projectedPointInB);
337
+
338
+ // case: bodyA center of mass is above the heightmap surface and no contact was found, we're really done
339
+ if (projectedPointInB.y >= interpolatedHeight) {
340
+ // this.world.tracker.timeEnd("compute contact manifolds");
341
+ return;
342
+ }
343
+
344
+ // case: bodyA center of mass is below the heightmap surface and no contact was found, it has tunneled through the heightmap
345
+
346
+ projectedPointOnHeightMap.copy(centerOfMassAInB);
347
+ projectedPointOnHeightMap.y = interpolatedHeight + 1e-1;
348
+ isometryWorldToB.inverseTransformPoint(projectedPointOnHeightMap); // tx from b to world
349
+
350
+ // update the broadphase. TODO: does this need to happen here?
351
+ // this.world.broadphaseModule.moveBody(bodyA.cachedComboId);
352
+ bodyA.markBodyAsDirty();
353
+ bodyA.computedCenterOfMassPosition.copy(projectedPointOnHeightMap);
354
+
355
+ // update the isometry
356
+ offset.copy(bodyA.computedCenterOfMassPosition);
357
+ negatedOffset.negateVector(offset);
358
+ isometryA.matrix.fromQuat(bodyA.orientation);
359
+ isometryB.fromRotationAndTranslation(bodyB.orientation, bodyB.computedCenterOfMassPosition);
360
+ isometryB.matrix.postTranslated(negatedOffset);
361
+
362
+ // re-run the collision detection (TODO: what impact does this have on any other contacts this body has made?)
363
+ this.collector.init(collideBodiesSettings, bodyA, bodyB);
364
+ this.collideShapesModule.collideShapes(
365
+ this.collector,
366
+ bodyA.shape!,
367
+ isometryA,
368
+ bodyB.shape!,
369
+ isometryB,
370
+ collideShapesSettings,
371
+ bodyA,
372
+ bodyB,
373
+ 0,
374
+ 0
375
+ );
376
+
377
+ // if still no contact, we're really done
378
+ if (this.collector.numHits === 0) {
379
+ // this.world.tracker.timeEnd("compute contact manifolds");
380
+ return;
381
+ }
382
+
383
+ // if we're here, we've applied the tunneling fix, found a contact, and should fall-through to the contact processing steps
384
+ }
385
+
386
+ const pair = this.contactManifoldModule.addContactPair(bodyA, bodyB);
387
+ // store the current relative translation to use for next frame's contact cache evaluation
388
+ inverseOrientationA.conjugateQuat(bodyA.orientation); // we're able to conjugate instead of invert because it's a unit quaternion
389
+ pair.translationAB.subtractVectors(bodyB.computedCenterOfMassPosition, bodyA.computedCenterOfMassPosition);
390
+ pair.translationAB.transformVectorByQuat(pair.translationAB, inverseOrientationA);
391
+ // TODO: add bodies-in-contact for contact constraint contact baumgarte multiplier
392
+
393
+ // iterate over all collected manifolds
394
+ for (const manifold of manifolds.contactManifoldPool) {
395
+ manifold.worldSpaceNormal.normalize();
396
+
397
+ if (manifold.numContacts > 4) {
398
+ this.contactManifoldModule.pruneContactPoints(manifold.worldSpaceNormal, manifold);
399
+ }
400
+
401
+ if (manifold.numContacts < 1) {
402
+ throw new Error("manifold has no contacts");
403
+ }
404
+
405
+ this.contactConstraintModule.addContactConstraints(
406
+ bodyA,
407
+ bodyB,
408
+ manifold,
409
+ timeStepSizeSeconds,
410
+ isWarmStartingEnabled,
411
+ minVelocityForElasticContact
412
+ );
413
+
414
+ // onCollide(manifold);
415
+ }
416
+ }
417
+
418
+ collideShape(
419
+ inShape: Shape,
420
+ inShapeScale: number,
421
+ inCenterOfMassTransform: Isometry,
422
+ ioCollector: CollisionCollector,
423
+ inCollideShapeSettings: CollisionSettings,
424
+ inbaseTranslation?: Vec3,
425
+ mask: number = AllFlag
426
+ ): void {
427
+ // TODO: handle other shapes
428
+ // TODO: handle non-static bodies
429
+ // iterate over all static bodies
430
+
431
+ // offset isometry A by the negated base offset
432
+ if (inbaseTranslation) {
433
+ offset.negateVector(inbaseTranslation);
434
+ } else {
435
+ offset.zero();
436
+ }
437
+
438
+ isometryA.matrix.postTranslatedMatrix(inCenterOfMassTransform.matrix, offset);
439
+
440
+ // get shapeA world bounds
441
+
442
+ boundsA.copy(inShape.computedAabb);
443
+ // expand the bounds by the max separation distance
444
+ boundsA.expand(inCollideShapeSettings ? inCollideShapeSettings.maxSeparation : 0.0);
445
+ // transform the bounds to world space
446
+ boundsA.transform(inCenterOfMassTransform);
447
+
448
+ collideShapeVisitor.initialize(
449
+ isometryA,
450
+ isometryB,
451
+ ioCollector,
452
+ this.collideShapesModule,
453
+ inShape,
454
+ offset,
455
+ ioCollector,
456
+ inCollideShapeSettings
457
+ );
458
+ const group = AllFlag; // CollisionFilter.AllFlag
459
+ this.world.broadphase.visitBodiesWithCollision(collideShapeVisitor, boundsA, group, mask);
460
+ }
461
+
462
+ castShape(
463
+ inShape: Shape,
464
+ inShapeScale: number,
465
+ inCenterOfMassTransform: Isometry,
466
+ inDisplacement: Vec3,
467
+ inShapeCastSettings: CastSettings,
468
+ inbaseTranslation: Vec3,
469
+ ioCollector: CastCollector,
470
+ mask: number = AllFlag
471
+ // mask: number = CollisionFilter.AllFlag
472
+ ) {
473
+ // TODO: handle other shapes
474
+ // TODO: handle non-static bodies
475
+ // iterate over all static bodies
476
+
477
+ const castModule = this.castShapesModule;
478
+
479
+ // get shapeA world bounds
480
+ boundsA.copy(inShape.computedAabb);
481
+
482
+ // expand the bounds by the max separation distance
483
+ boundsA.expand(inShapeCastSettings.collisionTolerance);
484
+
485
+ // transform the bounds to world space
486
+ boundsA.transform(inCenterOfMassTransform);
487
+
488
+ // the displacement is already in world space
489
+
490
+ const visitor = castShapeVisitor;
491
+ visitor.initialize(
492
+ isometryB,
493
+ ioCollector,
494
+ castModule,
495
+ ioCollector,
496
+ inShape,
497
+ inCenterOfMassTransform,
498
+ inShapeScale,
499
+ inDisplacement,
500
+ inbaseTranslation,
501
+ inShapeCastSettings
502
+ );
503
+ const group = AllFlag; // CollisionFilter.AllFlag
504
+ this.world.broadphase.visitBodiesWithCast(visitor, boundsA, inDisplacement, group, mask);
505
+ }
506
+
507
+ castRay(
508
+ onHit: (result: CastRayResult) => boolean,
509
+ ray: Ray,
510
+ settings: RayCastSettings,
511
+ belongsToGroups: number = AllFlag,
512
+ collidesWithGroups: number = AllFlag
513
+ ) {
514
+ if (settings.returnClosestOnly) {
515
+ const visitor = castRayVisitor;
516
+ const collector = castRayCollector;
517
+ visitor.init(ray, settings, this.castShapesModule, collector);
518
+ this.world.broadphase.raycastBodies(visitor, ray, belongsToGroups, collidesWithGroups);
519
+ if (collector.closestBody) {
520
+ raycastResult.body = collector.closestBody;
521
+ raycastResult.fraction = collector.closestFraction;
522
+ onHit(raycastResult);
523
+ }
524
+ }
525
+ }
526
+
527
+ estimateCollisionResponse(
528
+ result: EstimateCollisionResponseResult,
529
+ bodyA: Body,
530
+ bodyB: Body,
531
+ timeStepSizeSeconds: number
532
+ ): void {
533
+ result.reset();
534
+ const cache = this.contactManifoldModule.currentManifoldCache;
535
+ const key = createContactPairKey(bodyA, bodyB);
536
+ const pair = cache.pairMap.get(key);
537
+ if (!pair) {
538
+ return;
539
+ }
540
+ // we use just the first manifold for now
541
+ const manifold = pair.firstContactManifold;
542
+ if (!manifold) {
543
+ return;
544
+ }
545
+
546
+ estimateCollisionResponse(result, manifold, timeStepSizeSeconds);
547
+ }
548
+ }
549
+
550
+ const raycastResult: CastRayResult = {
551
+ // @ts-ignore
552
+ body: null,
553
+ fraction: Infinity,
554
+ };
555
+
556
+ export interface CastRayResult {
557
+ body: Body;
558
+ fraction: number;
559
+ }
560
+
561
+ class CastRayCollector extends CastCollector {
562
+ closestBody: Body | null;
563
+ closestFraction: number;
564
+
565
+ constructor() {
566
+ super();
567
+ this.closestBody = null;
568
+ this.closestFraction = Infinity;
569
+ }
570
+
571
+ reset() {
572
+ super.reset();
573
+ this.closestBody = null;
574
+ this.closestFraction = Infinity;
575
+ return this;
576
+ }
577
+
578
+ addHit(result: CastResult): void {
579
+ if (result.fraction < this.closestFraction) {
580
+ this.closestFraction = result.fraction;
581
+ this.closestBody = result.bodyB;
582
+ }
583
+ }
584
+ }
585
+
586
+ const castRayCollector = /*@__PURE__*/ new CastRayCollector();
587
+
588
+ class CastRayBodyVisitor implements BodyVisitor {
589
+ shouldExit: boolean;
590
+ settings: RayCastSettings | null;
591
+ ray: Ray | null;
592
+ castModule: CastShapesModule | null;
593
+ collector: CastRayCollector | null;
594
+
595
+ constructor() {
596
+ this.shouldExit = false;
597
+ this.ray = null;
598
+ this.settings = null;
599
+ this.castModule = null;
600
+ this.collector = null;
601
+ }
602
+
603
+ init(ray: Ray, settings: RayCastSettings, castModule: CastShapesModule, collector: CastRayCollector) {
604
+ this.shouldExit = false;
605
+ this.ray = ray;
606
+ this.settings = settings;
607
+ this.castModule = castModule;
608
+ this.collector = collector;
609
+ this.collector.reset();
610
+ }
611
+
612
+ visit(body: Body): void {
613
+ isometryB.fromRotationAndTranslation(body.orientation, body.computedCenterOfMassPosition);
614
+ this.castModule!.castRay(this.collector!, this.ray!, body.shape!, isometryB, 1.0, zeroVector, this.settings!, body);
615
+ }
616
+ }
617
+
618
+ const castRayVisitor = /*@__PURE__*/ new CastRayBodyVisitor();
619
+
620
+ class CollideShapeBodyVisitor {
621
+ shouldExit: boolean = false;
622
+
623
+ declare isometryA: Isometry;
624
+ declare isometryB: Isometry;
625
+ declare collector: CollisionCollector;
626
+ declare collisionModule: CollideShapesModule;
627
+ declare inShape: Shape;
628
+ declare inCollideShapeSettings: CollisionSettings;
629
+ declare offset: Vec3;
630
+ declare ioCollector: CollisionCollector;
631
+
632
+ initialize(
633
+ isometryA: Isometry,
634
+ isometryB: Isometry,
635
+ collector: CollisionCollector,
636
+ collisionModule: CollideShapesModule,
637
+ inShape: Shape,
638
+ offset: Vec3,
639
+ ioCollector: CollisionCollector,
640
+ inCollideShapeSettings: CollisionSettings
641
+ ): void {
642
+ this.isometryA = isometryA;
643
+ this.isometryB = isometryB;
644
+ this.collector = collector;
645
+ this.collisionModule = collisionModule;
646
+ this.inShape = inShape;
647
+ this.inCollideShapeSettings = inCollideShapeSettings;
648
+ this.offset = offset;
649
+ this.ioCollector = ioCollector;
650
+ }
651
+
652
+ visit(body: Body) {
653
+ // set the isometry, offset it by the negated base offset
654
+ this.isometryB.fromRotationAndTranslation(body.orientation, body.computedCenterOfMassPosition);
655
+ this.isometryB.matrix.postTranslatedMatrix(this.isometryB.matrix, this.offset);
656
+
657
+ // Notify collector of new body
658
+ this.ioCollector.body2 = body;
659
+
660
+ this.collisionModule.collideShapes(
661
+ this.ioCollector,
662
+ this.inShape,
663
+ this.isometryA,
664
+ body.shape!,
665
+ this.isometryB,
666
+ this.inCollideShapeSettings,
667
+ null,
668
+ body
669
+ );
670
+ }
671
+ }
672
+
673
+ export interface BodyVisitor {
674
+ shouldExit: boolean;
675
+ visit(body: Body): void;
676
+ }
677
+
678
+ class CastShapeBodyVisitor implements BodyVisitor {
679
+ shouldExit: boolean = false;
680
+
681
+ declare isometryB: Isometry;
682
+ declare collector: CastCollector;
683
+ declare castModule: CastShapesModule;
684
+ declare ioCollector: CastCollector;
685
+ declare inShape: Shape;
686
+ declare inCenterOfMassTransform: Isometry;
687
+ declare inShapeScale: number;
688
+ declare inDisplacement: Vec3;
689
+ declare inbaseTranslation: Vec3;
690
+ declare inShapeCastSettings: CastSettings;
691
+
692
+ initialize(
693
+ isometryB: Isometry,
694
+ collector: CastCollector,
695
+ castModule: CastShapesModule,
696
+ ioCollector: CastCollector,
697
+ inShape: Shape,
698
+ inCenterOfMassTransform: Isometry,
699
+ inShapeScale: number,
700
+ inDisplacement: Vec3,
701
+ inbaseTranslation: Vec3,
702
+ inShapeCastSettings: CastSettings
703
+ ): void {
704
+ this.isometryB = isometryB;
705
+ this.collector = collector;
706
+ this.castModule = castModule;
707
+ this.ioCollector = ioCollector;
708
+ this.inShape = inShape;
709
+ this.inCenterOfMassTransform = inCenterOfMassTransform;
710
+ this.inShapeScale = inShapeScale;
711
+ this.inDisplacement = inDisplacement;
712
+ this.inbaseTranslation = inbaseTranslation;
713
+ this.inShapeCastSettings = inShapeCastSettings;
714
+ }
715
+
716
+ visit(body: Body): void {
717
+ // set the isometry
718
+ this.isometryB.fromRotationAndTranslation(body.orientation, body.computedCenterOfMassPosition);
719
+
720
+ // Collect the transformed shape
721
+ // TODO: the type casting into ShapeWithSupport is not a good solution here, need to really rework the shape type system
722
+ // this.transformedShape.initialize(body.position, body.orientation, body.shape as ShapeWithSupport, id);
723
+
724
+ // Notify collector of new body
725
+ this.ioCollector.body2 = body;
726
+
727
+ this.castModule.castShapes(
728
+ this.ioCollector,
729
+ this.inShape,
730
+ this.inCenterOfMassTransform,
731
+ this.inShapeScale,
732
+ body.shape!,
733
+ this.isometryB,
734
+ 1.0,
735
+ this.inDisplacement,
736
+ this.inbaseTranslation,
737
+ this.inShapeCastSettings,
738
+ null,
739
+ body
740
+ );
741
+
742
+ // Do narrow phase collision check
743
+ // this.transformedShape.castShape(inShapeCast, inShapeCastSettings, inbaseTranslation, ioCollector);
744
+
745
+ // TODO: this may not be a good translation of
746
+ // UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
747
+
748
+ // Update early out fraction based on narrow phase collector
749
+ this.ioCollector.updateEarlyOutFraction(this.ioCollector.getEarlyOutFraction());
750
+ }
751
+ }
752
+
753
+ const boundsA = /*@__PURE__*/ Aabb.create();
754
+ const collideShapeVisitor = /*@__PURE__*/ new CollideShapeBodyVisitor();
755
+ const castShapeVisitor = /*@__PURE__*/ new CastShapeBodyVisitor();