mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-25 06:35:30 +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:
parent
91513206a0
commit
1747c1e01a
@ -64,7 +64,7 @@ add_openmw_dir (mwclass
|
||||
add_openmw_dir (mwmechanics
|
||||
mechanicsmanagerimp stat character creaturestats magiceffects movement actors activators
|
||||
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
|
||||
aiescort aiactivate
|
||||
aiescort aiactivate movementsolver
|
||||
)
|
||||
|
||||
add_openmw_dir (mwbase
|
||||
|
@ -20,6 +20,11 @@ namespace OEngine
|
||||
{
|
||||
class Fader;
|
||||
}
|
||||
|
||||
namespace Physic
|
||||
{
|
||||
class PhysicEngine;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ESM
|
||||
@ -300,6 +305,8 @@ namespace MWBase
|
||||
/// 2 - player is underwater \n
|
||||
/// 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
|
||||
virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0;
|
||||
|
@ -25,9 +25,13 @@
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "movementsolver.hpp"
|
||||
|
||||
|
||||
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)
|
||||
: mPtr(ptr), mAnimation(anim), mState(state), mSkipAnim(false)
|
||||
{
|
||||
mMovementSolver = new MovementSolver(mPtr);
|
||||
if(!mAnimation)
|
||||
return;
|
||||
|
||||
@ -93,12 +98,18 @@ CharacterController::CharacterController(const CharacterController &rhs)
|
||||
, mCurrentGroup(rhs.mCurrentGroup), mState(rhs.mState)
|
||||
, mSkipAnim(rhs.mSkipAnim)
|
||||
{
|
||||
mMovementSolver = new MovementSolver(mPtr);
|
||||
if(!mAnimation)
|
||||
return;
|
||||
/* We've been copied. Update the animation with the new controller. */
|
||||
mAnimation->setController(this);
|
||||
}
|
||||
|
||||
CharacterController::~CharacterController()
|
||||
{
|
||||
delete mMovementSolver;
|
||||
}
|
||||
|
||||
|
||||
void CharacterController::markerEvent(float time, const std::string &evt)
|
||||
{
|
||||
@ -160,18 +171,25 @@ Ogre::Vector3 CharacterController::update(float duration)
|
||||
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;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,6 +13,8 @@ namespace MWRender
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
class MovementSolver;
|
||||
|
||||
enum CharacterState {
|
||||
CharState_Idle,
|
||||
CharState_Idle2,
|
||||
@ -49,6 +51,8 @@ class CharacterController
|
||||
CharacterState mState;
|
||||
bool mSkipAnim;
|
||||
|
||||
MovementSolver *mMovementSolver;
|
||||
|
||||
protected:
|
||||
/* Called by the animation whenever a new text key is reached. */
|
||||
void markerEvent(float time, const std::string &evt);
|
||||
@ -58,6 +62,7 @@ protected:
|
||||
public:
|
||||
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state, bool loop);
|
||||
CharacterController(const CharacterController &rhs);
|
||||
virtual ~CharacterController();
|
||||
|
||||
Ogre::Vector3 update(float duration);
|
||||
|
||||
|
154
apps/openmw/mwmechanics/movementsolver.cpp
Normal file
154
apps/openmw/mwmechanics/movementsolver.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
37
apps/openmw/mwmechanics/movementsolver.hpp
Normal file
37
apps/openmw/mwmechanics/movementsolver.hpp
Normal 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 */
|
@ -347,6 +347,10 @@ namespace MWWorld
|
||||
/// 2 - player is underwater \n
|
||||
/// 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
|
||||
virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user