-
Notifications
You must be signed in to change notification settings - Fork 266
/
contacts.cpp
706 lines (594 loc) · 24.6 KB
/
contacts.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
/*
* Implementation file for the contact resolution system.
*
* Part of the Cyclone physics system.
*
* Copyright (c) Icosagon 2003. All Rights Reserved.
*
* This software is distributed under licence. Use of this software
* implies agreement with all terms and conditions of the accompanying
* software licence.
*/
#include <cyclone/contacts.h>
#include <memory.h>
#include <assert.h>
using namespace cyclone;
// Contact implementation
void Contact::setBodyData(RigidBody* one, RigidBody *two,
real friction, real restitution)
{
Contact::body[0] = one;
Contact::body[1] = two;
Contact::friction = friction;
Contact::restitution = restitution;
}
void Contact::matchAwakeState()
{
// Collisions with the world never cause a body to wake up.
if (!body[1]) return;
bool body0awake = body[0]->getAwake();
bool body1awake = body[1]->getAwake();
// Wake up only the sleeping one
if (body0awake ^ body1awake) {
if (body0awake) body[1]->setAwake();
else body[0]->setAwake();
}
}
/*
* Swaps the bodies in the current contact, so body 0 is at body 1 and
* vice versa. This also changes the direction of the contact normal,
* but doesn't update any calculated internal data. If you are calling
* this method manually, then call calculateInternals afterwards to
* make sure the internal data is up to date.
*/
void Contact::swapBodies()
{
contactNormal *= -1;
RigidBody *temp = body[0];
body[0] = body[1];
body[1] = temp;
}
/*
* Constructs an arbitrary orthonormal basis for the contact. This is
* stored as a 3x3 matrix, where each vector is a column (in other
* words the matrix transforms contact space into world space). The x
* direction is generated from the contact normal, and the y and z
* directionss are set so they are at right angles to it.
*/
inline
void Contact::calculateContactBasis()
{
Vector3 contactTangent[2];
// Check whether the Z-axis is nearer to the X or Y axis
if (real_abs(contactNormal.x) > real_abs(contactNormal.y))
{
// Scaling factor to ensure the results are normalised
const real s = (real)1.0f/real_sqrt(contactNormal.z*contactNormal.z +
contactNormal.x*contactNormal.x);
// The new X-axis is at right angles to the world Y-axis
contactTangent[0].x = contactNormal.z*s;
contactTangent[0].y = 0;
contactTangent[0].z = -contactNormal.x*s;
// The new Y-axis is at right angles to the new X- and Z- axes
contactTangent[1].x = contactNormal.y*contactTangent[0].x;
contactTangent[1].y = contactNormal.z*contactTangent[0].x -
contactNormal.x*contactTangent[0].z;
contactTangent[1].z = -contactNormal.y*contactTangent[0].x;
}
else
{
// Scaling factor to ensure the results are normalised
const real s = (real)1.0/real_sqrt(contactNormal.z*contactNormal.z +
contactNormal.y*contactNormal.y);
// The new X-axis is at right angles to the world X-axis
contactTangent[0].x = 0;
contactTangent[0].y = -contactNormal.z*s;
contactTangent[0].z = contactNormal.y*s;
// The new Y-axis is at right angles to the new X- and Z- axes
contactTangent[1].x = contactNormal.y*contactTangent[0].z -
contactNormal.z*contactTangent[0].y;
contactTangent[1].y = -contactNormal.x*contactTangent[0].z;
contactTangent[1].z = contactNormal.x*contactTangent[0].y;
}
// Make a matrix from the three vectors.
contactToWorld.setComponents(
contactNormal,
contactTangent[0],
contactTangent[1]);
}
Vector3 Contact::calculateLocalVelocity(unsigned bodyIndex, real duration)
{
RigidBody *thisBody = body[bodyIndex];
// Work out the velocity of the contact point.
Vector3 velocity =
thisBody->getRotation() % relativeContactPosition[bodyIndex];
velocity += thisBody->getVelocity();
// Turn the velocity into contact-coordinates.
Vector3 contactVelocity = contactToWorld.transformTranspose(velocity);
// Calculate the ammount of velocity that is due to forces without
// reactions.
Vector3 accVelocity = thisBody->getLastFrameAcceleration() * duration;
// Calculate the velocity in contact-coordinates.
accVelocity = contactToWorld.transformTranspose(accVelocity);
// We ignore any component of acceleration in the contact normal
// direction, we are only interested in planar acceleration
accVelocity.x = 0;
// Add the planar velocities - if there's enough friction they will
// be removed during velocity resolution
contactVelocity += accVelocity;
// And return it
return contactVelocity;
}
void Contact::calculateDesiredDeltaVelocity(real duration)
{
const static real velocityLimit = (real)0.25f;
// Calculate the acceleration induced velocity accumulated this frame
real velocityFromAcc = 0;
if (body[0]->getAwake())
{
velocityFromAcc+=
body[0]->getLastFrameAcceleration() * duration * contactNormal;
}
if (body[1] && body[1]->getAwake())
{
velocityFromAcc -=
body[1]->getLastFrameAcceleration() * duration * contactNormal;
}
// If the velocity is very slow, limit the restitution
real thisRestitution = restitution;
if (real_abs(contactVelocity.x) < velocityLimit)
{
thisRestitution = (real)0.0f;
}
// Combine the bounce velocity with the removed
// acceleration velocity.
desiredDeltaVelocity =
-contactVelocity.x
-thisRestitution * (contactVelocity.x - velocityFromAcc);
}
void Contact::calculateInternals(real duration)
{
// Check if the first object is NULL, and swap if it is.
if (!body[0]) swapBodies();
assert(body[0]);
// Calculate an set of axis at the contact point.
calculateContactBasis();
// Store the relative position of the contact relative to each body
relativeContactPosition[0] = contactPoint - body[0]->getPosition();
if (body[1]) {
relativeContactPosition[1] = contactPoint - body[1]->getPosition();
}
// Find the relative velocity of the bodies at the contact point.
contactVelocity = calculateLocalVelocity(0, duration);
if (body[1]) {
contactVelocity -= calculateLocalVelocity(1, duration);
}
// Calculate the desired change in velocity for resolution
calculateDesiredDeltaVelocity(duration);
}
void Contact::applyVelocityChange(Vector3 velocityChange[2],
Vector3 rotationChange[2])
{
// Get hold of the inverse mass and inverse inertia tensor, both in
// world coordinates.
Matrix3 inverseInertiaTensor[2];
body[0]->getInverseInertiaTensorWorld(&inverseInertiaTensor[0]);
if (body[1])
body[1]->getInverseInertiaTensorWorld(&inverseInertiaTensor[1]);
// We will calculate the impulse for each contact axis
Vector3 impulseContact;
if (friction == (real)0.0)
{
// Use the short format for frictionless contacts
impulseContact = calculateFrictionlessImpulse(inverseInertiaTensor);
}
else
{
// Otherwise we may have impulses that aren't in the direction of the
// contact, so we need the more complex version.
impulseContact = calculateFrictionImpulse(inverseInertiaTensor);
}
// Convert impulse to world coordinates
Vector3 impulse = contactToWorld.transform(impulseContact);
// Split in the impulse into linear and rotational components
Vector3 impulsiveTorque = relativeContactPosition[0] % impulse;
rotationChange[0] = inverseInertiaTensor[0].transform(impulsiveTorque);
velocityChange[0].clear();
velocityChange[0].addScaledVector(impulse, body[0]->getInverseMass());
// Apply the changes
body[0]->addVelocity(velocityChange[0]);
body[0]->addRotation(rotationChange[0]);
if (body[1])
{
// Work out body one's linear and angular changes
Vector3 impulsiveTorque = impulse % relativeContactPosition[1];
rotationChange[1] = inverseInertiaTensor[1].transform(impulsiveTorque);
velocityChange[1].clear();
velocityChange[1].addScaledVector(impulse, -body[1]->getInverseMass());
// And apply them.
body[1]->addVelocity(velocityChange[1]);
body[1]->addRotation(rotationChange[1]);
}
}
inline
Vector3 Contact::calculateFrictionlessImpulse(Matrix3 * inverseInertiaTensor)
{
Vector3 impulseContact;
// Build a vector that shows the change in velocity in
// world space for a unit impulse in the direction of the contact
// normal.
Vector3 deltaVelWorld = relativeContactPosition[0] % contactNormal;
deltaVelWorld = inverseInertiaTensor[0].transform(deltaVelWorld);
deltaVelWorld = deltaVelWorld % relativeContactPosition[0];
// Work out the change in velocity in contact coordiantes.
real deltaVelocity = deltaVelWorld * contactNormal;
// Add the linear component of velocity change
deltaVelocity += body[0]->getInverseMass();
// Check if we need to the second body's data
if (body[1])
{
// Go through the same transformation sequence again
Vector3 deltaVelWorld = relativeContactPosition[1] % contactNormal;
deltaVelWorld = inverseInertiaTensor[1].transform(deltaVelWorld);
deltaVelWorld = deltaVelWorld % relativeContactPosition[1];
// Add the change in velocity due to rotation
deltaVelocity += deltaVelWorld * contactNormal;
// Add the change in velocity due to linear motion
deltaVelocity += body[1]->getInverseMass();
}
// Calculate the required size of the impulse
impulseContact.x = desiredDeltaVelocity / deltaVelocity;
impulseContact.y = 0;
impulseContact.z = 0;
return impulseContact;
}
inline
Vector3 Contact::calculateFrictionImpulse(Matrix3 * inverseInertiaTensor)
{
Vector3 impulseContact;
real inverseMass = body[0]->getInverseMass();
// The equivalent of a cross product in matrices is multiplication
// by a skew symmetric matrix - we build the matrix for converting
// between linear and angular quantities.
Matrix3 impulseToTorque;
impulseToTorque.setSkewSymmetric(relativeContactPosition[0]);
// Build the matrix to convert contact impulse to change in velocity
// in world coordinates.
Matrix3 deltaVelWorld = impulseToTorque;
deltaVelWorld *= inverseInertiaTensor[0];
deltaVelWorld *= impulseToTorque;
deltaVelWorld *= -1;
// Check if we need to add body two's data
if (body[1])
{
// Set the cross product matrix
impulseToTorque.setSkewSymmetric(relativeContactPosition[1]);
// Calculate the velocity change matrix
Matrix3 deltaVelWorld2 = impulseToTorque;
deltaVelWorld2 *= inverseInertiaTensor[1];
deltaVelWorld2 *= impulseToTorque;
deltaVelWorld2 *= -1;
// Add to the total delta velocity.
deltaVelWorld += deltaVelWorld2;
// Add to the inverse mass
inverseMass += body[1]->getInverseMass();
}
// Do a change of basis to convert into contact coordinates.
Matrix3 deltaVelocity = contactToWorld.transpose();
deltaVelocity *= deltaVelWorld;
deltaVelocity *= contactToWorld;
// Add in the linear velocity change
deltaVelocity.data[0] += inverseMass;
deltaVelocity.data[4] += inverseMass;
deltaVelocity.data[8] += inverseMass;
// Invert to get the impulse needed per unit velocity
Matrix3 impulseMatrix = deltaVelocity.inverse();
// Find the target velocities to kill
Vector3 velKill(desiredDeltaVelocity,
-contactVelocity.y,
-contactVelocity.z);
// Find the impulse to kill target velocities
impulseContact = impulseMatrix.transform(velKill);
// Check for exceeding friction
real planarImpulse = real_sqrt(
impulseContact.y*impulseContact.y +
impulseContact.z*impulseContact.z
);
if (planarImpulse > impulseContact.x * friction)
{
// We need to use dynamic friction
impulseContact.y /= planarImpulse;
impulseContact.z /= planarImpulse;
impulseContact.x = deltaVelocity.data[0] +
deltaVelocity.data[1]*friction*impulseContact.y +
deltaVelocity.data[2]*friction*impulseContact.z;
impulseContact.x = desiredDeltaVelocity / impulseContact.x;
impulseContact.y *= friction * impulseContact.x;
impulseContact.z *= friction * impulseContact.x;
}
return impulseContact;
}
void Contact::applyPositionChange(Vector3 linearChange[2],
Vector3 angularChange[2],
real penetration)
{
const real angularLimit = (real)0.2f;
real angularMove[2];
real linearMove[2];
real totalInertia = 0;
real linearInertia[2];
real angularInertia[2];
// We need to work out the inertia of each object in the direction
// of the contact normal, due to angular inertia only.
for (unsigned i = 0; i < 2; i++) if (body[i])
{
Matrix3 inverseInertiaTensor;
body[i]->getInverseInertiaTensorWorld(&inverseInertiaTensor);
// Use the same procedure as for calculating frictionless
// velocity change to work out the angular inertia.
Vector3 angularInertiaWorld =
relativeContactPosition[i] % contactNormal;
angularInertiaWorld =
inverseInertiaTensor.transform(angularInertiaWorld);
angularInertiaWorld =
angularInertiaWorld % relativeContactPosition[i];
angularInertia[i] =
angularInertiaWorld * contactNormal;
// The linear component is simply the inverse mass
linearInertia[i] = body[i]->getInverseMass();
// Keep track of the total inertia from all components
totalInertia += linearInertia[i] + angularInertia[i];
// We break the loop here so that the totalInertia value is
// completely calculated (by both iterations) before
// continuing.
}
// Loop through again calculating and applying the changes
for (unsigned i = 0; i < 2; i++) if (body[i])
{
// The linear and angular movements required are in proportion to
// the two inverse inertias.
real sign = (i == 0)?1:-1;
angularMove[i] =
sign * penetration * (angularInertia[i] / totalInertia);
linearMove[i] =
sign * penetration * (linearInertia[i] / totalInertia);
// To avoid angular projections that are too great (when mass is large
// but inertia tensor is small) limit the angular move.
Vector3 projection = relativeContactPosition[i];
projection.addScaledVector(
contactNormal,
-relativeContactPosition[i].scalarProduct(contactNormal)
);
// Use the small angle approximation for the sine of the angle (i.e.
// the magnitude would be sine(angularLimit) * projection.magnitude
// but we approximate sine(angularLimit) to angularLimit).
real maxMagnitude = angularLimit * projection.magnitude();
if (angularMove[i] < -maxMagnitude)
{
real totalMove = angularMove[i] + linearMove[i];
angularMove[i] = -maxMagnitude;
linearMove[i] = totalMove - angularMove[i];
}
else if (angularMove[i] > maxMagnitude)
{
real totalMove = angularMove[i] + linearMove[i];
angularMove[i] = maxMagnitude;
linearMove[i] = totalMove - angularMove[i];
}
// We have the linear amount of movement required by turning
// the rigid body (in angularMove[i]). We now need to
// calculate the desired rotation to achieve that.
if (angularMove[i] == 0)
{
// Easy case - no angular movement means no rotation.
angularChange[i].clear();
}
else
{
// Work out the direction we'd like to rotate in.
Vector3 targetAngularDirection =
relativeContactPosition[i].vectorProduct(contactNormal);
Matrix3 inverseInertiaTensor;
body[i]->getInverseInertiaTensorWorld(&inverseInertiaTensor);
// Work out the direction we'd need to rotate to achieve that
angularChange[i] =
inverseInertiaTensor.transform(targetAngularDirection) *
(angularMove[i] / angularInertia[i]);
}
// Velocity change is easier - it is just the linear movement
// along the contact normal.
linearChange[i] = contactNormal * linearMove[i];
// Now we can start to apply the values we've calculated.
// Apply the linear movement
Vector3 pos;
body[i]->getPosition(&pos);
pos.addScaledVector(contactNormal, linearMove[i]);
body[i]->setPosition(pos);
// And the change in orientation
Quaternion q;
body[i]->getOrientation(&q);
q.addScaledVector(angularChange[i], ((real)1.0));
body[i]->setOrientation(q);
// We need to calculate the derived data for any body that is
// asleep, so that the changes are reflected in the object's
// data. Otherwise the resolution will not change the position
// of the object, and the next collision detection round will
// have the same penetration.
if (!body[i]->getAwake()) body[i]->calculateDerivedData();
}
}
// Contact resolver implementation
ContactResolver::ContactResolver(unsigned iterations,
real velocityEpsilon,
real positionEpsilon)
{
setIterations(iterations, iterations);
setEpsilon(velocityEpsilon, positionEpsilon);
}
ContactResolver::ContactResolver(unsigned velocityIterations,
unsigned positionIterations,
real velocityEpsilon,
real positionEpsilon)
{
setIterations(velocityIterations);
setEpsilon(velocityEpsilon, positionEpsilon);
}
void ContactResolver::setIterations(unsigned iterations)
{
setIterations(iterations, iterations);
}
void ContactResolver::setIterations(unsigned velocityIterations,
unsigned positionIterations)
{
ContactResolver::velocityIterations = velocityIterations;
ContactResolver::positionIterations = positionIterations;
}
void ContactResolver::setEpsilon(real velocityEpsilon,
real positionEpsilon)
{
ContactResolver::velocityEpsilon = velocityEpsilon;
ContactResolver::positionEpsilon = positionEpsilon;
}
void ContactResolver::resolveContacts(Contact *contacts,
unsigned numContacts,
real duration)
{
// Make sure we have something to do.
if (numContacts == 0) return;
if (!isValid()) return;
// Prepare the contacts for processing
prepareContacts(contacts, numContacts, duration);
// Resolve the interpenetration problems with the contacts.
adjustPositions(contacts, numContacts, duration);
// Resolve the velocity problems with the contacts.
adjustVelocities(contacts, numContacts, duration);
}
void ContactResolver::prepareContacts(Contact* contacts,
unsigned numContacts,
real duration)
{
// Generate contact velocity and axis information.
Contact* lastContact = contacts + numContacts;
for (Contact* contact=contacts; contact < lastContact; contact++)
{
// Calculate the internal contact data (inertia, basis, etc).
contact->calculateInternals(duration);
}
}
void ContactResolver::adjustVelocities(Contact *c,
unsigned numContacts,
real duration)
{
Vector3 velocityChange[2], rotationChange[2];
Vector3 deltaVel;
// iteratively handle impacts in order of severity.
velocityIterationsUsed = 0;
while (velocityIterationsUsed < velocityIterations)
{
// Find contact with maximum magnitude of probable velocity change.
real max = velocityEpsilon;
unsigned index = numContacts;
for (unsigned i = 0; i < numContacts; i++)
{
if (c[i].desiredDeltaVelocity > max)
{
max = c[i].desiredDeltaVelocity;
index = i;
}
}
if (index == numContacts) break;
// Match the awake state at the contact
c[index].matchAwakeState();
// Do the resolution on the contact that came out top.
c[index].applyVelocityChange(velocityChange, rotationChange);
// With the change in velocity of the two bodies, the update of
// contact velocities means that some of the relative closing
// velocities need recomputing.
for (unsigned i = 0; i < numContacts; i++)
{
// Check each body in the contact
for (unsigned b = 0; b < 2; b++) if (c[i].body[b])
{
// Check for a match with each body in the newly
// resolved contact
for (unsigned d = 0; d < 2; d++)
{
if (c[i].body[b] == c[index].body[d])
{
deltaVel = velocityChange[d] +
rotationChange[d].vectorProduct(
c[i].relativeContactPosition[b]);
// The sign of the change is negative if we're dealing
// with the second body in a contact.
c[i].contactVelocity +=
c[i].contactToWorld.transformTranspose(deltaVel)
* (b?-1:1);
c[i].calculateDesiredDeltaVelocity(duration);
}
}
}
}
velocityIterationsUsed++;
}
}
void ContactResolver::adjustPositions(Contact *c,
unsigned numContacts,
real duration)
{
unsigned i,index;
Vector3 linearChange[2], angularChange[2];
real max;
Vector3 deltaPosition;
// iteratively resolve interpenetrations in order of severity.
positionIterationsUsed = 0;
while (positionIterationsUsed < positionIterations)
{
// Find biggest penetration
max = positionEpsilon;
index = numContacts;
for (i=0; i<numContacts; i++)
{
if (c[i].penetration > max)
{
max = c[i].penetration;
index = i;
}
}
if (index == numContacts) break;
// Match the awake state at the contact
c[index].matchAwakeState();
// Resolve the penetration.
c[index].applyPositionChange(
linearChange,
angularChange,
max);
// Again this action may have changed the penetration of other
// bodies, so we update contacts.
for (i = 0; i < numContacts; i++)
{
// Check each body in the contact
for (unsigned b = 0; b < 2; b++) if (c[i].body[b])
{
// Check for a match with each body in the newly
// resolved contact
for (unsigned d = 0; d < 2; d++)
{
if (c[i].body[b] == c[index].body[d])
{
deltaPosition = linearChange[d] +
angularChange[d].vectorProduct(
c[i].relativeContactPosition[b]);
// The sign of the change is positive if we're
// dealing with the second body in a contact
// and negative otherwise (because we're
// subtracting the resolution)..
c[i].penetration +=
deltaPosition.scalarProduct(c[i].contactNormal)
* (b?1:-1);
}
}
}
}
positionIterationsUsed++;
}
}