solveTOI method
Implementation
void solveTOI(TimeStep step) {
final island = _toiIsland..listener = contactManager.contactListener;
if (_stepComplete) {
for (final b in bodies) {
b.flags &= ~Body.islandFlag;
b.sweep.alpha0 = 0.0;
}
for (final c in contactManager.contacts) {
// Invalidate TOI
c.flags &= ~(Contact.toiFlag | Contact.islandFlag);
c.toiCount = 0;
c.toi = 1.0;
}
}
// Find TOI events and solve them.
for (;;) {
// Find the first TOI.
Contact? minContact;
var minAlpha = 1.0;
for (final contact in contactManager.contacts) {
// Is this contact disabled?
if (contact.isEnabled == false) {
continue;
}
// Prevent excessive sub-stepping.
if (contact.toiCount > settings.maxSubSteps) {
continue;
}
var alpha = 1.0;
if ((contact.flags & Contact.toiFlag) != 0) {
// This contact has a valid cached TOI.
alpha = contact.toi;
} else {
final fixtureA = contact.fixtureA;
final fixtureB = contact.fixtureB;
// Is there a sensor?
if (fixtureA.isSensor || fixtureB.isSensor) {
continue;
}
final bodyA = fixtureA.body;
final bodyB = fixtureB.body;
final typeA = bodyA.bodyType;
final typeB = bodyB.bodyType;
assert(typeA == BodyType.dynamic || typeB == BodyType.dynamic);
final activeA = bodyA.isAwake && typeA != BodyType.static;
final activeB = bodyB.isAwake && typeB != BodyType.static;
// Is at least one body active (awake and dynamic or kinematic)?
if (activeA == false && activeB == false) {
continue;
}
final collideA = bodyA.isBullet || typeA != BodyType.dynamic;
final collideB = bodyB.isBullet || typeB != BodyType.dynamic;
// Are these two non-bullet dynamic bodies?
if (collideA == false && collideB == false) {
continue;
}
// Compute the TOI for this contact.
// Put the sweeps onto the same time interval.
var alpha0 = bodyA.sweep.alpha0;
if (bodyA.sweep.alpha0 < bodyB.sweep.alpha0) {
alpha0 = bodyB.sweep.alpha0;
bodyA.sweep.advance(alpha0);
// NOTE: The following line is ignored due to a false positive
// analyzer warning.
// https://github.com/dart-lang/linter/issues/811
// ignore: invariant_booleans
} else if (bodyB.sweep.alpha0 < bodyA.sweep.alpha0) {
alpha0 = bodyA.sweep.alpha0;
bodyB.sweep.advance(alpha0);
}
assert(alpha0 < 1.0);
final indexA = contact.indexA;
final indexB = contact.indexB;
// Compute the time of impact in interval [0, minTOI]
final input = _toiInput;
input.proxyA.set(fixtureA.shape, indexA);
input.proxyB.set(fixtureB.shape, indexB);
input.sweepA.setFrom(bodyA.sweep);
input.sweepB.setFrom(bodyB.sweep);
input.tMax = 1.0;
toi.timeOfImpact(_toiOutput, input, distance);
// Beta is the fraction of the remaining portion of the .
final beta = _toiOutput.t;
if (_toiOutput.state == TOIOutputState.touching) {
alpha = min(alpha0 + (1.0 - alpha0) * beta, 1.0);
} else {
alpha = 1.0;
}
contact.toi = alpha;
contact.flags |= Contact.toiFlag;
}
if (alpha < minAlpha) {
// This is the minimum TOI found so far.
minContact = contact;
minAlpha = alpha;
}
}
if (minContact == null || 1.0 - 10.0 * settings.epsilon < minAlpha) {
// No more TOI events. Done!
_stepComplete = true;
break;
}
final bodyA = minContact.fixtureA.body;
final bodyB = minContact.fixtureB.body;
_backup1.setFrom(bodyA.sweep);
_backup2.setFrom(bodyB.sweep);
// Advance the bodies to the TOI.
bodyA.advance(minAlpha);
bodyB.advance(minAlpha);
// The TOI contact likely has some new contact points.
minContact.update(contactManager.contactListener);
minContact.flags &= ~Contact.toiFlag;
++minContact.toiCount;
// Is the contact solid?
if (minContact.isEnabled == false || minContact.isTouching() == false) {
// Restore the sweeps.
minContact.isEnabled = false;
bodyA.sweep.setFrom(_backup1);
bodyB.sweep.setFrom(_backup2);
bodyA.synchronizeTransform();
bodyB.synchronizeTransform();
continue;
}
bodyA.setAwake(true);
bodyB.setAwake(true);
// Build the island
island.clear();
island.addBody(bodyA);
island.addBody(bodyB);
island.addContact(minContact);
bodyA.flags |= Body.islandFlag;
bodyB.flags |= Body.islandFlag;
minContact.flags |= Contact.islandFlag;
// Get contacts on bodyA and bodyB.
for (final body in [bodyA, bodyB]) {
if (body.bodyType == BodyType.dynamic) {
for (final contact in body.contacts) {
// Has this contact already been added to the island?
if ((contact.flags & Contact.islandFlag) != 0) {
continue;
}
// Only add static, kinematic, or bullet bodies.
final other = contact.getOtherBody(body);
if (other.bodyType == BodyType.dynamic &&
!body.isBullet &&
!other.isBullet) {
continue;
}
// Skip sensors.
final sensorA = contact.fixtureA.isSensor;
final sensorB = contact.fixtureB.isSensor;
if (sensorA || sensorB) {
continue;
}
// Tentatively advance the body to the TOI.
_backup1.setFrom(other.sweep);
if ((other.flags & Body.islandFlag) == 0) {
other.advance(minAlpha);
}
// Update the contact points
contact.update(contactManager.contactListener);
// Was the contact disabled by the user?
if (contact.isEnabled == false) {
other.sweep.setFrom(_backup1);
other.synchronizeTransform();
continue;
}
// Are there contact points?
if (contact.isTouching() == false) {
other.sweep.setFrom(_backup1);
other.synchronizeTransform();
continue;
}
// Add the contact to the island
contact.flags |= Contact.islandFlag;
island.addContact(contact);
// Has the other body already been added to the island?
if ((other.flags & Body.islandFlag) != 0) {
continue;
}
// Add the other body to the island.
other.flags |= Body.islandFlag;
if (other.bodyType != BodyType.static) {
other.setAwake(true);
}
island.addBody(other);
}
}
}
_subStep.dt = (1.0 - minAlpha) * step.dt;
_subStep.invDt = 1.0 / _subStep.dt;
_subStep.dtRatio = 1.0;
_subStep.positionIterations = 20;
_subStep.velocityIterations = step.velocityIterations;
_subStep.warmStarting = false;
island.solveTOI(_subStep, bodyA.islandIndex, bodyB.islandIndex);
// Reset island flags and synchronize broad-phase proxies.
for (final bodyMeta in island.bodies) {
final body = bodyMeta.body;
body.flags &= ~Body.islandFlag;
if (body.bodyType != BodyType.dynamic) {
continue;
}
body.synchronizeFixtures();
// Invalidate all contact TOIs on this displaced body.
for (final contact in body.contacts) {
contact.flags &= ~(Contact.toiFlag | Contact.islandFlag);
}
}
// Commit fixture proxy movements to the broad-phase so that new contacts
// are created. Also, some contacts can be destroyed.
contactManager.findNewContacts();
if (_subStepping) {
_stepComplete = false;
break;
}
}
}