Box2D is a popular physics engine used mainly in games. You can see which games were created with Box2D in a YouTube video. I guess the most popular game using Box2D is Angry Birds.
Box2D calculates the physics of rigid bodies, that is, it cannot emulate the movement of liquids or used in games like “Worms”.
- ✏️ A “shape” in Box2D is either a circle, or a polygon (no more than 8 sides), or a segment.
- ✏️ A “body” consists of 1+ shapes, you can set artitraty density, friction, elasticity to it.
- ✏️ The body can have “constraints”, for example, it may be unable to rotate or move along the X/Y axis.
- ✏️ There may be “connections” of different types between bodies that will keep bodies together.
- ✏️ The “connections” may also have different constraints, for example, when emulation the human elbow, the possible angle between the parts of the arm is limited.
- ✏️ The “world” contains these objects and manages memory and emulation process.
- ✏️ At any time, it’s possible to add/remove bodies, apply force, rotation, momentum… As well as checking collisions of bodies, doing raycasts, calculating the distance between bodies and much more.
These simple concepts can be combined into the very complex configurations - an example of transport on YouTube:
The time step
constant defines how much time has “passed” since the previous recalculation of the world. Usually the world is recalculated 60 times per second (time step = 1.0f / 60.0f
).
The Box2D algorithms use simple mathematics. The high school level: calculation of normals, rotation matrices, product of vectors, trigonometry. The level of the first year of university: the Euler method.
Box2D can be built incredibly fast in just 5 seconds, even with the test application.
Box2D is written in a mix of C and C++: the don’t use namespaces, they make constants via enum, there are inhouse std::list
s for objects, and so on.
For example, if an object is designed to live in a list, then the pointer to the next object in the list is contained right in the class, not somewhere else:
1
2
for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
b->... // do something with the body
Box2D uses allocators of of two popular types:
- 💾 Small objects’ allocator (b2BlockAllocator) - instead of calling
malloc
/free
for small objects often, the allocator allocates 16kb of memory right away and construct objects there (while there is a place). - 💾 Stack allocator (b2StackAllocator) - the stack holds 100kb of memory, objects are constructed in this chunk of memory. It allows to avoid memory allocation when recalculating the “world”.
All objects should be created via the “world” (b2World):
1
b2Body* b = myWorld->CreateBody(&bodyDef);
The user should delete the objects via the “world” as well, though the destructor b2World::~b2World()
will clean up everything that has not been deleted by the user.
⏱ The engine uses a number of techniques to recalculate the “world” faster. The movement (angle, momentum, …) of the objects is calculated fast, but to calculate collisions between objects they use data structures.
You can read in wikipedia about hardships of collision detections. The naive collision detection algorithm works in $O(n^2)$ complexity, but it could be close to $O(n)$ when optimized ($n$ is the amount of objects). Box2D uses the “dynamic tree” (b2DynamicTree) - the world is divided into two parts, every of the part in its turn also divided into two parts, and so on.
🐷 We can imagine some game logic, on example of pigs from Angry Birds. If you have played Angry Birds, you might remember that pigs are quite fragile, but they can survive (losing a fraction of “health”) if they fall from a small height or a small block falls on the pig. The momentum of the collision with another object can be considered as the measure of damage for a pig.
$ Damage = M_{pig} * V_{pig} + M_{obj} * V_{obj} $
One can iterate over all the “contacts” between the bodies to catch the beginning of a collision:
1
2
for (b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext())
contact->... //do something with the contact
But this will be non-optimal and ugly. It’s better to use a callback to collision with b2ContactListener.