====== LAB9: Physics ====== In this lab we will scratch the surface of using the Bullet physics blibrary with Ogre. {{ :public:t-gede-14-1:ogrebullet1.png?nolink |}}{{ :public:t-gede-14-1:ogrebullet2.png?nolink |}} ===== Discussion ===== Discussion thread for this lab is on Piazza ===== Preperation ===== You can use your own project or start fresh with the base application {{:public:t-gede-14-1:lab6_handout.7z|code}} ===== Bullet ===== - Setup Bullet Project. - Downlaod [[https://github.com/bulletphysics/bullet3/releases|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. - Build Bullet - Run \build\vs2010.bat to generate the VS 2010 project files. - Open the \build\vs2010\0BulletSolution.sln With you VS of choice. - 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. - Change the C/C++->Code Generation->Runtime Library to Multi Threaded DLL (/MDd (Debug) and /MD (Release)) - Build the solution for debug and release. ===== Lab ===== And now for the Ogre project. - **Add** ''\lib;'' to ''Linker->General->Additional Library Directories'' (Debug and Release) - **Add** ''\src;'' to ''C++->General->Additional Include Directories'' (Debug and Release) - **Add** ''BulletCollision_vs2010_debug.lib;BulletDynamics_vs2010_debug.lib;LinearMath_vs2010_debug.lib'' to the ''Linker->Input->Additional Dependencies'' (Debug) - **Add** ''BulletCollision_vs2010.lib;BulletDynamics_vs2010.lib;LinearMath_vs2010.lib'' to the ''Linker->Input->Additional Dependencies'' (Release) - **Create class Physics.** // Header! #ifndef PHYSICS_H #define PHYSICS_H #include "btBulletCollisionCommon.h" #include "btBulletDynamicsCommon.h" #include "OgreMotionState.h" #include #include class Physics{ btDefaultCollisionConfiguration* collisionConfiguration; btCollisionDispatcher* dispatcher; btBroadphaseInterface* overlappingPairCache; btSequentialImpulseConstraintSolver* solver; btDiscreteDynamicsWorld* dynamicsWorld; std::vector collisionShapes; std::map 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; } - **Create a pointer** to physics in your Application, and initialize it in the startup function. _physicsEngine = new Physics(); _physicsEngine->initObjects(); - **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. - **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. - **Now scale** the sinbad node down by 0.2 ''_SinbadNode->scale(Ogre::Vector3(0.2f,0.2f,0.2f));'' - **Change** the nearClippingPlaneDistance of the camera to 0.5 - **In the frameListener class,** change the movement speed of the camera to something around 10-20. - **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. - 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; } - 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. - 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 - **Now we are ready to create some rigid Bodies.** - **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); - **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); } } - **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. - **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;'' - **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(_InputManager->createInputObject(OIS::OISMouse, false)); with this // 7.3 Initialize the Mouse input listener. try { _Mouse = static_cast(_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 :). - **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); } - **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); ** 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 ''\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).