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.
- package/LICENSE.txt +20 -0
- package/README.md +21 -0
- package/dist/planck-with-testbed.d.ts +4433 -0
- package/dist/planck-with-testbed.js +20730 -0
- package/dist/planck-with-testbed.js.map +1 -0
- package/dist/planck-with-testbed.umd.cjs +20730 -0
- package/dist/planck-with-testbed.umd.cjs.map +1 -0
- package/dist/planck.d.ts +4343 -0
- package/dist/planck.js +13516 -0
- package/dist/planck.js.map +1 -0
- package/dist/planck.umd.cjs +13516 -0
- package/dist/planck.umd.cjs.map +1 -0
- package/package.json +105 -0
- package/src/Settings.ts +238 -0
- package/src/__test__/Basic.test.ts +43 -0
- package/src/__test__/CCD.test.ts +70 -0
- package/src/__test__/Collision.test.ts +133 -0
- package/src/__test__/Math.test.ts +105 -0
- package/src/__test__/Pool.test.ts +48 -0
- package/src/__test__/World.test.ts +73 -0
- package/src/collision/AABB.ts +287 -0
- package/src/collision/BroadPhase.ts +210 -0
- package/src/collision/Distance.ts +962 -0
- package/src/collision/DynamicTree.ts +907 -0
- package/src/collision/Manifold.ts +420 -0
- package/src/collision/Raycast.ts +30 -0
- package/src/collision/Shape.ts +114 -0
- package/src/collision/TimeOfImpact.ts +502 -0
- package/src/collision/shape/BoxShape.ts +34 -0
- package/src/collision/shape/ChainShape.ts +360 -0
- package/src/collision/shape/CircleShape.ts +202 -0
- package/src/collision/shape/CollideCircle.ts +66 -0
- package/src/collision/shape/CollideCirclePolygon.ts +142 -0
- package/src/collision/shape/CollideEdgeCircle.ts +185 -0
- package/src/collision/shape/CollideEdgePolygon.ts +528 -0
- package/src/collision/shape/CollidePolygon.ts +280 -0
- package/src/collision/shape/EdgeShape.ts +316 -0
- package/src/collision/shape/PolygonShape.ts +581 -0
- package/src/common/Geo.ts +589 -0
- package/src/common/Jacobian.ts +17 -0
- package/src/common/Mat22.ts +221 -0
- package/src/common/Mat33.ts +224 -0
- package/src/common/Math.ts +96 -0
- package/src/common/Rot.ts +218 -0
- package/src/common/Sweep.ts +119 -0
- package/src/common/Transform.ts +203 -0
- package/src/common/Vec2.ts +624 -0
- package/src/common/Vec3.ts +188 -0
- package/src/dynamics/Body.ts +1198 -0
- package/src/dynamics/Contact.ts +1366 -0
- package/src/dynamics/Fixture.ts +506 -0
- package/src/dynamics/Joint.ts +226 -0
- package/src/dynamics/Position.ts +44 -0
- package/src/dynamics/Solver.ts +890 -0
- package/src/dynamics/Velocity.ts +18 -0
- package/src/dynamics/World.ts +1169 -0
- package/src/dynamics/joint/DistanceJoint.ts +463 -0
- package/src/dynamics/joint/FrictionJoint.ts +396 -0
- package/src/dynamics/joint/GearJoint.ts +591 -0
- package/src/dynamics/joint/MotorJoint.ts +430 -0
- package/src/dynamics/joint/MouseJoint.ts +390 -0
- package/src/dynamics/joint/PrismaticJoint.ts +903 -0
- package/src/dynamics/joint/PulleyJoint.ts +529 -0
- package/src/dynamics/joint/RevoluteJoint.ts +745 -0
- package/src/dynamics/joint/RopeJoint.ts +383 -0
- package/src/dynamics/joint/WeldJoint.ts +544 -0
- package/src/dynamics/joint/WheelJoint.ts +683 -0
- package/src/dynamics/joint/__test__/DistanceJoint.test.ts +66 -0
- package/src/index.ts +60 -0
- package/src/internal.ts +20 -0
- package/src/main.ts +3 -0
- package/src/serializer/__test__/Serialize.test.ts +52 -0
- package/src/serializer/__test__/Validator.test.ts +55 -0
- package/src/serializer/index.ts +257 -0
- package/src/serializer/schema.json +168 -0
- package/src/util/Pool.ts +120 -0
- package/src/util/Testbed.ts +157 -0
- package/src/util/Timer.ts +15 -0
- package/src/util/options.ts +28 -0
- 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
|
+
}
|