diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7cfa38ff2..2d0964f25 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -55,29 +55,46 @@ namespace MWPhysics static const float sMaxSlope = 49.0f; static const float sStepSizeUp = 34.0f; static const float sStepSizeDown = 62.0f; + static const float sMinStep = 10.f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static const int sMaxIterations = 8; - // FIXME: move to a separate file - class MovementSolver + static bool isActor(const btCollisionObject *obj) + { + assert(obj); + return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; + } + + template + static bool isWalkableSlope(const Vec3 &normal) + { + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + return (normal.z() > sMaxSlopeCos); + } + + static bool canStepDown(const ActorTracer &stepper) + { + return stepper.mHitObject && isWalkableSlope(stepper.mPlaneNormal) && !isActor(stepper.mHitObject); + } + + class Stepper { private: - static float getSlope(osg::Vec3f normal) - { - normal.normalize(); - return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f))); - } + const btCollisionWorld *mColWorld; + const btCollisionObject *mColObj; - enum StepMoveResult - { - Result_Blocked, // unable to move over obstacle - Result_MaxSlope, // unable to end movement on this slope - Result_Success - }; + ActorTracer mTracer, mUpStepper, mDownStepper; + bool mHaveMoved; - static StepMoveResult stepMove(const btCollisionObject *colobj, osg::Vec3f &position, - const osg::Vec3f &toMove, float &remainingTime, const btCollisionWorld* collisionWorld) + public: + Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj) + : mColWorld(colWorld) + , mColObj(colObj) + , mHaveMoved(true) + {} + + bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime) { /* * Slide up an incline or set of stairs. Should be called only after a @@ -123,12 +140,14 @@ namespace MWPhysics * +--+ +-------- * ============================================== */ - ActorTracer tracer, stepper; - - stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), collisionWorld); - if(stepper.mFraction < std::numeric_limits::epsilon()) - return Result_Blocked; // didn't even move the smallest representable amount - // (TODO: shouldn't this be larger? Why bother with such a small amount?) + if (mHaveMoved) + { + mHaveMoved = false; + mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); + if(mUpStepper.mFraction < std::numeric_limits::epsilon()) + return false; // didn't even move the smallest representable amount + // (TODO: shouldn't this be larger? Why bother with such a small amount?) + } /* * Try moving from the elevated position using tracer. @@ -143,9 +162,10 @@ namespace MWPhysics * +--+ * ============================================== */ - tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, collisionWorld); - if(tracer.mFraction < std::numeric_limits::epsilon()) - return Result_Blocked; // didn't even move the smallest representable amount + osg::Vec3f tracerPos = mUpStepper.mEndPos; + mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld); + if(mTracer.mFraction < std::numeric_limits::epsilon()) + return false; // didn't even move the smallest representable amount /* * Try moving back down sStepSizeDown using stepper. @@ -162,26 +182,40 @@ namespace MWPhysics * +--+ +--+ * ============================================== */ - stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), collisionWorld); - if (getSlope(stepper.mPlaneNormal) > sMaxSlope) - return Result_MaxSlope; - if(stepper.mFraction < 1.0f) + mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); + if (!canStepDown(mDownStepper)) + { + // Try again with increased step length + if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep) + return false; + + osg::Vec3f direction = toMove; + direction.normalize(); + mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld); + if (mTracer.mFraction < 0.001f) + return false; + + mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); + if (!canStepDown(mDownStepper)) + return false; + } + if (mDownStepper.mFraction < 1.0f) { - // don't allow stepping up other actors - if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) - return Result_Blocked; // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing // NOTE: caller's variables 'position' & 'remainingTime' are modified here - position = stepper.mEndPos; - remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance - return Result_Success; + position = mDownStepper.mEndPos; + remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance + mHaveMoved = true; + return true; } - - return Result_Blocked; + return false; } + }; - + class MovementSolver + { + private: ///Project a vector u on another vector v static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) { @@ -229,14 +263,14 @@ namespace MWPhysics collisionWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit() && - ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 35 - || getSlope(tracer.mPlaneNormal) > sMaxSlope)) + ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length2() > 35*35 + || !isWalkableSlope(tracer.mPlaneNormal))) { - actor->setOnGround(getSlope(toOsg(resultCallback1.m_hitNormalWorld)) <= sMaxSlope); + actor->setOnGround(isWalkableSlope(resultCallback1.m_hitNormalWorld)); return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, 1.f); } - actor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); + actor->setOnGround(isWalkableSlope(tracer.mPlaneNormal)); return tracer.mEndPos; } @@ -312,8 +346,8 @@ namespace MWPhysics velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); } + Stepper stepper(collisionWorld, colobj); osg::Vec3f origVelocity = velocity; - osg::Vec3f newPosition = position; /* * A loop to find newPosition using tracer, if successful different from the starting position. @@ -332,10 +366,7 @@ namespace MWPhysics newPosition.z() <= swimlevel) { const osg::Vec3f down(0,0,-1); - float movelen = velocity.normalize(); - osg::Vec3f reflectdir = reflect(velocity, down); - reflectdir.normalize(); - velocity = slide(reflectdir, down)*movelen; + velocity = slide(velocity, down); // NOTE: remainingTime is unchanged before the loop continues continue; // velocity updated, calculate nextpos again } @@ -364,19 +395,25 @@ namespace MWPhysics break; } + // We are touching something. + if (tracer.mFraction < 1E-9f) + { + // Try to separate by backing off slighly to unstuck the solver + const osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-3f; + newPosition += backOff; + } + // We hit something. Check if we can step up. + float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); osg::Vec3f oldPosition = newPosition; - // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) - // NOTE: stepMove modifies newPosition if successful - const float minStep = 10.f; - StepMoveResult result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, collisionWorld); - if (result == Result_MaxSlope && (velocity*remainingTime).length() < minStep) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent + bool result = false; + if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) { - osg::Vec3f normalizedVelocity = velocity; - normalizedVelocity.normalize(); - result = stepMove(colobj, newPosition, normalizedVelocity*minStep, remainingTime, collisionWorld); + // Try to step up onto it. + // NOTE: stepMove does not allow stepping over, modifies newPosition if successful + result = stepper.step(newPosition, velocity*remainingTime, remainingTime); } - if(result == Result_Success) + if (result) { // don't let pure water creatures move out of water after stepMove if (ptr.getClass().isPureWaterCreature(ptr) @@ -386,23 +423,19 @@ namespace MWPhysics else { // Can't move this way, try to find another spot along the plane - osg::Vec3f direction = velocity; - float movelen = direction.normalize(); - osg::Vec3f reflectdir = reflect(velocity, tracer.mPlaneNormal); - reflectdir.normalize(); + osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal); + + // Do not allow sliding upward if there is gravity. + // Stepping will have taken care of that. + if(!(newPosition.z() < swimlevel || isFlying)) + newVelocity.z() = std::min(newVelocity.z(), 0.0f); - osg::Vec3f newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; if ((newVelocity-velocity).length2() < 0.01) break; - if ((velocity * origVelocity) <= 0.f) + if ((newVelocity * origVelocity) <= 0.f) break; // ^ dot product velocity = newVelocity; - - // Do not allow sliding upward if there is gravity. Stepping will have taken - // care of that. - if(!(newPosition.z() < swimlevel || isFlying)) - velocity.z() = std::min(velocity.z(), 0.0f); } } @@ -413,7 +446,7 @@ namespace MWPhysics osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f)); tracer.doTrace(colobj, from, to, collisionWorld); - if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope + if(tracer.mFraction < 1.0f && isWalkableSlope(tracer.mPlaneNormal) && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor) { const btCollisionObject* standingOn = tracer.mHitObject; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 420ca1a9e..feda68ca5 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -78,6 +78,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; + mHitPoint = toOsg(newTraceCallback.m_hitPointWorld); mHitObject = newTraceCallback.m_hitCollisionObject; } else @@ -85,6 +86,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star mEndPos = end; mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; + mHitPoint = end; mHitObject = NULL; } } diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h index 7b7d0391e..0297c9e07 100644 --- a/apps/openmw/mwphysics/trace.h +++ b/apps/openmw/mwphysics/trace.h @@ -15,6 +15,7 @@ namespace MWPhysics { osg::Vec3f mEndPos; osg::Vec3f mPlaneNormal; + osg::Vec3f mHitPoint; const btCollisionObject* mHitObject; float mFraction;