Skip to main content

History: OgreODE Ray Model Character Controller

Source of version: 5

Copy to clipboard
            {img src="img/wiki_up/ODE_logo2.png" alt="" imalign="right"}
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. 

__CharacterController.h__
{CODE(wrap="1", colors="c++")}
#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_MaxAngle, r_WalkStrength 
		, r_WalkSpeed, r_RunSpeedMultiplier;

	Ogre::Real r_JumpHeight, r_Radius;
	OgreOde::Contact	*p_Contact;
	bool	b_onGround, b_isFalling;	
};

#endif
{CODE}

__CharacterController.cpp__
{CODE(wrap="1", colors="c++")}
#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 );
	else 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_MaxAngle =			90;
	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*/ ));
	}
}
{CODE}

__Main.cpp__
{CODE(wrap="1", colors="c++")}
#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
{CODE}


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...
        

History

Information Version
Wed 19 of Sep, 2012 12:19 GMT-0000 Zero_Zero minor cleanup 11
Wed 19 of Sep, 2012 12:17 GMT-0000 Zero_Zero minor cleanup 10
Wed 19 of Sep, 2012 11:56 GMT-0000 Zero_Zero minor cleanup 9
Wed 19 of Sep, 2012 11:52 GMT-0000 Zero_Zero minor cleanup 8
Wed 19 of Sep, 2012 08:58 GMT-0000 Zero_Zero 7
Wed 19 of Sep, 2012 07:59 GMT-0000 Zero_Zero 6
Wed 19 of Sep, 2012 07:47 GMT-0000 Zero_Zero 5
Wed 19 of Sep, 2012 07:41 GMT-0000 Zero_Zero 4
Wed 19 of Sep, 2012 07:21 GMT-0000 Zero_Zero 3
Wed 19 of Sep, 2012 06:50 GMT-0000 Zero_Zero 2
Wed 19 of Sep, 2012 06:13 GMT-0000 system created from structure 1