Table of Contents

LAB9: Physics

In this lab we will scratch the surface of using the Bullet physics blibrary with Ogre.

Discussion

Discussion thread for this lab is on Piazza

Preperation

You can use your own project or start fresh with the base application code

Bullet

  1. Setup Bullet Project.
  2. Downlaod Bullet I recommend downloading the 2.83 version. If you want to test something else, that is fine, I just haven't tested that with the following code.
  3. Build Bullet
    1. Run <bullet root>\build\vs2010.bat to generate the VS 2010 project files.
    2. Open the <bullet root>\build\vs2010\0BulletSolution.sln With you VS of choice.
    3. Make sure you compile using the v100 version of the visual studio compiler
      Note: that you can select properties for one project, then multi select the projects you want to change the properties for to change the value in multiple projects at the same time.
    4. Change the C/C++→Code Generation→Runtime Library to Multi Threaded DLL (/MDd (Debug) and /MD (Release))
    5. Build the solution for debug and release.

Lab

And now for the Ogre project.

  1. Add <Bullet Root>\lib; to Linker→General→Additional Library Directories (Debug and Release)
  2. Add <Bullet Root>\src; to C++→General→Additional Include Directories (Debug and Release)
  3. Add BulletCollision_vs2010_debug.lib;BulletDynamics_vs2010_debug.lib;LinearMath_vs2010_debug.lib to the Linker→Input→Additional Dependencies (Debug)
  4. Add BulletCollision_vs2010.lib;BulletDynamics_vs2010.lib;LinearMath_vs2010.lib to the Linker→Input→Additional Dependencies (Release)
  5. Create class Physics.
    // Header!
     
    #ifndef PHYSICS_H
    #define PHYSICS_H
    #include "btBulletCollisionCommon.h"
    #include "btBulletDynamicsCommon.h"
    #include "OgreMotionState.h"
    #include <vector>
    #include <map>
     
    class Physics{
    	btDefaultCollisionConfiguration* collisionConfiguration;
    	btCollisionDispatcher* dispatcher;
    	btBroadphaseInterface* overlappingPairCache;
    	btSequentialImpulseConstraintSolver* solver;
    	btDiscreteDynamicsWorld* dynamicsWorld;
    	std::vector<btCollisionShape *> collisionShapes;
    	std::map<std::string, btRigidBody *> physicsAccessors;
    public:
    	Physics();
            void initObjects();
    	virtual ~Physics();
    	btDiscreteDynamicsWorld* getDynamicsWorld();
     
    };
     
    #endif //PHYSICS_H
     
    // Implementation!
     
    #include "Physics.h"
     
    Physics::Physics() {
     
    }
     
    Physics::~Physics() {
    	delete dynamicsWorld;
    	delete solver;
    	delete overlappingPairCache;
    	delete dispatcher;
    	delete collisionConfiguration;
    }
     
    void Physics::initObjects() {
    	collisionConfiguration = new btDefaultCollisionConfiguration();
    	dispatcher = new btCollisionDispatcher(collisionConfiguration);
    	overlappingPairCache = new btDbvtBroadphase();
    	solver = new btSequentialImpulseConstraintSolver();
    	dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
    }
     
    btDiscreteDynamicsWorld* Physics::getDynamicsWorld() {
    	return dynamicsWorld;
    }
  6. Create a pointer to physics in your Application, and initialize it in the startup function.
    	_physicsEngine = new Physics();
    	_physicsEngine->initObjects();
  7. Before we start there are some basic changes that have to made to the environment, in order to make the physics look more realist, we must make sure that our environmet is in a logical scale. If you have the handout code, Sinbad will be around 9.5 units high, Bullet library uses 1 unit as equal to 1 meter.
    1. You can check out Sinbad's actualt height by doing _SinbadNode→getBoundingBox().getSize(), this function will return the bounding box the the sceneNode, which is an axis aligned box that encompasses the entities of the SceneNode and is defined by the vertex extremas + an additional 2% scale.
    2. Now scale the sinbad node down by 0.2 _SinbadNode→scale(Ogre::Vector3(0.2f,0.2f,0.2f));
    3. Change the nearClippingPlaneDistance of the camera to 0.5
    4. In the frameListener class, change the movement speed of the camera to something around 10-20.
  8. Now to create Rigid Bodies we must first let bullet know what form of rigid body we want, and we must also tell bullet the size, position and orientation of our RigidBody.
    1. Add this function to your Physics class.
      btRigidBody* AddDynamicCubeRigidBoidy(Ogre::SceneNode* node, Ogre::Entity* ent, btScalar mass)
       
      btRigidBody* Physics::AddDynamicCubeRigidBoidy(Ogre::SceneNode* node, Ogre::Entity* ent, btScalar mass) {
      	Ogre::Vector3 aabbSize = ent->getBoundingBox().getSize() / 1.02f;
       
      	btScalar x = 0.5f * node->getScale().x * aabbSize.x;
      	btScalar y = 0.5f * node->getScale().y * aabbSize.y;
      	btScalar z = 0.5f * node->getScale().z * aabbSize.z;
      	btCollisionShape* colShape = new btBoxShape(btVector3(x, y, z));
       
      	/// Create Dynamic Objects
      	btTransform startTransform;
      	startTransform.setIdentity();
       
      	//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(node->getPosition().x, node->getPosition().y, node->getPosition().z));
       
      	btQuaternion initRotation(node->getOrientation().x, node->getOrientation().y, node->getOrientation().z, node->getOrientation().w);
      	startTransform.setRotation(initRotation);
       
      	OgreMotionState* motionState = new OgreMotionState(startTransform, node);
      	btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, motionState, colShape, localInertia);
      	btRigidBody* body = new btRigidBody(rbInfo);
       
      	dynamicsWorld->addRigidBody(body);
       
      	return body;
      }
    2. As you might have noticed, that function uses a class called OgreMotionState, this is a class that we must implement in order for Bullet to automatically transoform our SceneNode according to the movement of the physics object.
    3. Add the file OgreMotionState.h to your project.
      #ifndef OGREMOTIONSTATE_H
      #define OGREMOTIONSTATE_H
       
      #include "LinearMath\btMotionState.h"
      #include "OGRE\Ogre.h"
      class OgreMotionState : public btMotionState {
      public:
      	OgreMotionState(const btTransform &initialpos, Ogre::SceneNode *node) {
      		mVisibleobj = node;
      		mPos1 = initialpos;
      	}
       
      	virtual ~OgreMotionState() {
      	}
       
      	void setNode(Ogre::SceneNode *node) {
      		mVisibleobj = node;
      	}
       
      	virtual void getWorldTransform(btTransform &worldTrans) const {
      		worldTrans = mPos1;
      	}
       
      	virtual void setWorldTransform(const btTransform &worldTrans) {
      		if (NULL == mVisibleobj)
      			return;
      		btQuaternion rot = worldTrans.getRotation();
      		mVisibleobj->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());
      		btVector3 pos = worldTrans.getOrigin();
      		mVisibleobj->setPosition(pos.x(), pos.y(), pos.z());
      	}
       
      protected:
      	Ogre::SceneNode *mVisibleobj;
      	btTransform mPos1;
      };
       
      #endif //OGREMOTIONSTATE_H
  9. Now we are ready to create some rigid Bodies.
    1. Lets start by setting the ground as a collision plane. Make the following changes and add the bullet code to the definition of your ground plane.
      // Create the plane.
      	Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);
      	Ogre::MeshManager::getSingleton().createPlane("plane", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
      		1500, 1500, 200, 200, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);
       
      	// Set the plane as the ground and add a texture to it.
      	Ogre::Entity* ground = _sceneManager->createEntity("LightPlaneEntity", "plane");
      	Ogre::SceneNode* groundNode = _sceneManager->getRootSceneNode()->createChildSceneNode();
      	groundNode->attachObject(ground);
      	ground->setMaterialName("Examples/BeachStones");
       
      	groundNode->setPosition(0, -1, 0);
       
      	// Create the collision shape, and give it the ground plane normals.
      	btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(plane.normal.x, plane.normal.y, plane.normal.z), 0);
       
      	// Create the collision transform.
      	btTransform groundTransform;
       
      	// Set up the collision location and orientation.
      	groundTransform.setIdentity();
      	groundTransform.setOrigin(btVector3(groundNode->getPosition().x, groundNode->getPosition().y, groundNode->getPosition().z));
      	btQuaternion initRotation(groundNode->getOrientation().x, groundNode->getOrientation().y, groundNode->getOrientation().z, groundNode->getOrientation().w);
      	groundTransform.setRotation(initRotation);
       
      	// Give the plane a mass of 0, because our plane will be static, thus will not moce.
      	btScalar mass(0.0f);
       
      	// Set the ground as a static object.
      	bool isDynamic = false;
       
      	// This plane isnt going to be moving so i dont care about setting the motion state
      	btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
      	btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, groundShape);
      	btRigidBody* body = new btRigidBody(rbInfo);
       
      	//add the body to the dynamics world
      	_physicsEngine->getDynamicsWorld()->addRigidBody(body);
    2. Now lets add some cubes :), add this function to your create scene function.
      	int ringCount = 16;
      	int ringheight = 10;
      	for (int i = 0; i < ringheight; ++i) {
      		for (int j = 0; j < ringCount; ++j) {
      			Ogre::Entity* cubeEnt = _sceneManager->createEntity("Cube.mesh");
      			Ogre::SceneNode* cubeNode = _sceneManager->getRootSceneNode()->createChildSceneNode();
      			cubeNode->setScale(0.006f, 0.004f, 0.006f);
      			Ogre::Radian angle((Ogre::Math::TWO_PI / ringCount)*j + (Ogre::Math::TWO_PI / ringCount)*0.5f*(i%2) );
       
      			cubeNode->setOrientation(Ogre::Quaternion(-angle, Ogre::Vector3::UNIT_Y));
      			//cubeNode->setPosition(j*0.62+((i%2)*0.31f), i*0.405-(1-0.2), 0);
       
      			cubeNode->setPosition(Ogre::Math::Cos(angle) * 1.9f, i*0.405f - (1 - 0.2f), Ogre::Math::Sin(angle) * 1.9f);
       
      			cubeNode->attachObject(cubeEnt);
      			btRigidBody* temp = _physicsEngine->AddDynamicCubeRigidBoidy(cubeNode, cubeEnt, 1);
      			temp->setFriction(1);
      		}	
      	}
  10. And finally we must update the dynamicWorld and of course make the camera shoot some boxes! For this you will need to pass to the FrameListener a pointer to the sceneManager and the Physics engine.
    1. You can either do that via the constructor, or use the lazy way, and make them public, and set them like so _listener→_physicsEngine = _physicsEngine;
    2. Make the following modifications to your frameListener
      Make the FrameListener also inherit from OIS::MouseListener
      class MyFrameListener : public Ogre::FrameListener, OIS::MouseListener

      And add the three virtual functions of OIS::MouseListener

      virtual bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id) {
      	mouseMask |= 1 << (int)id;
      	return true;
      }
      virtual bool mouseMoved(const OIS::MouseEvent &arg) {
      	return true;
      }
      virtual bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) {
      	return true;
      }

      And in the constructor: replace

      _Mouse = static_cast<OIS::Mouse*>(_InputManager->createInputObject(OIS::OISMouse, false));

      with this

      // 7.3 Initialize the Mouse input listener.
      try {
      	_Mouse = static_cast<OIS::Mouse*>(_InputManager->createInputObject(OIS::OISMouse, true));
      	_Mouse->setEventCallback(this);
      	std::cout << "Successfuly created Mouse";
      }
      catch (...) {
      	std::cout << "Failed to initialize Mouse";
      	_Mouse = 0;
      }

      Also make sure to add the int member variable mouseMask, and set it to 0 in the FrameListener constructor. Now you can query whether a mouse button was pressed like so (mouseMask & (1 « (int)OIS::MouseButtonID::MB_Left)) make sure to add mouseMask = 0 before your call to _Mouse→capture() in you FrameStarted function so we don't retain old mouse states :).

  11. Now we spawn a cube from the origin of the camera and give it some linear velocity in the direction of the camera. You will have to pass a pointer to the SceneManager into the frameListener.
    if (  (mouseMask & (1 << (int)OIS::MouseButtonID::MB_Left))  ) {
    	// Create a cube entity.
    	Ogre::Entity* cubeEnt = _sceneManager->createEntity("Cube.mesh");
    	Ogre::SceneNode* cubeNode = _sceneManager->getRootSceneNode()->createChildSceneNode();
    
    	cubeNode->setScale(0.005f, 0.005f, 0.005f);
    	// Set the position of the cube to the front of the origin of the camera 
    	cubeNode->setPosition(_Cam->getPosition() + _Cam->getDirection());
    	cubeNode->attachObject(cubeEnt);
    
    	// Now make the cube a rigid body, give it some linearVelocity and add it to the physics world
    	btRigidBody* boxBody = _physicsEngine->AddDynamicCubeRigidBoidy(cubeNode, cubeEnt, 2);
    	boxBody->setLinearVelocity(btVector3(_Cam->getDirection().x, _Cam->getDirection().y, _Cam->getDirection().z) * 30);
    }
  12. Finally we update the physics world
    // Now update the physics world with the delta time. "Note: normally we would want to have the physics world update a little more independant of the framerate, but this will do for now :)"
    	_physicsEngine->getDynamicsWorld()->stepSimulation(evt.timeSinceLastFrame);

Attention!

Compile and run using a Release built target!!! Very important, unless you plan to fry some eggs and bacon on your laptop :) Just make sure to use the Release version of the plugins file when you run the application.It can be found under <YourOgreSDK>\bin\Release\plugins.cfg Check out these pre-processor variables to help you out
#if _DEBUG
	cf.load("../LabFiles/OgreConfig/resources_d.cfg");
#elif NDEBUG
	cf.load("../LabFiles/OgreConfig/resources.cfg");
#endif
 
#if _DEBUG
	_root = new Ogre::Root("../LabFiles/OgreConfig/plugins_d.cfg","../LabFiles/OgreConfig/ogre.cfg", "../LabFiles/ogre.log");
#elif NDEBUG
	_root = new Ogre::Root("../LabFiles/OgreConfig/plugins.cfg","../LabFiles/OgreConfig/ogre.cfg", "../LabFiles/ogre.log");
#endif

When You Are Finished

Upload your commented source files into Lab9 in MySchool (zip them up if more than one).