@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,659 @@
1
+ import { Body, BodyType, ColliderType } from "../Body";
2
+ import { ContactManifold } from "../manifold/ContactManifold";
3
+ import { ContactConstraint, ManifoldConstraint, ManifoldConstraintPool } from "./ManifoldConstraint";
4
+ import { ContactManifoldModule } from "../manifold/ContactManifoldModule";
5
+ import { average, clamp, squared } from "../../math/scalar";
6
+ import { Isometry } from "../../math/isometry";
7
+ import { Mat3 } from "../../math/mat3";
8
+ import { Mat4 } from "../../math/mat4";
9
+ import { createContactPairKey } from "../manifold/ContactManifold";
10
+ import { Vec3 } from "../../math/vec3";
11
+ import { destroyAllInstancesInPool } from "../../helpers";
12
+ import type { World } from "../../world";
13
+ import { WithPool } from "monomorph";
14
+
15
+ function getCombinedFriction(bodyA: Body, bodyB: Body, fallbackFrictionFunction: CoefficientFunctionType): number {
16
+ const frictionFunction =
17
+ bodyA.frictionFunction !== bodyB.frictionFunction
18
+ ? fallbackFrictionFunction
19
+ : (bodyA.frictionFunction as keyof typeof coefficientFunctions);
20
+ return coefficientFunctions[frictionFunction](bodyA.friction, bodyB.friction);
21
+ }
22
+
23
+ export const enum CoefficientFunctionType {
24
+ min,
25
+ max,
26
+ average,
27
+ }
28
+
29
+ const coefficientFunctions = {
30
+ [CoefficientFunctionType.min]: Math.min,
31
+ [CoefficientFunctionType.max]: Math.max,
32
+ [CoefficientFunctionType.average]: average,
33
+ } as const;
34
+
35
+ const contactPointPreserveLambdaMaxDistanceSquared = squared(0.01); // 1 cm
36
+
37
+ const inverseIsometryB = /*@__PURE__*/ Isometry.create();
38
+ const inverseInertiaTensorA = /*@__PURE__*/ Mat3.create();
39
+ const inverseInertiaTensorB = /*@__PURE__*/ Mat3.create();
40
+ const worldToLocalA = /*@__PURE__*/ Mat4.create();
41
+ const worldToLocalB = /*@__PURE__*/ Mat4.create();
42
+
43
+ const worldContactPointA = /*@__PURE__*/ Vec3.create();
44
+ const worldContactPointB = /*@__PURE__*/ Vec3.create();
45
+ const oldContactPointA = /*@__PURE__*/ Vec3.create();
46
+ const oldContactPointB = /*@__PURE__*/ Vec3.create();
47
+ const combinedWorldContactPoint = /*@__PURE__*/ Vec3.create();
48
+ const worldMomentArmA = /*@__PURE__*/ Vec3.create();
49
+ const worldMomentArmB = /*@__PURE__*/ Vec3.create();
50
+ const contactVelocityA = /*@__PURE__*/ Vec3.create();
51
+ const contactVelocityB = /*@__PURE__*/ Vec3.create();
52
+ const contactVelocityAB = /*@__PURE__*/ Vec3.create();
53
+ const penetrationVector = /*@__PURE__*/ Vec3.create();
54
+ const worldSpaceSurfaceVelocity = /*@__PURE__*/ Vec3.create();
55
+
56
+ const transformLocalToWorldA = /*@__PURE__*/ Isometry.create();
57
+ const transformLocalToWorldB = /*@__PURE__*/ Isometry.create();
58
+
59
+ const worldPointA = /*@__PURE__*/ Vec3.create();
60
+ const worldPointB = /*@__PURE__*/ Vec3.create();
61
+
62
+ const transformAToWorld = /*@__PURE__*/ Mat4.create();
63
+ const transformBToWorld = /*@__PURE__*/ Mat4.create();
64
+
65
+ const averageWorldContactPoint = /*@__PURE__*/ Vec3.create();
66
+ const momentArmA = /*@__PURE__*/ Vec3.create();
67
+ const momentArmB = /*@__PURE__*/ Vec3.create();
68
+
69
+ const positionDeltaA = /*@__PURE__*/ Vec3.create();
70
+ const rotationDeltaA = /*@__PURE__*/ Vec3.create();
71
+ const positionDeltaB = /*@__PURE__*/ Vec3.create();
72
+ const rotationDeltaB = /*@__PURE__*/ Vec3.create();
73
+
74
+ function getCombinedRestitution(
75
+ bodyA: Body,
76
+ bodyB: Body,
77
+ fallbackRestitutionFunction: CoefficientFunctionType
78
+ ): number {
79
+ const restitutionFunction =
80
+ bodyA.restitutionFunction !== bodyB.restitutionFunction
81
+ ? fallbackRestitutionFunction
82
+ : (bodyA.restitutionFunction as keyof typeof coefficientFunctions);
83
+ return coefficientFunctions[restitutionFunction](bodyA.restitution, bodyB.restitution);
84
+ }
85
+
86
+ export class ContactConstraintModule {
87
+ world: World;
88
+ manifoldConstraints: ManifoldConstraintPool;
89
+ contactManifoldModule: ContactManifoldModule;
90
+
91
+ constructor(world: World, contactManifoldModule: ContactManifoldModule) {
92
+ this.world = world;
93
+ this.contactManifoldModule = contactManifoldModule;
94
+ this.manifoldConstraints = new ManifoldConstraintPool(
95
+ this.contactManifoldModule.currentManifoldCache.options.maxContactManifolds,
96
+ 4
97
+ );
98
+ }
99
+
100
+ warmStartConstraints(): void {
101
+ // warm start the contact constraints
102
+ for (const constraint of this.manifoldConstraints.manifoldConstraintPool) {
103
+ constraint.warmStart();
104
+ }
105
+ }
106
+
107
+ solveFrictionVelocityConstraints(
108
+ manifoldConstraint: ManifoldConstraint,
109
+ transformLocalToWorldA: Isometry,
110
+ transformLocalToWorldB: Isometry
111
+ ): void {
112
+ // update the tangents
113
+ // TODO: is this needed during solving?
114
+ manifoldConstraint.updateTangentDirections();
115
+
116
+ for (let i = 0; i < manifoldConstraint.numContacts; i++) {
117
+ const contactConstraint = manifoldConstraint.contactConstraints!.getAtIndex(i)!;
118
+ // solve friction constraints if active
119
+ if (contactConstraint.tangentConstraint.isActive() || contactConstraint.bitangentConstraint.isActive()) {
120
+ let tangentLambda = contactConstraint.tangentConstraint.getTotalLambda(
121
+ manifoldConstraint.bodyA!,
122
+ manifoldConstraint.bodyB!,
123
+ manifoldConstraint.worldSpaceTangent
124
+ );
125
+ let bitangentLambda = contactConstraint.bitangentConstraint.getTotalLambda(
126
+ manifoldConstraint.bodyA!,
127
+ manifoldConstraint.bodyB!,
128
+ manifoldConstraint.worldSpaceBitangent
129
+ );
130
+ const totalLambdaSquared = squared(tangentLambda) + squared(bitangentLambda);
131
+
132
+ const maxLambda = manifoldConstraint.friction * contactConstraint.normalConstraint.totalLambda;
133
+
134
+ if (totalLambdaSquared > squared(maxLambda)) {
135
+ const scale = maxLambda / Math.sqrt(totalLambdaSquared);
136
+ tangentLambda *= scale;
137
+ bitangentLambda *= scale;
138
+ }
139
+
140
+ worldPointA.transformVectorFromMat4(contactConstraint.localPositionA, transformLocalToWorldA.matrix);
141
+ worldPointB.transformVectorFromMat4(contactConstraint.localPositionB, transformLocalToWorldB.matrix);
142
+
143
+ contactConstraint.tangentConstraint.applyImpulse(
144
+ manifoldConstraint.bodyA!,
145
+ manifoldConstraint.bodyB!,
146
+ manifoldConstraint.inverseMassA,
147
+ manifoldConstraint.inverseMassB,
148
+ manifoldConstraint.worldSpaceTangent,
149
+ tangentLambda
150
+ );
151
+
152
+ contactConstraint.bitangentConstraint.applyImpulse(
153
+ manifoldConstraint.bodyA!,
154
+ manifoldConstraint.bodyB!,
155
+ manifoldConstraint.inverseMassA,
156
+ manifoldConstraint.inverseMassB,
157
+ manifoldConstraint.worldSpaceBitangent,
158
+ bitangentLambda
159
+ );
160
+ }
161
+ }
162
+ }
163
+
164
+ solveNonPenetrationVelocityConstraints(
165
+ manifoldConstraint: ManifoldConstraint,
166
+ transformLocalToWorldA: Isometry,
167
+ transformLocalToWorldB: Isometry
168
+ ): void {
169
+ for (let i = 0; i < manifoldConstraint.numContacts; i++) {
170
+ const contactConstraint = manifoldConstraint.contactConstraints!.getAtIndex(i)!;
171
+
172
+ let totalLambda = contactConstraint.normalConstraint.getTotalLambda(
173
+ manifoldConstraint.bodyA!,
174
+ manifoldConstraint.bodyB!,
175
+ manifoldConstraint.worldSpaceNormal
176
+ );
177
+
178
+ totalLambda = clamp(totalLambda, 0, Infinity);
179
+
180
+ worldPointA.transformVectorFromMat4(contactConstraint.localPositionA, transformLocalToWorldA.matrix);
181
+ worldPointB.transformVectorFromMat4(contactConstraint.localPositionB, transformLocalToWorldB.matrix);
182
+
183
+ // apply the normal impulse
184
+ contactConstraint.normalConstraint.applyImpulse(
185
+ manifoldConstraint.bodyA!,
186
+ manifoldConstraint.bodyB!,
187
+ manifoldConstraint.inverseMassA,
188
+ manifoldConstraint.inverseMassB,
189
+ manifoldConstraint.worldSpaceNormal,
190
+ totalLambda
191
+ );
192
+ }
193
+ }
194
+
195
+ solveVelocityConstraint(constraint: ManifoldConstraint): void {
196
+ transformLocalToWorldA.fromRotationAndTranslation(
197
+ constraint.bodyA!.orientation,
198
+ constraint.bodyA!.computedCenterOfMassPosition
199
+ );
200
+ transformLocalToWorldB.fromRotationAndTranslation(
201
+ constraint.bodyB!.orientation,
202
+ constraint.bodyB!.computedCenterOfMassPosition
203
+ );
204
+
205
+ this.solveFrictionVelocityConstraints(constraint, transformLocalToWorldA, transformLocalToWorldB);
206
+ this.solveNonPenetrationVelocityConstraints(constraint, transformLocalToWorldA, transformLocalToWorldB);
207
+ }
208
+
209
+ solveVelocityConstraintsStep(): void {
210
+ for (const constraint of this.manifoldConstraints.manifoldConstraintPool) {
211
+ this.solveVelocityConstraint(constraint);
212
+ }
213
+ }
214
+
215
+ updateNonPenetrationConstraint(
216
+ constraint: ContactConstraint,
217
+ bodyA: Body,
218
+ bodyB: Body,
219
+ inverseMassA: number,
220
+ inverseMassB: number,
221
+ worldContactPointA: Vec3,
222
+ worldContactPointB: Vec3,
223
+ worldSpaceNormal: Vec3
224
+ ): void {
225
+ averageWorldContactPoint.averageOfVectors(worldContactPointA, worldContactPointB);
226
+ momentArmA.subtractVectors(averageWorldContactPoint, bodyA.computedCenterOfMassPosition);
227
+ momentArmB.subtractVectors(averageWorldContactPoint, bodyB.computedCenterOfMassPosition);
228
+
229
+ constraint.normalConstraint.initializeWithMassOverride(
230
+ bodyA,
231
+ bodyB,
232
+ inverseMassA,
233
+ inverseMassB,
234
+ momentArmA,
235
+ momentArmB,
236
+ worldSpaceNormal
237
+ );
238
+ }
239
+
240
+ solvePositionConstraintWithMassOverride(
241
+ constraint: ContactConstraint,
242
+ bodyA: Body,
243
+ bodyB: Body,
244
+ inverseMassA: number,
245
+ inverseMassB: number,
246
+ direction: Vec3,
247
+ constraintValue: number,
248
+ baumgarteValue: number,
249
+ worldContactPointA: Vec3,
250
+ worldContactPointB: Vec3
251
+ ): void {
252
+ // do nothing if no constraint or spring is active
253
+ if (constraintValue === 0 || constraint.normalConstraint.isSpringActive()) {
254
+ return;
255
+ }
256
+
257
+ const lambda = -constraint.normalConstraint.effectiveMass * baumgarteValue * constraintValue;
258
+
259
+ if (bodyA.type === BodyType.dynamic) {
260
+ positionDeltaA.scaleVector(direction, -lambda * inverseMassA);
261
+ bodyA.world?.broadphase.markDynamicBodyAsDirty(bodyA as WithPool<Body>);
262
+ bodyA.computedCenterOfMassPosition.addVectors(bodyA.computedCenterOfMassPosition, positionDeltaA);
263
+
264
+ rotationDeltaA.scaleVector(constraint.normalConstraint.mInvI1_R1PlusUxAxis, lambda);
265
+ bodyA.subRotationDelta(rotationDeltaA);
266
+ }
267
+
268
+ if (bodyB.type === BodyType.dynamic) {
269
+ positionDeltaB.scaleVector(direction, +lambda * inverseMassB);
270
+ bodyB.world?.broadphase.markDynamicBodyAsDirty(bodyB as WithPool<Body>);
271
+ bodyB.computedCenterOfMassPosition.addVectors(bodyB.computedCenterOfMassPosition, positionDeltaB);
272
+
273
+ rotationDeltaB.scaleVector(constraint.normalConstraint.mInvI2_R2xAxis, lambda);
274
+ bodyB.addRotationDelta(rotationDeltaB);
275
+ }
276
+ }
277
+
278
+ solvePositionConstraint(
279
+ manifoldConstraint: ManifoldConstraint,
280
+ penetrationSlop: number,
281
+ maxPenetrationDistance: number,
282
+ baumgarte: number
283
+ ): void {
284
+ // get body transforms
285
+ transformAToWorld.fromRotationTranslation(
286
+ manifoldConstraint.bodyA!.orientation,
287
+ manifoldConstraint.bodyA!.computedCenterOfMassPosition
288
+ );
289
+ transformBToWorld.fromRotationTranslation(
290
+ manifoldConstraint.bodyB!.orientation,
291
+ manifoldConstraint.bodyB!.computedCenterOfMassPosition
292
+ );
293
+
294
+ // recompute the world contact points from the cached local contact points
295
+
296
+ for (let i = 0; i < manifoldConstraint.numContacts; i++) {
297
+ const contactConstraint = manifoldConstraint.contactConstraints!.getAtIndex(i)!;
298
+ worldContactPointA.transformVectorFromMat4(contactConstraint.localPositionA, transformAToWorld);
299
+ worldContactPointB.transformVectorFromMat4(contactConstraint.localPositionB, transformBToWorld);
300
+
301
+ penetrationVector.subtractVectors(worldContactPointB, worldContactPointA);
302
+
303
+ // negative if penetrated, positive if separated
304
+ const separation = Math.max(
305
+ penetrationVector.dot(manifoldConstraint.worldSpaceNormal) + penetrationSlop,
306
+ -maxPenetrationDistance
307
+ );
308
+
309
+ // only solve if separation is negative (penetrated)
310
+ if (separation < 0) {
311
+ this.updateNonPenetrationConstraint(
312
+ contactConstraint,
313
+ manifoldConstraint.bodyA!,
314
+ manifoldConstraint.bodyB!,
315
+ manifoldConstraint.inverseMassA,
316
+ manifoldConstraint.inverseMassB,
317
+ worldContactPointA,
318
+ worldContactPointB,
319
+ manifoldConstraint.worldSpaceNormal
320
+ );
321
+
322
+ this.solvePositionConstraintWithMassOverride(
323
+ contactConstraint,
324
+ manifoldConstraint.bodyA!,
325
+ manifoldConstraint.bodyB!,
326
+ manifoldConstraint.inverseMassA,
327
+ manifoldConstraint.inverseMassB,
328
+ manifoldConstraint.worldSpaceNormal,
329
+ separation,
330
+ baumgarte,
331
+ worldContactPointA,
332
+ worldContactPointB
333
+ );
334
+ }
335
+ }
336
+ }
337
+
338
+ solvePositionConstraintsStep(penetrationSlop: number, maxPenetrationDistance: number, baumgarte: number) {
339
+ for (const constraint of this.manifoldConstraints.manifoldConstraintPool) {
340
+ this.solvePositionConstraint(constraint, penetrationSlop, maxPenetrationDistance, baumgarte);
341
+ }
342
+ }
343
+
344
+ cacheLambdas(): void {
345
+ // copy the total lambdas from each manifold constraint to the corresponding cached manifold
346
+ const cache = this.contactManifoldModule.currentManifoldCache;
347
+
348
+ for (const constraint of this.manifoldConstraints.manifoldConstraintPool) {
349
+ const manifold = cache.manifoldMap.get(constraint.key);
350
+ if (manifold === undefined) {
351
+ throw new Error(`expected to find manifold ${constraint.key} in cache`);
352
+ }
353
+
354
+ constraint.copyLambdasToManifold(manifold);
355
+ }
356
+ }
357
+
358
+ clearContactConstraints(): void {
359
+ destroyAllInstancesInPool(this.manifoldConstraints.manifoldConstraintPool);
360
+ }
361
+
362
+ addContactConstraints(
363
+ bodyA: Body,
364
+ bodyB: Body,
365
+ manifold: ContactManifold,
366
+ timeStepSizeSeconds: number,
367
+ isWarmStartingEnabled: boolean,
368
+ minVelocityForElasticContact: number
369
+ ) {
370
+ // ensure the world space normal is normalized
371
+ manifold.worldSpaceNormal.normalize();
372
+ // console.log("adding contact constraint", manifold.worldSpaceNormal.toObject());
373
+
374
+ // #v-ifdef DEV
375
+ // assert that the direction is normalized within some tolerance
376
+ if (manifold.worldSpaceNormal.isNormalized(1e-5) === false) {
377
+ throw new Error(`expected normalized manifold normal, got ${manifold.worldSpaceNormal} instead`);
378
+ }
379
+ // #v-endif
380
+
381
+ const cachedManifold = this.contactManifoldModule.currentManifoldCache.manifolds.createContactManifold({
382
+ bodyA: bodyA,
383
+ bodyB: bodyB,
384
+ worldSpaceNormal: manifold.worldSpaceNormal,
385
+ firstWorldSpaceNormal: manifold.firstWorldSpaceNormal,
386
+ penetrationDepth: manifold.penetrationDepth,
387
+ nextContactManifold: manifold.nextContactManifold,
388
+ numContacts: manifold.numContacts,
389
+ subShapeIdA: manifold.subShapeIdA,
390
+ subShapeIdB: manifold.subShapeIdB,
391
+ });
392
+
393
+ this.contactManifoldModule.currentManifoldCache.manifoldMap.set(cachedManifold.key, cachedManifold);
394
+
395
+ // for the cached manifold, we store the worldSpaceNormal in the space of bodyB
396
+
397
+ inverseIsometryB.fromInverseRotationAndTranslation(bodyB.orientation, bodyB.computedCenterOfMassPosition);
398
+ inverseIsometryB.matrix.multiply3x3(cachedManifold.worldSpaceNormal, manifold.worldSpaceNormal);
399
+ cachedManifold.worldSpaceNormal.normalize();
400
+
401
+ const previousManifold = this.contactManifoldModule.previousManifoldCache.manifoldMap.get(cachedManifold.key);
402
+ // check if the manifold exists in the previous cache
403
+ if (previousManifold !== undefined) {
404
+ // persistent contact
405
+ // TODO: get the previous lambdas per constraint and use them to warm start the constraints
406
+ cachedManifold.copyLambdas(previousManifold);
407
+ } else {
408
+ // new contact
409
+ }
410
+
411
+ // no constraints if either body does not resolve contacts
412
+ if (bodyA.colliderType !== ColliderType.resolveContact && bodyB.colliderType !== ColliderType.resolveContact) {
413
+ return;
414
+ }
415
+
416
+ // no constraints if neither body is dynamic
417
+ if (bodyA.type !== BodyType.dynamic && bodyB.type !== BodyType.dynamic) {
418
+ return;
419
+ }
420
+
421
+ const manifoldConstraint = this.manifoldConstraints.createManifoldConstraint({
422
+ bodyA: bodyA,
423
+ bodyB: bodyB,
424
+ subShapeIdA: manifold.subShapeIdA,
425
+ subShapeIdB: manifold.subShapeIdB,
426
+ friction: getCombinedFriction(bodyA, bodyB, CoefficientFunctionType.average),
427
+ restitution: getCombinedRestitution(bodyA, bodyB, CoefficientFunctionType.average),
428
+ inverseMassA: bodyA.inverseMass,
429
+ inverseMassB: bodyB.inverseMass,
430
+ numContacts: 0,
431
+ });
432
+
433
+ // TODO: which normal should we use, A or B?
434
+ manifoldConstraint.worldSpaceNormal.normalizeVector(manifold.worldSpaceNormal);
435
+ manifoldConstraint.updateTangentDirections();
436
+ manifoldConstraint.numContacts = manifold.numContacts;
437
+
438
+ // get mass properties
439
+
440
+ bodyA.computeInverseInertiaTensor(inverseInertiaTensorA);
441
+ bodyB.computeInverseInertiaTensor(inverseInertiaTensorB);
442
+
443
+ // get the world to local transform for each body
444
+
445
+ worldToLocalA.fromInverseRotationAndTranslation(bodyA.orientation, bodyA.computedCenterOfMassPosition);
446
+ worldToLocalB.fromInverseRotationAndTranslation(bodyB.orientation, bodyB.computedCenterOfMassPosition);
447
+
448
+ // iterate over the contacts and add the constraints
449
+ for (let i = 0; i < manifold.numContacts; i++) {
450
+ this.addContactConstraint(
451
+ bodyA,
452
+ bodyB,
453
+ manifold,
454
+ manifoldConstraint,
455
+ inverseInertiaTensorA,
456
+ inverseInertiaTensorB,
457
+ worldToLocalA,
458
+ worldToLocalB,
459
+ cachedManifold,
460
+ i,
461
+ timeStepSizeSeconds,
462
+ isWarmStartingEnabled,
463
+ minVelocityForElasticContact
464
+ );
465
+ }
466
+
467
+ // set the new cached manifold as the head of the current manifold cache
468
+ const contactPairKey = createContactPairKey(manifold.bodyA!, manifold.bodyB!);
469
+ const contactPair = this.contactManifoldModule.currentManifoldCache.pairMap.get(contactPairKey);
470
+
471
+ if (contactPair === undefined) {
472
+ throw new Error("contact pair not found");
473
+ }
474
+
475
+ // TODO: implement manifold list
476
+ const currentFirstManifold = contactPair.firstContactManifold;
477
+ if (currentFirstManifold === null) {
478
+ // case: no current first manifold, simply set the new manifold as the first
479
+ contactPair.firstContactManifold = cachedManifold;
480
+ } else {
481
+ // case: there is a current first manifold, set the current first as the next of the new manifold, and set the new manifold as the new first
482
+ cachedManifold.nextContactManifold = currentFirstManifold;
483
+ contactPair.firstContactManifold = cachedManifold;
484
+ }
485
+
486
+ contactPair.firstContactManifold = cachedManifold;
487
+
488
+ // reset the current cached manifold's lambdas
489
+ cachedManifold.resetLambdas();
490
+ }
491
+
492
+ addContactConstraint(
493
+ bodyA: Body,
494
+ bodyB: Body,
495
+ manifold: ContactManifold,
496
+ manifoldConstraint: ManifoldConstraint,
497
+ inverseInertiaTensorA: Mat3,
498
+ inverseInertiaTensorB: Mat3,
499
+ worldToLocalA: Mat4,
500
+ worldToLocalB: Mat4,
501
+ cachedManifold: ContactManifold,
502
+ index: number,
503
+ timeStepSizeSeconds: number,
504
+ isWarmStartingEnabled: boolean,
505
+ minVelocityForElasticContact: number
506
+ ): void {
507
+ const contactConstraint = manifoldConstraint.contactConstraints!.getAtIndex(index)!;
508
+ // init the constraint directions
509
+
510
+ // init the constraint local contact points
511
+
512
+ manifold.getContactPointA(worldContactPointA, index);
513
+ worldContactPointA.addVector(manifold.baseTranslation);
514
+ manifold.getContactPointB(worldContactPointB, index);
515
+ worldContactPointB.addVector(manifold.baseTranslation);
516
+
517
+ contactConstraint.localPositionA.transformVectorFromMat4(worldContactPointA, worldToLocalA);
518
+ contactConstraint.localPositionB.transformVectorFromMat4(worldContactPointB, worldToLocalB);
519
+
520
+ // init the lambdas to zero
521
+ contactConstraint.normalConstraint.totalLambda = 0;
522
+ contactConstraint.tangentConstraint.totalLambda = 0;
523
+ contactConstraint.bitangentConstraint.totalLambda = 0;
524
+
525
+ // check if we have a close contact point from last update
526
+ const oldManifold = this.contactManifoldModule.previousManifoldCache.manifoldMap.get(manifold.key);
527
+ if (oldManifold !== undefined) {
528
+ // check if any of the contact points are close enough to be considered the same contact
529
+
530
+ for (let i = 0; i < oldManifold.numContacts; i++) {
531
+ oldManifold.getContactPointA(oldContactPointA, index);
532
+ oldManifold.getContactPointB(oldContactPointB, index);
533
+
534
+ if (
535
+ contactConstraint.localPositionA.isClose(oldContactPointA, contactPointPreserveLambdaMaxDistanceSquared) &&
536
+ contactConstraint.localPositionB.isClose(oldContactPointB, contactPointPreserveLambdaMaxDistanceSquared)
537
+ ) {
538
+ if (isWarmStartingEnabled) {
539
+ contactConstraint.copyLambdaFromManifold(oldManifold, index);
540
+ }
541
+ break;
542
+ }
543
+ }
544
+ }
545
+
546
+ // store the contact points for next frame
547
+ cachedManifold.setContactPointA(contactConstraint.localPositionA, index);
548
+ cachedManifold.setContactPointB(contactConstraint.localPositionB, index);
549
+
550
+ // 1. init the velocity constraint
551
+ // wcp.TemplatedCalculateFrictionAndNonPenetrationConstraintProperties<Type1, Type2>(deltaTime, bodyA, bodyB, inverseMassA, inverseMassB, inverseInertiaA, inverseInertiaB,
552
+ // worldContactPointA, worldContactPointB, worldSpaceNormal, worldSpaceTangent, worldSpaceBitangent, settings, mPhysicsSettings.mMinVelocityForRestitution);
553
+
554
+ // init the world moment arms
555
+
556
+ combinedWorldContactPoint.averageOfVectors(worldContactPointA, worldContactPointB);
557
+ worldMomentArmA.subtractVectors(combinedWorldContactPoint, bodyA.computedCenterOfMassPosition);
558
+ worldMomentArmB.subtractVectors(combinedWorldContactPoint, bodyB.computedCenterOfMassPosition);
559
+
560
+ // init the contact point velocities
561
+
562
+ bodyA.computeVelocityOfPointRelativeToCenterOfMass(contactVelocityA, worldMomentArmA);
563
+ bodyB.computeVelocityOfPointRelativeToCenterOfMass(contactVelocityB, worldMomentArmB);
564
+ contactVelocityAB.subtractVectors(contactVelocityB, contactVelocityA);
565
+
566
+ // init the relative contact point speed along the normal
567
+ const normalSpeed = contactVelocityAB.dot(manifoldConstraint.worldSpaceNormal);
568
+
569
+ // init the contact penetration along the normal (positive -> penetrated, negative -> separated)
570
+
571
+ penetrationVector.subtractVectors(worldContactPointA, worldContactPointB);
572
+ const penetration = penetrationVector.dot(manifoldConstraint.worldSpaceNormal);
573
+
574
+ // #v-ifdef DEV
575
+ // assert that penetration is positive, TODO: handle negative penetration (speculative contacts)
576
+ if (penetration < 0) {
577
+ // throw new Error("got here without being in contact, handling of speculative contacts not implemented yet");
578
+ }
579
+ // #v-endif
580
+
581
+ // init the speculative contact velocity bias
582
+ const speculativeContactVelocityBias = Math.max(0, -penetration / timeStepSizeSeconds);
583
+
584
+ // init velocity bias (handles restitution)
585
+ let normalVelocityBias: number;
586
+
587
+ const isVelocityBigEnoughForRestitution =
588
+ manifoldConstraint.restitution > 0 &&
589
+ normalSpeed < -minVelocityForElasticContact &&
590
+ normalSpeed < -speculativeContactVelocityBias;
591
+
592
+ if (isVelocityBigEnoughForRestitution) {
593
+ if (normalSpeed < -speculativeContactVelocityBias) {
594
+ normalVelocityBias = manifoldConstraint.restitution * normalSpeed;
595
+ } else {
596
+ normalVelocityBias = speculativeContactVelocityBias;
597
+ }
598
+ } else {
599
+ normalVelocityBias = speculativeContactVelocityBias;
600
+ }
601
+
602
+ // init the normal constraint
603
+ contactConstraint.normalConstraint.initialize(
604
+ bodyA,
605
+ bodyB,
606
+ manifoldConstraint.inverseMassA,
607
+ manifoldConstraint.inverseMassB,
608
+ inverseInertiaTensorA,
609
+ inverseInertiaTensorB,
610
+ worldMomentArmA,
611
+ worldMomentArmB,
612
+ manifoldConstraint.worldSpaceNormal,
613
+ normalVelocityBias
614
+ );
615
+
616
+ // init the tangent constraints
617
+ if (manifoldConstraint.friction === 0) {
618
+ contactConstraint.tangentConstraint.effectiveMass = 0;
619
+ contactConstraint.tangentConstraint.totalLambda = 0;
620
+
621
+ contactConstraint.bitangentConstraint.effectiveMass = 0;
622
+ contactConstraint.bitangentConstraint.totalLambda = 0;
623
+ return;
624
+ }
625
+
626
+ // get the surface velocity relative to the tangents
627
+ // TODO: this world space velocity is a placeholder for something that a user can pass in through settings
628
+
629
+ worldSpaceSurfaceVelocity.zero();
630
+ const tangentSpeed = worldSpaceSurfaceVelocity.dot(manifoldConstraint.worldSpaceTangent);
631
+ const bitangentSpeed = worldSpaceSurfaceVelocity.dot(manifoldConstraint.worldSpaceBitangent);
632
+
633
+ // init the tangent constraints
634
+ contactConstraint.tangentConstraint.initialize(
635
+ bodyA,
636
+ bodyB,
637
+ manifoldConstraint.inverseMassA,
638
+ manifoldConstraint.inverseMassB,
639
+ inverseInertiaTensorA,
640
+ inverseInertiaTensorB,
641
+ worldMomentArmA,
642
+ worldMomentArmB,
643
+ manifoldConstraint.worldSpaceTangent,
644
+ tangentSpeed
645
+ );
646
+ contactConstraint.bitangentConstraint.initialize(
647
+ bodyA,
648
+ bodyB,
649
+ manifoldConstraint.inverseMassA,
650
+ manifoldConstraint.inverseMassB,
651
+ inverseInertiaTensorA,
652
+ inverseInertiaTensorB,
653
+ worldMomentArmA,
654
+ worldMomentArmB,
655
+ manifoldConstraint.worldSpaceBitangent,
656
+ bitangentSpeed
657
+ );
658
+ }
659
+ }