Box2D C++ tutorials - Collision filtering
Last edited: July 14 2013Chinese 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
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; |
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': 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); |
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 ) ); |
- All vehicles collide with the boundary
- Ships and aircraft do not collide
- All ships collide with all other ships
- Aircraft will collide with opposition aircraft, but not with their teammates
1 2 3 4 5 6 7 | enum _entityCategory { BOUNDARY = 0x0001, FRIENDLY_SHIP = 0x0002, ENEMY_SHIP = 0x0004, FRIENDLY_AIRCRAFT = 0x0008, ENEMY_AIRCRAFT = 0x0010, }; |
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 |
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 ) ); |
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
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 |
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); |