Friday, 11 January 2013

Collision filtering with Box2d



Having a lot of bodies in the level at the same time can significantly impact framerates. Especially collision processing is a big burden to process. Add to that the fact that it’s usually necessary for gameplay to not have all bodies collide (for instance, bricks flying from a building should not collide with the player but should collide with the terrain): a way to filter these collisions is a necessity. Luckily, Box2D has a way to do this.



            Every Box2D fixture has two bitfields (a bitfield consisting of 16 bits) for this task: a mask bit and a category bit. To determine whether fixtures collide, the following statement is checked:

uint16 catA = fixtureA.filter.categoryBits;
uint16 maskA = fixtureA.filter.maskBits;
uint16 catB = fixtureB.filter.categoryBits;
uint16 maskB = fixtureB.filter.maskBits;
 
if ((catA & maskB) != 0 && (catB & maskA) != 0)
{
  // fixtures can collide
}


The if statement checks if both the category bitfield of fixture A and the mask bitfield of fixture B have more than one 1 in common, and the same for the category bitfield of fixture B and mask bitfield of fixture A. If both are true, the fixtures collide.
            Let’s clarify this with an example:

Fixture A                                            


Category bitfield:
0x0001
Mask bitfield:
CatB|CatC

Fixture B

Category bitfield:
0x0002
Mask bitfield:
CatA

Fixture C

Category bitfield:
0x0004
Mask bitfield:
CatA




Notice the way the bitfields are written. Each bitfield consist of 4x4 bits. Every digit represents a power of 2 in one of the four blocks of bits. Written fully, the category bitfield of fixture A would be:
0000 0000 0000 0001

Fixture C’s category would be:

0000 0000 0000 0100

This way, the category bitfields can range from 0x0001 to 0x8000, thus allowing for 16 categories.
Now, lets see how the fixtures will collide. The mask bit of A is: CatB|CatC. This means that both bitfields are combined;

Category B = 0000 0000 0000 0010
Category C = 0000 0000 0000 0100
Mask A        = 0000 0000 0000 0110

This results in A colliding with B and C, and B and C not colliding with each other.  A mask of -1 means the fixture will collide with all objects, a mask of 0x000 means the fixture collides with no objects.

            For extra control or in order to set up collision filtering without bitfields, collision group indexes can be used. A combination of both systems can also be used, for instance when fixtures of a tank should all collide with the same fixtures, but not with each other.
            Group indexes work as follows (taken from the Box2d manual):

Collision groups let you specify an integral group index. You can have all fixtures with the same group index always collide (positive index) or never collide (negative index). Group indices are usually used for things that are somehow related, like the parts of a bicycle. In the following example, fixture1 and fixture2 always collide, but fixture3 and fixture4 never collide.
fixture1Def.filter.groupIndex = 2;
fixture2Def.filter.groupIndex = 2;
fixture3Def.filter.groupIndex = -8;
fixture4Def.filter.groupIndex = -8;
Collisions between fixtures of different group indices are filtered according the category and mask bits. In other words, group filtering has higher precedence than category filtering.
Note that group indexes are noted in integers, not bitfields. This is easier to learn, but less powerful than using categories and masks as is doesn’t allow for collision filtering between groups.
As you can see, setting up collision filtering in code is fairly straightforward, once you understand the principle. One difficulty we experienced was that SpriteHelper (the spritesheet & physics editor used for Tank Rampage) notes mask and category bits as integers. This results in a tedious process of determining collision masks on paper and converting them back to bitfields instead of being able to do this programmagiacally.

Please let me know if you want any more info on this subject, or perhaps a tutorial?


No comments:

Post a Comment