1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-22 10:53:54 +00:00

Move the movement solver code to mwworld's physics system

This commit is contained in:
Chris Robinson 2013-02-05 12:45:10 -08:00
parent 0a4568bd11
commit 2c39760bd5
9 changed files with 184 additions and 229 deletions

View file

@ -64,7 +64,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors activators mechanicsmanagerimp stat character creaturestats magiceffects movement actors activators
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
aiescort aiactivate movementsolver aiescort aiactivate
) )
add_openmw_dir (mwbase add_openmw_dir (mwbase

View file

@ -265,6 +265,7 @@ namespace MWMechanics
Ogre::Vector3 movement = iter->second.update(duration); Ogre::Vector3 movement = iter->second.update(duration);
mMovement.push_back(std::make_pair(iter->first, movement)); mMovement.push_back(std::make_pair(iter->first, movement));
} }
MWBase::Environment::get().getWorld()->doPhysics(mMovement, duration);
mMovement.clear(); mMovement.clear();
} }

View file

@ -29,8 +29,6 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "movementsolver.hpp"
namespace MWMechanics namespace MWMechanics
{ {
@ -79,7 +77,6 @@ static void getStateInfo(CharacterState state, std::string *group)
CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop) CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop)
: mPtr(ptr), mAnimation(anim), mState(state), mSkipAnim(false) : mPtr(ptr), mAnimation(anim), mState(state), mSkipAnim(false)
{ {
mMovementSolver = new MovementSolver();
if(!mAnimation) if(!mAnimation)
return; return;
@ -98,7 +95,6 @@ CharacterController::CharacterController(const CharacterController &rhs)
, mCurrentGroup(rhs.mCurrentGroup), mState(rhs.mState) , mCurrentGroup(rhs.mCurrentGroup), mState(rhs.mState)
, mSkipAnim(rhs.mSkipAnim) , mSkipAnim(rhs.mSkipAnim)
{ {
mMovementSolver = new MovementSolver();
if(!mAnimation) if(!mAnimation)
return; return;
/* We've been copied. Update the animation with the new controller. */ /* We've been copied. Update the animation with the new controller. */
@ -107,7 +103,6 @@ CharacterController::CharacterController(const CharacterController &rhs)
CharacterController::~CharacterController() CharacterController::~CharacterController()
{ {
delete mMovementSolver;
} }
@ -181,21 +176,14 @@ Ogre::Vector3 CharacterController::update(float duration)
} }
mSkipAnim = false; mSkipAnim = false;
if(duration > 0.0f) const ESM::Position &refpos = mPtr.getRefData().getPosition();
{ // Rotates first around z, then y, then x
const ESM::Position &refpos = mPtr.getRefData().getPosition(); movement = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[0]), Ogre::Vector3::UNIT_X)*
Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)*
Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)) *
movement;
// Rotates first around z, then y, then x return movement;
movement = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[0]), Ogre::Vector3::UNIT_X)*
Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)*
Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)) *
movement;
Ogre::Vector3 res = mMovementSolver->move(mPtr, movement, duration);
MWBase::Environment::get().getWorld()->moveObject(mPtr, res.x, res.y, res.z);
}
return Ogre::Vector3(0.0f);
} }

View file

@ -13,8 +13,6 @@ namespace MWRender
namespace MWMechanics namespace MWMechanics
{ {
class MovementSolver;
enum CharacterState { enum CharacterState {
CharState_Idle, CharState_Idle,
CharState_Idle2, CharState_Idle2,
@ -51,8 +49,6 @@ class CharacterController
CharacterState mState; CharacterState mState;
bool mSkipAnim; bool mSkipAnim;
MovementSolver *mMovementSolver;
protected: protected:
/* Called by the animation whenever a new text key is reached. */ /* Called by the animation whenever a new text key is reached. */
void markerEvent(float time, const std::string &evt); void markerEvent(float time, const std::string &evt);

View file

@ -1,164 +0,0 @@
#include "movementsolver.hpp"
#include "libs/openengine/bullet/trace.h"
#include "libs/openengine/bullet/physic.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include <cmath>
namespace MWMechanics
{
static const float sMaxSlope = 45.0f;
MovementSolver::MovementSolver()
: mEngine(MWBase::Environment::get().getWorld()->getPhysicEngine())
{
}
MovementSolver::~MovementSolver()
{
// nothing to do
}
void MovementSolver::clipVelocity(const Ogre::Vector3& in, const Ogre::Vector3& normal, Ogre::Vector3& out, const float overbounce)
{
//Math stuff. Basically just project the velocity vector onto the plane represented by the normal.
//More specifically, it projects velocity onto the normal, takes that result, multiplies it by overbounce and then subtracts it from velocity.
float backoff;
backoff = in.dotProduct(normal);
if(backoff < 0.0f)
backoff *= overbounce;
else
backoff /= overbounce;
out = in - (normal*backoff);
}
void MovementSolver::projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction)
{
Ogre::Vector3 normalizedDirection(direction);
normalizedDirection.normalise();
// no divide by normalizedDirection.length necessary because it's normalized
velocity = normalizedDirection * velocity.dotProduct(normalizedDirection);
}
bool MovementSolver::stepMove(Ogre::Vector3& position, const Ogre::Vector3 &velocity, float remainingTime, float verticalRotation, const Ogre::Vector3 &halfExtents, bool isInterior)
{
traceResults trace; // no initialization needed
newtrace(&trace, position+Ogre::Vector3(0.0f,0.0f,STEPSIZE),
position+Ogre::Vector3(0.0f,0.0f,STEPSIZE)+velocity*remainingTime,
halfExtents, verticalRotation, isInterior, mEngine);
if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope))
return false;
newtrace(&trace, trace.endpos, trace.endpos-Ogre::Vector3(0,0,STEPSIZE), halfExtents, verticalRotation, isInterior, mEngine);
if(getSlope(trace.planenormal) < sMaxSlope)
{
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
position = trace.endpos;
return true;
}
return false;
}
float MovementSolver::getSlope(const Ogre::Vector3 &normal)
{
return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
}
Ogre::Vector3 MovementSolver::move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time)
{
Ogre::Vector3 position(ptr.getRefData().getPosition().pos);
/* Anything to collide with? */
OEngine::Physic::PhysicActor *physicActor = mEngine->getCharacter(ptr.getRefData().getHandle());
if(!physicActor || !physicActor->getCollisionMode())
return position + movement;
traceResults trace; //no initialization needed
int iterations=0, maxIterations=50; //arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
float verticalVelocity = physicActor->getVerticalForce();
Ogre::Vector3 horizontalVelocity = movement/time;
Ogre::Vector3 velocity(horizontalVelocity.x, horizontalVelocity.y, verticalVelocity); // we need a copy of the velocity before we start clipping it for steps
Ogre::Vector3 clippedVelocity(horizontalVelocity.x, horizontalVelocity.y, verticalVelocity);
float remainingTime = time;
bool isInterior = !ptr.getCell()->isExterior();
float verticalRotation = physicActor->getRotation().getYaw().valueDegrees();
Ogre::Vector3 halfExtents = physicActor->getHalfExtents();
Ogre::Vector3 lastNormal(0.0f);
Ogre::Vector3 currentNormal(0.0f);
Ogre::Vector3 up(0.0f, 0.0f, 1.0f);
Ogre::Vector3 newPosition = position;
newtrace(&trace, position, position+Ogre::Vector3(0,0,-10), halfExtents, verticalRotation, isInterior, mEngine);
if(trace.fraction < 1.0f)
{
if(getSlope(trace.planenormal) > sMaxSlope)
{
// if we're on a really steep slope, don't listen to user input
clippedVelocity.x = clippedVelocity.y = 0.0f;
}
else
{
// if we're within 10 units of the ground, force velocity to track the ground
clipVelocity(clippedVelocity, trace.planenormal, clippedVelocity, 1.0f);
}
}
do {
// trace to where character would go if there were no obstructions
newtrace(&trace, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, verticalRotation, isInterior, mEngine);
newPosition = trace.endpos;
currentNormal = trace.planenormal;
remainingTime = remainingTime * (1.0f-trace.fraction);
// check for obstructions
if(trace.fraction != 1.0f)
{
//std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
if(getSlope(currentNormal) > sMaxSlope || currentNormal == lastNormal)
{
if(stepMove(newPosition, velocity, remainingTime, verticalRotation, halfExtents, mEngine))
std::cout<< "stepped" <<std::endl;
else
{
Ogre::Vector3 resultantDirection = currentNormal.crossProduct(up);
resultantDirection.normalise();
clippedVelocity = velocity;
projectVelocity(clippedVelocity, resultantDirection);
// just this isn't enough sometimes. It's the same problem that causes steps to be necessary on even uphill terrain.
clippedVelocity += currentNormal*clippedVelocity.length()/50.0f;
std::cout<< "clipped velocity: "<<clippedVelocity <<std::endl;
}
}
else
clipVelocity(clippedVelocity, currentNormal, clippedVelocity, 1.0f);
}
lastNormal = currentNormal;
iterations++;
} while(iterations < maxIterations && remainingTime != 0.0f);
verticalVelocity = clippedVelocity.z;
verticalVelocity -= time*400;
physicActor->setVerticalForce(verticalVelocity);
return newPosition;
}
}

View file

@ -1,41 +0,0 @@
#ifndef GAME_MWMECHANICS_MOVEMENTSOLVER_H
#define GAME_MWMECHANICS_MOVEMENTSOLVER_H
#include <OgreVector3.h>
namespace MWWorld
{
class Ptr;
}
namespace OEngine
{
namespace Physic
{
class PhysicEngine;
}
}
namespace MWMechanics
{
class MovementSolver
{
public:
MovementSolver();
virtual ~MovementSolver();
Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time);
private:
bool stepMove(Ogre::Vector3& position, const Ogre::Vector3 &velocity, float remainingTime, float verticalRotation, const Ogre::Vector3 &halfExtents, bool isInterior);
void clipVelocity(const Ogre::Vector3& in, const Ogre::Vector3& normal, Ogre::Vector3& out, const float overbounce);
void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction);
float getSlope(const Ogre::Vector3 &normal);
OEngine::Physic::PhysicEngine *mEngine;
};
}
#endif /* GAME_MWMECHANICS_MOVEMENTSOLVER_H */

View file

@ -21,6 +21,153 @@ using namespace Ogre;
namespace MWWorld namespace MWWorld
{ {
static const float sMaxSlope = 45.0f;
class MovementSolver
{
private:
static bool stepMove(Ogre::Vector3& position, const Ogre::Vector3 &velocity, float remainingTime,
float verticalRotation, const Ogre::Vector3 &halfExtents, bool isInterior,
OEngine::Physic::PhysicEngine *engine)
{
traceResults trace; // no initialization needed
newtrace(&trace, position+Ogre::Vector3(0.0f,0.0f,STEPSIZE),
position+Ogre::Vector3(0.0f,0.0f,STEPSIZE)+velocity*remainingTime,
halfExtents, verticalRotation, isInterior, engine);
if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope))
return false;
newtrace(&trace, trace.endpos, trace.endpos-Ogre::Vector3(0,0,STEPSIZE), halfExtents, verticalRotation, isInterior, engine);
if(getSlope(trace.planenormal) < sMaxSlope)
{
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
position = trace.endpos;
return true;
}
return false;
}
static void clipVelocity(const Ogre::Vector3& in, const Ogre::Vector3& normal, Ogre::Vector3& out,
const float overbounce)
{
//Math stuff. Basically just project the velocity vector onto the plane represented by the normal.
//More specifically, it projects velocity onto the normal, takes that result, multiplies it by overbounce and then subtracts it from velocity.
float backoff;
backoff = in.dotProduct(normal);
if(backoff < 0.0f)
backoff *= overbounce;
else
backoff /= overbounce;
out = in - (normal*backoff);
}
static void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction)
{
Ogre::Vector3 normalizedDirection(direction);
normalizedDirection.normalise();
// no divide by normalizedDirection.length necessary because it's normalized
velocity = normalizedDirection * velocity.dotProduct(normalizedDirection);
}
static float getSlope(const Ogre::Vector3 &normal)
{
return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
}
public:
static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
OEngine::Physic::PhysicEngine *engine)
{
Ogre::Vector3 position(ptr.getRefData().getPosition().pos);
/* Anything to collide with? */
OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle());
if(!physicActor || !physicActor->getCollisionMode())
return position + movement;
traceResults trace; //no initialization needed
int iterations=0, maxIterations=50; //arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
float verticalVelocity = physicActor->getVerticalForce();
Ogre::Vector3 horizontalVelocity = movement/time;
Ogre::Vector3 velocity(horizontalVelocity.x, horizontalVelocity.y, verticalVelocity); // we need a copy of the velocity before we start clipping it for steps
Ogre::Vector3 clippedVelocity(horizontalVelocity.x, horizontalVelocity.y, verticalVelocity);
float remainingTime = time;
bool isInterior = !ptr.getCell()->isExterior();
float verticalRotation = physicActor->getRotation().getYaw().valueDegrees();
Ogre::Vector3 halfExtents = physicActor->getHalfExtents();
Ogre::Vector3 lastNormal(0.0f);
Ogre::Vector3 currentNormal(0.0f);
Ogre::Vector3 up(0.0f, 0.0f, 1.0f);
Ogre::Vector3 newPosition = position;
newtrace(&trace, position, position+Ogre::Vector3(0,0,-10), halfExtents, verticalRotation, isInterior, engine);
if(trace.fraction < 1.0f)
{
if(getSlope(trace.planenormal) > sMaxSlope)
{
// if we're on a really steep slope, don't listen to user input
clippedVelocity.x = clippedVelocity.y = 0.0f;
}
else
{
// if we're within 10 units of the ground, force velocity to track the ground
clipVelocity(clippedVelocity, trace.planenormal, clippedVelocity, 1.0f);
}
}
do {
// trace to where character would go if there were no obstructions
newtrace(&trace, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, verticalRotation, isInterior, engine);
newPosition = trace.endpos;
currentNormal = trace.planenormal;
remainingTime = remainingTime * (1.0f-trace.fraction);
// check for obstructions
if(trace.fraction != 1.0f)
{
//std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
if(getSlope(currentNormal) > sMaxSlope || currentNormal == lastNormal)
{
if(stepMove(newPosition, velocity, remainingTime, verticalRotation, halfExtents, isInterior, engine))
std::cout<< "stepped" <<std::endl;
else
{
Ogre::Vector3 resultantDirection = currentNormal.crossProduct(up);
resultantDirection.normalise();
clippedVelocity = velocity;
projectVelocity(clippedVelocity, resultantDirection);
// just this isn't enough sometimes. It's the same problem that causes steps to be necessary on even uphill terrain.
clippedVelocity += currentNormal*clippedVelocity.length()/50.0f;
std::cout<< "clipped velocity: "<<clippedVelocity <<std::endl;
}
}
else
clipVelocity(clippedVelocity, currentNormal, clippedVelocity, 1.0f);
}
lastNormal = currentNormal;
iterations++;
} while(iterations < maxIterations && remainingTime != 0.0f);
verticalVelocity = clippedVelocity.z;
verticalVelocity -= time*400;
physicActor->setVerticalForce(verticalVelocity);
return newPosition;
}
};
PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) : PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) :
mRender(_rend), mEngine(0), mFreeFly (true) mRender(_rend), mEngine(0), mFreeFly (true)
{ {
@ -185,6 +332,11 @@ namespace MWWorld
} }
} }
Ogre::Vector3 PhysicsSystem::move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time)
{
return MovementSolver::move(ptr, movement, time, mEngine);
}
void PhysicsSystem::addHeightField (float* heights, void PhysicsSystem::addHeightField (float* heights,
int x, int y, float yoffset, int x, int y, float yoffset,

View file

@ -35,6 +35,8 @@ namespace MWWorld
bool toggleCollisionMode(); bool toggleCollisionMode();
Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time);
std::pair<float, std::string> getFacedHandle (MWWorld::World& world, float queryDistance); std::pair<float, std::string> getFacedHandle (MWWorld::World& world, float queryDistance);
std::vector < std::pair <float, std::string> > getFacedHandles (float queryDistance); std::vector < std::pair <float, std::string> > getFacedHandles (float queryDistance);
std::vector < std::pair <float, std::string> > getFacedHandles (float mouseX, float mouseY, float queryDistance); std::vector < std::pair <float, std::string> > getFacedHandles (float mouseX, float mouseY, float queryDistance);

View file

@ -836,6 +836,27 @@ namespace MWWorld
void World::doPhysics(const PtrMovementList &actors, float duration) void World::doPhysics(const PtrMovementList &actors, float duration)
{ {
/* No duration? Shouldn't be any movement, then. */
if(duration <= 0.0f)
return;
PtrMovementList::const_iterator player(actors.end());
for(PtrMovementList::const_iterator iter(actors.begin());iter != actors.end();iter++)
{
if(iter->first.getRefData().getHandle() == "player")
{
/* Handle player last, in case a cell transition occurs */
player = iter;
continue;
}
Ogre::Vector3 vec = mPhysics->move(iter->first, iter->second, duration);
moveObjectImp(iter->first, vec.x, vec.y, vec.z);
}
if(player != actors.end())
{
Ogre::Vector3 vec = mPhysics->move(player->first, player->second, duration);
moveObjectImp(player->first, vec.x, vec.y, vec.z);
}
} }
bool World::toggleCollisionMode() bool World::toggleCollisionMode()