From 76b812f75fa1c6acab36605ac9359ce37869a23d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 01:11:57 -0700 Subject: [PATCH] Improve actor movement collision handling --- apps/openmw/mwworld/physicssystem.cpp | 127 ++++++++++++-------------- 1 file changed, 56 insertions(+), 71 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 371783bc54..e8450e1d29 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -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: "< 0.0f); + //std::cout<<"angle: "<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; } };