mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-01 12:01:51 +00:00
187 lines
7.4 KiB
C++
187 lines
7.4 KiB
C++
#include "stepper.hpp"
|
|
|
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
|
|
|
#include <components/misc/constants.hpp>
|
|
|
|
#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;
|
|
}
|
|
}
|