Saturday 31 August 2013

Getting Started : Bullet Physics



Working with the Bullet engine has easily been the most straightforward. It was very easy to setup, and follow documentation to create an identical sample as the previous engines. In my case it was simply a dynamic box that falls and bounces off a static floor with a similar motion. It has been my experience so far, that default values can be very different (coefficient of friction, restitution, etc), and the calculation resolving the collision of two rigid bodies can lead to very different motion.

0) Setting up the Environment

As in the past I use Premake to generate my Visual Studio solution files and build from within the IDE. I am using 2.81-rev2613 version of the Bullet engine, and the lua file I feed to Premake will be differentiated from my other examples by these few lines.

   includedirs { bulletSDK }
   includedirs { bulletSDK .. '/src' }

   links { bulletSDK .. "/lib/BulletDynamics_vs2008_debug",
           bulletSDK .. "/lib/BulletCollision_vs2008_debug",
           bulletSDK .. "/lib/LinearMath_vs2008_debug"}

    configuration "Debug"
      flags { "Symbols" }
      defines { "WIN32", "_DEBUG" }

1) Including the Headers

Instead of trying to include any specific headers I thought I might need, I used the common header file.

    #include <btBulletDynamicsCommon.h>

which will get you everything from the rigid bodies to the constraints and solvers.

2) Initialization

Using default settings, we will need at least these four things.

    btDefaultCollisionConfiguration* gCollisionConfig  = new btDefaultCollisionConfiguration();
    btCollisionDispatcher* gDispatcher =  new btCollisionDispatcher(gCollisionConfig);
    btBroadphaseInterface* gOverlappingPairCache =  new btDbvtBroadphase();
    btSequentialImpulseConstraintSolver* gSolver =  new btSequentialImpulseConstraintSolver();

3) Creating the World

Again very straightforward if you try to fulfill the parameters of the constructor.

    static btDiscreteDynamicsWorld* gWorld = new btDiscreteDynamicsWorld(
        gDispatcher, gOverlappingPairCache, gSolver, gCollisionConfig);

    gWorld->setGravity(btVector3(0, 0, -9.8));

Many other physics engines have methods of partitioning the world, or collecting bodies into collision groups for better performance. If Bullet has something similar I will certainly highlight this fact in the future. 

4) Creating Bodies and Shapes

It will be important this time to create the necessary shapes first before creating the body. It would seem that the body cannot exist without any shapes.

    btVector3 kHalfExtent(0.5f, 0.5f, 0.5f);
    btConvexShape* pBoxShape = new btBoxShape(kHalfExtent);

A default, inertia tensor needs to be initialized first. This is, of course, only the diagonal elements of a tensor representing rotation about a regular axis. The actual tensor values will be recalculated based on the shape and mass provided.

    btScalar kMass = btScalar(1.0f);
    btVector3 kLocalInertia(0,0,0);
    pBoxShape->calculateLocalInertia(kMass , kLocalInertia);

    btTransform kBoxTform;
    kBoxTform.setIdentity();
    kBoxTform.setOrigin(btVector3(0,0,5));
    btDefaultMotionState* pMotionState = new btDefaultMotionState(kBoxTform);

All of the elements before are required to finally set the construction information for rigid body. It is important to note that a btRigidBody only takes one btCollisionShape pointer as an arguement. In order to have multiple shapes represent the collision geometry, one can use btCompoundShape. Also, there is only one transform for the position and orientation of the body, and not for the shape. Since the btCollisionShape is only used for geometry purposes, the same object can be re-used in different bodies (as long as the geometry is not changed).

    btRigidBody::btRigidBodyConstructionInfo kRBInfo(
        kMass, pMotionState, pBoxShape, kLocalInertia);

    btRigidBody* pBoxBody = new btRigidBody(kRBInfo);
    pBoxBody->setActivationState(ACTIVE_TAG);
    pBoxBody->setRestitution(0.5);

    gWorld->addRigidBody(pBoxBody);

5) Simulation Step

    int iMaxSubsteps = 10;
    m_world->stepSimulation(myTimestep, iMaxSubsteps);

6) Shutdown/Cleanup

I believe there is a critical error in the demos provided in bullet which I hope is corrected in the following modified code. The problem is that the condition of the 'for' loop is dependent on the number of collision objects in the dynamics world, but as we iterate through the loop we remove each object from that world, reducing the wold size. If we are iterating upward, and the condition limit is moving downward we never properly clear about half of the collision objects.  Also, we are responsible for deleting everything we have created earlier, the destructor of any collision object will not properly clear any member pointers we provided earlier. In particular we created a btDefaultMotionState, and used one of the created btCollisionShapes in the btRigidBodyConstructionInfo. So both the motion state, and the collision shapes must be manually deleted.

If you have not created an array already to store the collision shapes. Recall that collision shapes may be reused between rigid bodies, and so we must create a non-degenerate list.

    btAlignedObjectArray<btCollisionShape*> collideShapes;

Since the number of collision objects in the world is going to decrease, we record the original number first.

    int numCollide = gWorld->getNumCollisionObjects();
    for(int i = numCollide-1; i >= 0; i--)
    {
        btCollisionObject *pObj = objArray.at(i);
        gWorld->removeCollisionObject(obj);
        btRigidBody* pBody = btRigidBody::upcast(pObj);
        if (pBody && pBody->getMotionState())
        {
            delete pBody->getMotionState();
        }
        btCollisionShape* pShape = pObj->getCollisionShape();
        
        if(pShape && (collideShapes.findLinearSearch(pShape) == collideShapes.size()))
        {
            collideShapes.push_back(pShape);
        }
        delete pObj;
     }

Similarly the size of the array will decrease as we remove shapes from it, but we could also write the loop this way to ensure all shapes are properly deleted.

    for(int i = collideShapes.size()-1; i >= 0; )
    {
        btCollisionShape* pShape = collideShapes.at(i);
        collideShapes.remove(pShape);
        delete pShape;
    }

Finally we can delete the remaining objects in the reverse order that they where constructed.

    delete gWorld;
    delete gSolver;
    delete gOverlappingPairCache;
    delete gDispatcher;
    delete gCollisionConfig;

Bullet has easily been the most straightforward in setting up a working demo. I am unsure of how it performs against other engines at the moment, but I am very excited to work with it again. Hopefully this has been helpful if the documentation and demos were not helpful enough. Happy Coding!

** the user manual is under the main folder, not under the 'docs' folder which contains the quick-start guide.**