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,890 @@
|
|
|
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 { SettingsInternal as Settings } from "../Settings";
|
|
12
|
+
import { EPSILON } from "../common/Math";
|
|
13
|
+
import { Body } from "./Body";
|
|
14
|
+
import type { Contact } from "./Contact";
|
|
15
|
+
import { Joint } from "./Joint";
|
|
16
|
+
import { TimeOfImpact, TOIInput, TOIOutput, TOIOutputState } from "../collision/TimeOfImpact";
|
|
17
|
+
import { Distance, DistanceInput, DistanceOutput, SimplexCache } from "../collision/Distance";
|
|
18
|
+
import { World } from "./World";
|
|
19
|
+
import { Sweep } from "../common/Sweep";
|
|
20
|
+
|
|
21
|
+
/** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT;
|
|
22
|
+
/** @internal */ const math_abs = Math.abs;
|
|
23
|
+
/** @internal */ const math_sqrt = Math.sqrt;
|
|
24
|
+
/** @internal */ const math_min = Math.min;
|
|
25
|
+
|
|
26
|
+
export class TimeStep {
|
|
27
|
+
/** time step */
|
|
28
|
+
dt: number = 0;
|
|
29
|
+
/** inverse time step (0 if dt == 0) */
|
|
30
|
+
inv_dt: number = 0;
|
|
31
|
+
velocityIterations: number = 0;
|
|
32
|
+
positionIterations: number = 0;
|
|
33
|
+
warmStarting: boolean = false;
|
|
34
|
+
blockSolve: boolean = true;
|
|
35
|
+
|
|
36
|
+
/** timestep ratio for variable timestep */
|
|
37
|
+
inv_dt0: number = 0.0;
|
|
38
|
+
/** dt * inv_dt0 */
|
|
39
|
+
dtRatio: number = 1;
|
|
40
|
+
|
|
41
|
+
reset(dt: number): void {
|
|
42
|
+
if (this.dt > 0.0) {
|
|
43
|
+
this.inv_dt0 = this.inv_dt;
|
|
44
|
+
}
|
|
45
|
+
this.dt = dt;
|
|
46
|
+
this.inv_dt = dt == 0 ? 0 : 1 / dt;
|
|
47
|
+
this.dtRatio = dt * this.inv_dt0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// reuse
|
|
52
|
+
/** @internal */ const s_subStep = new TimeStep();
|
|
53
|
+
/** @internal */ const c = geo.vec2(0, 0);
|
|
54
|
+
/** @internal */ const v = geo.vec2(0, 0);
|
|
55
|
+
/** @internal */ const translation = geo.vec2(0, 0);
|
|
56
|
+
/** @internal */ const input = new TOIInput();
|
|
57
|
+
/** @internal */ const output = new TOIOutput();
|
|
58
|
+
/** @internal */ const backup = new Sweep();
|
|
59
|
+
/** @internal */ const backup1 = new Sweep();
|
|
60
|
+
/** @internal */ const backup2 = new Sweep();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Contact impulses for reporting. Impulses are used instead of forces because
|
|
64
|
+
* sub-step forces may approach infinity for rigid body collisions. These match
|
|
65
|
+
* up one-to-one with the contact points in Manifold.
|
|
66
|
+
*/
|
|
67
|
+
export class ContactImpulse {
|
|
68
|
+
// TODO: merge with Contact class?
|
|
69
|
+
|
|
70
|
+
private readonly contact: Contact;
|
|
71
|
+
private readonly normals: number[];
|
|
72
|
+
private readonly tangents: number[];
|
|
73
|
+
|
|
74
|
+
constructor(contact: Contact) {
|
|
75
|
+
this.contact = contact;
|
|
76
|
+
this.normals = [];
|
|
77
|
+
this.tangents = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
recycle() {
|
|
81
|
+
this.normals.length = 0;
|
|
82
|
+
this.tangents.length = 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get normalImpulses(): number[] {
|
|
86
|
+
const contact = this.contact;
|
|
87
|
+
const normals = this.normals;
|
|
88
|
+
normals.length = 0;
|
|
89
|
+
for (let p = 0; p < contact.v_points.length; ++p) {
|
|
90
|
+
normals.push(contact.v_points[p].normalImpulse);
|
|
91
|
+
}
|
|
92
|
+
return normals;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get tangentImpulses(): number[] {
|
|
96
|
+
const contact = this.contact;
|
|
97
|
+
const tangents = this.tangents;
|
|
98
|
+
tangents.length = 0;
|
|
99
|
+
for (let p = 0; p < contact.v_points.length; ++p) {
|
|
100
|
+
tangents.push(contact.v_points[p].tangentImpulse);
|
|
101
|
+
}
|
|
102
|
+
return tangents;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Finds and solves islands. An island is a connected subset of the world.
|
|
108
|
+
*/
|
|
109
|
+
export class Solver {
|
|
110
|
+
m_world: World;
|
|
111
|
+
m_stack: Body[];
|
|
112
|
+
m_bodies: Body[];
|
|
113
|
+
m_contacts: Contact[];
|
|
114
|
+
m_joints: Joint[];
|
|
115
|
+
|
|
116
|
+
constructor(world: World) {
|
|
117
|
+
this.m_world = world;
|
|
118
|
+
this.m_stack = [];
|
|
119
|
+
this.m_bodies = [];
|
|
120
|
+
this.m_contacts = [];
|
|
121
|
+
this.m_joints = [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
clear(): void {
|
|
125
|
+
this.m_stack.length = 0;
|
|
126
|
+
this.m_bodies.length = 0;
|
|
127
|
+
this.m_contacts.length = 0;
|
|
128
|
+
this.m_joints.length = 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
addBody(body: Body): void {
|
|
132
|
+
if (_ASSERT) console.assert(body instanceof Body, "Not a Body!", body);
|
|
133
|
+
this.m_bodies.push(body);
|
|
134
|
+
// why?
|
|
135
|
+
// body.c_position.c.setZero();
|
|
136
|
+
// body.c_position.a = 0;
|
|
137
|
+
// body.c_velocity.v.setZero();
|
|
138
|
+
// body.c_velocity.w = 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
addContact(contact: Contact): void {
|
|
142
|
+
// if (_ASSERT) console.assert(contact instanceof Contact, 'Not a Contact!', contact);
|
|
143
|
+
this.m_contacts.push(contact);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
addJoint(joint: Joint): void {
|
|
147
|
+
if (_ASSERT) console.assert(joint instanceof Joint, "Not a Joint!", joint);
|
|
148
|
+
this.m_joints.push(joint);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
solveWorld(step: TimeStep): void {
|
|
152
|
+
const world = this.m_world;
|
|
153
|
+
|
|
154
|
+
// Clear all the island flags.
|
|
155
|
+
for (let b = world.m_bodyList; b; b = b.m_next) {
|
|
156
|
+
b.m_islandFlag = false;
|
|
157
|
+
}
|
|
158
|
+
for (let c = world.m_contactList; c; c = c.m_next) {
|
|
159
|
+
c.m_islandFlag = false;
|
|
160
|
+
}
|
|
161
|
+
for (let j = world.m_jointList; j; j = j.m_next) {
|
|
162
|
+
j.m_islandFlag = false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Build and simulate all awake islands.
|
|
166
|
+
const stack = this.m_stack;
|
|
167
|
+
let loop = -1;
|
|
168
|
+
for (let seed = world.m_bodyList; seed; seed = seed.m_next) {
|
|
169
|
+
loop++;
|
|
170
|
+
if (seed.m_islandFlag) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (seed.isAwake() == false || seed.isActive() == false) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The seed can be dynamic or kinematic.
|
|
179
|
+
if (seed.isStatic()) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Reset island and stack.
|
|
184
|
+
this.clear();
|
|
185
|
+
|
|
186
|
+
stack.push(seed);
|
|
187
|
+
|
|
188
|
+
seed.m_islandFlag = true;
|
|
189
|
+
|
|
190
|
+
// Perform a depth first search (DFS) on the constraint graph.
|
|
191
|
+
while (stack.length > 0) {
|
|
192
|
+
// Grab the next body off the stack and add it to the island.
|
|
193
|
+
const b = stack.pop();
|
|
194
|
+
if (_ASSERT) console.assert(b.isActive() == true);
|
|
195
|
+
this.addBody(b);
|
|
196
|
+
|
|
197
|
+
// Make sure the body is awake (without resetting sleep timer).
|
|
198
|
+
b.m_awakeFlag = true;
|
|
199
|
+
|
|
200
|
+
// To keep islands as small as possible, we don't
|
|
201
|
+
// propagate islands across static bodies.
|
|
202
|
+
if (b.isStatic()) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Search all contacts connected to this body.
|
|
207
|
+
for (let ce = b.m_contactList; ce; ce = ce.next) {
|
|
208
|
+
const contact = ce.contact;
|
|
209
|
+
|
|
210
|
+
// Has this contact already been added to an island?
|
|
211
|
+
if (contact.m_islandFlag) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Is this contact solid and touching?
|
|
216
|
+
if (contact.isEnabled() == false || contact.isTouching() == false) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Skip sensors.
|
|
221
|
+
const sensorA = contact.m_fixtureA.m_isSensor;
|
|
222
|
+
const sensorB = contact.m_fixtureB.m_isSensor;
|
|
223
|
+
if (sensorA || sensorB) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.addContact(contact);
|
|
228
|
+
contact.m_islandFlag = true;
|
|
229
|
+
|
|
230
|
+
const other = ce.other;
|
|
231
|
+
|
|
232
|
+
// Was the other body already added to this island?
|
|
233
|
+
if (other.m_islandFlag) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// if (_ASSERT) console.assert(stack.length < world.m_bodyCount);
|
|
238
|
+
stack.push(other);
|
|
239
|
+
other.m_islandFlag = true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Search all joints connect to this body.
|
|
243
|
+
for (let je = b.m_jointList; je; je = je.next) {
|
|
244
|
+
if (je.joint.m_islandFlag == true) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const other = je.other;
|
|
249
|
+
|
|
250
|
+
// Don't simulate joints connected to inactive bodies.
|
|
251
|
+
if (other.isActive() == false) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.addJoint(je.joint);
|
|
256
|
+
je.joint.m_islandFlag = true;
|
|
257
|
+
|
|
258
|
+
if (other.m_islandFlag) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// if (_ASSERT) console.assert(stack.length < world.m_bodyCount);
|
|
263
|
+
stack.push(other);
|
|
264
|
+
other.m_islandFlag = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.solveIsland(step);
|
|
269
|
+
|
|
270
|
+
// Post solve cleanup.
|
|
271
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
272
|
+
// Allow static bodies to participate in other islands.
|
|
273
|
+
// TODO: are they added at all?
|
|
274
|
+
const b = this.m_bodies[i];
|
|
275
|
+
if (b.isStatic()) {
|
|
276
|
+
b.m_islandFlag = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
solveIsland(step: TimeStep): void {
|
|
283
|
+
// B2: Island Solve
|
|
284
|
+
const world = this.m_world;
|
|
285
|
+
const gravity = world.m_gravity;
|
|
286
|
+
const allowSleep = world.m_allowSleep;
|
|
287
|
+
|
|
288
|
+
const h = step.dt;
|
|
289
|
+
|
|
290
|
+
// Integrate velocities and apply damping. Initialize the body state.
|
|
291
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
292
|
+
const body = this.m_bodies[i];
|
|
293
|
+
|
|
294
|
+
geo.copyVec2(c, body.m_sweep.c);
|
|
295
|
+
const a = body.m_sweep.a;
|
|
296
|
+
geo.copyVec2(v, body.m_linearVelocity);
|
|
297
|
+
let w = body.m_angularVelocity;
|
|
298
|
+
|
|
299
|
+
// Store positions for continuous collision.
|
|
300
|
+
geo.copyVec2(body.m_sweep.c0, body.m_sweep.c);
|
|
301
|
+
body.m_sweep.a0 = body.m_sweep.a;
|
|
302
|
+
|
|
303
|
+
if (body.isDynamic()) {
|
|
304
|
+
// Integrate velocities.
|
|
305
|
+
geo.plusScaleVec2(v, h * body.m_gravityScale, gravity);
|
|
306
|
+
geo.plusScaleVec2(v, h * body.m_invMass, body.m_force);
|
|
307
|
+
w += h * body.m_invI * body.m_torque;
|
|
308
|
+
/**
|
|
309
|
+
* <pre>
|
|
310
|
+
* Apply damping.
|
|
311
|
+
* ODE: dv/dt + c * v = 0
|
|
312
|
+
* Solution: v(t) = v0 * exp(-c * t)
|
|
313
|
+
* Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)
|
|
314
|
+
* v2 = exp(-c * dt) * v1
|
|
315
|
+
* Pade approximation:
|
|
316
|
+
* v2 = v1 * 1 / (1 + c * dt)
|
|
317
|
+
* </pre>
|
|
318
|
+
*/
|
|
319
|
+
geo.scaleVec2(v, 1.0 / (1.0 + h * body.m_linearDamping), v);
|
|
320
|
+
w *= 1.0 / (1.0 + h * body.m_angularDamping);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
geo.copyVec2(body.c_position.c, c);
|
|
324
|
+
body.c_position.a = a;
|
|
325
|
+
geo.copyVec2(body.c_velocity.v, v);
|
|
326
|
+
body.c_velocity.w = w;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
330
|
+
const contact = this.m_contacts[i];
|
|
331
|
+
contact.initConstraint(step);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
335
|
+
const contact = this.m_contacts[i];
|
|
336
|
+
contact.initVelocityConstraint(step);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (step.warmStarting) {
|
|
340
|
+
// Warm start.
|
|
341
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
342
|
+
const contact = this.m_contacts[i];
|
|
343
|
+
contact.warmStartConstraint(step);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (let i = 0; i < this.m_joints.length; ++i) {
|
|
348
|
+
const joint = this.m_joints[i];
|
|
349
|
+
joint.initVelocityConstraints(step);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Solve velocity constraints
|
|
353
|
+
for (let i = 0; i < step.velocityIterations; ++i) {
|
|
354
|
+
for (let j = 0; j < this.m_joints.length; ++j) {
|
|
355
|
+
const joint = this.m_joints[j];
|
|
356
|
+
joint.solveVelocityConstraints(step);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for (let j = 0; j < this.m_contacts.length; ++j) {
|
|
360
|
+
const contact = this.m_contacts[j];
|
|
361
|
+
contact.solveVelocityConstraint(step);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Store impulses for warm starting
|
|
366
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
367
|
+
const contact = this.m_contacts[i];
|
|
368
|
+
contact.storeConstraintImpulses(step);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Integrate positions
|
|
372
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
373
|
+
const body = this.m_bodies[i];
|
|
374
|
+
|
|
375
|
+
geo.copyVec2(c, body.c_position.c);
|
|
376
|
+
let a = body.c_position.a;
|
|
377
|
+
geo.copyVec2(v, body.c_velocity.v);
|
|
378
|
+
let w = body.c_velocity.w;
|
|
379
|
+
|
|
380
|
+
// Check for large velocities
|
|
381
|
+
geo.scaleVec2(translation, h, v);
|
|
382
|
+
const translationLengthSqr = geo.lengthSqrVec2(translation);
|
|
383
|
+
if (translationLengthSqr > Settings.maxTranslationSquared) {
|
|
384
|
+
const ratio = Settings.maxTranslation / math_sqrt(translationLengthSqr);
|
|
385
|
+
geo.mulVec2(v, ratio);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const rotation = h * w;
|
|
389
|
+
if (rotation * rotation > Settings.maxRotationSquared) {
|
|
390
|
+
const ratio = Settings.maxRotation / math_abs(rotation);
|
|
391
|
+
w *= ratio;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Integrate
|
|
395
|
+
geo.plusScaleVec2(c, h, v);
|
|
396
|
+
a += h * w;
|
|
397
|
+
|
|
398
|
+
geo.copyVec2(body.c_position.c, c);
|
|
399
|
+
body.c_position.a = a;
|
|
400
|
+
geo.copyVec2(body.c_velocity.v, v);
|
|
401
|
+
body.c_velocity.w = w;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Solve position constraints
|
|
405
|
+
let positionSolved = false;
|
|
406
|
+
for (let i = 0; i < step.positionIterations; ++i) {
|
|
407
|
+
let minSeparation = 0.0;
|
|
408
|
+
for (let j = 0; j < this.m_contacts.length; ++j) {
|
|
409
|
+
const contact = this.m_contacts[j];
|
|
410
|
+
const separation = contact.solvePositionConstraint(step);
|
|
411
|
+
minSeparation = math_min(minSeparation, separation);
|
|
412
|
+
}
|
|
413
|
+
// We can't expect minSpeparation >= -Settings.linearSlop because we don't
|
|
414
|
+
// push the separation above -Settings.linearSlop.
|
|
415
|
+
const contactsOkay = minSeparation >= -3.0 * Settings.linearSlop;
|
|
416
|
+
|
|
417
|
+
let jointsOkay = true;
|
|
418
|
+
for (let j = 0; j < this.m_joints.length; ++j) {
|
|
419
|
+
const joint = this.m_joints[j];
|
|
420
|
+
const jointOkay = joint.solvePositionConstraints(step);
|
|
421
|
+
jointsOkay = jointsOkay && jointOkay;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (contactsOkay && jointsOkay) {
|
|
425
|
+
// Exit early if the position errors are small.
|
|
426
|
+
positionSolved = true;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Copy state buffers back to the bodies
|
|
432
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
433
|
+
const body = this.m_bodies[i];
|
|
434
|
+
|
|
435
|
+
geo.copyVec2(body.m_sweep.c, body.c_position.c);
|
|
436
|
+
body.m_sweep.a = body.c_position.a;
|
|
437
|
+
geo.copyVec2(body.m_linearVelocity, body.c_velocity.v);
|
|
438
|
+
body.m_angularVelocity = body.c_velocity.w;
|
|
439
|
+
body.synchronizeTransform();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.postSolveIsland();
|
|
443
|
+
|
|
444
|
+
if (allowSleep) {
|
|
445
|
+
let minSleepTime = Infinity;
|
|
446
|
+
|
|
447
|
+
const linTolSqr = Settings.linearSleepToleranceSqr;
|
|
448
|
+
const angTolSqr = Settings.angularSleepToleranceSqr;
|
|
449
|
+
|
|
450
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
451
|
+
const body = this.m_bodies[i];
|
|
452
|
+
if (body.isStatic()) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (
|
|
457
|
+
body.m_autoSleepFlag == false ||
|
|
458
|
+
body.m_angularVelocity * body.m_angularVelocity > angTolSqr ||
|
|
459
|
+
geo.lengthSqrVec2(body.m_linearVelocity) > linTolSqr
|
|
460
|
+
) {
|
|
461
|
+
body.m_sleepTime = 0.0;
|
|
462
|
+
minSleepTime = 0.0;
|
|
463
|
+
} else {
|
|
464
|
+
body.m_sleepTime += h;
|
|
465
|
+
minSleepTime = math_min(minSleepTime, body.m_sleepTime);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (minSleepTime >= Settings.timeToSleep && positionSolved) {
|
|
470
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
471
|
+
const body = this.m_bodies[i];
|
|
472
|
+
body.setAwake(false);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Find TOI contacts and solve them.
|
|
480
|
+
*/
|
|
481
|
+
solveWorldTOI(step: TimeStep): void {
|
|
482
|
+
const world = this.m_world;
|
|
483
|
+
|
|
484
|
+
if (world.m_stepComplete) {
|
|
485
|
+
for (let b = world.m_bodyList; b; b = b.m_next) {
|
|
486
|
+
b.m_islandFlag = false;
|
|
487
|
+
b.m_sweep.alpha0 = 0.0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
for (let c = world.m_contactList; c; c = c.m_next) {
|
|
491
|
+
// Invalidate TOI
|
|
492
|
+
c.m_toiFlag = false;
|
|
493
|
+
c.m_islandFlag = false;
|
|
494
|
+
c.m_toiCount = 0;
|
|
495
|
+
c.m_toi = 1.0;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Find TOI events and solve them.
|
|
500
|
+
while (true) {
|
|
501
|
+
// Find the first TOI.
|
|
502
|
+
let minContact: Contact | null = null;
|
|
503
|
+
let minAlpha = 1.0;
|
|
504
|
+
|
|
505
|
+
for (let c = world.m_contactList; c; c = c.m_next) {
|
|
506
|
+
// Is this contact disabled?
|
|
507
|
+
if (c.isEnabled() == false) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Prevent excessive sub-stepping.
|
|
512
|
+
if (c.m_toiCount > Settings.maxSubSteps) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let alpha = 1.0;
|
|
517
|
+
if (c.m_toiFlag) {
|
|
518
|
+
// This contact has a valid cached TOI.
|
|
519
|
+
alpha = c.m_toi;
|
|
520
|
+
} else {
|
|
521
|
+
const fA = c.getFixtureA();
|
|
522
|
+
const fB = c.getFixtureB();
|
|
523
|
+
|
|
524
|
+
// Is there a sensor?
|
|
525
|
+
if (fA.isSensor() || fB.isSensor()) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const bA = fA.getBody();
|
|
530
|
+
const bB = fB.getBody();
|
|
531
|
+
|
|
532
|
+
if (_ASSERT) console.assert(bA.isDynamic() || bB.isDynamic());
|
|
533
|
+
|
|
534
|
+
const activeA = bA.isAwake() && !bA.isStatic();
|
|
535
|
+
const activeB = bB.isAwake() && !bB.isStatic();
|
|
536
|
+
|
|
537
|
+
// Is at least one body active (awake and dynamic or kinematic)?
|
|
538
|
+
if (activeA == false && activeB == false) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const collideA = bA.isBullet() || !bA.isDynamic();
|
|
543
|
+
const collideB = bB.isBullet() || !bB.isDynamic();
|
|
544
|
+
|
|
545
|
+
// Are these two non-bullet dynamic bodies?
|
|
546
|
+
if (collideA == false && collideB == false) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Compute the TOI for this contact.
|
|
551
|
+
// Put the sweeps onto the same time interval.
|
|
552
|
+
let alpha0 = bA.m_sweep.alpha0;
|
|
553
|
+
|
|
554
|
+
if (bA.m_sweep.alpha0 < bB.m_sweep.alpha0) {
|
|
555
|
+
alpha0 = bB.m_sweep.alpha0;
|
|
556
|
+
bA.m_sweep.advance(alpha0);
|
|
557
|
+
} else if (bB.m_sweep.alpha0 < bA.m_sweep.alpha0) {
|
|
558
|
+
alpha0 = bA.m_sweep.alpha0;
|
|
559
|
+
bB.m_sweep.advance(alpha0);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (_ASSERT) console.assert(alpha0 < 1.0);
|
|
563
|
+
|
|
564
|
+
const indexA = c.getChildIndexA();
|
|
565
|
+
const indexB = c.getChildIndexB();
|
|
566
|
+
|
|
567
|
+
// const sweepA = bA.m_sweep;
|
|
568
|
+
// const sweepB = bB.m_sweep;
|
|
569
|
+
|
|
570
|
+
// Compute the time of impact in interval [0, minTOI]
|
|
571
|
+
input.proxyA.set(fA.getShape(), indexA);
|
|
572
|
+
input.proxyB.set(fB.getShape(), indexB);
|
|
573
|
+
input.sweepA.set(bA.m_sweep);
|
|
574
|
+
input.sweepB.set(bB.m_sweep);
|
|
575
|
+
input.tMax = 1.0;
|
|
576
|
+
|
|
577
|
+
TimeOfImpact(output, input);
|
|
578
|
+
|
|
579
|
+
// Beta is the fraction of the remaining portion of the [time?].
|
|
580
|
+
const beta = output.t;
|
|
581
|
+
if (output.state == TOIOutputState.e_touching) {
|
|
582
|
+
alpha = math_min(alpha0 + (1.0 - alpha0) * beta, 1.0);
|
|
583
|
+
} else {
|
|
584
|
+
alpha = 1.0;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
c.m_toi = alpha;
|
|
588
|
+
c.m_toiFlag = true;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (alpha < minAlpha) {
|
|
592
|
+
// This is the minimum TOI found so far.
|
|
593
|
+
minContact = c;
|
|
594
|
+
minAlpha = alpha;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (minContact == null || 1.0 - 10.0 * EPSILON < minAlpha) {
|
|
599
|
+
// No more TOI events. Done!
|
|
600
|
+
world.m_stepComplete = true;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Advance the bodies to the TOI.
|
|
605
|
+
const fA = minContact.getFixtureA();
|
|
606
|
+
const fB = minContact.getFixtureB();
|
|
607
|
+
const bA = fA.getBody();
|
|
608
|
+
const bB = fB.getBody();
|
|
609
|
+
|
|
610
|
+
backup1.set(bA.m_sweep);
|
|
611
|
+
backup2.set(bB.m_sweep);
|
|
612
|
+
|
|
613
|
+
bA.advance(minAlpha);
|
|
614
|
+
bB.advance(minAlpha);
|
|
615
|
+
|
|
616
|
+
// The TOI contact likely has some new contact points.
|
|
617
|
+
minContact.update(world);
|
|
618
|
+
minContact.m_toiFlag = false;
|
|
619
|
+
++minContact.m_toiCount;
|
|
620
|
+
|
|
621
|
+
// Is the contact solid?
|
|
622
|
+
if (minContact.isEnabled() == false || minContact.isTouching() == false) {
|
|
623
|
+
// Restore the sweeps.
|
|
624
|
+
minContact.setEnabled(false);
|
|
625
|
+
bA.m_sweep.set(backup1);
|
|
626
|
+
bB.m_sweep.set(backup2);
|
|
627
|
+
bA.synchronizeTransform();
|
|
628
|
+
bB.synchronizeTransform();
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
bA.setAwake(true);
|
|
633
|
+
bB.setAwake(true);
|
|
634
|
+
|
|
635
|
+
// Build the island
|
|
636
|
+
this.clear();
|
|
637
|
+
this.addBody(bA);
|
|
638
|
+
this.addBody(bB);
|
|
639
|
+
this.addContact(minContact);
|
|
640
|
+
|
|
641
|
+
bA.m_islandFlag = true;
|
|
642
|
+
bB.m_islandFlag = true;
|
|
643
|
+
minContact.m_islandFlag = true;
|
|
644
|
+
|
|
645
|
+
// Get contacts on bodyA and bodyB.
|
|
646
|
+
const bodies = [bA, bB];
|
|
647
|
+
for (let i = 0; i < bodies.length; ++i) {
|
|
648
|
+
const body = bodies[i];
|
|
649
|
+
if (body.isDynamic()) {
|
|
650
|
+
for (let ce = body.m_contactList; ce; ce = ce.next) {
|
|
651
|
+
// if (this.m_bodyCount == this.m_bodyCapacity) { break; }
|
|
652
|
+
// if (this.m_contactCount == this.m_contactCapacity) { break; }
|
|
653
|
+
|
|
654
|
+
const contact = ce.contact;
|
|
655
|
+
|
|
656
|
+
// Has this contact already been added to the island?
|
|
657
|
+
if (contact.m_islandFlag) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Only add if either is static, kinematic or bullet.
|
|
662
|
+
const other = ce.other;
|
|
663
|
+
if (other.isDynamic() && !body.isBullet() && !other.isBullet()) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Skip sensors.
|
|
668
|
+
const sensorA = contact.m_fixtureA.m_isSensor;
|
|
669
|
+
const sensorB = contact.m_fixtureB.m_isSensor;
|
|
670
|
+
if (sensorA || sensorB) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Tentatively advance the body to the TOI.
|
|
675
|
+
backup.set(other.m_sweep);
|
|
676
|
+
if (other.m_islandFlag == false) {
|
|
677
|
+
other.advance(minAlpha);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Update the contact points
|
|
681
|
+
contact.update(world);
|
|
682
|
+
|
|
683
|
+
// Was the contact disabled by the user?
|
|
684
|
+
// Are there contact points?
|
|
685
|
+
if (contact.isEnabled() == false || contact.isTouching() == false) {
|
|
686
|
+
other.m_sweep.set(backup);
|
|
687
|
+
other.synchronizeTransform();
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Add the contact to the island
|
|
692
|
+
contact.m_islandFlag = true;
|
|
693
|
+
this.addContact(contact);
|
|
694
|
+
|
|
695
|
+
// Has the other body already been added to the island?
|
|
696
|
+
if (other.m_islandFlag) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Add the other body to the island.
|
|
701
|
+
other.m_islandFlag = true;
|
|
702
|
+
|
|
703
|
+
if (!other.isStatic()) {
|
|
704
|
+
other.setAwake(true);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
this.addBody(other);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
s_subStep.reset((1.0 - minAlpha) * step.dt);
|
|
713
|
+
s_subStep.dtRatio = 1.0;
|
|
714
|
+
s_subStep.positionIterations = 20;
|
|
715
|
+
s_subStep.velocityIterations = step.velocityIterations;
|
|
716
|
+
s_subStep.warmStarting = false;
|
|
717
|
+
|
|
718
|
+
this.solveIslandTOI(s_subStep, bA, bB);
|
|
719
|
+
|
|
720
|
+
// Reset island flags and synchronize broad-phase proxies.
|
|
721
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
722
|
+
const body = this.m_bodies[i];
|
|
723
|
+
body.m_islandFlag = false;
|
|
724
|
+
|
|
725
|
+
if (!body.isDynamic()) {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
body.synchronizeFixtures();
|
|
730
|
+
|
|
731
|
+
// Invalidate all contact TOIs on this displaced body.
|
|
732
|
+
for (let ce = body.m_contactList; ce; ce = ce.next) {
|
|
733
|
+
ce.contact.m_toiFlag = false;
|
|
734
|
+
ce.contact.m_islandFlag = false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Commit fixture proxy movements to the broad-phase so that new contacts
|
|
739
|
+
// are created.
|
|
740
|
+
// Also, some contacts can be destroyed.
|
|
741
|
+
world.findNewContacts();
|
|
742
|
+
|
|
743
|
+
if (world.m_subStepping) {
|
|
744
|
+
world.m_stepComplete = false;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
solveIslandTOI(subStep: TimeStep, toiA: Body, toiB: Body): void {
|
|
751
|
+
// Initialize the body state.
|
|
752
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
753
|
+
const body = this.m_bodies[i];
|
|
754
|
+
geo.copyVec2(body.c_position.c, body.m_sweep.c);
|
|
755
|
+
body.c_position.a = body.m_sweep.a;
|
|
756
|
+
geo.copyVec2(body.c_velocity.v, body.m_linearVelocity);
|
|
757
|
+
body.c_velocity.w = body.m_angularVelocity;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
761
|
+
const contact = this.m_contacts[i];
|
|
762
|
+
contact.initConstraint(subStep);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Solve position constraints.
|
|
766
|
+
for (let i = 0; i < subStep.positionIterations; ++i) {
|
|
767
|
+
let minSeparation = 0.0;
|
|
768
|
+
for (let j = 0; j < this.m_contacts.length; ++j) {
|
|
769
|
+
const contact = this.m_contacts[j];
|
|
770
|
+
const separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB);
|
|
771
|
+
minSeparation = math_min(minSeparation, separation);
|
|
772
|
+
}
|
|
773
|
+
// We can't expect minSpeparation >= -Settings.linearSlop because we don't
|
|
774
|
+
// push the separation above -Settings.linearSlop.
|
|
775
|
+
const contactsOkay = minSeparation >= -1.5 * Settings.linearSlop;
|
|
776
|
+
if (contactsOkay) {
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (false) {
|
|
782
|
+
// Is the new position really safe?
|
|
783
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
784
|
+
const c = this.m_contacts[i];
|
|
785
|
+
const fA = c.getFixtureA();
|
|
786
|
+
const fB = c.getFixtureB();
|
|
787
|
+
|
|
788
|
+
const bA = fA.getBody();
|
|
789
|
+
const bB = fB.getBody();
|
|
790
|
+
|
|
791
|
+
const indexA = c.getChildIndexA();
|
|
792
|
+
const indexB = c.getChildIndexB();
|
|
793
|
+
|
|
794
|
+
const input = new DistanceInput();
|
|
795
|
+
input.proxyA.set(fA.getShape(), indexA);
|
|
796
|
+
input.proxyB.set(fB.getShape(), indexB);
|
|
797
|
+
geo.copyTransform(input.transformA, bA.getTransform());
|
|
798
|
+
geo.copyTransform(input.transformB, bB.getTransform());
|
|
799
|
+
input.useRadii = false;
|
|
800
|
+
|
|
801
|
+
const output = new DistanceOutput();
|
|
802
|
+
const cache = new SimplexCache();
|
|
803
|
+
Distance(output, cache, input);
|
|
804
|
+
|
|
805
|
+
if (output.distance == 0 || cache.count == 3) {
|
|
806
|
+
cache.count += 0;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Leap of faith to new safe state.
|
|
812
|
+
geo.copyVec2(toiA.m_sweep.c0, toiA.c_position.c);
|
|
813
|
+
toiA.m_sweep.a0 = toiA.c_position.a;
|
|
814
|
+
geo.copyVec2(toiB.m_sweep.c0, toiB.c_position.c);
|
|
815
|
+
toiB.m_sweep.a0 = toiB.c_position.a;
|
|
816
|
+
|
|
817
|
+
// No warm starting is needed for TOI events because warm
|
|
818
|
+
// starting impulses were applied in the discrete solver.
|
|
819
|
+
for (let i = 0; i < this.m_contacts.length; ++i) {
|
|
820
|
+
const contact = this.m_contacts[i];
|
|
821
|
+
contact.initVelocityConstraint(subStep);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Solve velocity constraints.
|
|
825
|
+
for (let i = 0; i < subStep.velocityIterations; ++i) {
|
|
826
|
+
for (let j = 0; j < this.m_contacts.length; ++j) {
|
|
827
|
+
const contact = this.m_contacts[j];
|
|
828
|
+
contact.solveVelocityConstraint(subStep);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Don't store the TOI contact forces for warm starting
|
|
833
|
+
// because they can be quite large.
|
|
834
|
+
|
|
835
|
+
const h = subStep.dt;
|
|
836
|
+
|
|
837
|
+
// Integrate positions
|
|
838
|
+
for (let i = 0; i < this.m_bodies.length; ++i) {
|
|
839
|
+
const body = this.m_bodies[i];
|
|
840
|
+
|
|
841
|
+
geo.copyVec2(c, body.c_position.c);
|
|
842
|
+
let a = body.c_position.a;
|
|
843
|
+
geo.copyVec2(v, body.c_velocity.v);
|
|
844
|
+
let w = body.c_velocity.w;
|
|
845
|
+
|
|
846
|
+
// Check for large velocities
|
|
847
|
+
geo.scaleVec2(translation, h, v);
|
|
848
|
+
const translationLengthSqr = geo.lengthSqrVec2(translation);
|
|
849
|
+
if (translationLengthSqr > Settings.maxTranslationSquared) {
|
|
850
|
+
const ratio = Settings.maxTranslation / math_sqrt(translationLengthSqr);
|
|
851
|
+
geo.mulVec2(v, ratio);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const rotation = h * w;
|
|
855
|
+
if (rotation * rotation > Settings.maxRotationSquared) {
|
|
856
|
+
const ratio = Settings.maxRotation / math_abs(rotation);
|
|
857
|
+
w *= ratio;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Integrate
|
|
861
|
+
geo.plusScaleVec2(c, h, v);
|
|
862
|
+
a += h * w;
|
|
863
|
+
|
|
864
|
+
geo.copyVec2(body.c_position.c, c);
|
|
865
|
+
body.c_position.a = a;
|
|
866
|
+
geo.copyVec2(body.c_velocity.v, v);
|
|
867
|
+
body.c_velocity.w = w;
|
|
868
|
+
|
|
869
|
+
// Sync bodies
|
|
870
|
+
geo.copyVec2(body.m_sweep.c, c);
|
|
871
|
+
body.m_sweep.a = a;
|
|
872
|
+
geo.copyVec2(body.m_linearVelocity, v);
|
|
873
|
+
body.m_angularVelocity = w;
|
|
874
|
+
body.synchronizeTransform();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
this.postSolveIsland();
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** @internal */
|
|
881
|
+
postSolveIsland(): void {
|
|
882
|
+
for (let c = 0; c < this.m_contacts.length; ++c) {
|
|
883
|
+
const contact = this.m_contacts[c];
|
|
884
|
+
this.m_world.postSolve(contact, contact.m_impulse);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// @ts-ignore
|
|
890
|
+
Solver.TimeStep = TimeStep;
|