diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 0585fe91f3..fc2ea57b40 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -206,7 +206,7 @@ namespace MWPhysics if((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions - tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); // check for obstructions if(tracer.mFraction >= 1.0f) @@ -341,7 +341,7 @@ namespace MWPhysics osg::Vec3f from = newPosition; auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); - tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld); + tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld, actor.mIsOnGround); if(tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index af85658910..5ef6833701 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -36,7 +36,7 @@ namespace MWPhysics // 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); + mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld, onGround); float upDistance = 0; if(!mUpStepper.mHitObject) @@ -117,7 +117,7 @@ namespace MWPhysics downStepSize = upDistance; else downStepSize = moveDistance + upDistance + sStepSizeDown; - mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld); + 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 diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 049d026e8e..b7930bfa53 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -12,38 +12,84 @@ namespace MWPhysics { -void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) +ActorConvexCallback sweepHelper(const btCollisionObject *actor, const btVector3& from, const btVector3& to, const btCollisionWorld* world, bool actorFilter) { - const btVector3 btstart = Misc::Convert::toBullet(start); - const btVector3 btend = Misc::Convert::toBullet(end); - const btTransform &trans = actor->getWorldTransform(); - btTransform from(trans); - btTransform to(trans); - from.setOrigin(btstart); - to.setOrigin(btend); - - const btVector3 motion = btstart-btend; - ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); - // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; + btTransform transFrom(trans); + btTransform transTo(trans); + transFrom.setOrigin(from); + transTo.setOrigin(to); const btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); - world->convexSweepTest(static_cast(shape), from, to, newTraceCallback); + + const btVector3 motion = from - to; // FIXME: this is backwards; means ActorConvexCallback is doing dot product tests backwards too + ActorConvexCallback traceCallback(actor, motion, btScalar(0.0), world); + // Inherit the actor's collision group and mask + traceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; + traceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; + if(actorFilter) + traceCallback.m_collisionFilterMask &= ~CollisionType_Actor; + + world->convexSweepTest(static_cast(shape), transFrom, transTo, traceCallback); + return traceCallback; +} + +void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace) +{ + const btVector3 btstart = Misc::Convert::toBullet(start); + btVector3 btend = Misc::Convert::toBullet(end); + + // Because Bullet's collision trace tests touch *all* geometry in its path, a lot of long collision tests + // will unnecessarily test against complex meshes that are dozens of units away. This wouldn't normally be + // a problem, but bullet isn't the fastest in the world when it comes to doing tests against triangle meshes. + // Therefore, we try out a short trace first, then only fall back to the full length trace if needed. + // This trace needs to be at least a couple units long, but there's no one particular ideal length. + // The length of 2.1 chosen here is a "works well in practice after testing a few random lengths" value. + // (Also, we only do this short test if the intended collision trace is long enough for it to make sense.) + const float fallback_length = 2.1f; + bool doing_short_trace = false; + // For some reason, typical scenes perform a little better if we increase the threshold length for the length test. + // (Multiplying by 2 in 'square distance' units gives us about 1.4x the threshold length. In benchmarks this was + // slightly better for the performance of normal scenes than 4.0, and just plain better than 1.0.) + if(attempt_short_trace && (btend-btstart).length2() > fallback_length*fallback_length*2.0) + { + btend = btstart + (btend-btstart).normalized()*fallback_length; + doing_short_trace = true; + } + + const auto traceCallback = sweepHelper(actor, btstart, btend, world, false); // Copy the hit data over to our trace results struct: - if(newTraceCallback.hasHit()) + if(traceCallback.hasHit()) { - mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mFraction = traceCallback.m_closestHitFraction; + // ensure fraction is correct (covers intended distance traveled instead of actual distance traveled) + if(doing_short_trace && (end-start).length2() > 0.0) + mFraction *= (btend-btstart).length() / (end-start).length(); + mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; - mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); - mHitObject = newTraceCallback.m_hitCollisionObject; + mHitPoint = Misc::Convert::toOsg(traceCallback.m_hitPointWorld); + mHitObject = traceCallback.m_hitCollisionObject; } else { + if(doing_short_trace) + { + btend = Misc::Convert::toBullet(end); + const auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); + + if(newTraceCallback.hasHit()) + { + mFraction = newTraceCallback.m_closestHitFraction; + mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mEndPos = (end-start)*mFraction + start; + mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); + mHitObject = newTraceCallback.m_hitCollisionObject; + return; + } + } + // fallthrough mEndPos = end; mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; @@ -54,25 +100,11 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { - const btVector3 btstart = Misc::Convert::toBullet(start); - const btVector3 btend = Misc::Convert::toBullet(end); - - const btTransform &trans = actor->getCollisionObject()->getWorldTransform(); - btTransform from(trans.getBasis(), btstart); - btTransform to(trans.getBasis(), btend); - - const btVector3 motion = btstart-btend; - ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); - // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; - newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; - - world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback); - if(newTraceCallback.hasHit()) + const auto traceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true); + if(traceCallback.hasHit()) { - mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mFraction = traceCallback.m_closestHitFraction; + mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; } else diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h index 0297c9e076..af38756b3e 100644 --- a/apps/openmw/mwphysics/trace.h +++ b/apps/openmw/mwphysics/trace.h @@ -20,7 +20,7 @@ namespace MWPhysics float mFraction; - void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); + void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace = false); void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); }; }