planck-v2 2.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.

Potentially problematic release.


This version of planck-v2 might be problematic. Click here for more details.

Files changed (80) hide show
  1. package/LICENSE.txt +20 -0
  2. package/README.md +21 -0
  3. package/dist/planck-with-testbed.d.ts +4433 -0
  4. package/dist/planck-with-testbed.js +20730 -0
  5. package/dist/planck-with-testbed.js.map +1 -0
  6. package/dist/planck-with-testbed.umd.cjs +20730 -0
  7. package/dist/planck-with-testbed.umd.cjs.map +1 -0
  8. package/dist/planck.d.ts +4343 -0
  9. package/dist/planck.js +13516 -0
  10. package/dist/planck.js.map +1 -0
  11. package/dist/planck.umd.cjs +13516 -0
  12. package/dist/planck.umd.cjs.map +1 -0
  13. package/package.json +105 -0
  14. package/src/Settings.ts +238 -0
  15. package/src/__test__/Basic.test.ts +43 -0
  16. package/src/__test__/CCD.test.ts +70 -0
  17. package/src/__test__/Collision.test.ts +133 -0
  18. package/src/__test__/Math.test.ts +105 -0
  19. package/src/__test__/Pool.test.ts +48 -0
  20. package/src/__test__/World.test.ts +73 -0
  21. package/src/collision/AABB.ts +287 -0
  22. package/src/collision/BroadPhase.ts +210 -0
  23. package/src/collision/Distance.ts +962 -0
  24. package/src/collision/DynamicTree.ts +907 -0
  25. package/src/collision/Manifold.ts +420 -0
  26. package/src/collision/Raycast.ts +30 -0
  27. package/src/collision/Shape.ts +114 -0
  28. package/src/collision/TimeOfImpact.ts +502 -0
  29. package/src/collision/shape/BoxShape.ts +34 -0
  30. package/src/collision/shape/ChainShape.ts +360 -0
  31. package/src/collision/shape/CircleShape.ts +202 -0
  32. package/src/collision/shape/CollideCircle.ts +66 -0
  33. package/src/collision/shape/CollideCirclePolygon.ts +142 -0
  34. package/src/collision/shape/CollideEdgeCircle.ts +185 -0
  35. package/src/collision/shape/CollideEdgePolygon.ts +528 -0
  36. package/src/collision/shape/CollidePolygon.ts +280 -0
  37. package/src/collision/shape/EdgeShape.ts +316 -0
  38. package/src/collision/shape/PolygonShape.ts +581 -0
  39. package/src/common/Geo.ts +589 -0
  40. package/src/common/Jacobian.ts +17 -0
  41. package/src/common/Mat22.ts +221 -0
  42. package/src/common/Mat33.ts +224 -0
  43. package/src/common/Math.ts +96 -0
  44. package/src/common/Rot.ts +218 -0
  45. package/src/common/Sweep.ts +119 -0
  46. package/src/common/Transform.ts +203 -0
  47. package/src/common/Vec2.ts +624 -0
  48. package/src/common/Vec3.ts +188 -0
  49. package/src/dynamics/Body.ts +1198 -0
  50. package/src/dynamics/Contact.ts +1366 -0
  51. package/src/dynamics/Fixture.ts +506 -0
  52. package/src/dynamics/Joint.ts +226 -0
  53. package/src/dynamics/Position.ts +44 -0
  54. package/src/dynamics/Solver.ts +890 -0
  55. package/src/dynamics/Velocity.ts +18 -0
  56. package/src/dynamics/World.ts +1169 -0
  57. package/src/dynamics/joint/DistanceJoint.ts +463 -0
  58. package/src/dynamics/joint/FrictionJoint.ts +396 -0
  59. package/src/dynamics/joint/GearJoint.ts +591 -0
  60. package/src/dynamics/joint/MotorJoint.ts +430 -0
  61. package/src/dynamics/joint/MouseJoint.ts +390 -0
  62. package/src/dynamics/joint/PrismaticJoint.ts +903 -0
  63. package/src/dynamics/joint/PulleyJoint.ts +529 -0
  64. package/src/dynamics/joint/RevoluteJoint.ts +745 -0
  65. package/src/dynamics/joint/RopeJoint.ts +383 -0
  66. package/src/dynamics/joint/WeldJoint.ts +544 -0
  67. package/src/dynamics/joint/WheelJoint.ts +683 -0
  68. package/src/dynamics/joint/__test__/DistanceJoint.test.ts +66 -0
  69. package/src/index.ts +60 -0
  70. package/src/internal.ts +20 -0
  71. package/src/main.ts +3 -0
  72. package/src/serializer/__test__/Serialize.test.ts +52 -0
  73. package/src/serializer/__test__/Validator.test.ts +55 -0
  74. package/src/serializer/index.ts +257 -0
  75. package/src/serializer/schema.json +168 -0
  76. package/src/util/Pool.ts +120 -0
  77. package/src/util/Testbed.ts +157 -0
  78. package/src/util/Timer.ts +15 -0
  79. package/src/util/options.ts +28 -0
  80. package/src/util/stats.ts +26 -0
@@ -0,0 +1,1366 @@
1
+ /*
2
+ * Planck.js
3
+ *
4
+ * Copyright (c) Erin Catto, Ali Shakiba
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+ import * as geo from "../common/Geo";
11
+ import { ShapeType } from "../collision/Shape";
12
+ import { clamp } from "../common/Math";
13
+ import { TransformValue } from "../common/Transform";
14
+ import { Mat22Value } from "../common/Mat22";
15
+ import { SettingsInternal as Settings } from "../Settings";
16
+ import { Manifold, ManifoldType, WorldManifold } from "../collision/Manifold";
17
+ import { testOverlap } from "../collision/Distance";
18
+ import { Fixture } from "./Fixture";
19
+ import { Body } from "./Body";
20
+ import { ContactImpulse, TimeStep } from "./Solver";
21
+ import { Pool } from "../util/Pool";
22
+ import { getTransform } from "./Position";
23
+
24
+ /** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT;
25
+ /** @internal */ const math_abs = Math.abs;
26
+ /** @internal */ const math_sqrt = Math.sqrt;
27
+ /** @internal */ const math_max = Math.max;
28
+ /** @internal */ const math_min = Math.min;
29
+
30
+ // Solver debugging is normally disabled because the block solver sometimes has to deal with a poorly conditioned effective mass matrix.
31
+ /** @internal */ const DEBUG_SOLVER = false;
32
+
33
+ /** @internal */ const contactPool = new Pool<Contact>({
34
+ create() {
35
+ return new Contact();
36
+ },
37
+ release(contact: Contact) {
38
+ contact.recycle();
39
+ },
40
+ });
41
+
42
+ /** @internal */ const oldManifold = new Manifold();
43
+
44
+ /** @internal */ const worldManifold = new WorldManifold();
45
+
46
+ /**
47
+ * A contact edge is used to connect bodies and contacts together in a contact
48
+ * graph where each body is a node and each contact is an edge. A contact edge
49
+ * belongs to a doubly linked list maintained in each attached body. Each
50
+ * contact has two contact nodes, one for each attached body.
51
+ */
52
+ export class ContactEdge {
53
+ contact: Contact;
54
+ prev: ContactEdge | null = null;
55
+ next: ContactEdge | null = null;
56
+ other: Body | null = null;
57
+ constructor(contact: Contact) {
58
+ this.contact = contact;
59
+ }
60
+
61
+ /** @internal */
62
+ recycle() {
63
+ this.prev = null;
64
+ this.next = null;
65
+ this.other = null;
66
+ }
67
+ }
68
+
69
+ export type EvaluateFunction = (
70
+ manifold: Manifold,
71
+ xfA: TransformValue,
72
+ fixtureA: Fixture,
73
+ indexA: number,
74
+ xfB: TransformValue,
75
+ fixtureB: Fixture,
76
+ indexB: number,
77
+ ) => void;
78
+
79
+ /**
80
+ * Friction mixing law. The idea is to allow either fixture to drive the
81
+ * friction to zero. For example, anything slides on ice.
82
+ */
83
+ export function mixFriction(friction1: number, friction2: number): number {
84
+ return math_sqrt(friction1 * friction2);
85
+ }
86
+
87
+ /**
88
+ * Restitution mixing law. The idea is allow for anything to bounce off an
89
+ * inelastic surface. For example, a superball bounces on anything.
90
+ */
91
+ export function mixRestitution(restitution1: number, restitution2: number): number {
92
+ return restitution1 > restitution2 ? restitution1 : restitution2;
93
+ }
94
+
95
+ // TODO: move this to Settings?
96
+ /** @internal */ const s_registers = [];
97
+
98
+ // TODO: merge with ManifoldPoint?
99
+ export class VelocityConstraintPoint {
100
+ rA = geo.vec2(0, 0);
101
+ rB = geo.vec2(0, 0);
102
+ normalImpulse = 0;
103
+ tangentImpulse = 0;
104
+ normalMass = 0;
105
+ tangentMass = 0;
106
+ velocityBias = 0;
107
+
108
+ recycle() {
109
+ geo.zeroVec2(this.rA);
110
+ geo.zeroVec2(this.rB);
111
+ this.normalImpulse = 0;
112
+ this.tangentImpulse = 0;
113
+ this.normalMass = 0;
114
+ this.tangentMass = 0;
115
+ this.velocityBias = 0;
116
+ }
117
+ }
118
+
119
+ /** @internal */ const cA = geo.vec2(0, 0);
120
+ /** @internal */ const vA = geo.vec2(0, 0);
121
+ /** @internal */ const cB = geo.vec2(0, 0);
122
+ /** @internal */ const vB = geo.vec2(0, 0);
123
+ /** @internal */ const tangent = geo.vec2(0, 0);
124
+ /** @internal */ const xfA = geo.transform(0, 0, 0);
125
+ /** @internal */ const xfB = geo.transform(0, 0, 0);
126
+ /** @internal */ const pointA = geo.vec2(0, 0);
127
+ /** @internal */ const pointB = geo.vec2(0, 0);
128
+ /** @internal */ const clipPoint = geo.vec2(0, 0);
129
+ /** @internal */ const planePoint = geo.vec2(0, 0);
130
+ /** @internal */ const rA = geo.vec2(0, 0);
131
+ /** @internal */ const rB = geo.vec2(0, 0);
132
+ /** @internal */ const P = geo.vec2(0, 0);
133
+ /** @internal */ const normal = geo.vec2(0, 0);
134
+ /** @internal */ const point = geo.vec2(0, 0);
135
+ /** @internal */ const dv = geo.vec2(0, 0);
136
+ /** @internal */ const dv1 = geo.vec2(0, 0);
137
+ /** @internal */ const dv2 = geo.vec2(0, 0);
138
+ /** @internal */ const b = geo.vec2(0, 0);
139
+ /** @internal */ const a = geo.vec2(0, 0);
140
+ /** @internal */ const x = geo.vec2(0, 0);
141
+ /** @internal */ const d = geo.vec2(0, 0);
142
+ /** @internal */ const P1 = geo.vec2(0, 0);
143
+ /** @internal */ const P2 = geo.vec2(0, 0);
144
+
145
+ /**
146
+ * The class manages contact between two shapes. A contact exists for each
147
+ * overlapping AABB in the broad-phase (except if filtered). Therefore a contact
148
+ * object may exist that has no contact points.
149
+ */
150
+ export class Contact {
151
+ // Nodes for connecting bodies.
152
+ /** @internal */ m_nodeA = new ContactEdge(this);
153
+ /** @internal */ m_nodeB = new ContactEdge(this);
154
+ /** @internal */ m_fixtureA: Fixture | null = null;
155
+ /** @internal */ m_fixtureB: Fixture | null = null;
156
+ /** @internal */ m_indexA = -1;
157
+ /** @internal */ m_indexB = -1;
158
+ /** @internal */ m_evaluateFcn: EvaluateFunction | null = null;
159
+ /** @internal */ m_manifold: Manifold = new Manifold();
160
+ /** @internal */ m_prev: Contact | null = null;
161
+ /** @internal */ m_next: Contact | null = null;
162
+ /** @internal */ m_toi = 1.0;
163
+ /** @internal */ m_toiCount = 0;
164
+ // This contact has a valid TOI in m_toi
165
+ /** @internal */ m_toiFlag = false;
166
+ /** @internal */ m_friction = 0.0;
167
+ /** @internal */ m_restitution = 0.0;
168
+ /** @internal */ m_tangentSpeed = 0.0;
169
+ /** @internal This contact can be disabled (by user) */
170
+ m_enabledFlag = true;
171
+ /** @internal Used when crawling contact graph when forming islands. */
172
+ m_islandFlag = false;
173
+ /** @internal Set when the shapes are touching. */
174
+ m_touchingFlag = false;
175
+ /** @internal This contact needs filtering because a fixture filter was changed. */
176
+ m_filterFlag = false;
177
+ /** @internal This bullet contact had a TOI event */
178
+ m_bulletHitFlag = false;
179
+
180
+ /** @internal Contact reporting impulse object cache */
181
+ m_impulse: ContactImpulse = new ContactImpulse(this);
182
+
183
+ // VelocityConstraint
184
+ /** @internal */ v_points = [new VelocityConstraintPoint(), new VelocityConstraintPoint()]; // [maxManifoldPoints];
185
+ /** @internal */ v_normal = geo.vec2(0, 0);
186
+ /** @internal */ v_normalMass: Mat22Value = geo.mat22();
187
+ /** @internal */ v_K: Mat22Value = geo.mat22();
188
+ /** @internal */ v_pointCount = 0;
189
+ /** @internal */ v_tangentSpeed = 0;
190
+ /** @internal */ v_friction = 0;
191
+ /** @internal */ v_restitution = 0;
192
+ /** @internal */ v_invMassA = 0;
193
+ /** @internal */ v_invMassB = 0;
194
+ /** @internal */ v_invIA = 0;
195
+ /** @internal */ v_invIB = 0;
196
+
197
+ // PositionConstraint
198
+ /** @internal */ p_localPoints = [geo.vec2(0, 0), geo.vec2(0, 0)]; // [maxManifoldPoints];
199
+ /** @internal */ p_localNormal = geo.vec2(0, 0);
200
+ /** @internal */ p_localPoint = geo.vec2(0, 0);
201
+ /** @internal */ p_localCenterA = geo.vec2(0, 0);
202
+ /** @internal */ p_localCenterB = geo.vec2(0, 0);
203
+ /** @internal */ p_type = ManifoldType.e_unset;
204
+ /** @internal */ p_radiusA = 0;
205
+ /** @internal */ p_radiusB = 0;
206
+ /** @internal */ p_pointCount = 0;
207
+ /** @internal */ p_invMassA = 0;
208
+ /** @internal */ p_invMassB = 0;
209
+ /** @internal */ p_invIA = 0;
210
+ /** @internal */ p_invIB = 0;
211
+
212
+ /** @internal */
213
+ initialize(fA: Fixture, indexA: number, fB: Fixture, indexB: number, evaluateFcn: EvaluateFunction) {
214
+ this.m_fixtureA = fA;
215
+ this.m_fixtureB = fB;
216
+
217
+ this.m_indexA = indexA;
218
+ this.m_indexB = indexB;
219
+
220
+ this.m_evaluateFcn = evaluateFcn;
221
+
222
+ this.m_friction = mixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction);
223
+ this.m_restitution = mixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution);
224
+ }
225
+
226
+ /** @internal */
227
+ recycle() {
228
+ this.m_nodeA.recycle();
229
+ this.m_nodeB.recycle();
230
+ this.m_fixtureA = null;
231
+ this.m_fixtureB = null;
232
+ this.m_indexA = -1;
233
+ this.m_indexB = -1;
234
+ this.m_evaluateFcn = null;
235
+ this.m_manifold.recycle();
236
+ this.m_prev = null;
237
+ this.m_next = null;
238
+ this.m_toi = 1;
239
+ this.m_toiCount = 0;
240
+ this.m_toiFlag = false;
241
+ this.m_friction = 0;
242
+ this.m_restitution = 0;
243
+ this.m_tangentSpeed = 0;
244
+ this.m_enabledFlag = true;
245
+ this.m_islandFlag = false;
246
+ this.m_touchingFlag = false;
247
+ this.m_filterFlag = false;
248
+ this.m_bulletHitFlag = false;
249
+
250
+ this.m_impulse.recycle();
251
+
252
+ // VelocityConstraint
253
+ for (const point of this.v_points) {
254
+ point.recycle();
255
+ }
256
+ geo.zeroVec2(this.v_normal);
257
+ geo.zeroMat22(this.v_normalMass);
258
+ geo.zeroMat22(this.v_K);
259
+ this.v_pointCount = 0;
260
+ this.v_tangentSpeed = 0;
261
+ this.v_friction = 0;
262
+ this.v_restitution = 0;
263
+ this.v_invMassA = 0;
264
+ this.v_invMassB = 0;
265
+ this.v_invIA = 0;
266
+ this.v_invIB = 0;
267
+
268
+ // PositionConstraint
269
+ for (const point of this.p_localPoints) {
270
+ geo.zeroVec2(point);
271
+ }
272
+ geo.zeroVec2(this.p_localNormal);
273
+ geo.zeroVec2(this.p_localPoint);
274
+ geo.zeroVec2(this.p_localCenterA);
275
+ geo.zeroVec2(this.p_localCenterB);
276
+ this.p_type = ManifoldType.e_unset;
277
+ this.p_radiusA = 0;
278
+ this.p_radiusB = 0;
279
+ this.p_pointCount = 0;
280
+ this.p_invMassA = 0;
281
+ this.p_invMassB = 0;
282
+ this.p_invIA = 0;
283
+ this.p_invIB = 0;
284
+ }
285
+
286
+ initConstraint(step: TimeStep): void {
287
+ const fixtureA = this.m_fixtureA;
288
+ const fixtureB = this.m_fixtureB;
289
+ if (fixtureA === null || fixtureB === null) return;
290
+ const bodyA = fixtureA.m_body;
291
+ const bodyB = fixtureB.m_body;
292
+ if (bodyA === null || bodyB === null) return;
293
+ const shapeA = fixtureA.m_shape;
294
+ const shapeB = fixtureB.m_shape;
295
+ if (shapeA === null || shapeB === null) return;
296
+
297
+ const manifold = this.m_manifold;
298
+
299
+ const pointCount = manifold.pointCount;
300
+ if (_ASSERT) console.assert(pointCount > 0);
301
+
302
+ this.v_invMassA = bodyA.m_invMass;
303
+ this.v_invMassB = bodyB.m_invMass;
304
+ this.v_invIA = bodyA.m_invI;
305
+ this.v_invIB = bodyB.m_invI;
306
+
307
+ this.v_friction = this.m_friction;
308
+ this.v_restitution = this.m_restitution;
309
+ this.v_tangentSpeed = this.m_tangentSpeed;
310
+
311
+ this.v_pointCount = pointCount;
312
+
313
+ geo.zeroMat22(this.v_K);
314
+ geo.zeroMat22(this.v_normalMass);
315
+
316
+ this.p_invMassA = bodyA.m_invMass;
317
+ this.p_invMassB = bodyB.m_invMass;
318
+ this.p_invIA = bodyA.m_invI;
319
+ this.p_invIB = bodyB.m_invI;
320
+ geo.copyVec2(this.p_localCenterA, bodyA.m_sweep.localCenter);
321
+ geo.copyVec2(this.p_localCenterB, bodyB.m_sweep.localCenter);
322
+
323
+ this.p_radiusA = shapeA.m_radius;
324
+ this.p_radiusB = shapeB.m_radius;
325
+
326
+ this.p_type = manifold.type;
327
+ geo.copyVec2(this.p_localNormal, manifold.localNormal);
328
+ geo.copyVec2(this.p_localPoint, manifold.localPoint);
329
+ this.p_pointCount = pointCount;
330
+
331
+ for (let j = 0; j < Settings.maxManifoldPoints; ++j) {
332
+ this.v_points[j].recycle();
333
+ geo.zeroVec2(this.p_localPoints[j]);
334
+ }
335
+
336
+ for (let j = 0; j < pointCount; ++j) {
337
+ const cp = manifold.points[j];
338
+ const vcp = this.v_points[j];
339
+ if (step.warmStarting) {
340
+ vcp.normalImpulse = step.dtRatio * cp.normalImpulse;
341
+ vcp.tangentImpulse = step.dtRatio * cp.tangentImpulse;
342
+ }
343
+ geo.copyVec2(this.p_localPoints[j], cp.localPoint);
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Get the contact manifold. Do not modify the manifold unless you understand
349
+ * the internals of the library.
350
+ */
351
+ getManifold(): Manifold {
352
+ return this.m_manifold;
353
+ }
354
+
355
+ /**
356
+ * Get the world manifold.
357
+ */
358
+ getWorldManifold(worldManifold: WorldManifold | null): WorldManifold | undefined {
359
+ const fixtureA = this.m_fixtureA;
360
+ const fixtureB = this.m_fixtureB;
361
+ if (fixtureA === null || fixtureB === null) return;
362
+ const bodyA = fixtureA.m_body;
363
+ const bodyB = fixtureB.m_body;
364
+ if (bodyA === null || bodyB === null) return;
365
+ const shapeA = fixtureA.m_shape;
366
+ const shapeB = fixtureB.m_shape;
367
+ if (shapeA === null || shapeB === null) return;
368
+
369
+ return this.m_manifold.getWorldManifold(
370
+ worldManifold,
371
+ bodyA.getTransform(),
372
+ shapeA.m_radius,
373
+ bodyB.getTransform(),
374
+ shapeB.m_radius,
375
+ );
376
+ }
377
+
378
+ /**
379
+ * Enable/disable this contact. This can be used inside the pre-solve contact
380
+ * listener. The contact is only disabled for the current time step (or sub-step
381
+ * in continuous collisions).
382
+ */
383
+ setEnabled(flag: boolean): void {
384
+ this.m_enabledFlag = !!flag;
385
+ }
386
+
387
+ /**
388
+ * Has this contact been disabled?
389
+ */
390
+ isEnabled(): boolean {
391
+ return this.m_enabledFlag;
392
+ }
393
+
394
+ /**
395
+ * Is this contact touching?
396
+ */
397
+ isTouching(): boolean {
398
+ return this.m_touchingFlag;
399
+ }
400
+
401
+ /**
402
+ * Get the next contact in the world's contact list.
403
+ */
404
+ getNext(): Contact | null {
405
+ return this.m_next;
406
+ }
407
+
408
+ /**
409
+ * Get fixture A in this contact.
410
+ */
411
+ getFixtureA(): Fixture {
412
+ return this.m_fixtureA;
413
+ }
414
+
415
+ /**
416
+ * Get fixture B in this contact.
417
+ */
418
+ getFixtureB(): Fixture {
419
+ return this.m_fixtureB;
420
+ }
421
+
422
+ /**
423
+ * Get the child primitive index for fixture A.
424
+ */
425
+ getChildIndexA(): number {
426
+ return this.m_indexA;
427
+ }
428
+
429
+ /**
430
+ * Get the child primitive index for fixture B.
431
+ */
432
+ getChildIndexB(): number {
433
+ return this.m_indexB;
434
+ }
435
+
436
+ /**
437
+ * Flag this contact for filtering. Filtering will occur the next time step.
438
+ */
439
+ flagForFiltering(): void {
440
+ this.m_filterFlag = true;
441
+ }
442
+
443
+ /**
444
+ * Override the default friction mixture. You can call this in
445
+ * "pre-solve" callback. This value persists until set or reset.
446
+ */
447
+ setFriction(friction: number): void {
448
+ this.m_friction = friction;
449
+ }
450
+
451
+ /**
452
+ * Get the friction.
453
+ */
454
+ getFriction(): number {
455
+ return this.m_friction;
456
+ }
457
+
458
+ /**
459
+ * Reset the friction mixture to the default value.
460
+ */
461
+ resetFriction(): void {
462
+ const fixtureA = this.m_fixtureA;
463
+ const fixtureB = this.m_fixtureB;
464
+ if (fixtureA === null || fixtureB === null) return;
465
+ this.m_friction = mixFriction(fixtureA.m_friction, fixtureB.m_friction);
466
+ }
467
+
468
+ /**
469
+ * Override the default restitution mixture. You can call this in
470
+ * "pre-solve" callback. The value persists until you set or reset.
471
+ */
472
+ setRestitution(restitution: number): void {
473
+ this.m_restitution = restitution;
474
+ }
475
+
476
+ /**
477
+ * Get the restitution.
478
+ */
479
+ getRestitution(): number {
480
+ return this.m_restitution;
481
+ }
482
+
483
+ /**
484
+ * Reset the restitution to the default value.
485
+ */
486
+ resetRestitution(): void {
487
+ const fixtureA = this.m_fixtureA;
488
+ const fixtureB = this.m_fixtureB;
489
+ if (fixtureA === null || fixtureB === null) return;
490
+ this.m_restitution = mixRestitution(fixtureA.m_restitution, fixtureB.m_restitution);
491
+ }
492
+
493
+ /**
494
+ * Set the desired tangent speed for a conveyor belt behavior. In meters per
495
+ * second.
496
+ */
497
+ setTangentSpeed(speed: number): void {
498
+ this.m_tangentSpeed = speed;
499
+ }
500
+
501
+ /**
502
+ * Get the desired tangent speed. In meters per second.
503
+ */
504
+ getTangentSpeed(): number {
505
+ return this.m_tangentSpeed;
506
+ }
507
+
508
+ /**
509
+ * Called by Update method, and implemented by subclasses.
510
+ */
511
+ evaluate(manifold: Manifold, xfA: TransformValue, xfB: TransformValue): void {
512
+ const fixtureA = this.m_fixtureA;
513
+ const fixtureB = this.m_fixtureB;
514
+ if (fixtureA === null || fixtureB === null) return;
515
+ this.m_evaluateFcn(manifold, xfA, fixtureA, this.m_indexA, xfB, fixtureB, this.m_indexB);
516
+ }
517
+
518
+ /**
519
+ * Updates the contact manifold and touching status.
520
+ *
521
+ * Note: do not assume the fixture AABBs are overlapping or are valid.
522
+ *
523
+ * @param listener.beginContact
524
+ * @param listener.endContact
525
+ * @param listener.preSolve
526
+ */
527
+ update(listener?: {
528
+ beginContact(contact: Contact): void;
529
+ endContact(contact: Contact): void;
530
+ preSolve(contact: Contact, oldManifold: Manifold): void;
531
+ }): void {
532
+ const fixtureA = this.m_fixtureA;
533
+ const fixtureB = this.m_fixtureB;
534
+ if (fixtureA === null || fixtureB === null) return;
535
+ const bodyA = fixtureA.m_body;
536
+ const bodyB = fixtureB.m_body;
537
+ if (bodyA === null || bodyB === null) return;
538
+ const shapeA = fixtureA.m_shape;
539
+ const shapeB = fixtureB.m_shape;
540
+ if (shapeA === null || shapeB === null) return;
541
+
542
+ // Re-enable this contact.
543
+ this.m_enabledFlag = true;
544
+
545
+ let touching = false;
546
+ const wasTouching = this.m_touchingFlag;
547
+
548
+ const sensorA = fixtureA.m_isSensor;
549
+ const sensorB = fixtureB.m_isSensor;
550
+ const sensor = sensorA || sensorB;
551
+
552
+ const xfA = bodyA.m_xf;
553
+ const xfB = bodyB.m_xf;
554
+
555
+ // Is this contact a sensor?
556
+ if (sensor) {
557
+ touching = testOverlap(shapeA, this.m_indexA, shapeB, this.m_indexB, xfA, xfB);
558
+
559
+ // Sensors don't generate manifolds.
560
+ this.m_manifold.pointCount = 0;
561
+ } else {
562
+ oldManifold.recycle();
563
+ oldManifold.set(this.m_manifold);
564
+ this.m_manifold.recycle();
565
+
566
+ this.evaluate(this.m_manifold, xfA, xfB);
567
+ touching = this.m_manifold.pointCount > 0;
568
+
569
+ // Match old contact ids to new contact ids and copy the
570
+ // stored impulses to warm start the solver.
571
+ for (let i = 0; i < this.m_manifold.pointCount; ++i) {
572
+ const nmp = this.m_manifold.points[i];
573
+ nmp.normalImpulse = 0.0;
574
+ nmp.tangentImpulse = 0.0;
575
+
576
+ for (let j = 0; j < oldManifold.pointCount; ++j) {
577
+ const omp = oldManifold.points[j];
578
+ if (omp.id.key === nmp.id.key) {
579
+ nmp.normalImpulse = omp.normalImpulse;
580
+ nmp.tangentImpulse = omp.tangentImpulse;
581
+ break;
582
+ }
583
+ }
584
+ }
585
+
586
+ if (touching !== wasTouching) {
587
+ bodyA.setAwake(true);
588
+ bodyB.setAwake(true);
589
+ }
590
+ }
591
+
592
+ this.m_touchingFlag = touching;
593
+
594
+ const hasListener = typeof listener === "object" && listener !== null;
595
+
596
+ if (!wasTouching && touching && hasListener) {
597
+ listener.beginContact(this);
598
+ }
599
+
600
+ if (wasTouching && !touching && hasListener) {
601
+ listener.endContact(this);
602
+ }
603
+
604
+ if (!sensor && touching && hasListener && oldManifold) {
605
+ listener.preSolve(this, oldManifold);
606
+ }
607
+ }
608
+
609
+ solvePositionConstraint(step: TimeStep): number {
610
+ return this._solvePositionConstraint(step, null, null);
611
+ }
612
+
613
+ solvePositionConstraintTOI(step: TimeStep, toiA: Body, toiB: Body): number {
614
+ return this._solvePositionConstraint(step, toiA, toiB);
615
+ }
616
+
617
+ private _solvePositionConstraint(step: TimeStep, toiA: Body | null, toiB: Body | null): number {
618
+ const toi = toiA !== null && toiB !== null ? true : false;
619
+ let minSeparation = 0.0;
620
+
621
+ const fixtureA = this.m_fixtureA;
622
+ const fixtureB = this.m_fixtureB;
623
+ if (fixtureA === null || fixtureB === null) return minSeparation;
624
+ const bodyA = fixtureA.m_body;
625
+ const bodyB = fixtureB.m_body;
626
+ if (bodyA === null || bodyB === null) return minSeparation;
627
+
628
+ // const velocityA = bodyA.c_velocity;
629
+ // const velocityB = bodyB.c_velocity;
630
+ const positionA = bodyA.c_position;
631
+ const positionB = bodyB.c_position;
632
+
633
+ const localCenterA = this.p_localCenterA;
634
+ const localCenterB = this.p_localCenterB;
635
+
636
+ let mA = 0.0;
637
+ let iA = 0.0;
638
+ if (!toi || bodyA === toiA || bodyA === toiB) {
639
+ mA = this.p_invMassA;
640
+ iA = this.p_invIA;
641
+ }
642
+
643
+ let mB = 0.0;
644
+ let iB = 0.0;
645
+ if (!toi || bodyB === toiA || bodyB === toiB) {
646
+ mB = this.p_invMassB;
647
+ iB = this.p_invIB;
648
+ }
649
+
650
+ geo.copyVec2(cA, positionA.c);
651
+ let aA = positionA.a;
652
+
653
+ geo.copyVec2(cB, positionB.c);
654
+ let aB = positionB.a;
655
+
656
+ // Solve normal constraints
657
+ for (let j = 0; j < this.p_pointCount; ++j) {
658
+ getTransform(xfA, localCenterA, cA, aA);
659
+ getTransform(xfB, localCenterB, cB, aB);
660
+
661
+ // PositionSolverManifold
662
+ let separation: number;
663
+ switch (this.p_type) {
664
+ case ManifoldType.e_circles: {
665
+ geo.transformVec2(pointA, xfA, this.p_localPoint);
666
+ geo.transformVec2(pointB, xfB, this.p_localPoints[0]);
667
+ geo.subVec2(normal, pointB, pointA);
668
+ geo.normalizeVec2(normal);
669
+
670
+ geo.combine2Vec2(point, 0.5, pointA, 0.5, pointB);
671
+ separation = geo.dotVec2(pointB, normal) - geo.dotVec2(pointA, normal) - this.p_radiusA - this.p_radiusB;
672
+ break;
673
+ }
674
+
675
+ case ManifoldType.e_faceA: {
676
+ geo.rotVec2(normal, xfA.q, this.p_localNormal);
677
+ geo.transformVec2(planePoint, xfA, this.p_localPoint);
678
+ geo.transformVec2(clipPoint, xfB, this.p_localPoints[j]);
679
+ separation =
680
+ geo.dotVec2(clipPoint, normal) - geo.dotVec2(planePoint, normal) - this.p_radiusA - this.p_radiusB;
681
+ geo.copyVec2(point, clipPoint);
682
+ break;
683
+ }
684
+
685
+ case ManifoldType.e_faceB: {
686
+ geo.rotVec2(normal, xfB.q, this.p_localNormal);
687
+ geo.transformVec2(planePoint, xfB, this.p_localPoint);
688
+ geo.transformVec2(clipPoint, xfA, this.p_localPoints[j]);
689
+ separation =
690
+ geo.dotVec2(clipPoint, normal) - geo.dotVec2(planePoint, normal) - this.p_radiusA - this.p_radiusB;
691
+ geo.copyVec2(point, clipPoint);
692
+
693
+ // Ensure normal points from A to B
694
+ geo.negVec2(normal);
695
+ break;
696
+ }
697
+ // todo: what should we do here?
698
+ default: {
699
+ return minSeparation;
700
+ }
701
+ }
702
+
703
+ geo.subVec2(rA, point, cA);
704
+ geo.subVec2(rB, point, cB);
705
+
706
+ // Track max constraint error.
707
+ minSeparation = math_min(minSeparation, separation);
708
+
709
+ const baumgarte = toi ? Settings.toiBaugarte : Settings.baumgarte;
710
+ const linearSlop = Settings.linearSlop;
711
+ const maxLinearCorrection = Settings.maxLinearCorrection;
712
+
713
+ // Prevent large corrections and allow slop.
714
+ const C = clamp(baumgarte * (separation + linearSlop), -maxLinearCorrection, 0.0);
715
+
716
+ // Compute the effective mass.
717
+ const rnA = geo.crossVec2Vec2(rA, normal);
718
+ const rnB = geo.crossVec2Vec2(rB, normal);
719
+ const K = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
720
+
721
+ // Compute normal impulse
722
+ const impulse = K > 0.0 ? -C / K : 0.0;
723
+
724
+ geo.scaleVec2(P, impulse, normal);
725
+
726
+ geo.minusScaleVec2(cA, mA, P);
727
+ aA -= iA * geo.crossVec2Vec2(rA, P);
728
+
729
+ geo.plusScaleVec2(cB, mB, P);
730
+ aB += iB * geo.crossVec2Vec2(rB, P);
731
+ }
732
+
733
+ geo.copyVec2(positionA.c, cA);
734
+ positionA.a = aA;
735
+
736
+ geo.copyVec2(positionB.c, cB);
737
+ positionB.a = aB;
738
+
739
+ return minSeparation;
740
+ }
741
+
742
+ initVelocityConstraint(step: TimeStep): void {
743
+ const fixtureA = this.m_fixtureA;
744
+ const fixtureB = this.m_fixtureB;
745
+ if (fixtureA === null || fixtureB === null) return;
746
+ const bodyA = fixtureA.m_body;
747
+ const bodyB = fixtureB.m_body;
748
+ if (bodyA === null || bodyB === null) return;
749
+
750
+ const velocityA = bodyA.c_velocity;
751
+ const velocityB = bodyB.c_velocity;
752
+
753
+ const positionA = bodyA.c_position;
754
+ const positionB = bodyB.c_position;
755
+
756
+ const radiusA = this.p_radiusA;
757
+ const radiusB = this.p_radiusB;
758
+ const manifold = this.m_manifold;
759
+
760
+ const mA = this.v_invMassA;
761
+ const mB = this.v_invMassB;
762
+ const iA = this.v_invIA;
763
+ const iB = this.v_invIB;
764
+ const localCenterA = this.p_localCenterA;
765
+ const localCenterB = this.p_localCenterB;
766
+
767
+ geo.copyVec2(cA, positionA.c);
768
+ const aA = positionA.a;
769
+ geo.copyVec2(vA, velocityA.v);
770
+ const wA = velocityA.w;
771
+
772
+ geo.copyVec2(cB, positionB.c);
773
+ const aB = positionB.a;
774
+ geo.copyVec2(vB, velocityB.v);
775
+ const wB = velocityB.w;
776
+
777
+ if (_ASSERT) console.assert(manifold.pointCount > 0);
778
+
779
+ getTransform(xfA, localCenterA, cA, aA);
780
+ getTransform(xfB, localCenterB, cB, aB);
781
+
782
+ worldManifold.recycle();
783
+ manifold.getWorldManifold(worldManifold, xfA, radiusA, xfB, radiusB);
784
+
785
+ geo.copyVec2(this.v_normal, worldManifold.normal);
786
+
787
+ for (let j = 0; j < this.v_pointCount; ++j) {
788
+ const vcp = this.v_points[j]; // VelocityConstraintPoint
789
+ const wmp = worldManifold.points[j];
790
+
791
+ geo.subVec2(vcp.rA, wmp, cA);
792
+ geo.subVec2(vcp.rB, wmp, cB);
793
+
794
+ const rnA = geo.crossVec2Vec2(vcp.rA, this.v_normal);
795
+ const rnB = geo.crossVec2Vec2(vcp.rB, this.v_normal);
796
+
797
+ const kNormal = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
798
+
799
+ vcp.normalMass = kNormal > 0.0 ? 1.0 / kNormal : 0.0;
800
+
801
+ geo.crossVec2Num(tangent, this.v_normal, 1.0);
802
+
803
+ const rtA = geo.crossVec2Vec2(vcp.rA, tangent);
804
+ const rtB = geo.crossVec2Vec2(vcp.rB, tangent);
805
+
806
+ const kTangent = mA + mB + iA * rtA * rtA + iB * rtB * rtB;
807
+
808
+ vcp.tangentMass = kTangent > 0.0 ? 1.0 / kTangent : 0.0;
809
+
810
+ // Setup a velocity bias for restitution.
811
+ vcp.velocityBias = 0.0;
812
+ geo.dvp(dv, vB, wB, vcp.rB, vA, wA, vcp.rA);
813
+ const vRel = geo.dotVec2(this.v_normal, dv);
814
+ if (vRel < -Settings.velocityThreshold) {
815
+ vcp.velocityBias = -this.v_restitution * vRel;
816
+ }
817
+ }
818
+
819
+ // If we have two points, then prepare the block solver.
820
+ if (this.v_pointCount == 2 && step.blockSolve) {
821
+ const vcp1 = this.v_points[0]; // VelocityConstraintPoint
822
+ const vcp2 = this.v_points[1]; // VelocityConstraintPoint
823
+
824
+ const rn1A = geo.crossVec2Vec2(vcp1.rA, this.v_normal);
825
+ const rn1B = geo.crossVec2Vec2(vcp1.rB, this.v_normal);
826
+ const rn2A = geo.crossVec2Vec2(vcp2.rA, this.v_normal);
827
+ const rn2B = geo.crossVec2Vec2(vcp2.rB, this.v_normal);
828
+
829
+ const k11 = mA + mB + iA * rn1A * rn1A + iB * rn1B * rn1B;
830
+ const k22 = mA + mB + iA * rn2A * rn2A + iB * rn2B * rn2B;
831
+ const k12 = mA + mB + iA * rn1A * rn2A + iB * rn1B * rn2B;
832
+
833
+ // Ensure a reasonable condition number.
834
+ const k_maxConditionNumber = 1000.0;
835
+ if (k11 * k11 < k_maxConditionNumber * (k11 * k22 - k12 * k12)) {
836
+ // K is safe to invert.
837
+ geo.setVec2(this.v_K.ex, k11, k12);
838
+ geo.setVec2(this.v_K.ey, k12, k22);
839
+ // this.v_normalMass.set(this.v_K.getInverse());
840
+ const a = this.v_K.ex.x;
841
+ const b = this.v_K.ey.x;
842
+ const c = this.v_K.ex.y;
843
+ const d = this.v_K.ey.y;
844
+ let det = a * d - b * c;
845
+ if (det !== 0.0) {
846
+ det = 1.0 / det;
847
+ }
848
+ this.v_normalMass.ex.x = det * d;
849
+ this.v_normalMass.ey.x = -det * b;
850
+ this.v_normalMass.ex.y = -det * c;
851
+ this.v_normalMass.ey.y = det * a;
852
+ } else {
853
+ // The constraints are redundant, just use one.
854
+ // TODO_ERIN use deepest?
855
+ this.v_pointCount = 1;
856
+ }
857
+ }
858
+
859
+ geo.copyVec2(positionA.c, cA);
860
+ positionA.a = aA;
861
+ geo.copyVec2(velocityA.v, vA);
862
+ velocityA.w = wA;
863
+
864
+ geo.copyVec2(positionB.c, cB);
865
+ positionB.a = aB;
866
+ geo.copyVec2(velocityB.v, vB);
867
+ velocityB.w = wB;
868
+ }
869
+
870
+ warmStartConstraint(step: TimeStep): void {
871
+ const fixtureA = this.m_fixtureA;
872
+ const fixtureB = this.m_fixtureB;
873
+ if (fixtureA === null || fixtureB === null) return;
874
+ const bodyA = fixtureA.m_body;
875
+ const bodyB = fixtureB.m_body;
876
+ if (bodyA === null || bodyB === null) return;
877
+
878
+ const velocityA = bodyA.c_velocity;
879
+ const velocityB = bodyB.c_velocity;
880
+ // const positionA = bodyA.c_position;
881
+ // const positionB = bodyB.c_position;
882
+
883
+ const mA = this.v_invMassA;
884
+ const iA = this.v_invIA;
885
+ const mB = this.v_invMassB;
886
+ const iB = this.v_invIB;
887
+
888
+ geo.copyVec2(vA, velocityA.v);
889
+ let wA = velocityA.w;
890
+ geo.copyVec2(vB, velocityB.v);
891
+ let wB = velocityB.w;
892
+
893
+ geo.copyVec2(normal, this.v_normal);
894
+ geo.crossVec2Num(tangent, normal, 1.0);
895
+
896
+ for (let j = 0; j < this.v_pointCount; ++j) {
897
+ const vcp = this.v_points[j]; // VelocityConstraintPoint
898
+
899
+ geo.combine2Vec2(P, vcp.normalImpulse, normal, vcp.tangentImpulse, tangent);
900
+
901
+ wA -= iA * geo.crossVec2Vec2(vcp.rA, P);
902
+ geo.minusScaleVec2(vA, mA, P);
903
+ wB += iB * geo.crossVec2Vec2(vcp.rB, P);
904
+ geo.plusScaleVec2(vB, mB, P);
905
+ }
906
+
907
+ geo.copyVec2(velocityA.v, vA);
908
+ velocityA.w = wA;
909
+ geo.copyVec2(velocityB.v, vB);
910
+ velocityB.w = wB;
911
+ }
912
+
913
+ storeConstraintImpulses(step: TimeStep): void {
914
+ const manifold = this.m_manifold;
915
+ for (let j = 0; j < this.v_pointCount; ++j) {
916
+ manifold.points[j].normalImpulse = this.v_points[j].normalImpulse;
917
+ manifold.points[j].tangentImpulse = this.v_points[j].tangentImpulse;
918
+ }
919
+ }
920
+
921
+ solveVelocityConstraint(step: TimeStep): void {
922
+ const fixtureA = this.m_fixtureA;
923
+ const fixtureB = this.m_fixtureB;
924
+ if (fixtureA === null || fixtureB === null) return;
925
+ const bodyA = fixtureA.m_body;
926
+ const bodyB = fixtureB.m_body;
927
+ if (bodyA === null || bodyB === null) return;
928
+
929
+ const velocityA = bodyA.c_velocity;
930
+ // const positionA = bodyA.c_position;
931
+
932
+ const velocityB = bodyB.c_velocity;
933
+ // const positionB = bodyB.c_position;
934
+
935
+ const mA = this.v_invMassA;
936
+ const iA = this.v_invIA;
937
+ const mB = this.v_invMassB;
938
+ const iB = this.v_invIB;
939
+
940
+ geo.copyVec2(vA, velocityA.v);
941
+ let wA = velocityA.w;
942
+ geo.copyVec2(vB, velocityB.v);
943
+ let wB = velocityB.w;
944
+
945
+ geo.copyVec2(normal, this.v_normal);
946
+ geo.crossVec2Num(tangent, normal, 1.0);
947
+ const friction = this.v_friction;
948
+
949
+ if (_ASSERT) console.assert(this.v_pointCount == 1 || this.v_pointCount == 2);
950
+
951
+ // Solve tangent constraints first because non-penetration is more important
952
+ // than friction.
953
+ for (let j = 0; j < this.v_pointCount; ++j) {
954
+ const vcp = this.v_points[j]; // VelocityConstraintPoint
955
+
956
+ // Relative velocity at contact
957
+ geo.dvp(dv, vB, wB, vcp.rB, vA, wA, vcp.rA);
958
+
959
+ // Compute tangent force
960
+ const vt = geo.dotVec2(dv, tangent) - this.v_tangentSpeed;
961
+ let lambda = vcp.tangentMass * -vt;
962
+
963
+ // Clamp the accumulated force
964
+ const maxFriction = friction * vcp.normalImpulse;
965
+ const newImpulse = clamp(vcp.tangentImpulse + lambda, -maxFriction, maxFriction);
966
+ lambda = newImpulse - vcp.tangentImpulse;
967
+ vcp.tangentImpulse = newImpulse;
968
+
969
+ // Apply contact impulse
970
+ geo.scaleVec2(P, lambda, tangent);
971
+
972
+ geo.minusScaleVec2(vA, mA, P);
973
+ wA -= iA * geo.crossVec2Vec2(vcp.rA, P);
974
+
975
+ geo.plusScaleVec2(vB, mB, P);
976
+ wB += iB * geo.crossVec2Vec2(vcp.rB, P);
977
+ }
978
+
979
+ // Solve normal constraints
980
+ if (this.v_pointCount == 1 || step.blockSolve == false) {
981
+ for (let i = 0; i < this.v_pointCount; ++i) {
982
+ const vcp = this.v_points[i]; // VelocityConstraintPoint
983
+
984
+ // Relative velocity at contact
985
+ geo.dvp(dv, vB, wB, vcp.rB, vA, wA, vcp.rA);
986
+
987
+ // Compute normal impulse
988
+ const vn = geo.dotVec2(dv, normal);
989
+ let lambda = -vcp.normalMass * (vn - vcp.velocityBias);
990
+
991
+ // Clamp the accumulated impulse
992
+ const newImpulse = math_max(vcp.normalImpulse + lambda, 0.0);
993
+ lambda = newImpulse - vcp.normalImpulse;
994
+ vcp.normalImpulse = newImpulse;
995
+
996
+ // Apply contact impulse
997
+ geo.scaleVec2(P, lambda, normal);
998
+
999
+ geo.minusScaleVec2(vA, mA, P);
1000
+ wA -= iA * geo.crossVec2Vec2(vcp.rA, P);
1001
+
1002
+ geo.plusScaleVec2(vB, mB, P);
1003
+ wB += iB * geo.crossVec2Vec2(vcp.rB, P);
1004
+ }
1005
+ } else {
1006
+ // Block solver developed in collaboration with Dirk Gregorius (back in
1007
+ // 01/07 on Box2D_Lite).
1008
+ // Build the mini LCP for this contact patch
1009
+ //
1010
+ // vn = A * x + b, vn >= 0, x >= 0 and vn_i * x_i = 0 with i = 1..2
1011
+ //
1012
+ // A = J * W * JT and J = ( -n, -r1 x n, n, r2 x n )
1013
+ // b = vn0 - velocityBias
1014
+ //
1015
+ // The system is solved using the "Total enumeration method" (s. Murty).
1016
+ // The complementary constraint vn_i * x_i
1017
+ // implies that we must have in any solution either vn_i = 0 or x_i = 0.
1018
+ // So for the 2D contact problem the cases
1019
+ // vn1 = 0 and vn2 = 0, x1 = 0 and x2 = 0, x1 = 0 and vn2 = 0, x2 = 0 and
1020
+ // vn1 = 0 need to be tested. The first valid
1021
+ // solution that satisfies the problem is chosen.
1022
+ //
1023
+ // In order to account of the accumulated impulse 'a' (because of the
1024
+ // iterative nature of the solver which only requires
1025
+ // that the accumulated impulse is clamped and not the incremental
1026
+ // impulse) we change the impulse variable (x_i).
1027
+ //
1028
+ // Substitute:
1029
+ //
1030
+ // x = a + d
1031
+ //
1032
+ // a := old total impulse
1033
+ // x := new total impulse
1034
+ // d := incremental impulse
1035
+ //
1036
+ // For the current iteration we extend the formula for the incremental
1037
+ // impulse
1038
+ // to compute the new total impulse:
1039
+ //
1040
+ // vn = A * d + b
1041
+ // = A * (x - a) + b
1042
+ // = A * x + b - A * a
1043
+ // = A * x + b'
1044
+ // b' = b - A * a;
1045
+
1046
+ const vcp1 = this.v_points[0]; // VelocityConstraintPoint
1047
+ const vcp2 = this.v_points[1]; // VelocityConstraintPoint
1048
+
1049
+ geo.setVec2(a, vcp1.normalImpulse, vcp2.normalImpulse);
1050
+ if (_ASSERT) console.assert(a.x >= 0.0 && a.y >= 0.0);
1051
+
1052
+ // Relative velocity at contact
1053
+ geo.dvp(dv1, vB, wB, vcp1.rB, vA, wA, vcp1.rA);
1054
+ geo.dvp(dv2, vB, wB, vcp2.rB, vA, wA, vcp2.rA);
1055
+
1056
+ // Compute normal velocity
1057
+ let vn1 = geo.dotVec2(dv1, normal);
1058
+ let vn2 = geo.dotVec2(dv2, normal);
1059
+
1060
+ geo.setVec2(b, vn1 - vcp1.velocityBias, vn2 - vcp2.velocityBias);
1061
+
1062
+ // Compute b'
1063
+ // b.sub(Mat22.mulVec2(this.v_K, a));
1064
+ b.x -= this.v_K.ex.x * a.x + this.v_K.ey.x * a.y;
1065
+ b.y -= this.v_K.ex.y * a.x + this.v_K.ey.y * a.y;
1066
+
1067
+ const k_errorTol = 1e-3;
1068
+ // NOT_USED(k_errorTol);
1069
+
1070
+ while (true) {
1071
+ //
1072
+ // Case 1: vn = 0
1073
+ //
1074
+ // 0 = A * x + b'
1075
+ //
1076
+ // Solve for x:
1077
+ //
1078
+ // x = - inv(A) * b'
1079
+ //
1080
+ // const x = Mat22.mulVec2(this.v_normalMass, b).neg();
1081
+ x.x = -(this.v_normalMass.ex.x * b.x + this.v_normalMass.ey.x * b.y);
1082
+ x.y = -(this.v_normalMass.ex.y * b.x + this.v_normalMass.ey.y * b.y);
1083
+
1084
+ if (x.x >= 0.0 && x.y >= 0.0) {
1085
+ // Get the incremental impulse
1086
+ geo.subVec2(d, x, a);
1087
+
1088
+ // Apply incremental impulse
1089
+ geo.scaleVec2(P1, d.x, normal);
1090
+ geo.scaleVec2(P2, d.y, normal);
1091
+
1092
+ // vA.subCombine(mA, P1, mA, P2);
1093
+ geo.combine3Vec2(vA, -mA, P1, -mA, P2, 1, vA);
1094
+ wA -= iA * (geo.crossVec2Vec2(vcp1.rA, P1) + geo.crossVec2Vec2(vcp2.rA, P2));
1095
+
1096
+ // vB.addCombine(mB, P1, mB, P2);
1097
+ geo.combine3Vec2(vB, mB, P1, mB, P2, 1, vB);
1098
+ wB += iB * (geo.crossVec2Vec2(vcp1.rB, P1) + geo.crossVec2Vec2(vcp2.rB, P2));
1099
+
1100
+ // Accumulate
1101
+ vcp1.normalImpulse = x.x;
1102
+ vcp2.normalImpulse = x.y;
1103
+
1104
+ if (DEBUG_SOLVER) {
1105
+ // Postconditions
1106
+ geo.dvp(dv1, vB, wB, vcp1.rB, vA, wA, vcp1.rA);
1107
+ geo.dvp(dv2, vB, wB, vcp2.rB, vA, wA, vcp2.rA);
1108
+
1109
+ // Compute normal velocity
1110
+ vn1 = geo.dotVec2(dv1, normal);
1111
+ vn2 = geo.dotVec2(dv2, normal);
1112
+
1113
+ if (_ASSERT) console.assert(math_abs(vn1 - vcp1.velocityBias) < k_errorTol);
1114
+ if (_ASSERT) console.assert(math_abs(vn2 - vcp2.velocityBias) < k_errorTol);
1115
+ }
1116
+ break;
1117
+ }
1118
+
1119
+ //
1120
+ // Case 2: vn1 = 0 and x2 = 0
1121
+ //
1122
+ // 0 = a11 * x1 + a12 * 0 + b1'
1123
+ // vn2 = a21 * x1 + a22 * 0 + b2'
1124
+ //
1125
+ x.x = -vcp1.normalMass * b.x;
1126
+ x.y = 0.0;
1127
+ vn1 = 0.0;
1128
+ vn2 = this.v_K.ex.y * x.x + b.y;
1129
+
1130
+ if (x.x >= 0.0 && vn2 >= 0.0) {
1131
+ // Get the incremental impulse
1132
+ geo.subVec2(d, x, a);
1133
+
1134
+ // Apply incremental impulse
1135
+ geo.scaleVec2(P1, d.x, normal);
1136
+ geo.scaleVec2(P2, d.y, normal);
1137
+
1138
+ // vA.subCombine(mA, P1, mA, P2);
1139
+ geo.combine3Vec2(vA, -mA, P1, -mA, P2, 1, vA);
1140
+ wA -= iA * (geo.crossVec2Vec2(vcp1.rA, P1) + geo.crossVec2Vec2(vcp2.rA, P2));
1141
+
1142
+ // vB.addCombine(mB, P1, mB, P2);
1143
+ geo.combine3Vec2(vB, mB, P1, mB, P2, 1, vB);
1144
+ wB += iB * (geo.crossVec2Vec2(vcp1.rB, P1) + geo.crossVec2Vec2(vcp2.rB, P2));
1145
+
1146
+ // Accumulate
1147
+ vcp1.normalImpulse = x.x;
1148
+ vcp2.normalImpulse = x.y;
1149
+
1150
+ if (DEBUG_SOLVER) {
1151
+ // Postconditions
1152
+ geo.dvp(dv1, vB, wB, vcp1.rB, vA, wA, vcp1.rA);
1153
+
1154
+ // Compute normal velocity
1155
+ vn1 = geo.dotVec2(dv1, normal);
1156
+
1157
+ if (_ASSERT) console.assert(math_abs(vn1 - vcp1.velocityBias) < k_errorTol);
1158
+ }
1159
+ break;
1160
+ }
1161
+
1162
+ //
1163
+ // Case 3: vn2 = 0 and x1 = 0
1164
+ //
1165
+ // vn1 = a11 * 0 + a12 * x2 + b1'
1166
+ // 0 = a21 * 0 + a22 * x2 + b2'
1167
+ //
1168
+ x.x = 0.0;
1169
+ x.y = -vcp2.normalMass * b.y;
1170
+ vn1 = this.v_K.ey.x * x.y + b.x;
1171
+ vn2 = 0.0;
1172
+
1173
+ if (x.y >= 0.0 && vn1 >= 0.0) {
1174
+ // Resubstitute for the incremental impulse
1175
+ geo.subVec2(d, x, a);
1176
+
1177
+ // Apply incremental impulse
1178
+ geo.scaleVec2(P1, d.x, normal);
1179
+ geo.scaleVec2(P2, d.y, normal);
1180
+
1181
+ // vA.subCombine(mA, P1, mA, P2);
1182
+ geo.combine3Vec2(vA, -mA, P1, -mA, P2, 1, vA);
1183
+ wA -= iA * (geo.crossVec2Vec2(vcp1.rA, P1) + geo.crossVec2Vec2(vcp2.rA, P2));
1184
+
1185
+ // vB.addCombine(mB, P1, mB, P2);
1186
+ geo.combine3Vec2(vB, mB, P1, mB, P2, 1, vB);
1187
+ wB += iB * (geo.crossVec2Vec2(vcp1.rB, P1) + geo.crossVec2Vec2(vcp2.rB, P2));
1188
+
1189
+ // Accumulate
1190
+ vcp1.normalImpulse = x.x;
1191
+ vcp2.normalImpulse = x.y;
1192
+
1193
+ if (DEBUG_SOLVER) {
1194
+ // Postconditions
1195
+ geo.dvp(dv2, vB, wB, vcp2.rB, vA, wA, vcp2.rA);
1196
+
1197
+ // Compute normal velocity
1198
+ vn2 = geo.dotVec2(dv2, normal);
1199
+
1200
+ if (_ASSERT) console.assert(math_abs(vn2 - vcp2.velocityBias) < k_errorTol);
1201
+ }
1202
+ break;
1203
+ }
1204
+
1205
+ //
1206
+ // Case 4: x1 = 0 and x2 = 0
1207
+ //
1208
+ // vn1 = b1
1209
+ // vn2 = b2;
1210
+ //
1211
+ x.x = 0.0;
1212
+ x.y = 0.0;
1213
+ vn1 = b.x;
1214
+ vn2 = b.y;
1215
+
1216
+ if (vn1 >= 0.0 && vn2 >= 0.0) {
1217
+ // Resubstitute for the incremental impulse
1218
+ geo.subVec2(d, x, a);
1219
+
1220
+ // Apply incremental impulse
1221
+ geo.scaleVec2(P1, d.x, normal);
1222
+ geo.scaleVec2(P2, d.y, normal);
1223
+
1224
+ // vA.subCombine(mA, P1, mA, P2);
1225
+ geo.combine3Vec2(vA, -mA, P1, -mA, P2, 1, vA);
1226
+ wA -= iA * (geo.crossVec2Vec2(vcp1.rA, P1) + geo.crossVec2Vec2(vcp2.rA, P2));
1227
+
1228
+ // vB.addCombine(mB, P1, mB, P2);
1229
+ geo.combine3Vec2(vB, mB, P1, mB, P2, 1, vB);
1230
+ wB += iB * (geo.crossVec2Vec2(vcp1.rB, P1) + geo.crossVec2Vec2(vcp2.rB, P2));
1231
+
1232
+ // Accumulate
1233
+ vcp1.normalImpulse = x.x;
1234
+ vcp2.normalImpulse = x.y;
1235
+
1236
+ break;
1237
+ }
1238
+
1239
+ // No solution, give up. This is hit sometimes, but it doesn't seem to
1240
+ // matter.
1241
+ break;
1242
+ }
1243
+ }
1244
+
1245
+ geo.copyVec2(velocityA.v, vA);
1246
+ velocityA.w = wA;
1247
+
1248
+ geo.copyVec2(velocityB.v, vB);
1249
+ velocityB.w = wB;
1250
+ }
1251
+
1252
+ /** @internal */
1253
+ static addType(type1: ShapeType, type2: ShapeType, callback: EvaluateFunction): void {
1254
+ s_registers[type1] = s_registers[type1] || {};
1255
+ s_registers[type1][type2] = callback;
1256
+ }
1257
+
1258
+ /** @internal */
1259
+ static create(fixtureA: Fixture, indexA: number, fixtureB: Fixture, indexB: number): Contact | null {
1260
+ const typeA = fixtureA.m_shape.m_type;
1261
+ const typeB = fixtureB.m_shape.m_type;
1262
+
1263
+ const contact = contactPool.allocate();
1264
+ let evaluateFcn;
1265
+ if ((evaluateFcn = s_registers[typeA] && s_registers[typeA][typeB])) {
1266
+ contact.initialize(fixtureA, indexA, fixtureB, indexB, evaluateFcn);
1267
+ } else if ((evaluateFcn = s_registers[typeB] && s_registers[typeB][typeA])) {
1268
+ contact.initialize(fixtureB, indexB, fixtureA, indexA, evaluateFcn);
1269
+ } else {
1270
+ return null;
1271
+ }
1272
+
1273
+ // Contact creation may swap fixtures.
1274
+ fixtureA = contact.m_fixtureA;
1275
+ fixtureB = contact.m_fixtureB;
1276
+ indexA = contact.getChildIndexA();
1277
+ indexB = contact.getChildIndexB();
1278
+ const bodyA = fixtureA.m_body;
1279
+ const bodyB = fixtureB.m_body;
1280
+
1281
+ // Connect to body A
1282
+ contact.m_nodeA.contact = contact;
1283
+ contact.m_nodeA.other = bodyB;
1284
+
1285
+ contact.m_nodeA.prev = null;
1286
+ contact.m_nodeA.next = bodyA.m_contactList;
1287
+ if (bodyA.m_contactList != null) {
1288
+ bodyA.m_contactList.prev = contact.m_nodeA;
1289
+ }
1290
+ bodyA.m_contactList = contact.m_nodeA;
1291
+
1292
+ // Connect to body B
1293
+ contact.m_nodeB.contact = contact;
1294
+ contact.m_nodeB.other = bodyA;
1295
+
1296
+ contact.m_nodeB.prev = null;
1297
+ contact.m_nodeB.next = bodyB.m_contactList;
1298
+ if (bodyB.m_contactList != null) {
1299
+ bodyB.m_contactList.prev = contact.m_nodeB;
1300
+ }
1301
+ bodyB.m_contactList = contact.m_nodeB;
1302
+
1303
+ // Wake up the bodies
1304
+ if (fixtureA.isSensor() == false && fixtureB.isSensor() == false) {
1305
+ bodyA.setAwake(true);
1306
+ bodyB.setAwake(true);
1307
+ }
1308
+
1309
+ return contact;
1310
+ }
1311
+
1312
+ /** @internal */
1313
+ static destroy(contact: Contact, listener: { endContact: (contact: Contact) => void }): void {
1314
+ const fixtureA = contact.m_fixtureA;
1315
+ const fixtureB = contact.m_fixtureB;
1316
+ if (fixtureA === null || fixtureB === null) return;
1317
+ const bodyA = fixtureA.m_body;
1318
+ const bodyB = fixtureB.m_body;
1319
+ if (bodyA === null || bodyB === null) return;
1320
+
1321
+ if (contact.isTouching()) {
1322
+ listener.endContact(contact);
1323
+ }
1324
+
1325
+ // Remove from body 1
1326
+ if (contact.m_nodeA.prev) {
1327
+ contact.m_nodeA.prev.next = contact.m_nodeA.next;
1328
+ }
1329
+
1330
+ if (contact.m_nodeA.next) {
1331
+ contact.m_nodeA.next.prev = contact.m_nodeA.prev;
1332
+ }
1333
+
1334
+ if (contact.m_nodeA == bodyA.m_contactList) {
1335
+ bodyA.m_contactList = contact.m_nodeA.next;
1336
+ }
1337
+
1338
+ // Remove from body 2
1339
+ if (contact.m_nodeB.prev) {
1340
+ contact.m_nodeB.prev.next = contact.m_nodeB.next;
1341
+ }
1342
+
1343
+ if (contact.m_nodeB.next) {
1344
+ contact.m_nodeB.next.prev = contact.m_nodeB.prev;
1345
+ }
1346
+
1347
+ if (contact.m_nodeB == bodyB.m_contactList) {
1348
+ bodyB.m_contactList = contact.m_nodeB.next;
1349
+ }
1350
+
1351
+ if (contact.m_manifold.pointCount > 0 && !fixtureA.m_isSensor && !fixtureB.m_isSensor) {
1352
+ bodyA.setAwake(true);
1353
+ bodyB.setAwake(true);
1354
+ }
1355
+
1356
+ // const typeA = fixtureA.getType();
1357
+ // const typeB = fixtureB.getType();
1358
+
1359
+ // const destroyFcn = s_registers[typeA][typeB].destroyFcn;
1360
+ // if (typeof destroyFcn === 'function') {
1361
+ // destroyFcn(contact);
1362
+ // }
1363
+
1364
+ contactPool.release(contact);
1365
+ }
1366
+ }