Note: The implementation below uses OgreOde Rays to position an entity's height over a mesh. If you want the benefits of creating a mesh and using that to make a terrain this code is for you =).
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. Also, I didn't spend much time working on this, so the camera view and comments aren't all that great. That being said..enjoy!
Edit: Updated 9-13-2012
#include "ExampleApplication.h" // include OgreOde files #include "OgreOde_Core.h" #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" #endif #ifdef __cplusplus extern "C" { #endif typedef struct { std::string charName; Ogre::SceneNode* charNode; OgreOde::RayGeometry* charRay; // radius used to position ray above actual node position (at feet) Ogre::Real radius; } ODE_CHAR_INFO; // 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 ) : ExampleFrameListener( win, cam ) { mSceneMgr = sMgr; // create OgreOde World - from SimpleScenes demo world = new OgreOde::World( mSceneMgr ); world->setGravity( Ogre::Vector3( 0, -9.80665, 0 )); world->setCFM( 10e-5 ); world->setERP( 0.8 ); world->setAutoSleep( true ); world->setContactCorrectionVelocity( 1.0 ); world->setCollisionListener( this ); world->setAutoSleepAngularThreshold( OgreOde::Utility::Infinity ); // Create something that will step the world, but don't do // it automatically stepper = new OgreOde::StepHandler( world , OgreOde::StepHandler::QuickStep , Ogre::Real( 0.01 ) // Step size , Ogre::Real( 1.0 / 4 ) // Max interval , Ogre::Real( 1.0 )); // Time scale stepper->setStepListener( this ); //create_ODE_Terrain( "yourTerrainFile.mesh", position, size ); create_ODE_Terrain( "racingcircuit.mesh" , Ogre::Vector3::ZERO , Ogre::Vector3( 10, 10, 10 )); /*Entity* ent = mSceneMgr->createEntity( "yourEntityName" , "yourEntityFile.mesh" ); */ Ogre::Entity* ent = mSceneMgr->createEntity( "Ninja" , "ninja.mesh" ); Ogre::SceneNode* entNode = mSceneMgr->getRootSceneNode()-> createChildSceneNode( "NinjaNode" , Ogre::Vector3( 0, 15, 0 )); entNode->attachObject( ent ); entNode->setScale( 0.05, 0.05, 0.05 ); create_ODE_character( "Ninja", entNode ); // attach and position the camera entNode->attachObject( cam ); cam->setPosition( 0, 20, 150 ); } ~MyListener( void ) { // destroy to remove the characters, terrain and stepper // before destroying the OgreOde world destroy_ALL_ODE_characters(); if( terrainTriMeshGeom ) delete terrainTriMeshGeom; delete stepper; stepper = NULL; delete world; world = NULL; } bool frameStarted( const Ogre::FrameEvent& evt ) { //mKeyboard->capture(); // quit app if( mKeyboard->isKeyDown( OIS::KC_ESCAPE )) return false; // basic movement Ogre::Vector3 direction; if( mKeyboard->isKeyDown( OIS::KC_UP )) { direction = ode_characters[ 0 ].charNode-> _getDerivedOrientation() * Ogre::Vector3::NEGATIVE_UNIT_Z; ode_characters[ 0 ].charNode->translate( direction * ( evt.timeSinceLastFrame * 100 )); } if( mKeyboard->isKeyDown( OIS::KC_DOWN )) { direction = ode_characters[ 0 ].charNode-> _getDerivedOrientation() * Ogre::Vector3::UNIT_Z; ode_characters[ 0 ].charNode->translate( direction * ( evt.timeSinceLastFrame * 100 )); } if( mKeyboard->isKeyDown( OIS::KC_LEFT )) { ode_characters[ 0 ].charNode->rotate( Ogre::Vector3::UNIT_Y, ( Ogre::Radian( 0.1 )) * ( evt.timeSinceLastFrame * 100 )); } if( mKeyboard->isKeyDown( OIS::KC_RIGHT )) { ode_characters[ 0 ].charNode-> rotate( Ogre::Vector3::UNIT_Y , ( Ogre::Radian( -0.1 )) * ( evt.timeSinceLastFrame * 100 )); } simulatePhysics( evt ); return true; } bool frameEnded( const FrameEvent& evt ) { return ExampleFrameListener::frameEnded( evt ); } // OgreOde::CollisionListener function bool collision( OgreOde::Contact* contact ) { // search through ode_characters and adjust each // charNode's height for( std::vector< ODE_CHAR_INFO >::iterator it = ode_characters.begin(); it != ode_characters.end(); it++ ) { if( contact->getFirstGeometry()->getID() == it->charRay->getID() || contact->getSecondGeometry()->getID() == it->charRay->getID() ) { // Clamp the charNode to the terrain on ray contact. Ogre::Vector3 pos = it->charNode->getPosition(); it->charNode->setPosition( Ogre::Vector3( pos.x , contact->getPosition().y + 1 , pos.z )); //it->charNode->setPosition( contact->getPosition() ); break; } } return true; } // OgreOde::StepListener function bool preStep( Ogre::Real time ) { return true; } // reposition OgreOde Ray(s) void simulatePhysics( const FrameEvent &evt ) { stepper->step( evt.timeSinceLastFrame ); world->synchronise(); Vector3 position; for( std::vector< ODE_CHAR_INFO >::iterator it = ode_characters.begin(); it != ode_characters.end(); it++ ) { // raise desired ray position a little above character's // scenenode. position = it->charNode->getPosition(); // may need to raise it higher for better accuracy position.y += ( it->radius * 2 ); //position.y += ( it->radius ); // fire ray downward it->charRay->setDefinition( position , Ogre::Vector3::NEGATIVE_UNIT_Y ); // add ray to collisionListener it->charRay->collide( terrainTriMeshGeom, this ); } } // Create the tri-mesh terrain from a mesh void create_ODE_Terrain( std::string meshFile , Ogre::Vector3 position, Ogre::Vector3 size ) { Ogre::SceneNode* sn = mSceneMgr->getRootSceneNode()->createChildSceneNode( "TerrainNode", position ); Ogre::Entity* ent = mSceneMgr->createEntity( "Terrain", meshFile.c_str() ); sn->attachObject( ent ); sn->setScale( size ); OgreOde::EntityInformer ei( ent, sn->_getFullTransform() ); terrainTriMeshGeom = ei.createStaticTriangleMesh( world , world->getDefaultSpace() ); terrainTriMeshGeom->setRayListener( this ); } // create the character object void create_ODE_character( std::string name , Ogre::SceneNode* baseNode ) { Ogre::AxisAlignedBox aab = baseNode->getAttachedObject( name.c_str() )->getBoundingBox(); Ogre::Vector3 min = aab.getMinimum() * baseNode->getScale(); Ogre::Vector3 max = aab.getMaximum() * baseNode->getScale(); Ogre::Vector3 size( fabs( max.x - min.x ) , fabs( max.y - min.y ) , fabs( max.z - min.z )); float radius = ( size.x > size.z ) ? size.z / 2.0 : size.x / 2; // create ray for character // associate node with array. Store data ODE_CHAR_INFO c; c.charName = name; c.charNode = baseNode; // may want to adjust the length of the ray for accuracy c.charRay = new OgreOde::RayGeometry( 100, world , world->getDefaultSpace() ); c.radius = radius; ode_characters.push_back( c ); } void destroy_ODE_character( std::string name ) { for( std::vector< ODE_CHAR_INFO >::iterator it = ode_characters.begin(); it != ode_characters.end(); it++ ) { if( it->charName == name ) { it->charNode = NULL; delete it->charRay; ode_characters.erase( it ); break; } } } void destroy_ALL_ODE_characters() { for( std::vector< ODE_CHAR_INFO >::iterator it = ode_characters.begin(); it != ode_characters.end(); it++ ) { it->charNode = NULL; delete it->charRay; } ode_characters.clear(); } private: std::vector< ODE_CHAR_INFO > ode_characters; OgreOde::World* world; OgreOde::StepHandler* stepper; Ogre::SceneManager* mSceneMgr; OgreOde::TriangleMeshGeometry* terrainTriMeshGeom; }; // 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 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
Another note to add: The length of the ray and where it starts (how much above the character) may need to be adjusted to work properly. For example, a Ray length of 1000 and position of 200 above the character should work fine.
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.