solveTOI method

void solveTOI(
  1. TimeStep step
)

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);

        // 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;
    }
  }
}