Box2D C++ tutorials - Anatomy of a collisionLast edited: December 05 2012
Chinese version -> 中文
What's in a collision?
In Box2D it's common to think of bodies colliding with each other, but it's really the fixtures which are used to detect when a collision occurs. Collisions can happen in all kinds of ways so they have a lot of information that can be used in the game logic. For example, you might want to know:
- when a collision starts and ends
- what point on the fixtures is touching
- the normal vector of the contact between the fixtures
- how much energy was involved and how the collision was responded to
The scenario will be two polygon fixtures colliding, in a zero-gravity world so we can control it easier. One fixture is a stationary box, the other is a triangle moving horizontally towards the box. This scenario is set up so that the bottom of the triangle will just collide with the top corner of the box. For this topic the finer details of this arrangement are not important, the focus is on what kind of information we can get at each step of the process so I will direct you to the source code if you'd like to replicate this.
Getting info about collisions
Information about a collision is contained in a b2Contact object. From this you can check which two fixtures are colliding, and find out about the location and direction of the collision reaction. There are two main ways you can get these b2Contact objects from Box2D. One is to look at the current list of contacts for each body, and the other is to use a contact listener. Let's take a quick look at each of these so we know what we're talking about in the rest of this topic.
- Checking the list of contacts
You can look at all the contacts in the world anytime by checking the world's contact list:
Or you can look at the contacts for a single body:
for (b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext()) contact->... //do something with the contact
A very important point to note if you do this, is that the existence of a contact in these lists does not mean that the two fixtures of the contact are actually touching - it only means their AABBs are touching. If you want to know if the fixtures themselves are really touching you can use IsTouching() to check. Moron that later.
for (b2ContactEdge* edge = body->GetContactList(); edge; edge = edge->next) edge->contact->... //do something with the contact
- Contact listeners
Checking the list of contacts becomes inefficient for large scenarios where many collisions are occuring frequently. Setting a contact listener allows you to have Box2D tell you when something interesting happens, rather than you doing all the work of keeping track of when things start and stop touching. A contact listener is a class (b2ContactListener) with four functions which you override as necessary.
Note that depending on what is happening, some events give us more than just the b2Contact object. During the world's Step function, when Box2D detects that one of these events has occured, it will 'call back' to these functions to let you know about it. Practical applications of these 'collision callbacks' will be looked at in other topics, for now we are focusing on when they occur.
1 2 3 4
void BeginContact(b2Contact* contact); void EndContact(b2Contact* contact); void PreSolve(b2Contact* contact, const b2Manifold* oldManifold); void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);
Either way you get these contacts, they contain the same information. The most fundamental piece of info is which two fixtures are colliding, which is obtained by:
b2Fixture* a = contact->GetFixtureA(); b2Fixture* b = contact->GetFixtureB();
Breakdown of a collision
Now let's take an in-depth look at the sequence of events in the collision above. Hopefully this will be easy to follow in table form, with a visual reference on the left to show what the scene looks like at each step. You might like to download the tutorials testbed from the source code page and run it while reading. In the testbed you can pause the simulation and then restart, then press 'Single Step' to see things happening in detail.
We should start from a point where the AABBs of the fixtures are still not overlapping, so we can follow the whole story. Click the 'AABBs' checkbox to see these as purple rectangles around each fixture.
Fixture AABBs begin to overlap
Although the fixtures themselves are not yet overlapping, at this point a b2Contact is made and added to the list of contacts for the world and the list of contacts for each body. If you are checking these contact lists as shown above you will be able to tell that a collision could potentially occur here, but in most cases you don't really care until the fixtures themselves really overlap.
|Continues until fixtures themselves overlap...|
Step n+1 (non bullet bodies)
Step n+1 (triangle as bullet body)
Fixtures begin to overlap
Zooming in on the upper corner of the box, you will see the transition as shown in the upper two images on the left. This occurs in one time step, which means that the real collision point (as shown by the dotted line in the top image) has been skipped over. This is because Box2D moves all the bodies and then checks for overlaps between them, at least with the default settings. If you want to get the real impact position you can set the body to be a 'bullet' body which will give you the result shown in the bottom image. You can do this by:
bodyDef.bullet = true; //before creating body, or body->SetBullet(true); //after creating body
Bullet bodies take more CPU time to calculate this accurate collision point, and for many applications they are not necessary. Just be aware that with the default settings, sometimes collisions can be missed completely - for example if the triangle in this example had been moving faster it may have skipped right over the corner of the box! If you have very fast moving bodies that must NOT skip over things like this, for example uh... bullets :) then you will need to set them as bullet bodies. For the rest of this discussion we will be continuing with the non-bullet body setting.
Collision points and the normal
At this point we have a contact which is actually touching, so that means we should be able to answer some of the questions at the beginning of this topic. First let's get the location and normal for the contact. For the code sections below, we'll assume that these are either inside the BeginContact of the contact listener, or that you have have obtained a contact to use by manually checking the contact lists of the body or world.
Internally, the contact stores the location of the collision point in local coordinates for the bodies involved, and this is usually not so great for us. But we can ask the contact to give us a more useful 'world manifold' which will hold the collision location in world coordinates. 'Manifold' is just a fancy name for the line which best separates the two fixtures.
1 2 3 4 5 6 7 8 9 10 11 12
//normal manifold contains all info... int numPoints = contact->GetManifold()->pointCount; //...world manifold is helpful for getting locations b2WorldManifold worldManifold; contact->GetWorldManifold( &worldManifold ); //draw collision points glBegin(GL_POINTS); for (int i = 0; i < numPoints; i++) glVertex2f(worldManifold.points[i].x, worldManifold.points[i].y); glEnd();
Although they are not at the exact location where the fixtures would first have touched (unless you're using bullet-type bodies), in practise these points are often adequate for use as collision points in game logic.
Next let's show the collision normal, which points from fixtureA to fixtureB:
1 2 3 4 5 6 7 8 9 10
float normalLength = 0.1f; b2Vec2 normalStart = worldManifold.points - normalLength * worldManifold.normal; b2Vec2 normalEnd = worldManifold.points + normalLength * worldManifold.normal; glBegin(GL_LINES); glColor3f(1,0,0);//red glVertex2f( normalStart.x, normalStart.y ); glColor3f(0,1,0);//green glVertex2f( normalEnd.x, normalEnd.y ); glEnd();
1 2 3
b2Vec2 vel1 = triangleBody->GetLinearVelocityFromWorldPoint( worldManifold.points ); b2Vec2 vel2 = squareBody->GetLinearVelocityFromWorldPoint( worldManifold.points ); b2Vec2 impactVelocity = vel1 - vel2;
Another thing to note is that not every collision will have two of these collision points. I have deliberatly chosen a relatively complex example where two corners of a polygon are overlapping, but more common collisions in real situations have only one such point. Here are some other collision examples where only one point is necessary:
Okay, so we've seen how to find the collision points and normal, and we are aware that these points and the normal will be used by Box2D to apply a collision response to correct the overlap. Let's get back to the sequence of events...
|While fixtures continue to overlap...|
Impact + 1
Impact + 2
Collision response is applied
When fixtures are overlapping, Box2D's default behavior is to apply an impulse to each of them to push them apart, but this does not always succeed in a single time step. As shown here, for this particular example the two fixtures will be overlapping for three time steps before the 'bounce' is complete and they separate again.
During this time we can step in and customize this behavior if we want to. If you are using the contact listener method, the PreSolve and PostSolve functions of your listener will be repeatedly called in every time step while the fixtures are overlapping, giving you a chance to alter the contact before it is processed by the collision response (PreSolve) and to find out what impulses were caused by the collision response after it has been applied (PostSolve).
To make this clearer, here is the output obtained for this example collision by putting a simple printf statement in the main Step function and each of the contact listener functions:
... Step Step BeginContact PreSolve PostSolve Step PreSolve PostSolve Step PreSolve PostSolve Step EndContact Step Step ...
PreSolve and PostSolve
Both PreSolve and PostSolve give you a b2Contact pointer, so we have access to the same points and normal information we just looked at for BeginContact. PreSolve gives us a chance to change the characteristics of the contact before the collision response is calculated, or even to cancel the response altogether, and from PostSolve we can find out what the collision response was.
Here are the alterations you can make to the contact in PreSolve:
1 2 3 4 5
void SetEnabled(bool flag);//non-persistent - need to set every time step //these available from v2.2.1 void SetFriction(float32 friction);//persists for duration of contact void SetRestitution(float32 restitution);//persists for duration of contact
It's important to note that the contact will revert back to being enabled in the next time step, so if you want to disable contacts like this you'll need to call SetEnable(false) every time step.
As well as the contact pointer, PreSolve has a second parameter from which we can find out info about the collision manifold of the previous time step. If anybody has an idea about what this could be used for, let me know :D
PostSolve is called after the collision response has been calculated and applied. This also has a second parameter, in which we can find information about the impulse that was applied. A common use for this information is to check if the size of the collision response was over a given threshold, perhaps to check if an object should break, etc. See the 'sticky projectiles' topic for an example of using PostSolve to determine whether an arrow should stick into a target when it hits.
Okay, on with the timeline...
Fixtures finish overlapping
The AABBs are still overlapping, so the contact remains in the contact list for the body/world.
|Continues until fixture AABBs no longer overlap...|
Fixture AABBs finish overlapping
While the EndContact call to the contact listener passes a b2Contact pointer, at this point the fixtures are no longer touching, so there will be no valid manifold information to get. However the EndContact event is still an indispensable part of the contact listener because it allows you to check which fixtures/bodies/game objects have ended contact. See the next topic for a basic example usage.
I hope this topic gives a clear overview of the events going on millisecond-by-millisecond under the hood of a Box2D collision. It may not have been the most interesting read (it sure was the least exciting topic to write so far!) but I get the feeling from reading questions on the forums that some of the details discussed here are often missed, and a lot of time is spent on various workarounds and wondering what's going on. I've also noticed a tendency to shy away from implementing a contact listener, when the listener usually becomes less work in the long run. Knowing these details should allow for a better understanding of what is actually possible, better design, and time saved in implementation.
Next: Collision callbacks