Ogre + Bullet - Beginner's Tutorial
Lately, I have been testing my skills with Ogre3D, a 3D rendering engine written in C++, and Bullet physics, which is used in many commercial games to give realistic movement and feel to movable objects in the world. I was amazed to see that there is no comprehensive tutorial on how to use Bullet with Ogre3D.
There is a library called OgreBullet, but I didn’t want to use that because I have had some problems compiling it for OS X. Moreover, frameworks such as these link against Ogre, and many times things get messy if you try to compile the latest version of Ogre3D from their mercurial repository, since the latest version may differ from the stable one, and OgreBullet functions may not find what they expect in both frameworks.
Anyway, without priding myself on being an expert in Bullet or Ogre3D, I would like to give others my findings on the matter and help them implement basic Bullet functionality to their Ogre projects while explaining how Bullet physics works in general.
What is “Bullet physics”?
Bullet Physics is a cross-platform framework for implementing realistic physics and collision detection into games and simulation applications. It is written in C++, and it is compatible with many game engines, Ogre3D being one of them. Prerequisites First of all, let me clarify that the installation method depends on your platform, and I will not talk about installing the library in this post. This post assumes that you have the following:
- A compiled and working Ogre3D installation in your system. The headers should be in the correct header search path
- A compiled and working Bullet Physics installation in your system. The headers should be already in the correct path
- A new project, ready to write code, in the tool of your choice. I used Xcode on OS X for this post, but I will stay as cross-platform as possible.
- A basic understanding of Ogre3D concepts.
- C++ knowledge (duh!)
How does it work?
Bullet physics works as an intermediate layer between your rendering process and your game logic. The process of actually implementing physics into actual actors in your Ogre3D application is the following:
- Define a world in Bullet physics. The gravity, global physics object properties, etc.
- Create an
Ogre::Entity
. The height and width of the entity should be known. - Attach the entity to a Node, as you would normally.
- Create a bullet physics entity, and assign it the initial properties, in terms of physics, and location/rotation. You must also define its shape (cube, triangle, custom object, etc) and its initial inertia.
- Create the physics entity to the Ogre Node you just create it. How you do this to associate them is up to you. Bullet has a
setUserPointer()
method to the rigid body, for your convenience, but you can place the associations in anstd::map
if you want. For this example, we use the first approach, for the sake of clarity. - Add the new physics entity to the world you created in step 1.
- When the items are all added to the Ogre’s scene graph, and associated with Bullet’s objects, we can call the
btDynamicsWorld::stepSimulation()
function, with a delta number. This will move the simulation according to the physics and the collision properties. For Ogre, the best location for that to happen is the function that is implemented by theOgre::FrameListener
class (your class).bool Engine::frameStarted (const Ogre::FrameEvent &evt)
- Now, iterate through all
btRigidBodies
of thebtDynamicsWorld
you created in step 1, get their new position and rotation, and apply these to theOgre::Node
s that you have added to the scene.
Time to code
I will only provide some sample code. It is up to you to adapt it to your needs and replace the meshes and materials mentioned in this post with your own. I would give my actual code if I could. Still, the architecture of an application such as this is so subjective and uniquely tied to each implementation that I don’t want to point anyone in a direction that would cause problems afterward. So, we have the following properties inside a class called “Physics.” A “Physics” instance is included by my Ogre::Framelistener subclass (which also implements my game logic)
class Physics {
btDefaultCollisionConfiguration* collisionConfiguration;
btCollisionDispatcher* dispatcher;
btBroadphaseInterface* overlappingPairCache;
btSequentialImpulseConstraintSolver* solver;
btDiscreteDynamicsWorld* dynamicsWorld;
std::vector<btCollisionShape *> collisionShapes;
std::map<std::string, btRigidBody *> physicsAccessors;
public:
......... <<irrelevant code here...>
};
We initialise the objects in a function of our choice. That could be a constructor or a function called elsewhere.
void Physics::initObjects() {
collisionConfiguration = new btDefaultCollisionConfiguration();
dispatcher = new btCollisionDispatcher(collisionConfiguration);
overlappingPairCache = new btDbvtBroadphase();
solver = new btSequentialImpulseConstraintSolver();
dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
}
That has created a dynamic world ready to accept our objects in it.
For most applications, the first thing they have to do is to create a floor, where objects and characters will step on. For this example, we will create a basic plane.
//create the actual plane in Ogre3D
Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);
Ogre::MeshPtr planePtr = Ogre::MeshManager::getSingleton().createPlane("ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500, 1500, 20, 20, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);
Ogre::Entity *entGround = sceneManager->createEntity("GroundEntity", "ground");
Ogre::SceneNode *groundNode = sceneManager->getRootSceneNode()->createChildSceneNode("groundNode");
groundNode->attachObject(entGround);
//create the plane entity to the physics engine, and attach it to the node
btTransform groundTransform;
groundTransform.setIdentity();
groundTransform.setOrigin(btVector3(0, -50, 0));
btScalar groundMass(0.); //the mass is 0, because the ground is immovable (static)
btVector3 localGroundInertia(0, 0, 0);
btCollisionShape *groundShape = new btBoxShape(btVector3(btScalar(50.), btScalar(50.), btScalar(50.)));
btDefaultMotionState *groundMotionState = new btDefaultMotionState(groundTransform);
groundShape->calculateLocalInertia(groundMass, localGroundInertia);
btRigidBody::btRigidBodyConstructionInfo groundRBInfo(groundMass, groundMotionState, groundShape, localGroundInertia);
btRigidBody *groundBody = new btRigidBody(groundRBInfo);
//add the body to the dynamics world
this->physicsEngine->getDynamicsWorld()->addRigidBody(groundBody);
Notice that I set the origin of the bullet physics ground to be at -50.0f
, which is different than the one used for the Ogre plane. This “magic number” came out with a lot of experimentation and trial and error. Until this day, I still cannot understand why this has to be different. I hope that someone smarter than me will explain to me when he reads this tutorial. From now on, let’s accept it as it is. EDIT “metaltomato” was kind enough to share his solution to this problem. Look at the comments below the article. Thanks a lot to ‘metaltomato’ for providing feedback on this.
Now, at any part of our application, we want to create a simple cube, and add it to out world.
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().getByName("Cube.mesh").staticCast<Ogre::Mesh>();
Ogre::Entity *entity = this->sceneManager->createEntity(mesh);
Ogre::SceneNode *newNode = this->sceneManager->getRootSceneNode()->createChildSceneNode(physicsCubeName);
newNode->attachObject(entity);
//create the new shape, and tell the physics that is a Box
btCollisionShape *newRigidShape = new btBoxShape(btVector3(1.0f, 1.0f, 1.0f));
this->physicsEngine->getCollisionShapes().push_back(newRigidShape);
//set the initial position and transform. For this demo, we set the tranform to be none
btTransform startTransform;
startTransform.setIdentity();
startTransform.setRotation(btQuaternion(1.0f, 1.0f, 1.0f, 0));
//set the mass of the object. a mass of "0" means that it is an immovable object
btScalar mass = 0.1f;
btVector3 localInertia(0,0,0);
startTransform.setOrigin(initialPosition);
newRigidShape->calculateLocalInertia(mass, localInertia);
//actually contruvc the body and add it to the dynamics world
btDefaultMotionState *myMotionState = new btDefaultMotionState(startTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, newRigidShape, localInertia);
btRigidBody *body = new btRigidBody(rbInfo);
body->setRestitution(1);
body->setUserPointer(newNode);
physicsEngine->getDynamicsWorld()->addRigidBody(body);
physicsEngine->trackRigidBodyWithName(body, physicsCubeName);
Last but not least, comes the code that has to be inserted into the FrameStarted()
function. We iterate through each rigid body of the scene, we get the associated note, and we update its rotation and position according to the rotation and position of the rigid body.
bool Engine::frameStarted (const Ogre::FrameEvent &evt){
if (this->physicsEngine != NULL){
physicsEngine->getDynamicsWorld()->stepSimulation(1.0f/60.0f); //suppose you have 60 frames per second
for (int i = 0; i< this->physicsEngine->getCollisionObjectCount(); i++) {
btCollisionObject* obj = this->physicsEngine->getDynamicsWorld()->getCollisionObjectArray()[i];
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState()){
btTransform trans;
body->getMotionState()->getWorldTransform(trans);
void *userPointer = body->getUserPointer();
if (userPointer) {
btQuaternion orientation = trans.getRotation();
Ogre::SceneNode *sceneNode = static_cast<Ogre::SceneNode *>(userPointer);
sceneNode->setPosition(Ogre::Vector3(trans.getOrigin().getX(), trans.getOrigin().getY(), trans.getOrigin().getZ()));
sceneNode->setOrientation(Ogre::Quaternion(orientation.getW(), orientation.getX(), orientation.getY(), orientation.getZ()));
}
}
}
}
return true;
}
Following this method, I have been able to produce the following result:
Of course, in the above video, there is a scripting system, and I have created a script using this system that creates cubes in random places. But the logic is the same as the one I am presenting here.
If you have any comments or questions, feel free to share them in the comment section below.
Happy coding!