Bullet Physics
Loading...
Searching...
No Matches
Basics
Todo
use the following resources to improve this article

basics

expected knowledge

To get started with bullet physics we have to figure out where it stands with respect to what level of detail is exposed to the user, for example a beginner programmer might hope that there is some simulation = createPhysicsSimulation() function that makes a simulation that automatically updates itself and then we would do simluation.addSphere() and so on, whereas a more advanced programmer would hope for more control from the get go.

This level of detail is also the same as what knowledge the library expects us to have in order for us to use it properly, and in the case of bullet, it's a basic understanding of how a physics engine works. Without this knowledge it will be hard to work with the library

physics simulation

To understand how a physics simulation works, let's think of a video game that has gravity, players that can move around, and guns which fire projectiles.

time

The first fundamental component of such a system, would be the concept of time, as without it nothing would happen, everything would be frozen in space and nothing would move. We as humans have a hard time grappling about a continuum of time, one way we've found to manage with it is by counting seconds, breaking it down into discreet steps.

We can teach our programs to count by using the while loop that runs continually and then measuring the amount of time that has passed since last iteration. Our physics simulation will also use this concept, moving from one state to another, while updating positions and information based on how much time has passed since last time we updated our simulation.

collisions

First things first, we should make sure our players don't fall through the floor when gravity is applied, so our physics engine simulation needs to be able to do collisions, this will also be useful for when two players collide.

Since our physics simulation is written in code, to some extent things will be run sequentially (ignoring any threading for now), and so given two objects, how would we know if they're colliding? Well we could grab one, and then another, and then based on their shape, figure out if they're intersecting in any way, and then we would know if they are colliding.

efficiency

Naively we could take this idea, iterate over each of the objects that could collide in our simulation and check for collisions. This is a good first start, but if we had N things that collide we could find a quick upperbound on how that scales up. We would have to check each one of the N objects against the N-1 others, so as the number of objects increases, the time to do these checks would increase at a rate of N^2, which becomes unwieldy

Note: by unwieldy I mean your game may become extremely laggy or come to a grinding halt, making an experience that none of your potential players are going to want to experience.

If we had an inexpensive test to quickly rule out objects or groups of objects of having the possibility of colliding, then we could cut down on the number of checks we would have to perform, which would bring our game back from being a laggy mess to a real-time experience.

The great thing is that over the years we've come up with many different solutions on how we can do this

  • spatial partitioning: Carve up space and cheaply exclude objects that are in different regions, examples include

    • binary space partitioning
    • octrees
    • grids

  • object partitioning: a similar idea to the one before but now the focus is around the space an object takes up rather than the section of space it's in, examples include

  • sort and sweep: place the objects in order spatially and rule out collisions between objects that aren't adjacent

Since this topic has such significance when it comes to game engines, it has been called broad phase collision detection, and is specifically about how to cut down on the number of collisions that need to be checked for, it also comes with another phase, which is called narrow phase collision detection which corresponds to the actual checking of collisions between objects.

constraints

TODO

back to bullet

In order for us to start a bullet physics simulation to run, it needs to know a few things:

  • Would you like to customize any part of the collision detection?
  • Please provide me with a dispatcher that will act on your behalf when a collision occurs
  • What kind of broadphase algorithm would you like to use?
  • What kind of contstraint solving agorithm would you like to use?

This translates into the following code:

#include "btBulletDynamicsCommon.h"
#include <stdio.h>
int main(int argc, char** argv)
{
int i;
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);
btBroadphaseInterface* overlappingPairCache = new btDbvtBroadphase();
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0, -10, 0));
//keep track of the shapes, we release memory at exit.
//make sure to re-use collision shapes among rigid bodies whenever possible!
btAlignedObjectArray<btCollisionShape*> collisionShapes;
//the ground is a cube of side 100 at position y = -56.
//the sphere will hit it at y = -6, with center at -5
{
btCollisionShape* groundShape = new btBoxShape(btVector3(btScalar(50.), btScalar(50.), btScalar(50.)));
collisionShapes.push_back(groundShape);
btTransform groundTransform;
groundTransform.setIdentity();
groundTransform.setOrigin(btVector3(0, -56, 0));
btScalar mass(0.);
//rigidbody is dynamic if and only if mass is non zero, otherwise static
bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
groundShape->calculateLocalInertia(mass, localInertia);
//using motionstate is optional, it provides interpolation capabilities, and only synchronizes 'active' objects
btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, groundShape, localInertia);
btRigidBody* body = new btRigidBody(rbInfo);
//add the body to the dynamics world
dynamicsWorld->addRigidBody(body);
}
{
//create a dynamic rigidbody
//btCollisionShape* colShape = new btBoxShape(btVector3(1,1,1));
btCollisionShape* colShape = new btSphereShape(btScalar(1.));
collisionShapes.push_back(colShape);
btTransform startTransform;
startTransform.setIdentity();
btScalar mass(1.f);
//rigidbody is dynamic if and only if mass is non zero, otherwise static
bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
colShape->calculateLocalInertia(mass, localInertia);
startTransform.setOrigin(btVector3(2, 10, 0));
//using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, colShape, localInertia);
btRigidBody* body = new btRigidBody(rbInfo);
dynamicsWorld->addRigidBody(body);
}
for (i = 0; i < 150; i++)
{
dynamicsWorld->stepSimulation(1.f / 60.f, 10);
//print positions of all objects
for (int j = dynamicsWorld->getNumCollisionObjects() - 1; j >= 0; j--)
{
btCollisionObject* obj = dynamicsWorld->getCollisionObjectArray()[j];
btRigidBody* body = btRigidBody::upcast(obj);
btTransform trans;
if (body && body->getMotionState())
{
body->getMotionState()->getWorldTransform(trans);
}
else
{
trans = obj->getWorldTransform();
}
printf("world pos object %d = %f,%f,%f\n", j, float(trans.getOrigin().getX()), float(trans.getOrigin().getY()), float(trans.getOrigin().getZ()));
}
}
//cleanup in the reverse order of creation/initialization
//remove the rigidbodies from the dynamics world and delete them
for (i = dynamicsWorld->getNumCollisionObjects() - 1; i >= 0; i--)
{
btCollisionObject* obj = dynamicsWorld->getCollisionObjectArray()[i];
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState())
{
delete body->getMotionState();
}
dynamicsWorld->removeCollisionObject(obj);
delete obj;
}
//delete collision shapes
for (int j = 0; j < collisionShapes.size(); j++)
{
btCollisionShape* shape = collisionShapes[j];
collisionShapes[j] = 0;
delete shape;
}
//delete dynamics world
delete dynamicsWorld;
//delete solver
delete solver;
//delete broadphase
delete overlappingPairCache;
//delete dispatcher
delete dispatcher;
delete collisionConfiguration;
//next line is optional: it will be cleared by the destructor when the array goes out of scope
collisionShapes.clear();
}

You can see that in the last line, we've actually created our world/simulation by creating a DiscreteDynamicsWorld (link to documentation for that)

On the next lines we can see how bullet deals with time through the stepSimulation(...) function call

Terminology

  • pair cache, an instance of a pair cache represents a pair of objects that were found overlapping in the broadphase collision detection phase.