Box2D C++ tutorials - Collision filtering

Last edited: July 14 2013

Chinese version -> 中文

Collision filtering


So far in every scene we have made, all the fixtures were able to collide with all the other fixtures. That is the default behaviour, but it's also possible to set up 'collision filters' to provide finer control over which fixtures can collide with each other. Collision filtering is implemented by setting some flags in the fixture definition when we create the fixture. These flags are:
  • categoryBits
  • maskBits
  • groupIndex
Each of the 'bits' values are a 16 bit integer so you can have up to 16 different categories for collision. There is a little more to it than that though, because it is the combination of these values that determines whether two fixtures will collide. The group index can be used to override the category/mask settings for a given set of fixtures.

Category and mask bits


The categoryBits flag can be thought of as the fixture saying 'I am a ...', and the maskBits is like saying 'I will collide with a ...'. The important point is that these conditions must be satisfied for both fixtures in order for collision to be allowed.

For example let say you have two categories, cat and mouse. The cats might say 'I am a cat and I will collide with cats and mice', but mice generally not being so interested in colliding with cats, could say 'I am a mouse and I will collide with mice'. With this set of rules, a cat/cat pair will collide, and a mouse/mouse pair will collide, but a cat/mouse pair will not collide (even though the cats were ok with it). Specifically, the check is done by a bitwise and of these two flags, so it might help to see the code:
1
2
3
  bool collide =
          (filterA.maskBits & filterB.categoryBits) != 0 &&
          (filterA.categoryBits & filterB.maskBits) != 0;
The default values are 0x0001 for categoryBits and 0xFFFF for maskBits, or in other words every fixture says 'I am a thing and I will collide with every other thing', and since all the fixtures have the same rules we found that everything was indeed colliding with everything else.

Let's experiment with changing these flags to see how they can be used. We need a scenario with many entities and we want to see them all bumping around together without flying off the screen, so I'll use the scene from the end of the drawing your own objects topic where we had a bunch of circular objects inside a 'fence': Collision filtering By now you should have a pretty good knowledge of how to set up a scene like this so I won't be covering it here. If you want to use your own scene the important point is to have many bodies that you can set a different size and color for.

In this example we want to set different size and color for each entity, and leave them as that color for the duration of the test. We already have a parameter in the constructor for a radius, so add the necessary code to set a color as well, and use it when rendering. We will also add parameters to set the categoryBits and maskBits in each entity:
1
2
3
4
5
6
7
8
9
10
11
12
  //Ball class member variable
  b2Color m_color;
  
  //edit Ball constructor
  Ball(b2World* world, float radius, b2Color color, uint16 categoryBits, uint16 maskBits) {
    m_color = color;  
    ...
    myFixtureDef.filter.categoryBits = categoryBits;
    myFixtureDef.filter.maskBits = maskBits;
  
  //in Ball::render
  glColor3f(m_color.r, m_color.g, m_color.b);
Ok now what will we use the size and color for? A good example for this feature is a top-down battlefield scene where you have vehicles of different categories, only some of which should collide with each other, eg. surface vehicles should not collide with aircraft. Let's set up a scenario where we have ships and aircraft, and each entity will also have a friendly or enemy status. We will use size to visually symbolize whether an entity is a ship or a plane, and color to show friendly/enemy status. Let's throw a bunch of entities into the scene, leaving the flags blank for now.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  //in FooTest constructor
  b2Color red(1,0,0);
  b2Color green(0,1,0);
  
  //large and green are friendly ships
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 3, green, 0, 0 ) );
  //large and red are enemy ships
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 3, red, 0, 0 ) );
  //small and green are friendly aircraft
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 1, green, 0, 0 ) );
  //small and red are enemy aircraft
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 1, red, 0, 0 ) );
I would also turn off gravity to make things easier to observe. You should have something like this: Collision filtering It's immediately obvious that nothing is colliding with anything anymore. This is what you get if the maskBits are zero in a fixture, it will never collide. However that's not what we wanted, so let's look at how to get more than just simple 'all or nothing' control. We'll use the following rules to set the allowable collisions between entities:
  1. All vehicles collide with the boundary
  2. Ships and aircraft do not collide
  3. All ships collide with all other ships
  4. Aircraft will collide with opposition aircraft, but not with their teammates
That seems like a pretty complex situation, but if we go through it from the point of view of each category, it's not so bad. Firstly let's define some bit flags to use for each category:
1
2
3
4
5
6
7
  enum _entityCategory {
    BOUNDARY =          0x0001,
    FRIENDLY_SHIP =     0x0002,
    ENEMY_SHIP =        0x0004,
    FRIENDLY_AIRCRAFT = 0x0008,
    ENEMY_AIRCRAFT =    0x0010,
  };
Since the default category for a fixture is 1, this arrangement means we don't need to do anything special for the boundary fixture. For the other ones, consider the 'I am a ... and I collide with ...' for each type of vehicle:
Entity I am a ...
(categoryBits)
I collide with ...
(maskBits)
Friendly ship FRIENDLY_SHIP BOUNDARY | FRIENDLY_SHIP | ENEMY_SHIP
Enemy ship ENEMY_SHIP BOUNDARY | FRIENDLY_SHIP | ENEMY_SHIP
Friendly aircraft FRIENDLY_AIRCRAFT BOUNDARY | ENEMY_AIRCRAFT
Enemy aircraft ENEMY_AIRCRAFT BOUNDARY | FRIENDLY_AIRCRAFT
So using the above as a guide we can go back and set the appropriate parameters when creating each entity.
1
2
3
4
5
6
7
8
9
10
11
12
  //large and green are friendly ships
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 3, green, FRIENDLY_SHIP, BOUNDARY | FRIENDLY_SHIP | ENEMY_SHIP ) );
  //large and red are enemy ships
  for (int i = 0; i < 3; i++)
    balls.push_back( new Ball(m_world, 3, red, ENEMY_SHIP, BOUNDARY | FRIENDLY_SHIP | ENEMY_SHIP ) );
  //small and green are friendly aircraft
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 1, green, FRIENDLY_AIRCRAFT, BOUNDARY | ENEMY_AIRCRAFT ) );
  //small and red are enemy aircraft
  for (int i = 0; i < 3; i++) 
    balls.push_back( new Ball(m_world, 1, red, ENEMY_AIRCRAFT, BOUNDARY | FRIENDLY_AIRCRAFT ) );
Now running this you should find that the rules specified above are fulfilled. Collision filtering

Using group indexes


The groupIndex flag of a fixture can be used to override the category and mask settings above. As the name implies it can be useful to group together fixtures that should either always collide, or never collide. The groupIndex is used as a signed integer instead of a bitflag. Here's how it works - read it slowly because it can be a bit confusing at first. When checking two fixtures to see if they should collide:
  • if either fixture has a groupIndex of zero, use the category/mask rules as above
  • if both groupIndex values are non-zero but different, use the category/mask rules as above
  • if both groupIndex values are the same and positive, collide
  • if both groupIndex values are the same and negative, don't collide
The default value for the groupIndex is zero, so it has not been playing a part in anything so far. Let's try a simple example where we override the category/mask settings above. We will put the boundary walls and one vehicle into the same group, and make the group value negative. If you paid attention to the explanation above, you'll see that this will specify that one sneaky vehicle can escape from the boundary fence.

You could change the Ball constructor to add a parameter for a groupIndex, or just do it a quick and hacky way:
1
2
3
4
5
6
7
8
9
10
  //in FooTest constructor, before creating walls
  myFixtureDef.filter.groupIndex = -1;//negative, will cause no collision
  
  //(hacky!) in global scope
  bool addedGroupIndex = false;
  
  //(hacky!) in Ball constructor, before creating fixture
  if ( !addedGroupIndex )
    myFixtureDef.filter.groupIndex = -1;//negative, same as boundary wall groupIndex
  addedGroupIndex = true;//only add one vehicle to the special group
Collision filtering If we had set the groupIndex to a positive value, this vehicle would always collide with the wall. In this case all the vehicles already collide with the walls anyway so it wouldn't have made any difference, that's why we set it negative so we could see something happen. In other situations it might be the opposite, for example the ships and aircraft never collide, but you might like to say that a subset of the aircraft can actually collide with ships. Low-flying seaplanes perhaps... :)

Need more control?


If you need even finer control over what should collide with what, you can set a contact filter callback in the world so that when Box2D needs to check if two fixtures should collide, instead of using the above rules it will give you the two fixtures and let you decide. The callback is used in the same way as the debug draw and collision callbacks, by subclassing b2ContactFilter, implementing the function below and letting the engine know about this by calling the world's SetContactFilter function.
1
  bool b2ContactFilter::ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB);

Changing the collision filter at run-time


Sometimes you might want to alter the collision filter of a fixture depending on events in the game. You can change each of the categoryBits, maskBits, groupIndex by setting a new b2Filter in the fixture. Quite often you only want to change one of these, so it's handy to be able to get the existing filter first, change the field you want, and put it back. If you already have a reference to the fixture you want to change, this is pretty easy:
1
2
3
4
5
6
7
8
9
10
  //get the existing filter
  b2Filter filter = fixture->GetFilterData();
  
  //change whatever you need to, eg.
  filter.categoryBits = ...;
  filter.maskBits = ...;
  filter.groupIndex = ...;
  
  //and set it back
  fixture->SetFilterData(filter);
If you only have a reference to the body, you'll need to iterate over the body's fixtures to find the one you want.



Next: Sensors