1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-01 12:01:51 +00:00
OpenMW/apps/openmw/mwphysics/trace.cpp
2022-09-22 21:35:26 +03:00

125 lines
5.6 KiB
C++

#include "trace.h"
#include <components/misc/convert.hpp>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <BulletCollision/CollisionShapes/btConvexShape.h>
#include "actor.hpp"
#include "actorconvexcallback.hpp"
#include "collisiontype.hpp"
namespace MWPhysics
{
ActorConvexCallback sweepHelper(const btCollisionObject* actor, const btVector3& from, const btVector3& to,
const btCollisionWorld* world, bool actorFilter)
{
const btTransform& trans = actor->getWorldTransform();
btTransform transFrom(trans);
btTransform transTo(trans);
transFrom.setOrigin(from);
transTo.setOrigin(to);
const btCollisionShape* shape = actor->getCollisionShape();
assert(shape->isConvex());
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<const btConvexShape*>(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 (traceCallback.hasHit())
{
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(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;
mHitPoint = end;
mHitObject = nullptr;
}
}
void ActorTracer::findGround(
const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
{
const auto traceCallback = sweepHelper(
actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true);
if (traceCallback.hasHit())
{
mFraction = traceCallback.m_closestHitFraction;
mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld);
mEndPos = (end - start) * mFraction + start;
}
else
{
mEndPos = end;
mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f);
mFraction = 1.0f;
}
}
}