1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 09:35:28 +00:00

Integrate a new movement solver to handle object movement and collisions

Temporary, and pretty breoken. Needs some serious integration fixes.
This commit is contained in:
Chris Robinson 2013-02-04 07:10:14 -08:00
parent 91513206a0
commit 1747c1e01a
7 changed files with 233 additions and 8 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 aiescort aiactivate movementsolver
) )
add_openmw_dir (mwbase add_openmw_dir (mwbase

View File

@ -20,6 +20,11 @@ namespace OEngine
{ {
class Fader; class Fader;
} }
namespace Physic
{
class PhysicEngine;
}
} }
namespace ESM namespace ESM
@ -300,6 +305,8 @@ namespace MWBase
/// 2 - player is underwater \n /// 2 - player is underwater \n
/// 3 - enemies are nearby (not implemented) /// 3 - enemies are nearby (not implemented)
/// \todo Probably shouldn't be here
virtual OEngine::Physic::PhysicEngine* getPhysicEngine() const = 0;
/// \todo Probably shouldn't be here /// \todo Probably shouldn't be here
virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0;

View File

@ -25,9 +25,13 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "movementsolver.hpp"
namespace MWMechanics namespace MWMechanics
{ {
@ -75,6 +79,7 @@ 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(mPtr);
if(!mAnimation) if(!mAnimation)
return; return;
@ -93,12 +98,18 @@ 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(mPtr);
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. */
mAnimation->setController(this); mAnimation->setController(this);
} }
CharacterController::~CharacterController()
{
delete mMovementSolver;
}
void CharacterController::markerEvent(float time, const std::string &evt) void CharacterController::markerEvent(float time, const std::string &evt)
{ {
@ -160,18 +171,25 @@ Ogre::Vector3 CharacterController::update(float duration)
setState(CharState_Idle, true); setState(CharState_Idle, true);
} }
// FIXME: The speed should actually be determined by the character's stance
// (running, sneaking, etc) and stats, rather than the length of the vector.
float speed = std::max(1.0f, vec.length() / 32.0f);
if(mAnimation)
mAnimation->setSpeedMult(speed);
Ogre::Vector3 movement = Ogre::Vector3::ZERO; Ogre::Vector3 movement = Ogre::Vector3::ZERO;
if(mAnimation && !mSkipAnim) if(mAnimation && !mSkipAnim)
{
// FIXME: The speed should actually be determined by the character's
// stance (running, sneaking, etc) and stats
mAnimation->setSpeedMult(1.0f);
movement += mAnimation->runAnimation(duration); movement += mAnimation->runAnimation(duration);
}
mSkipAnim = false; mSkipAnim = false;
return movement; if(duration > 0.0f)
{
Ogre::Vector3 pos(mPtr.getCellRef().mPos.pos);
// FIXME: Get the actual radius for the object. Maybe this should go into mwworld to replace pmove?
Ogre::Vector3 res = mMovementSolver->move(pos, movement, duration, Ogre::Vector3(15,15,30));
MWBase::Environment::get().getWorld()->moveObject(mPtr, res.x, res.y, res.z);
}
return Ogre::Vector3(0.0f);
} }

View File

@ -13,6 +13,8 @@ namespace MWRender
namespace MWMechanics namespace MWMechanics
{ {
class MovementSolver;
enum CharacterState { enum CharacterState {
CharState_Idle, CharState_Idle,
CharState_Idle2, CharState_Idle2,
@ -49,6 +51,8 @@ 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);
@ -58,6 +62,7 @@ protected:
public: public:
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop); CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop);
CharacterController(const CharacterController &rhs); CharacterController(const CharacterController &rhs);
virtual ~CharacterController();
Ogre::Vector3 update(float duration); Ogre::Vector3 update(float duration);

View File

@ -0,0 +1,154 @@
#include "movementsolver.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
namespace MWMechanics
{
MovementSolver::MovementSolver(const MWWorld::Ptr &ptr)
: mPtr(ptr)
, 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)
{
static const float maxslope = 45.0f;
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) > maxslope))
return false;
newtrace(&trace, trace.endpos, trace.endpos-Ogre::Vector3(0,0,STEPSIZE), halfExtents, verticalRotation, isInterior, mEngine);
if(getSlope(trace.planenormal) < maxslope)
{
// 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 Ogre::Vector3 &position, const Ogre::Vector3 &movement, float time, const Ogre::Vector3 &halfExtents)
{
mPhysicActor = mEngine->getCharacter(mPtr.getRefData().getHandle());
/* Anything to collide with? */
if(1 || !mPhysicActor || !mPhysicActor->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 maxslope=45;
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 = !mPtr.getCell()->isExterior();
float verticalRotation = mPhysicActor->getRotation().getYaw().valueDegrees();
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) > maxslope)
{
// 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) > maxslope || 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;
return newPosition;
}
}

View File

@ -0,0 +1,37 @@
#ifndef GAME_MWMECHANICS_MOVEMENTSOLVER_H
#define GAME_MWMECHANICS_MOVEMENTSOLVER_H
#include "libs/openengine/bullet/trace.h"
#include "libs/openengine/bullet/physic.hpp"
#include "../mwworld/ptr.hpp"
#include <cmath>
namespace MWMechanics
{
class MovementSolver
{
public:
MovementSolver(const MWWorld::Ptr &ptr);
virtual ~MovementSolver();
Ogre::Vector3 move(const Ogre::Vector3 &position, const Ogre::Vector3 &movement, float time, const Ogre::Vector3 &halfExtents);
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);
MWWorld::Ptr mPtr;
OEngine::Physic::PhysicEngine *mEngine;
OEngine::Physic::PhysicActor *mPhysicActor;
float verticalVelocity;
};
}
#endif /* GAME_MWMECHANICS_MOVEMENTSOLVER_H */

View File

@ -347,6 +347,10 @@ namespace MWWorld
/// 2 - player is underwater \n /// 2 - player is underwater \n
/// 3 - enemies are nearby (not implemented) /// 3 - enemies are nearby (not implemented)
/// \todo Probably shouldn't be here
virtual OEngine::Physic::PhysicEngine* getPhysicEngine() const
{ return mPhysEngine; }
/// \todo Probably shouldn't be here /// \todo Probably shouldn't be here
virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr); virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr);