Improve actor movement collision handling

This commit is contained in:
Chris Robinson 2013-08-17 01:11:57 -07:00 committed by scrawl
parent 8d925b7fd6
commit 76b812f75f

View file

@ -31,17 +31,22 @@ namespace MWWorld
static const float sMaxSlope = 60.0f; static const float sMaxSlope = 60.0f;
static const float sStepSize = 30.0f; static const float sStepSize = 30.0f;
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 50; static const int sMaxIterations = 4;
class MovementSolver class MovementSolver
{ {
private: private:
static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, const Ogre::Vector3 &velocity, float remainingTime, static float getSlope(const Ogre::Vector3 &normal)
{
return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
}
static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient,
const Ogre::Vector3 &velocity, float &remainingTime,
const Ogre::Vector3 &halfExtents, bool isInterior, const Ogre::Vector3 &halfExtents, bool isInterior,
OEngine::Physic::PhysicEngine *engine) OEngine::Physic::PhysicEngine *engine)
{ {
traceResults trace; // no initialization needed traceResults trace;
newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize),
halfExtents, isInterior, engine); halfExtents, isInterior, engine);
if(trace.fraction == 0.0f) if(trace.fraction == 0.0f)
@ -51,44 +56,33 @@ namespace MWWorld
halfExtents, isInterior, engine); halfExtents, isInterior, engine);
if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope))
return false; return false;
float movefrac = trace.fraction;
newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine);
if(getSlope(trace.planenormal) <= sMaxSlope) 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. // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
position = trace.endpos; position = trace.endpos;
remainingTime *= (1.0f-movefrac);
return true; return true;
} }
return false; return false;
} }
static void clipVelocity(Ogre::Vector3& inout, const Ogre::Vector3& normal, float overbounce=1.0f)
{
//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 = inout.dotProduct(normal);
if(backoff < 0.0f)
backoff *= overbounce;
else
backoff /= overbounce;
inout -= normal*backoff; ///Project a vector u on another vector v
static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v)
{
return v * u.dotProduct(v);
} }
static void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction) ///Helper for computing the character sliding
static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal)
{ {
Ogre::Vector3 normalizedDirection(direction); return direction - project(direction, planeNormal);
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: public:
static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine) static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine)
@ -104,7 +98,6 @@ namespace MWWorld
return position; return position;
bool wasCollisionMode = physicActor->getCollisionMode(); bool wasCollisionMode = physicActor->getCollisionMode();
physicActor->enableCollisions(false); physicActor->enableCollisions(false);
Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
@ -124,10 +117,9 @@ namespace MWWorld
if (wasCollisionMode) if (wasCollisionMode)
physicActor->enableCollisions(true); physicActor->enableCollisions(true);
if (hit) if(!hit)
return newPosition+Ogre::Vector3(0,0,4);
else
return position; return position;
return newPosition+Ogre::Vector3(0,0,4);
} }
static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
@ -147,14 +139,13 @@ namespace MWWorld
movement; movement;
} }
traceResults trace; //no initialization needed
bool onground = false;
float remainingTime = time;
bool isInterior = !ptr.getCell()->isExterior(); bool isInterior = !ptr.getCell()->isExterior();
Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
bool wasCollisionMode = physicActor->getCollisionMode();
physicActor->enableCollisions(false); physicActor->enableCollisions(false);
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::UNIT_Z);
traceResults trace;
bool onground = false;
const Ogre::Quaternion orient; // Don't rotate actor collision boxes
Ogre::Vector3 velocity; Ogre::Vector3 velocity;
if(!gravity) if(!gravity)
{ {
@ -175,53 +166,46 @@ namespace MWWorld
velocity.z += physicActor->getVerticalForce(); velocity.z += physicActor->getVerticalForce();
} }
Ogre::Vector3 clippedVelocity(velocity);
if(onground) if(onground)
{ {
// if we're on the ground, force velocity to track it // if we're on the ground, don't try to fall
clippedVelocity.z = velocity.z = std::max(0.0f, velocity.z); velocity.z = std::max(0.0f, velocity.z);
clipVelocity(clippedVelocity, trace.planenormal);
} }
const Ogre::Vector3 up(0.0f, 0.0f, 1.0f); const Ogre::Vector3 up(0.0f, 0.0f, 1.0f);
Ogre::Vector3 newPosition = position; Ogre::Vector3 newPosition = position;
int iterations = 0; float remainingTime = time;
do { for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations)
{
// trace to where character would go if there were no obstructions // trace to where character would go if there were no obstructions
newtrace(&trace, orient, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, isInterior, engine); newtrace(&trace, orient, newPosition, newPosition+velocity*remainingTime, halfExtents, isInterior, engine);
newPosition = trace.endpos;
remainingTime = remainingTime * (1.0f-trace.fraction);
// check for obstructions // check for obstructions
if(trace.fraction < 1.0f) if(trace.fraction >= 1.0f)
{ {
//std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n"; newPosition = trace.endpos;
if(getSlope(trace.planenormal) <= sMaxSlope) remainingTime *= (1.0f-trace.fraction);
{ break;
// We hit a slope we can walk on. Update velocity accordingly.
clipVelocity(clippedVelocity, trace.planenormal);
// We're only on the ground if gravity is affecting us
onground = gravity;
}
else
{
// Can't walk on this. Try to step up onto it.
if((gravity && !onground) ||
!stepMove(newPosition, orient, velocity, remainingTime, halfExtents, isInterior, engine))
{
Ogre::Vector3 resultantDirection = trace.planenormal.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 += trace.planenormal*clippedVelocity.length()/50.0f;
}
}
} }
iterations++; //std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
} while(iterations < sMaxIterations && remainingTime > 0.0f); // We hit something. Try to step up onto it.
if(stepMove(newPosition, orient, velocity, remainingTime, halfExtents, isInterior, engine))
onground = gravity;
else
{
// Can't move this way, try to find another spot along the plane
Ogre::Real movelen = velocity.normalise();
Ogre::Vector3 reflectdir = velocity.reflect(trace.planenormal);
reflectdir.normalise();
velocity = slide(reflectdir, trace.planenormal)*movelen;
// Do not allow sliding upward if there is gravity. Stepping will have taken
// care of that.
if(gravity)
velocity.z = std::min(velocity.z, 0.0f);
}
}
if(onground) if(onground)
{ {
@ -231,10 +215,11 @@ namespace MWWorld
else else
onground = false; onground = false;
} }
physicActor->setOnGround(onground); physicActor->setOnGround(onground);
physicActor->setVerticalForce(!onground ? clippedVelocity.z - time*627.2f : 0.0f); physicActor->setVerticalForce(!onground ? velocity.z - time*627.2f : 0.0f);
if (wasCollisionMode) physicActor->enableCollisions(true);
physicActor->enableCollisions(true);
return newPosition; return newPosition;
} }
}; };