Skip to main content

Image
Note: The implementation below uses an OgreODE RayGeometry and a CapsuleGeometry to build a ray model controller for game characters.
The end result looks like a corn dog or a lolly pop XD

I've written a short demo application, and it tested fine on my machine. If for some reason it doesn't work, just re-use the parts of code that work best for you.

Image

CharacterController.h

Copy to clipboard
#ifndef _CHARACTER_CONTROLLER_ #define _CHARACTER_CONTROLLER_ #include "OgreOde_Core.h" #include "ExampleFrameListener.h" // Declare a subclass of the ExampleFrameListener class class MyListener : public ExampleFrameListener , public OgreOde::CollisionListener , public OgreOde::StepListener , public OgreOde::TriangleMeshRayListener { public: MyListener( Ogre::RenderWindow* win , Ogre::Camera* cam , Ogre::SceneManager* sMgr , Ogre::Root* mRoot ); ~MyListener( void ); bool frameStarted( const Ogre::FrameEvent& evt ); bool frameEnded( const FrameEvent& evt ); // OgreOde::CollisionListener function bool collision( OgreOde::Contact* contact ); // OgreOde::StepListener function bool preStep( Ogre::Real time ) { //addForcesAndTorques(); return true; } // reposition OgreOde Ray(s) void simulatePhysics( const FrameEvent &evt ); // Will get called just before each time step, // since a timestep zeros the force accumulators void addForcesAndTorques( void ) {} void create_ODE_Terrain( std::string meshFile , Ogre::Vector3 position , Ogre::Vector3 size ); void player_Initialise( void ); void act_walk( bool walk_request = false , bool forward_walk = false ); void act_strafe( bool strafe_request = false , bool left_strafe = false ); void act_jump( bool jump_request = false ); private: OgreOde::World *p_World; OgreOde::StepHandler *p_Stepper; Ogre::SceneManager *p_SceneMgr; Ogre::Camera *p_Camera; Ogre::Entity *p_BaseEntity; //Your Player.mesh Ogre::SceneNode *p_BaseNode; Ogre::SceneNode *p_ModelNode; OgreOde::Space *p_PhysicsCharSpace; OgreOde::Body *p_BaseBody; OgreOde::Geometry *p_GeomBaseBody; OgreOde::TransformGeometry *p_GeomBody; // Use this for body area specific collisions. /*OgreOde::Geometry *p_GeomBaseHead; OgreOde::TransformGeometry *p_GeomHead; OgreOde::Body *p_HeadBody;*/ OgreOde::RayGeometry *p_BaseRay; OgreOde::TriangleMeshGeometry *p_terrainTriMeshGeom; Ogre::Real r_WalkStrength, r_WalkSpeed , r_RunSpeedMultiplier; Ogre::Real r_JumpHeight, r_Radius; OgreOde::Contact *p_Contact; bool b_onGround, b_isFalling; }; #endif


CharacterController.cpp

Copy to clipboard
#include "CharacterController.h" MyListener::MyListener( Ogre::RenderWindow* win, Ogre::Camera* cam , Ogre::SceneManager* sMgr, Ogre::Root* mRoot ) : ExampleFrameListener( win, cam ) { p_SceneMgr = sMgr; p_Camera = cam; // create OgreOde World p_World = new OgreOde::World( p_SceneMgr ); p_World->setCollisionListener( this ); p_World->setGravity( Ogre::Vector3( 0 , /*-9.80665*/ -9.83665, 0 )); p_World->setCFM( 10e-5 ); p_World->setERP( 0.8 ); p_World->setAutoSleep( true ); p_World->setAutoSleepSteps( 10 ); p_World->setAutoSleepAngularThreshold( 0.1 ); /*p_World->setAutoSleepAngularThreshold( OgreOde::Utility::Infinity );*/ p_World->setAutoSleepLinearThreshold( 0.1 ); //p_World->setContactCorrectionVelocity( 1 ); p_World->setContactCorrectionVelocity( 2 ); // Create something that will step the world, but // don't do it automatically. p_Stepper = new OgreOde::StepHandler( p_World , OgreOde::StepHandler::QuickStep , Ogre::Real( 0.01 ) // Step size , Ogre::Real( 1.0 / 4 ) // Max interval , Ogre::Real( 1.0 )); // Time scale p_Stepper->setStepListener( this ); /*create_ODE_Terrain( "yourTerrainFile.mesh" , position, size );*/ create_ODE_Terrain( "racingcircuit.mesh" , Ogre::Vector3::ZERO , Ogre::Vector3( 10, 10, 10 )); player_Initialise(); p_World->setShowDebugGeometries( true ); } MyListener::~MyListener( void ) { // destroy to remove the characters, terrain and // stepper before destroying the OgreOde world if( p_terrainTriMeshGeom ) delete p_terrainTriMeshGeom; delete p_Stepper; p_Stepper = NULL; delete p_World; p_World = NULL; } bool MyListener::frameStarted( const Ogre::FrameEvent& evt ) { // quit app if( mKeyboard->isKeyDown( OIS::KC_ESCAPE )) return false; // basic movement if( mKeyboard->isKeyDown( OIS::KC_UP )) act_walk( true, true ); else if( mKeyboard->isKeyDown( OIS::KC_DOWN )) act_walk( true, false ); if( mKeyboard->isKeyDown( OIS::KC_LEFT )) act_strafe( true, true ); else if( mKeyboard->isKeyDown( OIS::KC_RIGHT )) act_strafe( true, false ); if( mKeyboard->isKeyDown( OIS::KC_SPACE )) act_jump( true ); simulatePhysics( evt ); return true; } bool MyListener::frameEnded( const FrameEvent& evt ) { if( !mKeyboard->isKeyDown( OIS::KC_UP ) || !mKeyboard->isKeyDown( OIS::KC_DOWN )) act_walk( false ); else if( !mKeyboard->isKeyDown( OIS::KC_LEFT ) || !mKeyboard->isKeyDown( OIS::KC_RIGHT )) act_strafe( false ); if( !mKeyboard->isKeyDown( OIS::KC_SPACE )) act_jump( false ); return ExampleFrameListener::frameEnded( evt ); } bool MyListener::collision( OgreOde::Contact* contact ) { if( contact->getFirstGeometry()->getID() == p_BaseRay->getID() || contact->getSecondGeometry()->getID() == p_BaseRay->getID() ) { /*contact->setCoulombFriction( OgreOde::Utility::Infinity );*/ contact->setBouncyness( 0.0 ); p_Contact = contact; } return true; } void MyListener::simulatePhysics( const FrameEvent &evt ) { p_Stepper->step( evt.timeSinceLastFrame ); p_World->synchronise(); b_onGround = false; bool collideSuccess; OgreOde::Contact *tempContact; //p_BaseBody->setDamping( 10, 10 ); //p_BaseBody->setDamping( 0.1, 0.1 ); // Fire the ray downward p_BaseRay->setDefinition( p_BaseNode->getPosition() + Ogre::Vector3( 0, r_Radius * 2, 0 ) , Ogre::Vector3::NEGATIVE_UNIT_Y ); if( p_BaseRay->collide( p_terrainTriMeshGeom, this )) collideSuccess = true; else collideSuccess = false; if( collideSuccess == true ) { p_BaseBody->setAngularVelocity( Ogre::Vector3::ZERO ); tempContact = p_Contact; Ogre::Vector3 position = p_BaseBody->getPosition(); p_BaseBody->setPosition( Ogre::Vector3( position.x , tempContact->getPosition().y , position.z )); b_onGround = true; } p_BaseBody->setAngularVelocity( Ogre::Vector3::ZERO ); p_BaseBody->setOrientation( Ogre::Quaternion( p_BaseBody->getOrientation().xAxis() , Ogre::Vector3::UNIT_Y , p_BaseBody->getOrientation().zAxis() )); } void MyListener::create_ODE_Terrain( std::string meshFile , Ogre::Vector3 position, Ogre::Vector3 size ) { Ogre::SceneNode* sn = p_SceneMgr->getRootSceneNode()-> createChildSceneNode( "TerrainNode", position ); Ogre::Entity* ent = p_SceneMgr->createEntity( "Terrain", meshFile.c_str() ); sn->attachObject( ent ); sn->setScale( size ); OgreOde::EntityInformer ei( ent, sn->_getFullTransform() ); p_terrainTriMeshGeom = ei.createStaticTriangleMesh( p_World , p_World->getDefaultSpace() ); p_terrainTriMeshGeom->setRayListener( this ); } void MyListener::player_Initialise( void ) { r_WalkStrength = 1; r_RunSpeedMultiplier = 1.2; r_JumpHeight = 10; r_WalkSpeed = 15; b_onGround = false; p_PhysicsCharSpace = new OgreOde::SimpleSpace( p_World, p_World->getDefaultSpace() ); p_PhysicsCharSpace->setInternalCollisions( false ); p_BaseEntity = p_SceneMgr->createEntity( "Ninja", "ninja.mesh" ); p_BaseNode = p_SceneMgr->getRootSceneNode()-> createChildSceneNode( "NinjaNode" ); p_ModelNode = p_BaseNode->createChildSceneNode( "NinjaModel" , Ogre::Vector3( 0, /*-0.2*/ 0, 0 )); p_ModelNode->attachObject( p_BaseEntity ); //p_ModelNode->setScale( 0.1, 0.1, 0.1 ); p_ModelNode->setScale( 0.05, 0.05, 0.05 ); Ogre::AxisAlignedBox aab = p_ModelNode->getAttachedObject( "Ninja" )->getBoundingBox(); Ogre::Vector3 min = aab.getMinimum() * p_ModelNode->getScale(); Ogre::Vector3 max = aab.getMaximum() * p_ModelNode->getScale(); Ogre::Vector3 size( fabs( max.x - min.x ) , fabs( max.y - min.y ) , fabs( max.z - min.z )); r_Radius = ( size.x > size.z ) ? size.z / 3.0 : size.x / 3.0; p_BaseRay = new OgreOde::RayGeometry( r_Radius * 2 , p_World , p_PhysicsCharSpace ); p_BaseBody = new OgreOde::Body( p_World ); p_BaseBody->setMass( OgreOde::CapsuleMass( 70 * 2 , size.x / 2.0 //r_Radius , Ogre::Vector3::UNIT_Y , size.y - 6 * r_Radius )); p_BaseBody->setAutoSleep( false ); p_GeomBaseBody = new OgreOde::CapsuleGeometry( size.x / 2.0 //r_Radius , size.y - 6 * r_Radius , p_World ); p_GeomBaseBody->setPosition( Ogre::Vector3( 0 , size.y - (( size.y - 4 * r_Radius ) / 2 + 2 * r_Radius ) , 0 )); p_GeomBaseBody->setOrientation( Quaternion( Degree( 90 ) , Ogre::Vector3::UNIT_X )); p_GeomBody = new OgreOde::TransformGeometry( p_World, p_PhysicsCharSpace ); p_GeomBody->setEncapsulatedGeometry( p_GeomBaseBody ); p_GeomBody->setBody( p_BaseBody ); p_BaseNode->attachObject( p_BaseBody ); // position and attach the camera p_Camera->setPosition( p_ModelNode->getPosition() + Ogre::Vector3( 0, 20, 35 )); p_Camera->lookAt( p_ModelNode->getPosition() ); p_ModelNode->attachObject( p_Camera ); //p_GeomBody->setOffsetPosition( Ogre::Vector3( 0, 1.8, 0 )); // ??? Seems to make no difference in the demo if // commented out. // ??? I can't remember if or when this call is // necessary... //p_BaseEntity->setUserAny( Ogre::Any( p_GeomBody )); } void MyListener::act_walk( bool walk_request, bool forward_walk ) { if( walk_request == true ) { if( forward_walk == true ) { if( b_onGround == true ) { Ogre::Vector3 walkVect3 = Vector3( 0 , 0 , r_WalkSpeed * r_WalkStrength * r_RunSpeedMultiplier ); p_BaseBody->setLinearVelocity( p_BaseBody->getOrientation() * -walkVect3 ); } } else if( forward_walk == false ) { if( b_onGround == true ) { Ogre::Vector3 walkVect3 = Vector3( 0 , 0 , r_WalkSpeed * r_WalkStrength * r_RunSpeedMultiplier ); p_BaseBody->setLinearVelocity( p_BaseBody->getOrientation() * walkVect3 ); } } } else if( walk_request == false ) { if( b_onGround == true ) { p_BaseBody->setLinearVelocity( p_BaseBody->getOrientation() * Ogre::Vector3::ZERO ); } } } void MyListener::act_strafe( bool strafe_request , bool left_strafe ) { if( strafe_request == true ) { if( left_strafe == true ) { if( b_onGround == true ) { Ogre::Vector3 strafeVect3 = Vector3( r_WalkSpeed * r_WalkStrength * r_RunSpeedMultiplier , 0 , 0 ); p_BaseBody->setLinearVelocity( p_BaseBody->getOrientation() * -strafeVect3 ); } } else if( left_strafe == false ) { if( b_onGround == true ) { Ogre::Vector3 strafeVect3 = Vector3( r_WalkSpeed * r_WalkStrength * r_RunSpeedMultiplier , 0 , 0 ); p_BaseBody->setLinearVelocity( p_BaseBody->getOrientation() * strafeVect3 ); } } } else if( strafe_request == false ) { if( b_onGround == true ) { p_BaseBody->setLinearVelocity( p_BaseBody->getOrientation() * Ogre::Vector3::ZERO ); } } } void MyListener::act_jump( bool jump_request ) { if( b_onGround == true && jump_request == true ) { p_BaseBody->setLinearVelocity( Vector3( p_BaseBody->getLinearVelocity().x /*/ 1.9 */ , r_JumpHeight , p_BaseBody->getLinearVelocity().z /*/ 1.9*/ )); } }


Main.cpp

Copy to clipboard
#include "ExampleApplication.h" #include "CharacterController.h" // Declare a subclass of the ExampleApplication class class SampleApp : public ExampleApplication { public: SampleApp( void ) {} protected: // Define what is in the scene void createScene( void ) {} // Create new frame listener void createFrameListener( void ) { mFrameListener = new MyListener( mWindow, mCamera, mSceneMgr, mRoot ); mRoot->addFrameListener( mFrameListener ); } }; #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" #endif #ifdef __cplusplus extern "C" { #endif #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) #else int main( int argc, char **argv ) #endif { // Create application object SampleApp app; try { app.go(); } catch( Exception& e ) { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox( NULL, e.getFullDescription().c_str() , "An exception has occured!" , MB_OK | MB_ICONERROR | MB_TASKMODAL); #else std::cerr << "An exception has occured: " << e.getFullDescription(); #endif } return 0; } #ifdef __cplusplus } #endif



I have found strange effects when walking over parts of the mesh which overlap itself, such as a terrain with a built in bridge. One thing to try is to make the bridge as a separate trimesh and see if the character correctly walks over it.

by 0_0, Janitor Bob, Orange, the All-in-One...