What are collisions?
In Box2D, it is assumed that bodies collide with each other, but in fact, fixtures are used in calculating collisions (fixtures, word translations
exist , but I am not sure if there is an established one among them). Objects can collide in different ways, so the library provides a large amount of clarifying information that can be used in the game logic. For example, you might want to know the following:
- When the collision begins and ends
- Point of contact fixtures
- Normal to the line of contact fixtures
- What energy was applied and the result of a collision
Usually the collision occurs very quickly, however, in this article we will try to take one specific collision and slow it down in order to have time to consider the details of what is happening and the information that can be extracted from the event.
In our scenario, two polygonal fixtures in the world with zero gravity will collide (to better control the process). The first fixture is a stationary square, the second is a triangle moving horizontally towards the square.

')
Everything is set up so that the bottom of the triangle faces the top corner of the square. The intricacies of the implementation of this process are beyond the scope of the article - we will focus on what information can be extracted at each stage of the collision. If there is a desire to independently run the proposed example, the
source code is attached.
Collision information retrieval
Collision information is contained in a
b2Contact type
object . From it you can find out exactly which fixtures collide, and determine their position and direction of the resulting pulses. There are two ways to get
b2Contact objects in Box2D. The first is to loop through the current contact list of each object body, the second is to use the contact listener. We will consider each of them in order to further understand what is being said.
Contact list check
At any time, you can sort through all the contacts of the world (meaning
b2World )
for (b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext()) contact->...
or get body contacts of a certain object
for (b2ContactEdge* edge = body->GetContactList(); edge; edge = edge->next) edge->contact->...
If this approach is chosen,
it is very important to remember that the
presence of a contact in these lists does not mean that the fixtures are in contact - this only means that their
AABBs intersect. If you want to make sure that the fixtures themselves are in contact, use the
IsTouching () method. To this question we will return.
Contact listeners
Checking the contact list becomes ineffective in situations where collisions occur frequently and in large quantities. By establishing contact listeners, you instruct Box2D to report when something interesting happens, instead of manually watching for the start and end of collisions. The contact listener is an object of the
b2ContactListener class, some of the functions of which are replaced if necessary.
void BeginContact(b2Contact* contact); void EndContact(b2Contact* contact); void PreSolve(b2Contact* contact, const b2Manifold* oldManifold); void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);
I should note that, depending on the situation, some events give us not only the
b2Contact object. During the execution of the
Step function, when Box2D determines that a contact has occurred, it calls back certain functions of the listener to notify you. The practical use of "
callbacks for collisions " is discussed in a separate article. Here we will focus all our attention on what can be learned by handling collision events.
In general, I recommend the contact listener method. At first glance, it may seem a little awkward, but it is more effective and useful in the long run. Until now, I have not encountered a situation where checking the contact list would give a tangible gain.
Regardless of the method of obtaining contacts, they contain the same information. The most important data about intersecting fixtures can be obtained as follows.
b2Fixture* a = contact->GetFixtureA(); b2Fixture* b = contact->GetFixtureB();
When the contact list of a particular object is traversed, perhaps one of the collision fixtures is known, but if you use the contact listener, you will have to fully rely on these functions to understand what you are facing. A clearly defined order of the fixtures does not exist, so it is often necessary to set
user data (user data) in order to understand which particular object the fixture or body belongs to. With the
fixture object, you can use the
GetBody () method to get a reference to the body.
Clash step by step.
Now let's take a closer look at the sequence of events that occur during a collision. I hope, pictures with the description of each step will be enough to understand the material. You may want to download the
source code to the lesson in order to run it while reading. The test program allows you to pause the simulation, restart and execute it step by step.
Let's start with a situation where AABB fixtures do not intersect, so we can trace the story completely. Click the “AABBs” box to see the purple rectangular areas around each fixture.

AABB fixtures start to overlap
Despite the fact that the fixtures themselves have not yet intersected, at this stage an instance of
b2Contact is already created and added to the contact list of the world and the lists of each body. If you are viewing these lists, then by the presence of
b2Contact objects
you can judge that contact is possible in principle, although the fixtures are not necessarily intersected.
Result: the contact exists, but
IsTouching () returns false
We continue the simulation, until the fixtures directly cross ...
Fixtures begin to cross.

Approaching the top corner of the square, you can see the transition, as in the following images
Step n

Step n + 1 (not bullet objects)

All this happens in one step of the simulation, which means that the real point of intersection (to which the dotted line in the figure above leads), we slipped. This is because Box2D first displaces all the bodies and only then checks the intersection, at least this is its default behavior. If you need a real point of contact, you need to do the following
bodyDef.bullet = true;
Step n + 1 (triangle - bullet-object)

Bullet-bodies, spend more CPU time on calculations and are not required for most applications. Just remember that at normal settings, sometimes collisions can be skipped - in our example, if the triangle moved fast enough, it could fly through the corner of a square without initiating a collision. If you have very fast moving bodies whose contacts should not be skipped, for example, mmmm ... bullets :) then they should be declared as bullet-objects. Further presentation will be carried out for non-bullet-tel.
Result:
- IsTouching () returns true
- BeginContact callback function
Collision points and normal
At this point in our contact there is a real contact, which makes it possible to answer not some questions at the beginning of the article. First, let's get the normal and touch point. It is assumed that the following code is called either from the
BeginContact method of the
contact listener or from your method after first receiving the contact from the list.
The contact object contains information about collisions in the local coordinates of the bodies of the colliding objects, and this is not exactly what we need. However, you can ask the contact for a more useful
b2WorldManifold structure, which contains the collision position in world coordinates.
int numPoints = contact->GetManifold()->pointCount;

The resulting points will be used by Box2D to calculate the response to a collision to calculate the momentum, which will direct the fixtures in opposite directions. These will be inaccurate points of contact for fixtures (unless you have used bullet-objects), although in practice they are usually enough to calculate collisions in the game logic.
Next, we consider the collision normal, which is directed from fixture A to B:
float normalLength = 0.1f; b2Vec2 normalStart = worldManifold.points[0] - normalLength * worldManifold.normal; b2Vec2 normalEnd = worldManifold.points[0] + normalLength * worldManifold.normal; glBegin(GL_LINES); glColor3f(1,0,0);
It seems that for this collision, the fastest way to get rid of overlapping objects is to push the angle of the triangle up and to the left, and the square - down and to the right. I would like to draw your attention to the fact that the normal is just a direction, it is not tied to any point of contact - I have depicted it passing through points [0] for convenience.

It is also important to remember that the collision normal does not determine the angle between fixtures (the triangle moves generally horizontally) - it only sets the direction, following which the overlapping of objects is most quickly compensated. For example, imagine that the triangle moves a little faster, and the overlap looks like this:

Then the fastest way to split fixtures is to push the triangle up and to the right. So to use the normal to calculate the angle between objects is impractical. If you want to know the directions in which the figures will be divided, you can use the following code:
b2Vec2 vel1 = triangleBody->GetLinearVelocityFromWorldPoint( worldManifold.points[0] ); b2Vec2 vel2 = squareBody->GetLinearVelocityFromWorldPoint( worldManifold.points[0] ); b2Vec2 impactVelocity = vel1 - vel2;
It will allow to obtain the relative reaction rate of all points of the colliding bodies. In our simple case, it would be possible to limit ourselves to the linear velocity of the triangle, since it is known that the square is stationary and the triangle does not rotate. But the above code will take into account the cases when both things move or rotate.
It should also be noted that not every collision will have exactly two collision points. I intend to stop at a rather complicated example, when two corners of polygons overlap, but more often there is only one such point. Here are some examples of collisions for which one point is enough



So, we have just considered how to determine the points and the normal of a collision, on the basis of which Box2D will calculate the reaction aimed at compensating the overlap. Now back to the sequence of events.
Collision response
((b2Contact :: Update, b2Island :: Report))
Collision pitch

Collision pitch + 1

Collision pitch + 1

When the fixtures overlap, the default Box2d reaction is to apply a pulse to each of them in order to direct them in different directions. However, it is not always possible to do this in one step of the simulation. As shown in the figures for our example, the two fixtures will overlap in three steps, until the rebound takes place and they finally separate.
At this time, we can intervene and adjust the behavior of the model as we like. If the contact listener approach is used, the
PreSolve and
PostSolve methods will be called
at each step while the fixtures overlap , making it possible to modify the contact before it is processed by standard means of reaction to a collision (PreSolve), and find out which impulses were applied by Box2D ( PostSolve)
For greater clarity, here is the output of printf, which is placed in the Step function and each method of the contact listener:
... Step Step BeginContact PreSolve PostSolve Step PreSolve PostSolve Step PreSolve PostSolve Step EndContact Step Step ...
Result: PreSolve and PostSolve are called several times.
PreSolve and PostSolve
Both of these methods receive a pointer to
b2Contact as a parameter, so we have access to the same information about points and normals as in BeginContact.
PreSolve gives you the opportunity to change the characteristics of the contact before calculating the reaction to a collision, and even cancel the reaction completely.
PostSolve allows you to get information about the calculated reaction.
In
PreSolve, you can make the following settings for the contact object:
void SetEnabled(bool flag);
Calling
SetEnabled (false) deactivates the contact, so the reaction to the collision will not be calculated. This may be necessary when it is necessary to temporarily allow objects to fly through each other. A classic example is a one-sided wall or platform where a player can pass through a usually impassable object under certain conditions that can only be checked at run time — for example, the player’s position or direction of movement.
It is important to remember that
at the next iteration the contact is activated again , so if you need to turn it off for a long time, you will have to do it at each step.
In addition to the contact reference,
PreSolve contains a second parameter from which you can get the collision characteristics (points and normal) from the previous simulation step. If someone knows why this might be useful - tell me: D
PostSolve is called after the collision response has been calculated and applied. The method has a second parameter containing information about the impulse applied as a result. It is usually used to check whether a reaction has exceeded a certain threshold value, as a result of which an object can be destroyed, etc. The article "
sticky projectiles " contains an example of using the
PostSolve function to determine if an arrow should get stuck in a target.
Returning to the collision scenario.
Fixtures no longer overlap.
(b2Contact :: Update)
AABBs still overlap, so contact remains in the respective lists of the world and body.

(increased scale)

Result:
- Called EndContact
- IsTouching () returns false
This continues until the AABB ceases to overlap.
AABB fixtures do not overlap
(b2ContactManager :: Collide)

Result: the contact is removed from the contact list of the world and the body.
The
EndContact method receives a pointer to
b2Contact when the fixtures are no longer in contact, so that it no longer contains relevant information. However,
EndContact is an integral element of the contact listener, as it allows you to control when game objects leave the contact area.
Total
I hope that the article gives a clear overview of what is happening in millisecond after millisecond in the depths of the Box2D collision calculation mechanism. Perhaps it was not the most interesting reading, but after studying various issues on the forums, I didn’t have the feeling that some of the details were often missed, as a result, a lot of time was spent trying to figure out what was going on at all ... I also noticed a tendency when listener contacts are not solved, while it requires much less effort compared with the subsequent tasks. Knowledge of the described parts should give a better understanding of things that can be implemented, improve the design and save time.