History: OgreODE Ray Model Character Controller
Source of version: 11 (current)
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.
{IMG(src="http://i50.tinypic.com/347z9f7.png",thumb="y")}{IMG}
__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_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 );
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*/ ));
}
}
{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...