Collision Filtering

From Physics Simulation Wiki

Jump to: navigation, search


Collision Filtering

Selective collisions

Bullet provides three easy ways to ensure that only certain objects collide with each other: masks, broadphase filter callbacks and nearcallbacks.

It is worth noting that mask-based collision selection happens a lot further up the toolchain than the callback do. In short, if masks are sufficient for your purposes, use them; they perform better and are a lot simpler to use.

Of course, don't try to shoehorn something into a mask-based selection system that clearly doesn't fit there just because performance may be a little better. caveat emptor

Filtering collisions using masks

Bullet supports bitwise masks as a way of deciding whether or not things should collide with other things, or receive collisions.

For example, in a spaceship game, you could have your spaceships ignore collisions with other spaceships [the spaceships would just fly through each other], but always collide with walls [the spaceships always bounce off walls].

Your spaceship needs a callback when it collides with a wall [for example, to produce a “plink” sound], but the walls do nothing when you collide with them so they do not need to receive callbacks.

A third type of object, “powerup”, collides with walls and spaceships. Spaceships do not receive collisions from them, since we don't want the trajectory of the spaceship changed by collecting a powerup. The powerup object modifies the spaceship from its own collision callback.

In order to do this, you need a bit mask for the walls, spaceships, and powerups:

#define BIT(x) (1<<(x))
enum collisiontypes {
    COL_NOTHING = 0, //<Collide with nothing
    COL_SHIP = BIT(0), //<Collide with ships
    COL_WALL = BIT(1), //<Collide with walls
    COL_POWERUP = BIT(2) //<Collide with powerups

int shipCollidesWith = COL_WALL;
int wallCollidesWith = COL_NOTHING;
int powerupCollidesWith = COL_SHIP | COL_WALL;

After setting these up, simply add your body objects to the world as normal, except as the second and third parameters pass your collision type for that body, and the collision mask.

If you are not using a recent enough version of Bullet, you may find this overload of addRigidBody does not exist. In this case, use btDynamicsWorld::addCollisionObject instead of the more usual btDynamicsWorld::addRigidBody. You may have to set the gravity on the rigid body manually in this case.

btRigidBody ship; // Set up the other ship stuff
btRigidBody wall; // Set up the other wall stuff
btRigidBody powerup; // Set up the other powerup stuff

mWorld->addRigidBody(ship, COL_SHIP, shipCollidesWith);
mWorld->addRigidBody(wall, COL_WALL, wallCollidesWith);
mWorld->addRigidBody(powerup, COL_POWERUP, powerupCollidesWith);

Example: If we want to see if a powerup collides with a ship, we need to know the powerup's mask, and the ship's group. Powerup mask: 00000011 (3 in decimal - this was achieved by performing a bitwise OR between the Ship and the Wall groups) Ship group: 00000001

Before checking for a collision, bullet performs an AND between object's A mask, and object's B group. If the result is anything but 0, Bullet will perform collision response. In this example, we'll perform an AND between 00000011 and 00000001 and the result is 00000001. Since that is not 0, Bullet will consider this a positive result and continue on to collision response.

For more information about bitwise OR and AND: Wikipedia's article on Bitwise operations

Note: As of 16/03/10, Bullet uses a signed short int to store the collision groups and the collision masks. This means you can have up to 15 different groups, and 15 different masks.

Note: As of 17/08/10, collision masks must match both ways for there to be a collision. The above example is incorrect. If WALL collides with NOTHING, it will actually collide with nothing even though PLAYER is set to collide with WALL. WALL must collide with PLAYER and PLAYER must collide with WALL for there to be a collision.

Note: Bullet also defines its own collision constants, which are hardcoded as defaults into a few places. So some care is needed to make use of all 15 possible custom types. First, be sure to always specify your groups and masks explicitly when calling various methods such as btDiscreteDynamicsWorld::addRigidBody. A bit less obvious, know that many default callback structures initialize data members. For example, RayResultCallback has a member m_collisionFilterGroup which is initialized to DefaultFilter. There is no constructor argument for it (and perhaps a more intuitive default would be AllFilter), so it can be easy to miss. You can either set the member yourself after construction or derive your own callback types.

Custom collision filtering

It's worth noting that if you are using masks, and they're sufficient for your needs, then you do not need a custom collision filtering.

If you have more types of objects than bits available to you in the masks above, or some collisions are enabled or disabled based on other factors, then there are several ways to register callbacks to that implements custom logic and only passes on collisions that are the ones you want:

Filtering Collisions Using a Broadphase Filter Callback

One efficient way is to register a broadphase filter callback. This callback is called at a very early stage in the collision pipeline, and prevents collision pairs from being generated.

struct YourOwnFilterCallback : public btOverlapFilterCallback
	// return true when pairs need collision
	virtual bool	needBroadphaseCollision(btBroadphaseProxy* proxy0,btBroadphaseProxy* proxy1) const
		bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
		collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);

		//add some additional logic here that modified 'collides'
		return collides;

And then create an object of this class and register this callback using:

btOverlapFilterCallback * filterCallback = new YourOwnFilterCallback();

Filtering Collisions Using a Custom NearCallback

Another callback can be registered during the narrowphase, when all pairs are generated by the broadphase. The btCollisionDispatcher::dispatchAllCollisionPairs calls this narrowphase nearcallback for each pair that passes the 'btCollisionDispatcher::needsCollision' test. You can customize this nearcallback:

void MyNearCallback(btBroadphasePair& collisionPair,
  btCollisionDispatcher& dispatcher, const btDispatcherInfo& dispatchInfo) {

    // Do your collision logic here
    // Only dispatch the Bullet collision information if you want the physics to continue
    dispatcher.defaultNearCallback(collisionPair, dispatcher, dispatchInfo);


Deriving your own class from btCollisionDispatcher

For even more fine grain control over the collision dispatch, you can derive your own class from btCollisionDispatcher and override one or more of the following methods:

virtual bool	needsCollision(btCollisionObject* body0,btCollisionObject* body1);
virtual bool	needsResponse(btCollisionObject* body0,btCollisionObject* body1);
virtual void	dispatchAllCollisionPairs(btOverlappingPairCache* pairCache,const btDispatcherInfo& dispatchInfo,btDispatcher* dispatcher);
Personal tools