!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> » Oolong/Bullet Collision Detection -- Mobile Perspectives

Oolong/Bullet Collision Detection



By paul ~ April 11th, 2009. Filed under: Resources.

One of the things that I found incredibly attractive about Oolong and the Bullet physics package (which is embedded in Oolong) was that one could get a fantastic simulation of objects bouncing around and interacting with each other and fixed objects with very little code.  The "Falling Boxes" example in Oolong is really cool, even though the code is simple.  However, it’s pretty important to be able to "react" to these collisions, and it took me a bit to figure out the best way to accomplish that.

What I was hoping for was a way to register a call-back with a rigid body such that my function or method would get called whenever the body collided with something.  Further, after working with the ray testing functionality, I was hoping for the same kind of filtering capabilities that the ray testing interface provides. Unfortunately, that functionality doesn’t quite exist, so you have to "roll your own" from what is provided.

The best reference on collision detection in Bullet is, I believe, this one .  After reading through this carefully and considering the options, I decided to use the first strategy, which is to simply loop through the manifolds at the end of each simulation cycle.  I quickly discovered that there were several issues I needed to deal with:

  • A manifold will appear in the list and contain objects when there are no points of collision (getNumContacts() == 0) between the objects.  This surprised me!  Therefore, even though I didn’t care about the actual points of contact in my application, I needed to check the number of points and only register a collision if there were one or more.
  • Objects may remain in contact for a number of cycles.  The behavior I wanted was to be notified when objects came into contact and again when they separated.  In order to achieve this, I had to implement some state in my objects that indicated what other objects they were in contact with.  I used STL sets for this purpose, as shown below…  (4/24/2009 — I have noticed that the B-Tree implementation underlying the STL set is showing up in profiling with lots of calls for allocation.  While this is not hurting the performance of my app yet, I expect that it will at some point.  I will probably end up optimizing the set implementation, probably through the use of a custom allocator.)

Here is the code for the collision detection method, which is called after each simulation cycle:

void BMWWorld::_detectCollisions() {
  // For information on collision detection using Bullet, see:
  // http://www.bulletphysics.com/mediawiki-1.5.8/index.php?title=Collision_Callbacks_and_Triggers
  //
  // Detect collisions and notify the objects involved.
  int numManifolds = this->numManifolds();
  BMWObjectSet *pNewNotifications = NULL;
  for (int i=0; i < numManifolds; i++) {
    btPersistentManifold* contactManifold = this->contactManifold(i);
    int numContacts = contactManifold->getNumContacts();
    if (numContacts > 0) {
      // We only notify the collision if there is a point of contact.  I suppose it's possible
      // to have really rapid, transient collisions that occurred during the simulation but
      // are still not in contact when the simulation step comes to an end.  I think our system
      // is slow enough that we can ignore these safely, but might want to keep an eye on this
      // as a future enhancement.
      // Call collision handlers for each object.
      btCollisionObject* obA = static_cast<btCollisionObject*>(contactManifold->getBody0());
      BMWObject* pObA = static_cast<BMWObject*>(obA->getUserPointer());
      btCollisionObject* obB = static_cast<btCollisionObject*>(contactManifold->getBody1());
      BMWObject* pObB = static_cast<BMWObject*>(obB->getUserPointer());
      pObA->_notifyCollision(pObB);
      pObB->_notifyCollision(pObA);
      if (pNewNotifications == NULL)
        pNewNotifications = new BMWObjectSet;
      pNewNotifications->insert(pObA);
      pNewNotifications->insert(pObB);
    }

    // The following code allows you to find out where the objects are in contact
    // with other.
//    	for (int j=0; j < numContacts; j++) {
//    		btManifoldPoint& pt = contactManifold->getContactPoint(j);
//
//    		btVector3 ptA = pt.getPositionWorldOnA();
//    		btVector3 ptB = pt.getPositionWorldOnB();
//    	}
//
//	// Clear all contact points out of the manifold cache
//    	contactManifold->clearManifold();
  }

  // All collisions reported.  Notify all objects in the previous notification set that we're done with
  // collision processing.  That way they can notify any handlers that collisions are complete.
  if (_pLastNotifySet != NULL) {
    BMWObjectSet::iterator itr;
    for (itr=_pLastNotifySet->begin(); itr != _pLastNotifySet->end(); itr++)
      (*itr)->_collisionDetectionComplete();
    delete _pLastNotifySet;
  }

  // Replace the notification set with the new notification set.
  _pLastNotifySet = pNewNotifications;
} // _detectCollisions()

I then simply created a pure virtual collision handler interface and implemented it for objects for which I need to be notified about collisions.  Here it is:

// Objects that need to handle collisions should derive from this and set
// the handler in the BMWObject.
class BMWCollision {
public:
  BMWCollision() {};
  virtual ~BMWCollision() {};

  // pObj is the object with which the collision has occurred.  This is called when
  // contact is first detected with the other object.
  virtual void handleCollision(BMWObject* pOther) = 0;

  // Called with the set of objects that we were in contact with that we're no longer
  // in contact with.  May be null or empty!
  virtual void collisionComplete(BMWObjectSet* pCollSet) = 0;
}; // class BMWCollision

This interface is used by the objects when they determine that a collision has occurred or been completed, as shown here:

void BMWObject::_notifyCollision(BMWObject* pObj) {
  if (_pCollHandler == NULL)
    return;

  // No collision processing if there is not a handler defined.
  // Do screening of collision notifications.  The handler is
  // only called if the "other" object is not in the LastCollSet.
  // The "other" object is always added to the NewCollSet.  When
  // all notifications are complete (the collisionDetectionComplete()
  // method is called), the objects that are in the LastCollSet but
  // not in the NewCollSet are sent to the handler.  This way the
  // handler can be appropriately called for both the detection of a
  // collision and also when the collision is complete.
  if (_pNewCollSet == NULL)
    _pNewCollSet = new BMWObjectSet;
  _pNewCollSet->insert(pObj);

  if ((_pLastCollSet == NULL) || (_pLastCollSet->count(pObj) == 0))
    _pCollHandler->handleCollision(pObj);
} // notifyCollision()

void BMWObject::_collisionDetectionComplete() {
  if (_pCollHandler == NULL)
    return;

  // Remove objects in the new collision set from the last collision set
  BMWObjectSet::iterator itr;
  if ((_pNewCollSet != NULL) && (_pLastCollSet != NULL)) {
    for (itr=_pNewCollSet->begin(); itr != _pNewCollSet->end(); itr++)
        _pLastCollSet->erase(*itr);
  }

  // Notify collision handler about objects we're no longer colliding with
  // (those remaining in the last set.)
  _pCollHandler->collisionComplete(_pLastCollSet);

  // Delete the old set and swap in the new set.
  delete _pLastCollSet;
  _pLastCollSet = _pNewCollSet;
  _pNewCollSet = NULL;
} // collisionDetectionComplete()

Again, if you see problems or improvements, please share with the rest of us!  This is definitely a learning process on my part!

Comments are closed.