@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,420 @@
1
+ import {
2
+ createClass,
3
+ InputType,
4
+ LazyReferenceType,
5
+ MonomorphType,
6
+ NumberType,
7
+ PoolClass,
8
+ PropertyDefinitionMap,
9
+ PropertyDefinitionReference,
10
+ ReferenceListType,
11
+ ReferenceType,
12
+ } from "monomorph";
13
+ import { Body, BodyType } from "../Body";
14
+ import { Vec3 } from "../../math/vec3";
15
+ import { ContactManifold, createContactPairKey } from "../manifold/ContactManifold";
16
+ import { Mat3 } from "../../math/mat3";
17
+
18
+ const linearVelocityA = /*@__PURE__*/ Vec3.create();
19
+ const linearVelocityB = /*@__PURE__*/ Vec3.create();
20
+ const linearVelocityBA = /*@__PURE__*/ Vec3.create();
21
+ const angularVelocityA = /*@__PURE__*/ Vec3.create();
22
+ const angularVelocityB = /*@__PURE__*/ Vec3.create();
23
+
24
+ const inverseInertiaA = /*@__PURE__*/ Mat3.create();
25
+ const inverseInertiaB = /*@__PURE__*/ Mat3.create();
26
+
27
+ const directionalConstraintProps = {
28
+ mR1PlusUxAxis: MonomorphType(Vec3),
29
+ mR2xAxis: MonomorphType(Vec3),
30
+ mInvI1_R1PlusUxAxis: MonomorphType(Vec3),
31
+ mInvI2_R2xAxis: MonomorphType(Vec3),
32
+ effectiveMass: NumberType(0.0),
33
+ bias: NumberType(0.0),
34
+ softness: NumberType(0.0),
35
+ totalLambda: NumberType(0.0),
36
+ } as const satisfies PropertyDefinitionMap;
37
+
38
+ export class DirectionalConstraint extends createClass<DirectionalConstraint, typeof directionalConstraintProps>(
39
+ directionalConstraintProps
40
+ ) {
41
+ isSpringActive(): boolean {
42
+ return this.softness !== 0;
43
+ }
44
+
45
+ computeInverseEffectiveMassWithMassOverride(
46
+ bodyA: Body,
47
+ bodyB: Body,
48
+ inverseMassA: number,
49
+ inverseMassB: number,
50
+ momentArmA: Vec3,
51
+ momentArmB: Vec3,
52
+ direction: Vec3
53
+ ): number {
54
+ // recompute the inverse inertia tensors
55
+ bodyA.computeInverseInertiaTensor(inverseInertiaA);
56
+ bodyB.computeInverseInertiaTensor(inverseInertiaB);
57
+
58
+ return this.computeInverseEffectiveMass(
59
+ bodyA,
60
+ bodyB,
61
+ inverseMassA,
62
+ inverseMassB,
63
+ inverseInertiaA,
64
+ inverseInertiaB,
65
+ momentArmA,
66
+ momentArmB,
67
+ direction
68
+ );
69
+ }
70
+
71
+ initializeWithMassOverride(
72
+ bodyA: Body,
73
+ bodyB: Body,
74
+ inverseMassA: number,
75
+ inverseMassB: number,
76
+ momentArmA: Vec3,
77
+ momentArmB: Vec3,
78
+ direction: Vec3,
79
+ bias: number = 0
80
+ ): void {
81
+ const inverseEffectiveMass = this.computeInverseEffectiveMassWithMassOverride(
82
+ bodyA,
83
+ bodyB,
84
+ inverseMassA,
85
+ inverseMassB,
86
+ momentArmA,
87
+ momentArmB,
88
+ direction
89
+ );
90
+
91
+ if (inverseEffectiveMass === 0) {
92
+ this.effectiveMass = 0;
93
+ this.totalLambda = 0;
94
+ return;
95
+ }
96
+
97
+ this.effectiveMass = 1 / inverseEffectiveMass;
98
+ this.bias = bias;
99
+ this.softness = 0;
100
+ }
101
+
102
+ applyImpulse(
103
+ bodyA: Body,
104
+ bodyB: Body,
105
+ inverseMassA: number,
106
+ inverseMassB: number,
107
+ direction: Vec3,
108
+ inTotalLambda: number
109
+ ): void {
110
+ const deltaLambda = inTotalLambda - this.totalLambda;
111
+ this.totalLambda = inTotalLambda;
112
+
113
+ // if lambda is zero, do nothing
114
+ if (deltaLambda === 0) {
115
+ return;
116
+ }
117
+
118
+ if (bodyA.type === BodyType.dynamic) {
119
+ bodyA.linearVelocity.addScaled(direction, -deltaLambda * inverseMassA);
120
+ // console.log("applying delta-v of magnitude", -deltaLambda * inverseMassA, "in direction", direction.toObject());
121
+ bodyA.angularVelocity.addScaled(this.mInvI1_R1PlusUxAxis, -deltaLambda);
122
+ }
123
+
124
+ if (bodyB.type === BodyType.dynamic) {
125
+ bodyB.linearVelocity.addScaled(direction, +deltaLambda * inverseMassB);
126
+ bodyB.angularVelocity.addScaled(this.mInvI2_R2xAxis, +deltaLambda);
127
+ }
128
+ }
129
+ getBias(totalLambda: number): number {
130
+ return this.softness * totalLambda + this.bias;
131
+ }
132
+
133
+ getTotalLambda(bodyA: Body, bodyB: Body, direction: Vec3): number {
134
+ // #v-ifdef DEV
135
+ // assert at least one body is dynamic
136
+ if (bodyA.type !== BodyType.dynamic && bodyB.type !== BodyType.dynamic) {
137
+ throw new Error("at least one body must be dynamic");
138
+ }
139
+ // #v-endif
140
+ // calculate the jacobian multiplied by the velocity
141
+ let jv = 0;
142
+
143
+ // jacobian multiplied by the linear relative velocity
144
+ linearVelocityA.copy(bodyA.linearVelocity);
145
+ linearVelocityB.copy(bodyB.linearVelocity);
146
+ linearVelocityBA.subtractVectors(linearVelocityA, linearVelocityB);
147
+
148
+ jv += direction.dot(linearVelocityBA);
149
+
150
+ // jacobian multiplied by the angular relative velocity
151
+ angularVelocityA.copy(bodyA.angularVelocity);
152
+ angularVelocityB.copy(bodyB.angularVelocity);
153
+
154
+ if (bodyA.type !== BodyType.static) {
155
+ jv += this.mR1PlusUxAxis.dot(angularVelocityA);
156
+ }
157
+
158
+ if (bodyB.type !== BodyType.static) {
159
+ jv -= this.mR2xAxis.dot(angularVelocityB);
160
+ }
161
+
162
+ // compute lambda = -K^-1 * (jv - b)
163
+ const lambda = this.effectiveMass * (jv - this.getBias(this.totalLambda));
164
+ const accumulatedLambda = this.totalLambda + lambda;
165
+ return accumulatedLambda;
166
+ }
167
+
168
+ computeInverseEffectiveMass(
169
+ bodyA: Body,
170
+ bodyB: Body,
171
+ inverseMassA: number,
172
+ inverseMassB: number,
173
+ inverseInertiaA: Mat3,
174
+ inverseInertiaB: Mat3,
175
+ momentArmA: Vec3,
176
+ momentArmB: Vec3,
177
+ direction: Vec3
178
+ ): number {
179
+ // #v-ifdef DEV
180
+ // assert that the direction is normalized within some tolerance
181
+ if (direction.isNormalized(1e-5) === false) {
182
+ throw new Error(`expected normalized direction, got ${direction} instead`);
183
+ }
184
+ // #v-endif
185
+
186
+ // zero out the linear jacobians (to handle static bodies)
187
+ this.mR1PlusUxAxis.zero();
188
+ this.mR2xAxis.zero();
189
+
190
+ // if not a static body, calculate the linear jacobians
191
+ if (bodyA.type !== BodyType.static) {
192
+ this.mR1PlusUxAxis.crossVectors(momentArmA, direction);
193
+ }
194
+
195
+ if (bodyB.type !== BodyType.static) {
196
+ this.mR2xAxis.crossVectors(momentArmB, direction);
197
+ }
198
+
199
+ // the inverse effective mass is K = J * M^-1 * J_T
200
+ let inverseEffectiveMass = 0;
201
+
202
+ if (bodyA.type === BodyType.dynamic) {
203
+ this.mInvI1_R1PlusUxAxis.transformVectorFromMat3(this.mR1PlusUxAxis, inverseInertiaA);
204
+ inverseEffectiveMass += inverseMassA + this.mInvI1_R1PlusUxAxis.dot(this.mR1PlusUxAxis);
205
+ }
206
+
207
+ if (bodyB.type === BodyType.dynamic) {
208
+ this.mInvI2_R2xAxis.transformVectorFromMat3(this.mR2xAxis, inverseInertiaB);
209
+ inverseEffectiveMass += inverseMassB + this.mInvI2_R2xAxis.dot(this.mR2xAxis);
210
+ }
211
+
212
+ return inverseEffectiveMass;
213
+ }
214
+
215
+ initialize(
216
+ bodyA: Body,
217
+ bodyB: Body,
218
+ inverseMassA: number,
219
+ inverseMassB: number,
220
+ inverseInertiaA: Mat3,
221
+ inverseInertiaB: Mat3,
222
+ momentArmA: Vec3,
223
+ momentArmB: Vec3,
224
+ direction: Vec3,
225
+ bias: number
226
+ ): void {
227
+ const inverseEffectiveMass = this.computeInverseEffectiveMass(
228
+ bodyA,
229
+ bodyB,
230
+ inverseMassA,
231
+ inverseMassB,
232
+ inverseInertiaA,
233
+ inverseInertiaB,
234
+ momentArmA,
235
+ momentArmB,
236
+ direction
237
+ );
238
+
239
+ if (inverseEffectiveMass === 0) {
240
+ this.effectiveMass = 0;
241
+ this.totalLambda = 0;
242
+ return;
243
+ }
244
+
245
+ this.effectiveMass = 1 / inverseEffectiveMass;
246
+ this.bias = bias;
247
+ this.softness = 0;
248
+ }
249
+
250
+ isActive(): boolean {
251
+ return this.effectiveMass !== 0;
252
+ }
253
+
254
+ warmStart(bodyA: Body, bodyB: Body, inverseMassA: number, inverseMassB: number, direction: Vec3): void {
255
+ // this.applyImpulse(bodyA, bodyB, inverseMassA, inverseMassB, direction, this.totalLambda);
256
+ const deltaLambda = this.totalLambda;
257
+
258
+ if (bodyA.type === BodyType.dynamic) {
259
+ bodyA.linearVelocity.addScaled(direction, -deltaLambda * inverseMassA);
260
+ bodyA.angularVelocity.addScaled(this.mInvI1_R1PlusUxAxis, -deltaLambda);
261
+ }
262
+
263
+ if (bodyB.type === BodyType.dynamic) {
264
+ bodyB.linearVelocity.addScaled(direction, +deltaLambda * inverseMassB);
265
+ bodyB.angularVelocity.addScaled(this.mInvI2_R2xAxis, +deltaLambda);
266
+ }
267
+ }
268
+ }
269
+
270
+ const contactConstraintProps = {
271
+ normalConstraint: MonomorphType(DirectionalConstraint),
272
+ tangentConstraint: MonomorphType(DirectionalConstraint),
273
+ bitangentConstraint: MonomorphType(DirectionalConstraint),
274
+ localPositionA: MonomorphType(Vec3),
275
+ localPositionB: MonomorphType(Vec3),
276
+ } as const satisfies PropertyDefinitionMap;
277
+
278
+ export class ContactConstraint extends createClass<ContactConstraint, typeof contactConstraintProps>(
279
+ contactConstraintProps
280
+ ) {
281
+ getLambda(out: Vec3, index: number): void {
282
+ out.x = this.normalConstraint.totalLambda;
283
+ out.y = this.tangentConstraint.totalLambda;
284
+ out.z = this.bitangentConstraint.totalLambda;
285
+ }
286
+
287
+ setLambda(value: Vec3): void {
288
+ this.normalConstraint.totalLambda = value.x;
289
+ this.tangentConstraint.totalLambda = value.y;
290
+ this.bitangentConstraint.totalLambda = value.z;
291
+ }
292
+
293
+ copyLambdaFromManifold(manifold: ContactManifold, index: number): void {
294
+ this.setLambda(manifold.lambdas!.getAtIndex(index)!);
295
+ }
296
+
297
+ warmStart(
298
+ bodyA: Body,
299
+ bodyB: Body,
300
+ inverseMassA: number,
301
+ inverseMassB: number,
302
+ normal: Vec3,
303
+ tangent: Vec3,
304
+ bitangent: Vec3
305
+ ): void {
306
+ if (this.tangentConstraint.isActive() || this.bitangentConstraint.isActive()) {
307
+ this.tangentConstraint.warmStart(bodyA, bodyB, inverseMassA, inverseMassB, tangent);
308
+ this.bitangentConstraint.warmStart(bodyA, bodyB, inverseMassA, inverseMassB, bitangent);
309
+ }
310
+ this.normalConstraint.warmStart(bodyA, bodyB, inverseMassA, inverseMassB, normal);
311
+ }
312
+ }
313
+
314
+ const manifoldConstraintProps = {
315
+ bodyA: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
316
+ bodyB: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
317
+
318
+ subShapeIdA: NumberType(0),
319
+ subShapeIdB: NumberType(0),
320
+
321
+ friction: NumberType(0.0),
322
+ restitution: NumberType(0.0),
323
+
324
+ worldSpaceNormal: MonomorphType(Vec3),
325
+ worldSpaceTangent: MonomorphType(Vec3),
326
+ worldSpaceBitangent: MonomorphType(Vec3),
327
+
328
+ inverseMassA: NumberType(0.0),
329
+ inverseMassB: NumberType(0.0),
330
+
331
+ contactConstraints: ReferenceListType(ContactConstraint),
332
+ numContacts: NumberType(0),
333
+ } as const satisfies PropertyDefinitionMap;
334
+
335
+ /**
336
+ * a contact manifold constraint for one or more points
337
+ */
338
+ export class ManifoldConstraint extends createClass<ManifoldConstraint, typeof manifoldConstraintProps>(
339
+ manifoldConstraintProps
340
+ ) {
341
+ updateTangentDirections(): void {
342
+ this.worldSpaceTangent.computeNormalizedPerpendicular(this.worldSpaceNormal);
343
+ this.worldSpaceBitangent.crossVectors(this.worldSpaceNormal, this.worldSpaceTangent);
344
+ }
345
+
346
+ get key() {
347
+ return createContactPairKey(this.bodyA!, this.bodyB!);
348
+ }
349
+
350
+ copyLambdasToManifold(manifold: ContactManifold): void {
351
+ for (let i = 0; i < manifold.numContacts; i++) {
352
+ this.contactConstraints!.getAtIndex(i)!.getLambda(manifold.lambdas!.getAtIndex(i)!, i);
353
+ }
354
+ }
355
+
356
+ warmStart(): void {
357
+ // // recalculate tangents ?
358
+ this.updateTangentDirections();
359
+
360
+ // warm start the contact constraints
361
+ for (let i = 0; i < this.numContacts; i++) {
362
+ this.contactConstraints!.getAtIndex(i)!.warmStart(
363
+ this.bodyA!,
364
+ this.bodyB!,
365
+ this.inverseMassA,
366
+ this.inverseMassB,
367
+ this.worldSpaceNormal,
368
+ this.worldSpaceTangent,
369
+ this.worldSpaceBitangent
370
+ );
371
+ }
372
+ }
373
+ }
374
+
375
+ export class ManifoldConstraintPool {
376
+ // the contact manifold pool
377
+ manifoldConstraintPool: PoolClass<ManifoldConstraint>;
378
+ contactConstraintPool: PoolClass<ContactConstraint>;
379
+
380
+ numMaxManifolds: number;
381
+ numMaxContacts: number;
382
+
383
+ // list of pools for contact constraints
384
+ // contactConstraintsPoolList: PoolClass<ContactConstraint>[];
385
+
386
+ constructor(numMaxManifolds: number, numMaxContacts: number) {
387
+ this.manifoldConstraintPool = new ManifoldConstraint.Pool(); // numMaxManifolds
388
+ this.contactConstraintPool = new ContactConstraint.Pool(); // numMaxContacts * numMaxManifolds
389
+
390
+ this.numMaxManifolds = numMaxManifolds;
391
+ this.numMaxContacts = numMaxContacts;
392
+ }
393
+
394
+ createManifoldConstraint(data: InputType<ManifoldConstraint>): ManifoldConstraint {
395
+ data = data ?? {};
396
+
397
+ if (data.contactConstraints) {
398
+ data.contactConstraints.pool = this.contactConstraintPool;
399
+ } else {
400
+ data.contactConstraints = { pool: this.contactConstraintPool };
401
+ }
402
+
403
+ const manifold = ManifoldConstraint.create(data, this.manifoldConstraintPool);
404
+
405
+ // todo this behavior probably should change?
406
+ // currently, we create all the contactConstraints once the first time, and never destroy them.
407
+ // they technically should be destroyed, but this is more efficient for this use case
408
+ // since we will be reusing the same manifolds over and over again, so we should just
409
+ // increment a numManifolds instead that is reset to 0
410
+ if (manifold.contactConstraints.length >= this.numMaxContacts) {
411
+ return manifold;
412
+ }
413
+
414
+ for (let i = 0; i < this.numMaxContacts; i++) {
415
+ manifold.contactConstraints!.create();
416
+ }
417
+
418
+ return manifold;
419
+ }
420
+ }
@@ -0,0 +1,146 @@
1
+ import { createClass, MonomorphType, PropertyDefinitionMap } from "monomorph";
2
+ import { ContactManifold } from "../manifold/ContactManifold";
3
+ import { Vec3 } from "../../math/vec3";
4
+ import { BodyType } from "../Body";
5
+ import { Mat3 } from "../../math/mat3";
6
+
7
+ const estimateCollisionResponseResultProps = {
8
+ deltaLinearVelocityA: MonomorphType(Vec3),
9
+ deltaAngularVelocityA: MonomorphType(Vec3),
10
+ deltaLinearVelocityB: MonomorphType(Vec3),
11
+ deltaAngularVelocityB: MonomorphType(Vec3),
12
+ } as const satisfies PropertyDefinitionMap;
13
+
14
+ export class EstimateCollisionResponseResult extends createClass<
15
+ EstimateCollisionResponseResult,
16
+ typeof estimateCollisionResponseResultProps
17
+ >(estimateCollisionResponseResultProps) {
18
+ reset() {
19
+ this.deltaLinearVelocityA.zero();
20
+ this.deltaAngularVelocityA.zero();
21
+ this.deltaLinearVelocityB.zero();
22
+ this.deltaAngularVelocityB.zero();
23
+ }
24
+ }
25
+
26
+ export function estimateCollisionResponse(
27
+ result: EstimateCollisionResponseResult,
28
+ manifold: ContactManifold,
29
+ timeStepSizeSeconds: number
30
+ ) {
31
+ result.reset();
32
+ const damping = 0.9;
33
+ const penetrationResolution = 0.4;
34
+ const bodyA = manifold.bodyA!;
35
+ const bodyB = manifold.bodyB!;
36
+
37
+ for (let index = 0; index < manifold.numContacts; index++) {
38
+ manifold.getContactPointA(worldContactPointA, index);
39
+ worldContactPointA.addVector(manifold.baseTranslation);
40
+
41
+ manifold.getContactPointB(worldContactPointB, index);
42
+ worldContactPointB.addVector(manifold.baseTranslation);
43
+
44
+ combinedWorldContactPoint.averageOfVectors(worldContactPointA, worldContactPointB);
45
+ worldMomentArmA.subtractVectors(combinedWorldContactPoint, bodyA.computedCenterOfMassPosition);
46
+ worldMomentArmB.subtractVectors(combinedWorldContactPoint, bodyB.computedCenterOfMassPosition);
47
+
48
+ bodyA.computeVelocityOfPointRelativeToCenterOfMass(contactVelocityA, worldMomentArmA);
49
+ bodyB.computeVelocityOfPointRelativeToCenterOfMass(contactVelocityB, worldMomentArmB);
50
+ contactVelocityAB.subtractVectors(contactVelocityB, contactVelocityA);
51
+
52
+ // get the velocity along the contact normal
53
+ const normalSpeed = contactVelocityAB.dot(manifold.worldSpaceNormal);
54
+ const deltaSpeed =
55
+ -normalSpeed * damping - (Math.min(manifold.penetrationDepth, 0) * penetrationResolution) / timeStepSizeSeconds;
56
+
57
+ // don't apply impulses if separating
58
+ if (deltaSpeed < 0) {
59
+ return;
60
+ }
61
+
62
+ centerOfMassA.copy(bodyA.computedCenterOfMassPosition);
63
+ centerOfMassB.copy(bodyB.computedCenterOfMassPosition);
64
+
65
+ jacobianA.crossVectors(worldMomentArmA, manifold.worldSpaceNormal);
66
+ jacobianB.crossVectors(worldMomentArmB, manifold.worldSpaceNormal);
67
+
68
+ // compute inverse effective mass
69
+ const direction = manifold.worldSpaceNormal;
70
+ bodyA.computeInverseInertiaTensor(inverseInertiaA);
71
+ bodyB.computeInverseInertiaTensor(inverseInertiaB);
72
+ const inverseMassA = bodyA.inverseMass;
73
+ const inverseMassB = bodyB.inverseMass;
74
+
75
+ // #v-ifdef DEV
76
+ // assert that the direction is normalized within some tolerance
77
+ if (direction.isNormalized(1e-5) === false) {
78
+ throw new Error(`expected normalized direction, got ${direction} instead`);
79
+ }
80
+ // #v-endif
81
+
82
+ // zero out the linear jacobians (to handle static bodies)
83
+ mR1PlusUxAxis.zero();
84
+ mR2xAxis.zero();
85
+
86
+ // if not a static body, calculate the linear jacobians
87
+ if (bodyA.type !== BodyType.static) {
88
+ mR1PlusUxAxis.crossVectors(worldMomentArmA, direction);
89
+ }
90
+
91
+ if (bodyB.type !== BodyType.static) {
92
+ mR2xAxis.crossVectors(worldMomentArmB, direction);
93
+ }
94
+
95
+ // the inverse effective mass is K = J * M^-1 * J_T
96
+ let inverseEffectiveMass = 0;
97
+
98
+ if (bodyA.type === BodyType.dynamic) {
99
+ mInvI1_R1PlusUxAxis.transformVectorFromMat3(mR1PlusUxAxis, inverseInertiaA);
100
+ inverseEffectiveMass += inverseMassA + mInvI1_R1PlusUxAxis.dot(mR1PlusUxAxis);
101
+ }
102
+
103
+ if (bodyB.type === BodyType.dynamic) {
104
+ mInvI2_R2xAxis.transformVectorFromMat3(mR2xAxis, inverseInertiaB);
105
+ inverseEffectiveMass += inverseMassB + mInvI2_R2xAxis.dot(mR2xAxis);
106
+ }
107
+
108
+ // Impulse P = M dv
109
+ let impulseMagnitude = deltaSpeed / inverseEffectiveMass;
110
+
111
+ // Calculate the world space impulse to apply
112
+ impulse.scaleVector(manifold.worldSpaceNormal, impulseMagnitude);
113
+
114
+ const deltaLambda = impulseMagnitude;
115
+
116
+ if (bodyA.type === BodyType.dynamic) {
117
+ result.deltaLinearVelocityA.addScaledToVector(bodyA.linearVelocity, direction, -deltaLambda * inverseMassA);
118
+ result.deltaAngularVelocityA.addScaledToVector(bodyA.angularVelocity, mInvI1_R1PlusUxAxis, -deltaLambda);
119
+ }
120
+
121
+ if (bodyB.type === BodyType.dynamic) {
122
+ result.deltaLinearVelocityA.addScaledToVector(bodyB.linearVelocity, direction, +deltaLambda * inverseMassB);
123
+ result.deltaAngularVelocityB.addScaledToVector(bodyB.angularVelocity, mInvI2_R2xAxis, +deltaLambda);
124
+ }
125
+ }
126
+ }
127
+
128
+ const worldContactPointA = /*@__PURE__*/ Vec3.create();
129
+ const worldContactPointB = /*@__PURE__*/ Vec3.create();
130
+ const combinedWorldContactPoint = /*@__PURE__*/ Vec3.create();
131
+ const worldMomentArmA = /*@__PURE__*/ Vec3.create();
132
+ const worldMomentArmB = /*@__PURE__*/ Vec3.create();
133
+ const contactVelocityA = /*@__PURE__*/ Vec3.create();
134
+ const contactVelocityB = /*@__PURE__*/ Vec3.create();
135
+ const contactVelocityAB = /*@__PURE__*/ Vec3.create();
136
+ const centerOfMassA = /*@__PURE__*/ Vec3.create();
137
+ const centerOfMassB = /*@__PURE__*/ Vec3.create();
138
+ const jacobianA = /*@__PURE__*/ Vec3.create();
139
+ const jacobianB = /*@__PURE__*/ Vec3.create();
140
+ const impulse = /*@__PURE__*/ Vec3.create();
141
+ const mR1PlusUxAxis = /*@__PURE__*/ Vec3.create();
142
+ const mR2xAxis = /*@__PURE__*/ Vec3.create();
143
+ const mInvI1_R1PlusUxAxis = /*@__PURE__*/ Vec3.create();
144
+ const mInvI2_R2xAxis = /*@__PURE__*/ Vec3.create();
145
+ const inverseInertiaA = /*@__PURE__*/ Mat3.create();
146
+ const inverseInertiaB = /*@__PURE__*/ Mat3.create();