Integrating Bullet Physics

Bullet is an open source collision and physics simulation library written by Erwin Coumans who I believe worked or maybe still does work at SCEA (Sony Computer Entertainment America). Bullet provides a C++ API to a very robust Collision, Rigid Body and Soft body simulation system. At its core Bullet is comparable to commercial systems such as Havok but lacks the art pipeline integration, extensive documentation and support contracts that its commercial rivals offer. For an indie developer it’s an amazing leg up though. Collision is hard to do well. Physics is also hard to do well. Bullet does both very well.

Gradual Integration

Bullet has a layered compartmentalised design. So my plan was to first integrate collision and later Rigid body simulation. The documentation that Bullet offers is a little sparse but the source package includes a lot of demo code ranging from helloworld to a forkliftdemo to the classic how many boxes can I stack before the physics explodes. While it’s intimidating at first, collision and physics are so core to your game engine it’s important to spend the time integrating exactly what you need, where you need it.

Blender has Bullet integrated as well. Unfortunately not everything is exposed to the python export script. Rigid Body parameters to do with damping and most Soft body parameters are not available. The next version of Blender, which is a complete rewrite, should fix this. Unfortunately your export script will have to be rewritten as well.

Building the Bullet Codebase

The first thing I would recommend doing is building the bullet source package. Just follow the instructions in INSTALL. It’s worth installing CMake (as they suggest) as without it not ALL the demos will compile.

After compilation an executable called AllBulletDemos will be in in BulletSrc/Demos/AllBulletDemos unfortunately it doesn’t have all the demos in it but it does have a lot of them. Playing around with these demos will give you an idea of what Bullet is capable of and more importantly where to look for sample code. Bullet’s lacks hand holding documentation and you are expected to dig around in demos and find code that is useful to you.

alt text

Getting Data From Blender Into Collision

It’s well worth thinking about this quite carefully. Bullet recommends that you merge all static meshes into one big mesh. It also recommends that you don’t submit

  • large triangles (>10 units)
  • very small triangles
  • very long thin triangles.

In general the same routines you used in you blender geometry exporter will suffice for exporting the collision mesh. You should consider optimising the collision mesh to reduce the amount of data while remembering not to include degenerate (or near degenerate) triangles or overly large triangles.

alt text

In blender objects marked as Actors with Static selected are static collision meshes. To test for this is your python exporter:

1
2
if obj.rbFlags & Object.RBFlags["ACTOR"] and not obj.rbFlags & Object.RBFlags["RIGIDBODY"]: 
    #export the static mesh data

Bullet uses an OpenGL-ish method for submitting a large number of triangles for a mesh. This allows you to create an Index Vertex Array and from that build a Triangle Mesh Shape. One niggle is that the iPad uses 8 or 16 bit index values (GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT) whereas Bullet uses int (32 bit) indexes. This means you can’t submit your render index list to the collision system. In the long run you wouldn’t do this anyway but it would be nice to be able to use different index sizes.

1
2
3
4
5
6
7
8
mIndexVertexArray = new btTriangleIndexVertexArray(mNumIndices/3, 
    mIndices, sizeof(int)*3, 
    mNumVertex, (btScalar*),mVertex[0].v.x(), sizeof(ColVertex)); 
btVector3 aabbMin(-10000,-10000,-10000),aabbMax(10000,10000,10000); 
bool useQuantizedAabbCompression=true; 
mTriMeshShape = new btBvhTriangleMeshShape(mIndexVertexArray, 
    useQuantizedAabbCompression, 
    aabbMin, aabbMax);

Adding Meshes To Your Collision World

1
2
3
4
5
6
7
8
//Generic way to build and configure a Bullet Collision World with Dynamics 
mColConfiguration = new btDefaultCollisionConfiguration();
mColDispatcher = new btCollisionDispatcher(mColConfiguration);
mColOverlappingPairCache = new btDbvtBroadphase();
mColSolver = new btSequentialImpulseConstraintSolver;
mColDynamicsWorld = new btDiscreteDynamicsWorld(mColDispatcher, 
    mColOverlappingPairCache,mColSolver,mColConfiguration);
mColDynamicsWorld->setGravity(btVector3(0,-10,0));

Once you have created a world you need to populate it with you meshes. Bullet advises instancing meshes if possible and merging non instanced meshes into super meshes. When you instance a mesh you have an opportunity to use btScaledBvhTriangleMeshShape to scale the instance. You can’t scale btBvhTriangleMeshShape.

1
2
3
4
5
6
7
btVector3 localInertia(0,0,0);
btTransform bttransform = MatrixToBTTransform(transform);
btDefaultMotionState* motionState = new btDefaultMotionState(bttransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, motionState,
    trimesh, localInertia);
btRigidBody *body = new btRigidBody(rbInfo);
mColDynamicsWorld->addRigidBody(body);

Since we are adding static meshes there is no local inertia and mass is set to zero (massless entities in Bullet are considered to have an infinite mass and are immovable). We construct a rigid body from the trimesh and the add the rigid body to the world.

Casting A Ray From The Camera

CML has a handy function called make_pick_ray which helps in the process of making a camera space ray which can be projected into world space using the camera’s transform.

1
2
3
4
5
void make_pick_ray(scalar_type pick_x, scalar_type pick_y,
 const matrix4x4_type &view, 
 const matrix4x4_type &projection,
 const matrix4x4_type &viewport,
 vector_type &origin, vector_type &direction, bool normalize = true );
  • [IN] pick_x and pick_y is the x position of the touch on the iPad
  • [IN] view is the view transform. I store the rotation of the scene in this matrix depending on the orientation of the iPad
  • [IN] projection is the projection matrix you have used to project from world to screen space. I make this using cml::matrix_perspective_xfov_RH
  • [IN] viewport is the matrix that describes how to move from normalised or device independant screen space to actual screen space. I make it using cml::matrix_viewport
  • [OUT] origin is where the ray is starts in camera space
  • [OUT] direction is the direction of the ray, it has unit length if you set normalized to true else it’s the length of the ray to the far clip plain set in your projection matrix. Once again it’s in camera space. In order to get the ray into worldspace you need to multiply both origin and direction by your camera matrix (note not the inverse one you use for projection by the matrix that describes the position of the camera in world space).

Cast The Ray Into the Collision World

1
2
3
4
5
btCollisionWorld::ClosestRayResultCallback rayCallback( campos, rayto); 
mWorld->GetColWorld()->rayTest( raystart, rayto, rayCallback );
if( rayCallback.hasHit() ) { 
    btVector3 colpoint = rayCallback.m_hitPointWorld); 
    btVector3 colNormal = rayCallback.m_hitNormalWorld; }

This really only covered the most basic form of hit testing. Adding capsules, spheres, cylinders, cubes, convex hulls and compound objects are all supported. I suggest you look at the demos :)

Summary

I’ve only really covered the basics here. Bullet is a massive piece of code and if you haven’t played with a physics system before I really think you will struggle. The forum is a help but tbh both collision and physics are hard so you shouldn’t expect it to be a walk in the park. If your time isn’t cheap it may well be worth paying for a solution.