Character is flying off the landscape instead of walking

plfiorini

27-12-2008 12:18:07

Hi,

I'm writing my first little game which is a FPS using Ogre 1.6, Caelum and PLSM2.
Ode with its frontend OgreOde is the physics library of choice because I've seen some good examples to start from.
I'm using ode 0.10.1 and latest OgreOde from ogreaddons.

Here's the physics initialization code:

Ogre::LogManager::getSingleton().logMessage("*** Initialise physics manager ***");

// Get the scene manager
Ogre::SceneManager* sceneManager = GraphicsManager::getSingleton().getSceneManager();
if (!sceneManager)
return false;

// Create world
mWorld = new OgreOde::World(sceneManager);
mWorld->setGravity(Ogre::Vector3(0, -9.80665, 0));
mWorld->setCFM(10e-5);
mWorld->setERP(0.8);
mWorld->setAutoSleep(true);
mWorld->setContactCorrectionVelocity(1.0);
mWorld->setShowDebugGeometries(mDebugGeometries);

// Stepper
const Ogre::Real timeStep = 0.01; // 0.5
const Ogre::Real timeScale = Ogre::Real(1.0); //Ogre::Real(1.7)
const Ogre::Real maxFrameTime = Ogre::Real(1.0 / 60); // Ogre::Real(1.0 / 4)
mStepper = new OgreOde::StepHandler(mWorld, OgreOde::StepHandler::QuickStep,
timeStep, maxFrameTime, timeScale);
if (mAutoUpdate)
mStepper->setAutomatic(OgreOde::StepHandler::AutoMode_PostFrame, Ogre::Root::getSingletonPtr());

// Reduce frame rate for physics
Ogre::Root::getSingleton().setFrameSmoothingPeriod(5.0);

return true;


The mAutoUpdate parameter is true, so the update function in my main loop doesn't affect the loop.
Here's the code for the terrain, which is called after loading the terrain into PLSM2:


void PhysicsManager::registerTerrain()
{
// Get the scene manager and camera
Ogre::SceneManager* sceneManager = GraphicsManager::getSingleton().getSceneManager();
Ogre::Camera* camera = GraphicsManager::getSingleton().getCamera();

// Terrain information
int nodesPerSide = 0;
Ogre::Vector3 scale = Ogre::Vector3::ZERO;
sceneManager->getOption("PageSize", &nodesPerSide);
sceneManager->getOption("Scale", &scale);

// Terrain size
int width = 0, height = 0;
sceneManager->getOption("Width", &width);
sceneManager->getOption("Height", &height);

// World size
Ogre::Real worldWidth, worldHeight;
worldWidth = width * (nodesPerSide - 1) * scale.x;
worldHeight = height * (nodesPerSide - 1) * scale.z;

// Nodes per side for all pages
int nodesPerSideAllPagesWidth = width * (nodesPerSide);
int nodesPerSideAllPagesHeight = height * (nodesPerSide);

// Interpolation
bool noInterpolation = false;
sceneManager->getOption("queryNoInterpolation", &noInterpolation);

// Debug terrain parameters
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] PageSize=" +
Ogre::StringConverter::toString(nodesPerSide));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] Scale=" +
Ogre::StringConverter::toString(scale));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] Width=" +
Ogre::StringConverter::toString(width));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] Height=" +
Ogre::StringConverter::toString(height));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] WorldWidth=" +
Ogre::StringConverter::toString(worldWidth));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] WorldHeight=" +
Ogre::StringConverter::toString(worldHeight));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] NodesPerSideX=" +
Ogre::StringConverter::toString(nodesPerSideAllPagesWidth));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] NodesPerSideY=" +
Ogre::StringConverter::toString(nodesPerSideAllPagesHeight));
Ogre::LogManager::getSingleton().logMessage("[PhysicsManager] queryNoInterpolation=" +
Ogre::StringConverter::toString(noInterpolation));

// Terrain geometry
mTerrain = new OgreOde::TerrainGeometry(mWorld, mWorld->getDefaultSpace(),
scale, nodesPerSideAllPagesWidth, nodesPerSideAllPagesHeight,
worldWidth, worldHeight, true);
mTerrain->setHeightListener(this);
mWorld->setCollisionListener(this);

// Ray query
mRayQuery = sceneManager->createRayQuery(mRay);
mRayQuery->setQueryTypeMask(Ogre::SceneManager::WORLD_GEOMETRY_TYPE_MASK);
mRayQuery->setWorldFragmentType(Ogre::SceneQuery::WFT_SINGLE_INTERSECTION);
mRay.setDirection(Ogre::Vector3::NEGATIVE_UNIT_Y);

// Set origin
Ogre::Vector3 mPos = camera->getPosition() + (camera->getDirection() * 15);
mPos.y += 50;
mRay.setOrigin(mPos);
mRayQuery->setRay(mRay);

Ogre::RaySceneQueryResult& result = mRayQuery->execute();
Ogre::RaySceneQueryResult::iterator it = result.begin();
if (it != result.end() && it->worldFragment)
{
const Ogre::Real height = it->worldFragment->singleIntersection.y;
if (mPos.y < height)
mPos.y = height + 7;
}
}


As you can see the code is copied from the landscape tutorial, although I don't have a vehicle.
The PhysicsManager is a singleton derived from OgreOde::TerrainGeometryHeightListener and OgreOde::CollisionListener, here's the height and collision functions:


Ogre::Real PhysicsManager::heightAt(const Ogre::Vector3& position)
{
return mTerrain->getHeightAt(position);
}

bool PhysicsManager::collision(OgreOde::Contact* contact)
{
OgreOde::Geometry* const g1 = contact->getFirstGeometry();
OgreOde::Geometry* const g2 = contact->getSecondGeometry();
const bool isg1Sphere = g1 && (g1->getClass() != OgreOde::Geometry::Class_Sphere);
const bool isg2Sphere = g2 && (g2->getClass() != OgreOde::Geometry::Class_Sphere);

if (isg1Sphere || isg2Sphere)
{
}
else
{
contact->setBouncyness(0);
contact->setCoulombFriction(18);
}

return true;
}


When I run the game I can see the terrain rendered and the camera to its initial position but suddenly a message box says "GeomTransform encapsulated object must not be in space".
So I changed the code to *not* use mWorld->getDefaultSpace():

mTerrain = new OgreOde::TerrainGeometry(mWorld, 0,
scale, nodesPerSideAllPagesWidth, nodesPerSideAllPagesHeight,
worldWidth, worldHeight, true);


It doesn't throw an error now but I don't understand what's the difference between my application and Landscape demo with this parameter.
Unfortunately once the terrain is loaded the camera keeps falling throught the terrain.

So here it comes the player code:

FirstPersonCamera::FirstPersonCamera()
: mPlayerMass(2.5),
mPlayerWidth(5),
mPlayerHeight(12),
mAngularCoeff(0.3)
{
OgreOde::World* world = PhysicsManager::getSingleton().getWorld();
Ogre::SceneManager* sceneManager = GraphicsManager::getSingleton().getSceneManager();

////////////////////////////////////////
//Ogre::Entity* ninja = sceneManager->createEntity("ninja", "ninja.mesh");
////////////////////////////////////////

// Create a space without internal collisions
OgreOde::SimpleSpace* dollSpace = new OgreOde::SimpleSpace(world, world->getDefaultSpace());
dollSpace->setInternalCollisions(false);

// Create a scene node that will accomodate the camera and body, then attach to camera
mPlayerNode = sceneManager->getRootSceneNode()->createChildSceneNode("AinurPlayerNode");
mCameraPitchNode = mPlayerNode->createChildSceneNode("AinurCameraPitchNode");
mCameraPitchNode->attachObject(GraphicsManager::getSingleton().getCamera());

////////////////////////////////////////
//Ogre::SceneNode* modelNode = mPlayerNode->createChildSceneNode("ninja_model");
//modelNode->attachObject(ninja);
//mPlayerNode->setScale(0.05, 0.05, 0.05);
//Ogre::AxisAlignedBox aab = modelNode->getAttachedObject("ninja")->getBoundingBox();
//Ogre::Vector3 min = aab.getMinimum() * mPlayerNode->getScale();
//Ogre::Vector3 max = aab.getMaximum() * mPlayerNode->getScale();
//Ogre::Vector3 center = aab.getCenter() * mPlayerNode->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.0f : size.x/2.0f;
//mPlayerWidth = (size.x > size.z) ? size.z : size.x;
//mPlayerHeight = size.y;
////////////////////////////////////////

// Set up a sphere geometry to represent and act as the player's feet
mPlayerBodyFeet = new OgreOde::Body(world);
mPlayerBodyFeet->setMass(OgreOde::SphereMass(70 * mPlayerMass, mPlayerWidth / 2));
OgreOde::SphereGeometry* feetGeometry = new OgreOde::SphereGeometry(mPlayerWidth / 2,
world, dollSpace);
OgreOde::TransformGeometry* feetTransform = new OgreOde::TransformGeometry(world, dollSpace);
////////////////////////////////////////
//modelNode->translate(Ogre::Vector3(0, -(mPlayerWidth / 2)/mPlayerNode->getScale().y, 0));
////////////////////////////////////////
feetTransform->setBody(mPlayerBodyFeet);
feetTransform->setEncapsulatedGeometry(feetGeometry);

// Attach feet to player node
mPlayerNode->attachObject(mPlayerBodyFeet);

// Set up a capsule geometry to represent the rest of the player
mPlayerBodyTorso = new OgreOde::Body(world);
mPlayerBodyTorso->setMass(OgreOde::CapsuleMass(70 * mPlayerMass,
mPlayerWidth / 2, Ogre::Vector3::UNIT_Y, mPlayerWidth / 2));
mPlayerBodyTorso->setAffectedByGravity(false);
//mPlayerBodyTorso->setDamping(0, 50000);
OgreOde::TransformGeometry* torsoTransform = new OgreOde::TransformGeometry(world, dollSpace);
OgreOde::CapsuleGeometry* torsoGeometry = new OgreOde::CapsuleGeometry(mPlayerWidth / 2,
mPlayerHeight - 4 * (mPlayerWidth / 2), world, dollSpace);
torsoGeometry->setPosition(Ogre::Vector3(0, mPlayerHeight - ((mPlayerHeight - 4 *
(mPlayerWidth / 2)) / 2 + 2 * (mPlayerWidth / 2)), 0));
torsoGeometry->setOrientation(Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_X));
torsoTransform->setBody(mPlayerBodyTorso);
torsoTransform->setEncapsulatedGeometry(torsoGeometry);

// Attach torso to player node
mPlayerNode->attachObject(mPlayerBodyTorso);

// Join player torso with player feet, with a hinge joint
OgreOde::HingeJoint* joint = new OgreOde::HingeJoint(world);
joint->attach(mPlayerBodyTorso, mPlayerBodyFeet);
joint->setAxis(Ogre::Vector3::UNIT_X); //set the rotation axis

// Set bodies initial possition to prevent "exploding" :)
mPlayerBodyFeet->setOrientation(Ogre::Quaternion(Ogre::Radian(5.0), Ogre::Vector3(0, 0, 0)));
//mPlayerBodyFeet->setPosition(Ogre::Vector3::ZERO);
mPlayerBodyFeet->setPosition(PhysicsManager::getSingleton().getPlayerPosition());
mPlayerBodyTorso->setOrientation(Ogre::Quaternion(Ogre::Radian(5.0), Ogre::Vector3(0, 0, 0)));
//mPlayerBodyTorso->setPosition(Ogre::Vector3::ZERO);
mPlayerBodyTorso->setPosition(PhysicsManager::getSingleton().getPlayerPosition());
}

FirstPersonCamera::~FirstPersonCamera()
{
}


void FirstPersonCamera::update(double elapsed)
{
// Prevent player falling over
Ogre::Quaternion q = mPlayerBodyTorso->getOrientation();
Ogre::Vector3 x = q.xAxis();
Ogre::Vector3 y = q.yAxis();
Ogre::Vector3 z = q.zAxis();

mPlayerBodyTorso->wake();
mPlayerBodyTorso->setOrientation(Ogre::Quaternion(x, Ogre::Vector3::UNIT_Y, z));
}

void FirstPersonCamera::forward()
{
// Torso orientation
Ogre::Quaternion q = mPlayerBodyTorso->getOrientation();

// Apply an angular velocity (angular velocity, multiplied by the
// trigonometrical functions in order to get the correct relation
// between them so the velocity is applied in the right direction)
mPlayerBodyFeet->wake();
mPlayerBodyFeet->setAngularVelocity(q * Ogre::Vector3(mAngularCoeff * cos(1.0f),
0, mAngularCoeff * sin(1.0f)));
//mCameraPitchNode->setPosition(Ogre::Vector3(0, 0, (mPlayerWidth / 2) / mCameraPitchNode->getScale().z));
//->translate(Ogre::Vector3(0, (mPlayerWidth / 2) / mCameraPitchNode->getScale().y, 0));
}

void FirstPersonCamera::backward()
{
// Torso orientation
Ogre::Quaternion q = mPlayerBodyTorso->getOrientation();

// Apply an angular velocity (angular velocity, multiplied by the
// trigonometrical functions in order to get the correct relation
// between them so the velocity is applied in the right direction)
mPlayerBodyFeet->wake();
mPlayerBodyFeet->setAngularVelocity(q * Ogre::Vector3(mAngularCoeff * sin(1.0f),
0, mAngularCoeff * cos(1.0f)));
}

void FirstPersonCamera::left()
{
Ogre::Quaternion q1 = mPlayerBodyTorso->getOrientation();
Ogre::Quaternion q2(Ogre::Degree(4), Ogre::Vector3::UNIT_Y);
mPlayerBodyTorso->setOrientation(q1 * q2);
}

void FirstPersonCamera::right()
{
Ogre::Quaternion q1 = mPlayerBodyTorso->getOrientation();
Ogre::Quaternion q2(Ogre::Degree(-4), Ogre::Vector3::UNIT_Y);
mPlayerBodyTorso->setOrientation(q1 * q2);
}


The main loop is done this way:

void Core::run()
{
double currentTime = double(mTimer.getMicroseconds() / 1000000.0f);

while (!isQuitRequested())
{
// Pump window events
Ogre::WindowEventUtilities::messagePump();

// Calculate elapsed and advance current time
const double elapsed = double(mTimer.getMicroseconds() / 1000000.0f) - currentTime;
currentTime += elapsed;

// Capture input
InputManager::getSingleton().capture();

/*
* Update rendering and physics
*/

if (mCurGameState.get())
{
if (!mCurGameState->frameStarted(elapsed))
requestQuit("frameStarted() failed");
}

if (!Ogre::Root::getSingleton().renderOneFrame())
requestQuit("Ogre::Root::renderOneFrame() failed");

if (!PhysicsManager::getSingleton().update(elapsed)) // this just exits with true in "auto mode"
requestQuit("PhysicsManager::update() failed");

if (mCurGameState.get())
{
if (!mCurGameState->frameEnded(elapsed))
requestQuit("frameEnded() failed");
}
}
}


In the PlayState class, frameStarted() and frameEnded() are done this way:

bool PlayState::frameStarted(double elapsed)
{
// Quit application if it's requested by the user
if (mQuit)
return false;

// Don't move camera if we are paused
if (mPaused)
return true;

// Update player character position
PhysicsManager::getSingleton().getPlayer()->update(elapsed);

// Update world
World::getSingleton().update(elapsed);

return true;
}

bool PlayState::frameEnded(double elapsed)
{
// Update statistics
updateStats();

if (!mPaused)
{
// Handle camera movements when not paused
handleMovements(elapsed);

// Toggle compositors
if (mOldPostFilter != mPostFilter)
GraphicsManager::getSingleton().toggleCompositors(mPostFilter);
}

return true;
}

void PlayState::handleMovements(double elapsed)
{
// Get keyboard pointer and joystick state
OIS::Keyboard* keyboard = InputManager::getSingleton().getKeyboard();
OIS::JoyStickState js = InputManager::getSingleton().getJoystick(0)->getJoyStickState();

// Move player left
if (keyboard->isKeyDown(OIS::KC_LEFT) || keyboard->isKeyDown(OIS::KC_A))
PhysicsManager::getSingleton().getPlayer()->left();
// Move player right
if (keyboard->isKeyDown(OIS::KC_RIGHT) || keyboard->isKeyDown(OIS::KC_D))
PhysicsManager::getSingleton().getPlayer()->right();
// Move player forward
if (keyboard->isKeyDown(OIS::KC_UP) || keyboard->isKeyDown(OIS::KC_W))
PhysicsManager::getSingleton().getPlayer()->forward();
// Move player backward
if (keyboard->isKeyDown(OIS::KC_DOWN) || keyboard->isKeyDown(OIS::KC_S))
PhysicsManager::getSingleton().getPlayer()->backward();
}


When I press left or right the player rotates (it's supposed to do it, although I would like to learn how to let the camera move left and right like in any other FPS), when I press up and down in fact I see a rotation although I would like it to move forward and backward. That said I should learn how to make it move like in a FPS game, but the problem here is that the camera keeps moving and the player keeps falling through the landscape.

I also made a video: http://it.youtube.com/watch?v=VhhhmH1-KPI

dermont

31-12-2008 10:42:21

I don't think the "GeomTransform encapsulated object must not be in space" error is related to your TerrainGeometry. It looks as if it is a problem with transform geom in your player code, try removing the dollSpace parameter from feetGeometry and torsoGeometry, e.g:

OgreOde::SphereGeometry* feetGeometry = new OgreOde::SphereGeometry(mPlayerWidth / 2, world);
..
OgreOde::CapsuleGeometry* torsoGeometry = new OgreOde::CapsuleGeometry(mPlayerWidth / 2, mPlayerHeight - 4 * (mPlayerWidth / 2), world);