#include "stepper.hpp" #include #include #include #include "collisiontype.hpp" #include "constants.hpp" #include "movementsolver.hpp" namespace MWPhysics { static bool canStepDown(const ActorTracer& stepper) { if (!stepper.mHitObject) return false; static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); if (stepper.mPlaneNormal.z() <= sMaxSlopeCos) return false; return stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor; } Stepper::Stepper(const btCollisionWorld* colWorld, const btCollisionObject* colObj) : mColWorld(colWorld) , mColObj(colObj) { } bool Stepper::step( osg::Vec3f& position, osg::Vec3f& velocity, float& remainingTime, const bool& onGround, bool firstIteration) { if (velocity.x() == 0.0 && velocity.y() == 0.0) return false; // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the // ground. This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and // just prevent stepping on insane geometry. mUpStepper.doTrace( mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld, onGround); float upDistance = 0; if (!mUpStepper.mHitObject) upDistance = Constants::sStepSizeUp; else if (mUpStepper.mFraction * Constants::sStepSizeUp > sCollisionMargin) upDistance = mUpStepper.mFraction * Constants::sStepSizeUp - sCollisionMargin; else { return false; } auto toMove = velocity * remainingTime; osg::Vec3f tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); osg::Vec3f tracerDest; auto normalMove = toMove; auto moveDistance = normalMove.normalize(); // attempt 1: normal movement // attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to // avoid a glitch attempt 3: further, less tall fixed distance movement, same as above If you're making a full // conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems // with vanilla Morrowind assets. int attempt = 0; float downStepSize = 0; while (attempt < 3) { attempt++; if (attempt == 1) tracerDest = tracerPos + toMove; else if (!sDoExtraStairHacks) // early out if we have extra hacks disabled { return false; } else if (attempt == 2) { moveDistance = sMinStep; tracerDest = tracerPos + normalMove * sMinStep; } else if (attempt == 3) { if (upDistance > Constants::sStepSizeUp) { upDistance = Constants::sStepSizeUp; tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); } moveDistance = sMinStep2; tracerDest = tracerPos + normalMove * sMinStep2; } mTracer.doTrace(mColObj, tracerPos, tracerDest, mColWorld); if (mTracer.mHitObject) { // map against what we hit, minus the safety margin moveDistance *= mTracer.mFraction; if (moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything { return false; } moveDistance -= sCollisionMargin; tracerDest = tracerPos + normalMove * moveDistance; // safely eject from what we hit by the safety margin auto tempDest = tracerDest + mTracer.mPlaneNormal * sCollisionMargin * 2; ActorTracer tempTracer; tempTracer.doTrace(mColObj, tracerDest, tempDest, mColWorld); if (tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked // sCollisionMargin*2 distance) { auto effectiveFraction = tempTracer.mFraction * 2.0f - 1.0f; tracerDest += mTracer.mPlaneNormal * sCollisionMargin * effectiveFraction; } } if (attempt > 2) // do not allow stepping down below original height for attempt 3 downStepSize = upDistance; else downStepSize = moveDistance + upDistance + sStepSizeDown; mDownStepper.doTrace( mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld, onGround); // can't step down onto air, non-walkable-slopes, or actors // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were // intended to be valid at the bottoms of stairs (like the bottoms of the staircases in aldruhn's guild of // mages) The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep // (10.0) but it caused all sorts of other problems. Switched back to cylinders to avoid that and similer // problems. if (canStepDown(mDownStepper)) { break; } else { // do not try attempt 3 if we just tried attempt 2 and the horizontal distance was rather large // (forces actor to get snug against the defective ledge for attempt 3 to be tried) if (attempt == 2 && moveDistance > upDistance - (mDownStepper.mFraction * downStepSize)) { return false; } // do next attempt if first iteration of movement solver and not out of attempts if (firstIteration && attempt < 3) { continue; } return false; } } // note: can't downstep onto actors so no need to pick safety margin float downDistance = 0; if (mDownStepper.mFraction * downStepSize > sCollisionMargin) downDistance = mDownStepper.mFraction * downStepSize - sCollisionMargin; if (downDistance - sCollisionMargin - sGroundOffset > upDistance && !onGround) return false; auto newpos = tracerDest + osg::Vec3f(0.0f, 0.0f, -downDistance); if ((position - newpos).length2() < sCollisionMargin * sCollisionMargin) return false; if (mTracer.mHitObject) { auto planeNormal = mTracer.mPlaneNormal; if (onGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) { planeNormal.z() = 0; planeNormal.normalize(); } velocity = reject(velocity, planeNormal); } velocity = reject(velocity, mDownStepper.mPlaneNormal); position = newpos; remainingTime *= (1.0f - mTracer.mFraction); // remaining time is proportional to remaining distance return true; } }