bulletphysics / bullet3

Bullet Physics SDK: real-time collision detection and multi-physics simulation for VR, games, visual effects, robotics, machine learning etc.
http://bulletphysics.org
Other
12.61k stars 2.87k forks source link

easy activation upon removal API #1425

Closed jl closed 3 years ago

jl commented 6 years ago

Sometimes it is useful to remove an object from the world, but also wake up things surrounding / touching the object.

I've found some recommendations around the net to simply do the following:

void RemoveBodyWithActivation(btDiscreteDynamicsWorld* world, btRigidBody* body) {
  body->activate();
  world->removeRigidBody(body);
}

However, if I remove the bottom of some stacked objects, doing activate() immediately followed by removeRigidBody() does not activate other objects. I have, however, found a working solution: iterate through all pairs in the pair cache and activating the other object in any pair containing the target object to be removed:

// (Class boilerplate removed)
// For each pair, if it contains the object we are removing,
// first activate() the other object in the pair, then clean the pair
// from the cache.
virtual bool WakePairCallback::processOverlap(btBroadphasePair& pair) {
  auto wake_other = [this](btBroadphasePair& pair, btBroadphaseProxy* proxy) {
    auto coll_obj = static_cast<btCollisionObject*>(proxy->m_clientObject);
    printf("waking: %p\n", coll_obj);
    coll_obj->activate();
    pair_cache_->cleanOverlappingPair(pair, dispatcher_);
  };
  if (pair.m_pProxy0 == match_) {
    wake_other(pair, pair.m_pProxy1);
  } else if (pair.m_pProxy1 == match_) {
    wake_other(pair, pair.m_pProxy0);
  }
  return false;
}

void RemoveBodyWithActivation2(btDiscreteDynamicsWorld* world, btRigidBody* body) {
  btDispatcher* dispatcher = world->getDispatcher();
  btBroadphaseProxy* proxy = body->getBroadphaseHandle();

  // Call the above method on all pairs.
  WakePairCallback waker(proxy, world->getPairCache(), dispatcher);
  world->getPairCache()->processAllOverlappingPairs(&waker, dispatcher);

  // Prevent doing another search in btCollisionWorld::removeCollisionOject().
  body->setBroadphaseHandle(nullptr);
  world->getBroadphase()->destroyProxy(proxy, dispatcher);

  // No call to body->activate(), just remove it.
  world->removeRigidBody(body);
}

I'm doing this now from my own code, but it feels kind of hacky to do all this work that reaches into the internals, so proposing an API that might make things easier for other users with the same issue:

class btOverlappingPairCache {
  // Existing interface.
  virtual void cleanProxyFromPairs(btBroadphaseProxy* proxy, btDispatcher* dispatcher) = 0;
  // Proposed additional method.
  virtual void cleanProxyFromPairsAndActivateOthers(btBroadphaseProxy* proxy, btDispatcher* dispatcher) = 0;
};

class btDynamicsWorld {
  // Existing interface.
  virtual void removeRigidBody(btRigidBody* body) = 0;
  // Proposed interface.
  virtual void removeRigidBody(btRigidBody* body, bool activateOthers=false) = 0;
};

If you think this is something that seems reasonable to pursue, I might be able to put together a pull request, at least for concrete btHashedOverlappingPairCache and btDiscreteDynamicsWorld.

For reference, found this forum post where someone had this issue: http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=5530

erwincoumans commented 6 years ago

Yes, it sounds reasonable to make it easier to activate other bodies when removing a body.

If you would add a 'stepSimulation', after 'active' and before 'removeBody', the 'activate' will likely propagate, but since you remove the object before stepping the simulation, the other objects will never be activated.

If you make a small patch (no whitespace), I'll review/merge it.