forked from teamnwah/openmw-tes3coop
Improve actor movement collision handling
This commit is contained in:
parent
8d925b7fd6
commit
76b812f75f
1 changed files with 56 additions and 71 deletions
|
@ -31,17 +31,22 @@ namespace MWWorld
|
|||
static const float sMaxSlope = 60.0f;
|
||||
static const float sStepSize = 30.0f;
|
||||
// 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
|
||||
{
|
||||
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,
|
||||
OEngine::Physic::PhysicEngine *engine)
|
||||
{
|
||||
traceResults trace; // no initialization needed
|
||||
|
||||
traceResults trace;
|
||||
newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize),
|
||||
halfExtents, isInterior, engine);
|
||||
if(trace.fraction == 0.0f)
|
||||
|
@ -51,44 +56,33 @@ namespace MWWorld
|
|||
halfExtents, isInterior, engine);
|
||||
if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope))
|
||||
return false;
|
||||
float movefrac = trace.fraction;
|
||||
|
||||
newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, 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;
|
||||
remainingTime *= (1.0f-movefrac);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
normalizedDirection.normalise();
|
||||
|
||||
// no divide by normalizedDirection.length necessary because it's normalized
|
||||
velocity = normalizedDirection * velocity.dotProduct(normalizedDirection);
|
||||
return direction - project(direction, planeNormal);
|
||||
}
|
||||
|
||||
static float getSlope(const Ogre::Vector3 &normal)
|
||||
{
|
||||
return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
|
||||
}
|
||||
|
||||
public:
|
||||
static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine)
|
||||
|
@ -104,7 +98,6 @@ namespace MWWorld
|
|||
return position;
|
||||
|
||||
bool wasCollisionMode = physicActor->getCollisionMode();
|
||||
|
||||
physicActor->enableCollisions(false);
|
||||
|
||||
Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
|
||||
|
@ -124,10 +117,9 @@ namespace MWWorld
|
|||
if (wasCollisionMode)
|
||||
physicActor->enableCollisions(true);
|
||||
|
||||
if (hit)
|
||||
return newPosition+Ogre::Vector3(0,0,4);
|
||||
else
|
||||
if(!hit)
|
||||
return position;
|
||||
return newPosition+Ogre::Vector3(0,0,4);
|
||||
}
|
||||
|
||||
static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
|
||||
|
@ -147,14 +139,13 @@ namespace MWWorld
|
|||
movement;
|
||||
}
|
||||
|
||||
traceResults trace; //no initialization needed
|
||||
bool onground = false;
|
||||
float remainingTime = time;
|
||||
bool isInterior = !ptr.getCell()->isExterior();
|
||||
Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
|
||||
bool wasCollisionMode = physicActor->getCollisionMode();
|
||||
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;
|
||||
if(!gravity)
|
||||
{
|
||||
|
@ -175,53 +166,46 @@ namespace MWWorld
|
|||
velocity.z += physicActor->getVerticalForce();
|
||||
}
|
||||
|
||||
Ogre::Vector3 clippedVelocity(velocity);
|
||||
if(onground)
|
||||
{
|
||||
// if we're on the ground, force velocity to track it
|
||||
clippedVelocity.z = velocity.z = std::max(0.0f, velocity.z);
|
||||
clipVelocity(clippedVelocity, trace.planenormal);
|
||||
// if we're on the ground, don't try to fall
|
||||
velocity.z = std::max(0.0f, velocity.z);
|
||||
}
|
||||
|
||||
const Ogre::Vector3 up(0.0f, 0.0f, 1.0f);
|
||||
Ogre::Vector3 newPosition = position;
|
||||
int iterations = 0;
|
||||
do {
|
||||
float remainingTime = time;
|
||||
for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations)
|
||||
{
|
||||
// trace to where character would go if there were no obstructions
|
||||
newtrace(&trace, orient, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, isInterior, engine);
|
||||
newPosition = trace.endpos;
|
||||
remainingTime = remainingTime * (1.0f-trace.fraction);
|
||||
newtrace(&trace, orient, newPosition, newPosition+velocity*remainingTime, halfExtents, isInterior, engine);
|
||||
|
||||
// check for obstructions
|
||||
if(trace.fraction < 1.0f)
|
||||
if(trace.fraction >= 1.0f)
|
||||
{
|
||||
//std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
|
||||
if(getSlope(trace.planenormal) <= sMaxSlope)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
newPosition = trace.endpos;
|
||||
remainingTime *= (1.0f-trace.fraction);
|
||||
break;
|
||||
}
|
||||
|
||||
iterations++;
|
||||
} while(iterations < sMaxIterations && remainingTime > 0.0f);
|
||||
//std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
|
||||
// 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)
|
||||
{
|
||||
|
@ -231,10 +215,11 @@ namespace MWWorld
|
|||
else
|
||||
onground = false;
|
||||
}
|
||||
|
||||
physicActor->setOnGround(onground);
|
||||
physicActor->setVerticalForce(!onground ? clippedVelocity.z - time*627.2f : 0.0f);
|
||||
if (wasCollisionMode)
|
||||
physicActor->enableCollisions(true);
|
||||
physicActor->setVerticalForce(!onground ? velocity.z - time*627.2f : 0.0f);
|
||||
physicActor->enableCollisions(true);
|
||||
|
||||
return newPosition;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue