8 min read

(For more resources related to this topic, see here.)

Building a collision event system

In a game such as Angry Birds, we would want to know when a breakable object such as a pig or piece of wood has collided with something, so that we can determine the amount of damage that was dealt, and whether or not the object should be destroyed, which in turn spawns some particle effects and increments the player’s score. It’s the game logic’s job to distinguish between the objects, but it’s the physics engine’s responsibility to send these events in the first place and then we can extract this information from Bullet through its persistent manifolds.

Continue from here using the Chapter6.1_ CollisionEvents project files.

Explaining the persistent manifolds

Persistent manifolds are the objects that store information between pairs of objects that pass the broad phase. If we remember our physics engine theory the broad phase returns a shortlist of the object pairs that might be touching, but are not necessarily touching. They could still be a short distance apart from one another, so the existence of a manifold does not imply a collision. Once you have the manifolds, there’s still a little more work to do to verify if there is a collision between the object pair.

One of the most common mistakes made with the Bullet physics engine is to assume that the existence of a manifold is enough to signal a collision. This results in detecting collision events a couple of frames too early (while the objects are still approaching one another) and detecting separation events too late (once they’ve separated far enough away that they no longer pass the broad phase). This often results in a desire to blame Bullet for being sluggish, when the fault lies with the user’s original assumptions. Be warned!

Manifolds reside within the collision dispatcher, and Bullet keeps the same manifolds in memory for as long as the same object pairs keep passing the broad phase. This is useful if you want to keep querying the same contact information between pairs of objects over time. This is where the persistent part comes in, which serves to optimize the memory allocation process by minimizing how often the manifolds are created and destroyed.

Bullet is absolutely riddled with subtle optimizations and this is just one of them. This is all the more reason to use a known good physics solution like Bullet, instead of trying to take on the world and building your own!

The manifold class in question is btPersistentManifold and we can gain access to the manifold list through the collision dispatcher’s getNumManifolds() and getManifoldByIndexInternal() functions.

Each manifold contains a handful of different functions and member variables to make use of, but the ones we’re most interested in for now are getBody0(), getBody1(), and getNumContacts(). These functions return the two bodies in the object pair that passed the broad phase, and the number of contacts detected between them. We will use these functions to verify if a collision has actually taken place, and send the involved objects through an event.

Managing the collision event

There are essentially two ways to handle collision events: either send an event every update while two objects are touching (and continuously while they’re still touching), or send events both when the objects collide and when the objects separate.

In almost all cases it is wiser to pick the latter option, since it is simply an optimized version of the first. If we know when the objects start and stop touching, then we can assume that the objects are still touching between those two moments in time. So long as the system also informs us of peculiar cases in separation (such as if one object is destroyed, or teleports away while they’re still touching), then we have everything we need for a collision event system.

Bullet strives to be feature-rich, but also flexible, allowing us to build custom solutions to problems such as this; so this feature is not built into Bullet by default. In other words, we will need to build this logic ourselves. Our goals are simple; determine if a pair of objects have either collided or separated during the step, and if so, broadcast the corresponding event. The basic process is as follows:

  1. For each manifold, check if the two objects are touching (the number of contact points will be greater than zero).
  2. If so, add the pair to a list of pairs that we found in this step.
  3. If the same pair was not detected during the previous step, broadcast a collision event.
  4. Once we’ve finished checking the manifolds, create another list of collision objects that contains only the missing collision pairs between the previous step and this step.
  5. For each pair that is missing, broadcast a separation event.
  6. Overwrite the list of collision pairs from the previous step, with the list we created for this step.

There are several STL (Standard Template Library) objects and functions we can use to make these steps easier. An std::pair can be used to store the objects in pairs, and can be stored within an std::set. These sets let us perform rapid comparisons between two sets using a helpful function, std::set_difference(). This function tells us the elements that are present in the first set, but not in the second.

The following diagram shows how std::set_difference returns only objects pairs that are present in the first set, but missing from the second set. Note that it does not return new object pairs from the second set.

The most important function introduced in this article’s source code isCheckForCollisionEvents(). The code may look a little intimidating at first, but it simply implements the steps listed previously. The comments should help us to identify each step.

When we detect a collision or separation, we will want some way to inform the game logic of it. These two functions will do the job nicely:

virtual void CollisionEvent(btRigidBody* pBody0, btRigidBody * pBody1); virtual void SeparationEvent(btRigidBody * pBody0, btRigidBody * pBody1);

In order to test this feature, we introduce the following code to turn colliding objects white (and similar code to turn separating objects black):

void BulletOpenGLApplication::CollisionEvent(const btCollisionObject * pBody0, const btCollisionObject * pBody1) { GameObject* pObj0 = FindGameObject((btRigidBody*)pBody0); pObj0->SetColor(btVector3(1.0,1.0,1.0)); GameObject* pObj1 = FindGameObject((btRigidBody*)pBody1); pObj1->SetColor(btVector3(1.0,1.0,1.0)); }

Note that these color changing commands are commented out in future project code.

When we launch the application, we should expect colliding and separating objects to change to the colors give in CollisionEvent(). Colliding objects should turn white, and separated objects should turn black. But, when objects have finished moving, we observe something that might seem a little counterintuitive. The following screenshot shows the two objects colored differently once they come to rest:

But, if we think about the order of events for a moment, it begins to make sense:

  • When the first box collides with the ground plane, this turns both objects (the box and the ground plane) white
  • The second box then collides with the first turning the second box white, while the first box stays white.
  • Next, the second box separates from the first box, meaning both objects turn black.
  • Finally, the second box collides with the ground plane, turning the box white once again.

What was the last color that the first box turned to? The answer is black, because the last event it was involved in was a separation with the second box. But, how can the box be black if it’s touching something? This is an intentional design consequence of this particular style of collision event management; one where we only recognize the collision and separation events.

If we wanted objects to remember that they’re still touching something, we would have to introduce some internal method of counting how many objects they’re still in contact with, and incrementing/decrementing the count each time a collision or separation event comes along. This naturally consumes a little memory and processing time, but it’s certainly far more optimized than the alternative of spamming a new collision event every step while two objects are still touching. We want to avoid wasting CPU cycles telling ourselves information that we already know.

The CollisionEvent() and SeparationEvent() functions can be used by a game logic to determine if, when, and how two objects have collided. Since they hand over the rigid bodies involved in the collision, we can determine all kinds of important physics information, such as the points of contact (where they hit), and the difference in velocity/impulse force of the two bodies (how hard they hit). From there we can construct pretty much whatever physics collision-related game logic we desire.

Try picking up, or introducing more objects with the left/right mouse buttons, causing further separations and collisions until you get a feel for how this system works.

Summary

Very little game logic can be built around a physics engine without a collision event system, so we made Bullet broadcast collision and separation events to our application so that it can be used by our game logic. This works by checking the list of manifolds, and creating logic that keeps track of important changes in these data structures.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here