Tuesday, October 25, 2011

Bullet physics with Ogre3D

This will look at my first attempt to integrate the Ogre3D rendering engine with the Bullet physics engine. Both are excellent open source projects that have been used in a number of well known commercial games as well as countless open source projects.
I am not going to cover how to install the Ogre or Bullet SDK's or configure your development environment to make use of them.. if your attempting something like this then you should know how to do that already. (I can cover it in a separate post if there is enough demand for it).
I am also not going to tell you how to set up an Ogre application. There are plenty of tutorials already available for that sort of thing. I am using an application based of an excellent tutorial that can be found here (http://www.ogre3d.org/tikiwiki/Advanced%20Ogre%20Framework). This tutorial covers a basic framework as well as an application state system so I am using the basic concepts from there and then integrating the Bullet physics library.


It is probably also worth noting that there is existing projects/libraries that provide wrappers for using Bullet physics with Ogre3d. I am doing a basic example of a manual implementation, not using those wrappers to give a better understanding of the basics of integrating a rendering engine with a physics engine.

The basic state system application I am starting with executes and loads a menu state, once the user clicks on the "Play game" button it loads up the Game states. As the Game state starts we will begin plugging in the code required to enable this state to have physics. (As per the tutorial above once complete, the state already has the code required to handle input, update the render system, etc. Some of the code shown here will build on that)

Please also note : This code is very rough as this is my first attempt at doing this sort of thing so I hope the experts can forgive that and as always it leaves plenty of room for improvement. :)

To include the Bullet header file I have added the folders to Visual Studios includes path/library path. Then in the gamestate header.. (or wherever is appropriate for your project) I have added : #include <btBulletDynamicsCommon.h>

I also added the following code in the header to define some methods in my GameState class as follows.

// Methods for the bullet physics integration
void CreateCube(const btVector3 &Position, btScalar Mass);
void ClearObjects();
void UpdatePhysics(unsigned int DeltaTime);
// Finished methods for Physics

CreateCube as you probably guessed is a method that creates a Cube. This method will create a cube to be rendered by the Ogre render system as well as a rigid body physic object which will allow the cube to behave as a physical object in our world.
ClearObjects is a cleanup method to clear the physics objects from memory. Should be called before you application or state terminates.
UpdatePhysics is the method that will handle the updating of the rendered objects.

We also need to add a couple of properties to the class to handle the physics objects and the physics world.
// Physics properties
int m_pNumEntities; // Helps keep track of how many boxes we will create
btDiscreteDynamicsWorld *World; // The world that we will be simulating physics for
std::vector<btRigidBody *> Objects; // Storing the physics objects that need to be updated every frame.

We then move on to the main code which I think you will find is not as complex as you might think. It would certainly help to have a much better understanding of 3d graphics programming and 3d math than I do. If so you should have no problems.

The code for my game state is not surprisingly contained in gamestate.cpp. In there I already have a method that is called when the state is started/entered. Here it is :

void GameState::enter()
{
    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Entering GameState...");

    m_pSceneMgr = OgreFramework::getSingletonPtr()->m_pRoot->createSceneManager(Ogre::ST_GENERIC, "GameSceneMgr");
    m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.7f, 0.7f, 0.7f));

    m_pCamera = m_pSceneMgr->createCamera("GameCamera");
    m_pCamera->setPosition(Ogre::Vector3(0,20,-450));
    m_pCamera->lookAt(Ogre::Vector3(0, 0, 0));
    m_pCamera->setNearClipDistance(5);

    m_pCamera->setAspectRatio(Ogre::Real(OgreFramework::getSingletonPtr()->m_pViewport->getActualWidth()) / Ogre::Real(OgreFramework::getSingletonPtr()->m_pViewport->getActualHeight()));

    OgreFramework::getSingletonPtr()->m_pViewport->setCamera(m_pCamera);

    // Setup Physics
    btBroadphaseInterface *BroadPhase = new btAxisSweep3(btVector3(-1000,-1000,-1000), btVector3(1000,1000,1000));
    btDefaultCollisionConfiguration *CollisionConfiguration = new btDefaultCollisionConfiguration();
    btCollisionDispatcher *Dispatcher = new btCollisionDispatcher(CollisionConfiguration);
    btSequentialImpulseConstraintSolver *Solver = new btSequentialImpulseConstraintSolver();
    World = new btDiscreteDynamicsWorld(Dispatcher, BroadPhase, Solver, CollisionConfiguration);
    // End Physics

    buildGUI();

    createScene();
}

The bit we are really interested in is the bit about the physics between // Setup Physics and // End Physics. Without going into to much detail, this creates our world of physical simulation. Given that I am so new to this to be honest I am not sure what options are available here and what effect it will have. I will in future posts attempt to explain in more detail as I go.

You will notice here the call to createScene(). This was already in my code and it seemed like a good place to create the ground. Ours will be for now be a dull flat grey ground, but will provide a good surface to drop cubes onto. :)

The createScene code looks like this :
void GameState::createScene()
{
    m_pSceneMgr->createLight("Light")->setPosition(75,75,75);

    // Physics

    // Create a ground plane in the Ogre Rendering Engine.
    Ogre::Entity *ent;
    Ogre::Plane p;
    p.normal = Ogre::Vector3(0,1,0); p.d = 0;
    Ogre::MeshManager::getSingleton().createPlane(
        "FloorPlane", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        p, 200000, 200000, 20, 20, true, 1, 9000,9000,Ogre::Vector3::UNIT_Z);
    ent = m_pSceneMgr->createEntity("floor", "FloorPlane");
    ent->setMaterialName("Test/Floor");
    Ogre::SceneNode *node = m_pSceneMgr->getRootSceneNode()->createChildSceneNode();
    node->attachObject(ent);


    // Create the physics transformations for the ground
    btTransform Transform;
    Transform.setIdentity();
    Transform.setOrigin(btVector3(0,0,0));

    // Give it to the motion state and setup the rigid body shape also as a plane.
    btDefaultMotionState *MotionState = new btDefaultMotionState(Transform);
    btCollisionShape *Shape = new btStaticPlaneShape(btVector3(0,1,0),0);

    // Add Mass as 0 so it doesn't try and move with gravity.
    btVector3 LocalInertia;
    Shape->calculateLocalInertia(0, LocalInertia);

    // Create the rigid body object
    btRigidBody *RigidBody = new btRigidBody(0, MotionState, Shape, LocalInertia);

    // Store a pointer to the Ogre SceneNode so we can update it later
    RigidBody->setUserPointer((void *) (node));

    // Add it to the physics world
    World->addRigidBody(RigidBody);
    Objects.push_back(RigidBody);
    m_pNumEntities++;

    // End Physics
  
}

I have added comments here to show what is happening. We start by using Ogres built in methods to create a ground plane which gives us a rather ugly gray ground as far as the eye can see. :) (You could of course add textures to it if you wanted but since I am just experimenting I haven't bothered.)
We then create a physics rigid body plane shape with 0 mass so it isn't affected by other objects or gravity.
Calling the RigidBody->setUserPointer method allows us to store a pointer back to the SceneNode in Ogre which we need so that we can update the position of the object as it interacts with the world later on.
(You could additionally use physics plane shapes to put invisible walls around the world if you were making a game and wanted to stop people walking off the edge of a map for example.)

Next we need to be able to create an object that can demonstrate the physics in action. This is where the CreateCube method comes in. Here it is.

void GameState::CreateCube(const btVector3 &Position, btScalar Mass)
{
    // empty ogre vectors for the cubes size and position
    Ogre::Vector3 size = Ogre::Vector3::ZERO;
    Ogre::Vector3 pos = Ogre::Vector3::ZERO;
 
    // Convert the bullet physics vector to the ogre vector
    pos.x = Position.getX();
    pos.y = Position.getY();
    pos.z = Position.getZ();


    // Create a cube by using a cube mesh (you can find one in the ogre samples)
    Ogre::Entity *entity = m_pSceneMgr->createEntity(
        "Box" + Ogre::StringConverter::toString(m_pNumEntities),
        "cube.mesh");
    entity->setCastShadows(true);

    // This gets us the size of the bounding box but since the bounding box in Ogre
    // is a little bigger than the object itself we will cut that down slightly to make the physics
    // more accurate.
    Ogre::AxisAlignedBox boundingB = entity->getBoundingBox();
    size = boundingB.getSize()*0.95f;
   
    // apply a material to the cube so it isn't gray like the ground
    entity->setMaterialName("Test/Cube");

    // Create a sceneNode and attach the entity for rendering
    Ogre::SceneNode *node = m_pSceneMgr->getRootSceneNode()->createChildSceneNode();
    node->attachObject(entity);
    node->setPosition(pos);  // This gives it the initial position as provided

    // Physics
    btTransform Transform;
    Transform.setIdentity();
    Transform.setOrigin(Position);  // Set the position of the rigid body to match the sceneNode

    // Give it to the motion state
    btDefaultMotionState *MotionState = new btDefaultMotionState(Transform);

   // Being new myself I'm not sure why this happen but we give the rigid body half the size
   // of our cube and tell it to create a BoxShape (cube)
    btVector3 HalfExtents(size.x*0.5f,size.y*0.5f,size.z*0.5f);
    btCollisionShape *Shape = new btBoxShape(HalfExtents);

    // Add Mass to the object so it is appropriately affected by other objects and gravity
    // In this case we pass the mass into the function when we call it.
    btVector3 LocalInertia;
    Shape->calculateLocalInertia(Mass, LocalInertia);

    // Create the rigid body object
    btRigidBody *RigidBody = new btRigidBody(Mass, MotionState, Shape, LocalInertia);

    // Store a pointer to the Ogre Node so we can update it later
    RigidBody->setUserPointer((void *) (node));

    // Add it to the physics world
    World->addRigidBody(RigidBody);
    Objects.push_back(RigidBody);
    m_pNumEntities++;
}

All pretty easy stuff really. I'm sure by now you can see how this could be extended to provide different shaped objects with different mass etc to see how they react differently with each other.

Next we need to create the function that updates the render system based on the calculations the physic library has done. Essentially this is what allows the render system to visualize the physics interactions.
Don't get worried. It's probably easier than the rest. We don't need to worry about the calculations as the physics library has done all that for us. We just need to tell Ogre how to view it.

Here it is :
void GameState::UpdatePhysics(unsigned int DeltaTime)
{
    // This is where the physics library does it's calculations based on the time since last frame.
    // Incorporating the time since last frame (DeltaTime) should give us smooth movement
    World->stepSimulation(DeltaTime * 0.001f, 60);
   
    btRigidBody *TObject;

    // For each of the rigid body objects in our world (stored in the vector)
    for(std::vector<btRigidBody *>::iterator it = Objects.begin(); it != Objects.end(); ++it) {
        // Get the SceneNode using the pointer we saved when we created the objects
        Ogre::SceneNode *node = static_cast<Ogre::SceneNode *>((*it)->getUserPointer());
        TObject = *it;

        // Get the calculated position from the rigidbody object
        // and set the position of the rendered object
        btVector3 Point = TObject->getCenterOfMassPosition();
        node->setPosition(Ogre::Vector3((float)Point[0], (float)Point[1], (float)Point[2]));
       
        // Get the Orientation of the rigidbody as a bullet Quaternion
        // Convert it to an Ogre quaternion
        btQuaternion btq = TObject->getOrientation();
        Ogre::Quaternion quart = Ogre::Quaternion(btq.w(),btq.x(),btq.y(),btq.z());
       
        // Set the orientation of the rendered Object
        node->setOrientation(quart);
    }
}

Nothing complicated there either. I can appreciate though that trying to understand it might give you a headache.. or maybe that's just me. In any case. Only one thing left and that is to destroy all our rigid body objects when we are done with them.

Here it is : (this one is really complex, I hope you can follow)
void GameState::ClearObjects() {
    Objects.clear();
}

You can then just add a call to the CreateCube method from your input handler. I called CreateCube when I press the 1 key, but how you do that is really up to you. You could give it a set position and see what happens when they stack up to that position or you could create them at the camera position if you can move your camera around.
Have FUN!
Obviously you will need to adjust it to suit your own project.. or better yet use an existing wrapper. However it should help get a basic understanding of what is involved in integrating a physics engine with a rendering engine. If your up to it you can try adding the function to create a sphere object.. I will probably post again soon with that code.. but I'm sure you would learn more by giving it a go yourself.

More complex objects such as character meshes, terrain etc I will also investigate in a future post. I know this has been a fun experiment for me. Hopefully someone will find it useful or even just a bit of fun.

I ended up with something like this :

1 comment:

  1. Nice! Could you please upload your source code to have a better idea?

    ReplyDelete