diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 16501c432d..568da999bc 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -59,6 +59,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2; mConvexShape = static_cast(mShape.get()); + mConvexShape->setMargin(0.001); // make sure bullet isn't using the huge default convex shape margin of 0.04 mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index c275b63c7b..b2d189e874 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -15,7 +15,7 @@ namespace MWPhysics // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static constexpr int sMaxIterations = 8; // Allows for more precise movement solving without getting stuck or snagging too easily. - static constexpr float sCollisionMargin = 0.1f; + static constexpr float sCollisionMargin = 0.2f; // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues static constexpr float sAllowedPenetration = 0.0f; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fc2ea57b40..2f2abca487 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -252,6 +252,8 @@ namespace MWPhysics remainingTime *= (1.0f-tracer.mFraction); auto planeNormal = tracer.mPlaneNormal; + // need to know the unadjusted normal to handle certain types of seams properly + const auto origPlaneNormal = planeNormal; // If we touched the ground this frame, and whatever we ran into is a wall of some sort, // pretend that its collision normal is pointing horizontally @@ -275,10 +277,11 @@ namespace MWPhysics bool usedSeamLogic = false; // check for the current and previous collision planes forming an acute angle; slide along the seam if they do + // for this, we want to use the original plane normal, or else certain types of geometry will snag if(numTimesSlid > 0) { - auto dotA = lastSlideNormal * planeNormal; - auto dotB = lastSlideNormalFallback * planeNormal; + auto dotA = lastSlideNormal * origPlaneNormal; + auto dotB = lastSlideNormalFallback * origPlaneNormal; if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide dotB = 1.0; if(dotA <= 0.0 || dotB <= 0.0) @@ -291,14 +294,14 @@ namespace MWPhysics lastSlideNormal = lastSlideNormalFallback; } - auto constraintVector = bestNormal ^ planeNormal; // cross product + auto constraintVector = bestNormal ^ origPlaneNormal; // cross product if(constraintVector.length2() > 0) // only if it's not zero length { constraintVector.normalize(); newVelocity = project(velocity, constraintVector); // version of surface rejection for acute crevices/seams - auto averageNormal = bestNormal + planeNormal; + auto averageNormal = bestNormal + origPlaneNormal; averageNormal.normalize(); tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; @@ -309,27 +312,40 @@ namespace MWPhysics } // otherwise just keep the normal vector rejection - // if this isn't the first iteration, or if the first iteration is also the last iteration, // move away from the collision plane slightly, if possible // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings // this is different from the normal collision margin, because the normal collision margin is along the movement path, // but this is along the collision normal - if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) + if(!usedSeamLogic) { tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; } + // short circuit if we went backwards, but only if it was mostly horizontal and we're on the ground + if (seenGround && newVelocity * origVelocity <= 0.0f) + { + auto perpendicular = newVelocity ^ origVelocity; + if (perpendicular.length2() > 0.0f) + { + perpendicular.normalize(); + if (std::abs(perpendicular.z()) > 0.7071f) + break; + } + } + // Do not allow sliding up steep slopes if there is gravity. - if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal)) + // The purpose of this is to prevent air control from letting you slide up tall, unwalkable slopes. + // For that purpose, it is not necessary to do it when trying to slide along acute seams/crevices (i.e. usedSeamLogic) + // and doing so would actually break air control in some situations where vanilla allows air control. + // Vanilla actually allows you to slide up slopes as long as you're in the "walking" animation, which can be true even + // in the air, so allowing this for seams isn't a compatibility break. + if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal) && !usedSeamLogic) newVelocity.z() = std::min(newVelocity.z(), velocity.z()); - if (newVelocity * origVelocity <= 0.0f) - break; - numTimesSlid += 1; lastSlideNormalFallback = lastSlideNormal; - lastSlideNormal = planeNormal; + lastSlideNormal = origPlaneNormal; velocity = newVelocity; } }