782 lines
29 KiB
C++
782 lines
29 KiB
C++
|
//=================================================================================================
|
||
|
//
|
||
|
// Trace Logic
|
||
|
//
|
||
|
// Split out from vjolt_collide.cpp due to being a can of worms
|
||
|
//
|
||
|
//=================================================================================================
|
||
|
|
||
|
#include "cbase.h"
|
||
|
|
||
|
#include "coordsize.h" // DIST_EPSILON
|
||
|
|
||
|
#include "vjolt_debugrender.h"
|
||
|
#include "vjolt_util.h"
|
||
|
|
||
|
#include "vjolt_collide.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
namespace VJoltTrace
|
||
|
{
|
||
|
|
||
|
static constexpr float kCollisionTolerance = 1.0e-3f;
|
||
|
static constexpr float kCharacterPadding = 0.02f;
|
||
|
|
||
|
// Also in vjolt_collide.cpp, should unify or just remove entirely
|
||
|
static constexpr float kMaxConvexRadius = JPH::cDefaultConvexRadius;
|
||
|
|
||
|
static ConVar vjolt_trace_debug( "vjolt_trace_debug", "0", FCVAR_CHEAT );
|
||
|
static ConVar vjolt_trace_debug_castray( "vjolt_trace_debug_castray", "0", FCVAR_CHEAT );
|
||
|
static ConVar vjolt_trace_debug_collidepoint( "vjolt_trace_debug_collidepoint", "0", FCVAR_CHEAT );
|
||
|
static ConVar vjolt_trace_debug_castbox( "vjolt_trace_debug_castbox", "0", FCVAR_CHEAT );
|
||
|
static ConVar vjolt_trace_debug_collidebox( "vjolt_trace_debug_collidebox", "0", FCVAR_CHEAT );
|
||
|
|
||
|
// Josh: Enables a hack to make portals work. For some reason when we enable colliding with
|
||
|
// backfaces, the player gets easily stuck in all sorts of things!
|
||
|
// Slart and I have not been able to determine the root cause of this problem and have tried for a long time...
|
||
|
//
|
||
|
// Slart: Portal 2 probably passes in a bad winding order in the polyhedron or something, dunno if it affects Portal 1
|
||
|
static ConVar vjolt_trace_portal_hack( "vjolt_trace_portal_hack", "0", FCVAR_NONE );
|
||
|
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
//
|
||
|
// Collectors and helpers
|
||
|
//
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
|
||
|
#ifdef JPH_DEBUG_RENDERER
|
||
|
|
||
|
static void PrintTrace( const trace_t *tr, JoltPhysicsDebugRenderer &debugRenderer )
|
||
|
{
|
||
|
Vector textPos = tr->endpos;
|
||
|
textPos.z += 4.0f;
|
||
|
|
||
|
constexpr float dur = 0.1f;
|
||
|
constexpr int r = 255;
|
||
|
constexpr int g = 255;
|
||
|
constexpr int b = 255;
|
||
|
constexpr int a = 255;
|
||
|
|
||
|
IVJoltDebugOverlay *pDebugOverlay = debugRenderer.GetDebugOverlay();
|
||
|
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 0, dur, r,g,b,a, "startpos : [ %g %g %g ]", tr->startpos.x, tr->startpos.y, tr->startpos.z );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 1, dur, r,g,b,a, "endpos : [ %g %g %g ]", tr->endpos.x, tr->endpos.y, tr->endpos.z );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 2, dur, r,g,b,a, "fraction : %g", tr->fraction );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 3, dur, r,g,b,a, "contents : %d", tr->contents );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 4, dur, r,g,b,a, "allsolid : %d", (int)tr->allsolid );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 5, dur, r,g,b,a, "startsolid : %d", (int)tr->startsolid );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 6, dur, r,g,b,a, "normal : [ %g %g %g ]", tr->plane.normal.x, tr->plane.normal.y, tr->plane.normal.z );
|
||
|
pDebugOverlay->AddTextOverlayRGB( textPos, 7, dur, r,g,b,a, "dist : %g", tr->plane.dist );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Collector for VJolt_CastRay
|
||
|
// Returns the closest hit within the contents mask
|
||
|
//
|
||
|
class ContentsCollector_CastRay final : public JPH::CastRayCollector
|
||
|
{
|
||
|
public:
|
||
|
ContentsCollector_CastRay( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo )
|
||
|
: m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {}
|
||
|
|
||
|
void AddHit( const JPH::RayCastResult &inResult ) override
|
||
|
{
|
||
|
// Test if this collision is closer than the previous one
|
||
|
const float theirEarlyOut = inResult.GetEarlyOutFraction();
|
||
|
const float ourEarlyOut = GetEarlyOutFraction();
|
||
|
if ( !m_DidHit || theirEarlyOut < ourEarlyOut )
|
||
|
{
|
||
|
const uint32 gameData = static_cast<uint32>( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) );
|
||
|
const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID;
|
||
|
|
||
|
if ( contents & m_ContentsMask )
|
||
|
{
|
||
|
// Update our early out fraction
|
||
|
UpdateEarlyOutFraction( theirEarlyOut );
|
||
|
|
||
|
// Store hit info locally
|
||
|
m_Fraction = inResult.mFraction;
|
||
|
m_SubShapeID = inResult.mSubShapeID2;
|
||
|
m_ResultContents = contents;
|
||
|
|
||
|
m_DidHit = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
// Inputs
|
||
|
const JPH::Shape * m_pShape = nullptr;
|
||
|
uint32 m_ContentsMask = 0;
|
||
|
IConvexInfo * m_pConvexInfo = nullptr;
|
||
|
|
||
|
public:
|
||
|
// Outputs (only use if m_DidHit is true)
|
||
|
float m_Fraction = 1.0f; // Use this instead of GetEarlyOutFraction
|
||
|
JPH::SubShapeID m_SubShapeID; // Subshape hit
|
||
|
uint32 m_ResultContents = 0; // Contents of hit subshape
|
||
|
|
||
|
bool m_DidHit = false;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Collector for VJolt_CollidePoint
|
||
|
// Returns the first hit within the contents mask
|
||
|
//
|
||
|
class ContentsCollector_CollidePoint final : public JPH::CollidePointCollector
|
||
|
{
|
||
|
public:
|
||
|
ContentsCollector_CollidePoint( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo )
|
||
|
: m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {}
|
||
|
|
||
|
void AddHit( const JPH::CollidePointResult &inResult ) override
|
||
|
{
|
||
|
// Return the first hit only
|
||
|
if ( m_DidHit )
|
||
|
return;
|
||
|
|
||
|
const uint32 gameData = static_cast<uint32>( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) );
|
||
|
const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID;
|
||
|
|
||
|
if ( contents & m_ContentsMask )
|
||
|
{
|
||
|
// Store hit info locally
|
||
|
m_SubShapeID = inResult.mSubShapeID2;
|
||
|
m_ResultContents = contents;
|
||
|
|
||
|
m_DidHit = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
// Inputs
|
||
|
const JPH::Shape * m_pShape = nullptr;
|
||
|
uint32 m_ContentsMask = 0;
|
||
|
IConvexInfo * m_pConvexInfo = nullptr;
|
||
|
|
||
|
public:
|
||
|
// Outputs (only use if m_DidHit is true)
|
||
|
JPH::SubShapeID m_SubShapeID; // Subshape hit
|
||
|
uint32 m_ResultContents = 0; // Contents of hit subshape
|
||
|
|
||
|
bool m_DidHit = false;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Filter for generic shape casts or collides
|
||
|
// Ensures that objects which do not fit within the contents mask are excluded
|
||
|
//
|
||
|
class ContentsFilter_Shape final : public JPH::ShapeFilter
|
||
|
{
|
||
|
public:
|
||
|
ContentsFilter_Shape( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo )
|
||
|
: m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {}
|
||
|
|
||
|
bool ShouldCollide( const JPH::SubShapeID& inSubShapeID2 ) const override
|
||
|
{
|
||
|
const uint32 gameData = static_cast<uint32>( m_pShape->GetSubShapeUserData( inSubShapeID2 ) );
|
||
|
const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID;
|
||
|
|
||
|
return !!( contents & m_ContentsMask );
|
||
|
}
|
||
|
|
||
|
bool ShouldCollide( const JPH::SubShapeID &inSubShapeID1, const JPH::SubShapeID &inSubShapeID2 ) const override
|
||
|
{
|
||
|
return ShouldCollide( inSubShapeID2 );
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
// Input
|
||
|
const JPH::Shape * m_pShape = nullptr;
|
||
|
uint32 m_ContentsMask = 0;
|
||
|
IConvexInfo * m_pConvexInfo = nullptr;
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Collector for generic shape casts
|
||
|
//
|
||
|
class ContentsCollector_CastShape final : public JPH::CastShapeCollector
|
||
|
{
|
||
|
public:
|
||
|
ContentsCollector_CastShape( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo )
|
||
|
: m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {}
|
||
|
|
||
|
void AddHit( const JPH::ShapeCastResult &inResult ) override
|
||
|
{
|
||
|
const uint32 gameData = static_cast<uint32>( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) );
|
||
|
const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID;
|
||
|
|
||
|
// Ensure that the contents filter was used
|
||
|
VJoltAssert( contents & m_ContentsMask );
|
||
|
|
||
|
// Test if this collision is closer than the previous one
|
||
|
const float theirEarlyOut = inResult.GetEarlyOutFraction();
|
||
|
const float ourEarlyOut = GetEarlyOutFraction();
|
||
|
if ( !m_DidHit || theirEarlyOut < ourEarlyOut )
|
||
|
{
|
||
|
// Update our early out fraction
|
||
|
UpdateEarlyOutFraction( theirEarlyOut );
|
||
|
|
||
|
m_Fraction = inResult.mFraction;
|
||
|
m_ResultContents = contents;
|
||
|
m_SubShapeID = inResult.mSubShapeID2;
|
||
|
m_ContactPoint = inResult.mContactPointOn2;
|
||
|
m_PenetrationAxis = inResult.mPenetrationAxis;
|
||
|
m_PenetrationDepth = inResult.mPenetrationDepth;
|
||
|
|
||
|
m_DidHit = true;
|
||
|
m_HitBackFace = inResult.mIsBackFaceHit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
// Input
|
||
|
const JPH::Shape * m_pShape = nullptr;
|
||
|
uint32 m_ContentsMask = 0;
|
||
|
IConvexInfo * m_pConvexInfo = nullptr;
|
||
|
|
||
|
public:
|
||
|
// Outputs (only use if m_DidHit is true)
|
||
|
float m_Fraction = 1.0f;
|
||
|
uint32 m_ResultContents = 0; // Contents of the subshape that we hit
|
||
|
JPH::SubShapeID m_SubShapeID; // SubShapeID that we hit
|
||
|
JPH::Vec3 m_ContactPoint; // Point of impact relative to the COM of the object we hit
|
||
|
JPH::Vec3 m_PenetrationAxis;
|
||
|
float m_PenetrationDepth = 0.0f;
|
||
|
|
||
|
bool m_DidHit = false; // Set to true if we hit anything
|
||
|
bool m_HitBackFace = false; // Set to true if the hit was against a backface
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Collector for generic shape collides
|
||
|
//
|
||
|
class ContentsCollector_CollideShape final : public JPH::CollideShapeCollector
|
||
|
{
|
||
|
public:
|
||
|
ContentsCollector_CollideShape( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo )
|
||
|
: m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {}
|
||
|
|
||
|
// Called whenever a hit occurs, for compound objects this can be called multiple times
|
||
|
void AddHit( const JPH::CollideShapeResult &inResult ) override
|
||
|
{
|
||
|
// Get the contents of the subshape that we hit
|
||
|
const uint32 gameData = static_cast<uint32>( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) );
|
||
|
const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID;
|
||
|
|
||
|
VJoltAssert( contents & m_ContentsMask );
|
||
|
|
||
|
if ( inResult.GetEarlyOutFraction() < GetEarlyOutFraction() )
|
||
|
{
|
||
|
UpdateEarlyOutFraction( inResult.GetEarlyOutFraction() );
|
||
|
|
||
|
m_ResultContents = contents;
|
||
|
m_SubShapeID = inResult.mSubShapeID2;
|
||
|
m_ContactPoint = inResult.mContactPointOn2;
|
||
|
m_PenetrationAxis = inResult.mPenetrationAxis;
|
||
|
m_PenetrationDepth = inResult.mPenetrationDepth;
|
||
|
|
||
|
m_DidHit = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
// Input
|
||
|
const JPH::Shape * m_pShape = nullptr;
|
||
|
uint32 m_ContentsMask = 0;
|
||
|
IConvexInfo * m_pConvexInfo = nullptr;
|
||
|
|
||
|
public:
|
||
|
// Output, only valid if m_didHit is true
|
||
|
uint32 m_ResultContents = 0; // Contents of the subshape that we hit
|
||
|
JPH::SubShapeID m_SubShapeID; // SubShapeID that we hit
|
||
|
JPH::Vec3 m_ContactPoint; // Point of impact relative to the COM of the object we hit
|
||
|
JPH::Vec3 m_PenetrationAxis;
|
||
|
float m_PenetrationDepth = 0.0f;
|
||
|
|
||
|
bool m_DidHit = false; // Set to true if we hit anything
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Contents collector for simple collide operations (no contents)
|
||
|
//
|
||
|
class ContentsCollector_SimpleCollide final : public JPH::CollideShapeCollector
|
||
|
{
|
||
|
public:
|
||
|
void AddHit( const ResultType &inResult ) override
|
||
|
{
|
||
|
intersection = true;
|
||
|
}
|
||
|
|
||
|
bool intersection = false;
|
||
|
};
|
||
|
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
//
|
||
|
// Tracing functions
|
||
|
//
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
|
||
|
//
|
||
|
// Casts a ray against a shape
|
||
|
//
|
||
|
static void CastRay( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
const JPH::Shape *pShape = pCollide->ToShape();
|
||
|
|
||
|
JPH::Vec3 position = SourceToJolt::Distance( collideOrigin );
|
||
|
JPH::Quat rotation = SourceToJolt::Angle( collideAngles );
|
||
|
JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() );
|
||
|
|
||
|
// Create ray
|
||
|
JPH::RayCast joltRay = { SourceToJolt::Distance( ray.m_Start ), SourceToJolt::Distance( ray.m_Delta ) };
|
||
|
// Transform the ray by the inverse of the position/rotation
|
||
|
joltRay = joltRay.Transformed( queryTransform.InversedRotationTranslation() );
|
||
|
|
||
|
// Set-up the settings
|
||
|
JPH::RayCastSettings settings;
|
||
|
settings.mBackFaceMode = JPH::EBackFaceMode::CollideWithBackFaces;
|
||
|
settings.mTreatConvexAsSolid = true;
|
||
|
|
||
|
//
|
||
|
// Hello!
|
||
|
// This code mimics IVP's behaviour where solids are solid, and the ray is totally clipped when inside.
|
||
|
// the BSP code (the gold standard) allows traces that start inside of solid objects to pass through backfaces on the convex.
|
||
|
// BSP sets traces that start inside of objects as startsolid 1, IVP doesn't do this :(
|
||
|
// So in the future, to mimic the BSP logic, we should make traces that begin inside of objects startsolid and only allsolid
|
||
|
// if they never leave the object
|
||
|
//
|
||
|
|
||
|
// Create our collector and cast away!
|
||
|
ContentsCollector_CastRay collector( pShape, contentsMask, pConvexInfo );
|
||
|
pShape->CastRay( joltRay, settings, JPH::SubShapeIDCreator(), collector );
|
||
|
|
||
|
if ( !collector.m_DidHit )
|
||
|
{
|
||
|
VJoltAssert( collector.m_Fraction == 1.0f );
|
||
|
}
|
||
|
|
||
|
// Populate pTrace's members
|
||
|
pTrace->fraction = collector.m_Fraction;
|
||
|
pTrace->startpos = ray.m_Start + ray.m_StartOffset;
|
||
|
pTrace->endpos = pTrace->startpos + ( ray.m_Delta * pTrace->fraction );
|
||
|
pTrace->allsolid = pTrace->fraction == 0.0f;
|
||
|
pTrace->startsolid = pTrace->fraction == 0.0f;
|
||
|
|
||
|
// If we hit, we fill in the plane and contents too
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
JPH::Vec3 normal = queryTransform.GetRotation() * pShape->GetSurfaceNormal( collector.m_SubShapeID, joltRay.GetPointOnRay( collector.m_Fraction ) );
|
||
|
pTrace->plane.normal = Vector( normal.GetX(), normal.GetY(), normal.GetZ() );
|
||
|
pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal );
|
||
|
|
||
|
pTrace->contents = collector.m_ResultContents;
|
||
|
}
|
||
|
|
||
|
#if defined JPH_DEBUG_RENDERER
|
||
|
|
||
|
// Debug trace visualizing
|
||
|
IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay();
|
||
|
if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_castray.GetBool() && pOverlay )
|
||
|
{
|
||
|
JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance();
|
||
|
|
||
|
JPH::Color rayColor( 255, 0, 0, 255 );
|
||
|
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
rayColor.r = 0;
|
||
|
rayColor.g = 255;
|
||
|
|
||
|
JPH::Vec3 normal = queryTransform.GetRotation() * pShape->GetSurfaceNormal( collector.m_SubShapeID, joltRay.GetPointOnRay( collector.m_Fraction ) );
|
||
|
debugRenderer.DrawArrow( queryTransform * ( joltRay.mOrigin + ( joltRay.mDirection * collector.GetEarlyOutFraction() ) ), queryTransform * ( joltRay.mOrigin + joltRay.mDirection * collector.GetEarlyOutFraction() ) + normal, JPH::Color::sRed, 0.3f );
|
||
|
}
|
||
|
|
||
|
debugRenderer.DrawArrow( queryTransform * joltRay.mOrigin, queryTransform * ( joltRay.mOrigin + ( joltRay.mDirection * collector.GetEarlyOutFraction() ) ), rayColor, 0.3f );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Collides a point against a shape
|
||
|
//
|
||
|
static void CollidePoint( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
const JPH::Shape *pShape = pCollide->ToShape();
|
||
|
|
||
|
JPH::Vec3 position = SourceToJolt::Distance( collideOrigin );
|
||
|
JPH::Quat rotation = SourceToJolt::Angle( collideAngles );
|
||
|
JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() );
|
||
|
|
||
|
JPH::Vec3 point = queryTransform.InversedRotationTranslation() * SourceToJolt::Distance(ray.m_Start);
|
||
|
|
||
|
ContentsCollector_CollidePoint collector( pShape, contentsMask, pConvexInfo );
|
||
|
pShape->CollidePoint( point, JPH::SubShapeIDCreator(), collector );
|
||
|
|
||
|
// Populate pTrace's members
|
||
|
pTrace->fraction = collector.m_DidHit ? 0.0f : 1.0f;
|
||
|
pTrace->startpos = ray.m_Start + ray.m_StartOffset;
|
||
|
pTrace->endpos = pTrace->startpos;
|
||
|
pTrace->allsolid = collector.m_DidHit;
|
||
|
pTrace->startsolid = collector.m_DidHit;
|
||
|
|
||
|
// This will be zero if we didn't hit
|
||
|
pTrace->contents = collector.m_ResultContents;
|
||
|
|
||
|
#if defined JPH_DEBUG_RENDERER
|
||
|
|
||
|
// Debug trace visualizing
|
||
|
IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay();
|
||
|
if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_collidepoint.GetBool() && pOverlay )
|
||
|
{
|
||
|
JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance();
|
||
|
|
||
|
JPH::Color rayColor( 0, 255, 0, 0 );
|
||
|
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
rayColor.a = 255;
|
||
|
}
|
||
|
|
||
|
debugRenderer.DrawMarker( point, rayColor, 0.3f );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static float CalculateSourceFraction( const Vector &rayDelta, float fraction, const Vector &normal )
|
||
|
{
|
||
|
const float rayLength = rayDelta.Length();
|
||
|
float invBaseLength = 0.0f;
|
||
|
Vector rayDir = vec3_origin;
|
||
|
if ( rayLength )
|
||
|
{
|
||
|
rayDir = rayDelta.Normalized();
|
||
|
invBaseLength = 1.0f / rayLength;
|
||
|
}
|
||
|
|
||
|
float hitLength = rayLength * fraction;
|
||
|
float dot = DotProduct( rayDir, normal );
|
||
|
if ( dot < 0.0f )
|
||
|
hitLength += DIST_EPSILON / dot;
|
||
|
|
||
|
hitLength = Max( hitLength, 0.0f );
|
||
|
|
||
|
return hitLength * invBaseLength;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Casts a box against a shape
|
||
|
//
|
||
|
static void CastBoxVsShape( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
const JPH::Shape *pShape = pCollide->ToShape();
|
||
|
|
||
|
JPH::Vec3 origin = SourceToJolt::Distance( ray.m_Start ); // Origin of the box or ray we're casting
|
||
|
JPH::Vec3 direction = SourceToJolt::Distance( ray.m_Delta ); // Direction and distance of the cast
|
||
|
JPH::Vec3 halfExtent = SourceToJolt::Distance( ray.m_Extents );
|
||
|
|
||
|
JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); // Position of the object we're casting against
|
||
|
JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); // Rotation of the object we're casting against
|
||
|
JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() );
|
||
|
|
||
|
JPH::BoxShape boxShape( halfExtent, kMaxConvexRadius );
|
||
|
JPH::ShapeCast shapeCast( &boxShape, JPH::Vec3::sReplicate( 1.0f ), JPH::Mat44::sTranslation( origin ), direction );
|
||
|
|
||
|
JPH::ShapeCastSettings settings;
|
||
|
//settings.mBackFaceModeTriangles = JPH::EBackFaceMode::CollideWithBackFaces;
|
||
|
// Josh: Had to re-enable CollideWithBackFaces to allow triggers for the Portal Environment to work.
|
||
|
// Come back here if we start getting stuck on things again...
|
||
|
if ( vjolt_trace_portal_hack.GetBool() )
|
||
|
settings.mBackFaceModeConvex = JPH::EBackFaceMode::CollideWithBackFaces;
|
||
|
//settings.mCollisionTolerance = kCollisionTolerance;
|
||
|
settings.mUseShrunkenShapeAndConvexRadius = true;
|
||
|
settings.mReturnDeepestPoint = true;
|
||
|
|
||
|
ContentsFilter_Shape filter( pShape, contentsMask, pConvexInfo );
|
||
|
ContentsCollector_CastShape collector( pShape, contentsMask, pConvexInfo );
|
||
|
JPH::CollisionDispatch::sCastShapeVsShapeWorldSpace( shapeCast, settings, pShape, JPH::Vec3::sReplicate( 1.0f ), filter, queryTransform, JPH::SubShapeIDCreator(), JPH::SubShapeIDCreator(), collector );
|
||
|
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
JPH::Vec3 normal = -( collector.m_PenetrationAxis.Normalized() );
|
||
|
pTrace->plane.normal = Vector( normal.GetX(), normal.GetY(), normal.GetZ() );
|
||
|
|
||
|
pTrace->fraction = CalculateSourceFraction( ray.m_Delta, collector.m_Fraction, pTrace->plane.normal );
|
||
|
|
||
|
//Log_Msg( LOG_VJolt, "Depth: %g, InitialFraction = %g, NewFraction = %g\n", collector.m_PenetrationDepth, flInitialFraction, pTrace->fraction );
|
||
|
|
||
|
pTrace->startpos = ray.m_Start + ray.m_StartOffset;
|
||
|
pTrace->endpos = pTrace->startpos + ( ray.m_Delta * pTrace->fraction );
|
||
|
|
||
|
pTrace->endpos -= pTrace->plane.normal * collector.m_PenetrationDepth;
|
||
|
|
||
|
pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal );
|
||
|
pTrace->contents = collector.m_ResultContents;
|
||
|
|
||
|
// If penetrating more than DIST_EPSILON, consider it an intersection
|
||
|
//constexpr float PenetrationEpsilon = DIST_EPSILON;
|
||
|
static constexpr float kMinRequiredPenetration = 0.005f + kCharacterPadding;
|
||
|
|
||
|
pTrace->allsolid = collector.m_PenetrationDepth > kMinRequiredPenetration && pTrace->fraction == 0.0f;
|
||
|
pTrace->startsolid = collector.m_PenetrationDepth > kMinRequiredPenetration && pTrace->fraction == 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTrace->fraction = 1.0f;
|
||
|
pTrace->startpos = ray.m_Start + ray.m_StartOffset;
|
||
|
pTrace->endpos = pTrace->startpos + ray.m_Delta;
|
||
|
|
||
|
// We didn't hit anything, so we must be completely free right?
|
||
|
pTrace->allsolid = false;
|
||
|
pTrace->startsolid = false;
|
||
|
}
|
||
|
|
||
|
#if defined JPH_DEBUG_RENDERER
|
||
|
|
||
|
// Debug trace visualizing
|
||
|
IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay();
|
||
|
if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_castbox.GetBool() && pOverlay )
|
||
|
{
|
||
|
JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance();
|
||
|
|
||
|
JPH::Color color( 255, 64, 64, 255 );
|
||
|
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
color.r = 64;
|
||
|
color.g = 255;
|
||
|
|
||
|
JPH::Vec3 hitPos = origin + ( direction * collector.m_Fraction );
|
||
|
debugRenderer.DrawArrow( hitPos, hitPos + collector.m_PenetrationAxis, JPH::Color::sRed, 0.3f );
|
||
|
}
|
||
|
|
||
|
boxShape.Draw( &debugRenderer, queryTransform, JPH::Vec3::sReplicate( 1.0f ), color, false, false );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Collides a box against a shape
|
||
|
//
|
||
|
static void CollideBoxVsShape( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
const JPH::Shape *pShape = pCollide->ToShape();
|
||
|
|
||
|
JPH::Vec3 origin = SourceToJolt::Distance( ray.m_Start ); // Origin of the box or ray we're casting
|
||
|
JPH::Vec3 direction = SourceToJolt::Distance( ray.m_Delta ); // Direction and distance of the cast
|
||
|
JPH::Vec3 halfExtent = SourceToJolt::Distance( ray.m_Extents );
|
||
|
|
||
|
JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); // Position of the object we're casting against
|
||
|
JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); // Rotation of the object we're casting against
|
||
|
JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() );
|
||
|
|
||
|
JPH::BoxShape boxShape( halfExtent, kMaxConvexRadius );
|
||
|
|
||
|
JPH::CollideShapeSettings settings;
|
||
|
//settings.mMaxSeparationDistance = DIST_EPSILON;
|
||
|
//settings.mBackFaceMode = JPH::EBackFaceMode::CollideWithBackFaces;
|
||
|
|
||
|
ContentsCollector_CollideShape collector( pShape, contentsMask, pConvexInfo );
|
||
|
JPH::CollisionDispatch::sCollideShapeVsShape(
|
||
|
&boxShape, pShape,
|
||
|
JPH::Vec3::sReplicate( 1.0f ), JPH::Vec3::sReplicate( 1.0f ),
|
||
|
JPH::Mat44::sIdentity(), queryTransform,
|
||
|
JPH::SubShapeIDCreator(), JPH::SubShapeIDCreator(),
|
||
|
settings, collector );
|
||
|
|
||
|
pTrace->fraction = collector.m_DidHit ? 0.0f : 1.0f;
|
||
|
pTrace->startpos = ray.m_Start + ray.m_StartOffset;
|
||
|
pTrace->endpos = pTrace->startpos;
|
||
|
pTrace->allsolid = collector.m_DidHit;
|
||
|
pTrace->startsolid = collector.m_DidHit;
|
||
|
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
JPH::Vec3 normal = -( ( queryTransform.GetRotation() * collector.m_PenetrationAxis ).Normalized() );
|
||
|
pTrace->plane.normal = Vector( normal.GetX(), normal.GetY(), normal.GetZ() );
|
||
|
pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal );
|
||
|
|
||
|
pTrace->contents = collector.m_ResultContents;
|
||
|
}
|
||
|
|
||
|
#if defined JPH_DEBUG_RENDERER
|
||
|
|
||
|
// Debug trace visualizing
|
||
|
IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay();
|
||
|
if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_collidebox.GetBool() && pOverlay )
|
||
|
{
|
||
|
JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance();
|
||
|
|
||
|
JPH::Color color( 255, 64, 64, 255 );
|
||
|
|
||
|
if ( collector.m_DidHit )
|
||
|
{
|
||
|
color.r = 64;
|
||
|
color.g = 255;
|
||
|
}
|
||
|
|
||
|
boxShape.Draw( &debugRenderer, queryTransform, JPH::Vec3::sReplicate( 1.0f ), color, false, false );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Collides a shape against a shape
|
||
|
//
|
||
|
// Slart: This sucks, it could be implemented better in the environment interface using a bool and two physics objects instead... Basically all the code using this wants it to see
|
||
|
// if a shape is inside another shape.
|
||
|
//
|
||
|
static void CollideShapeVsShape( const Vector &start, const Vector &end, const CPhysCollide *pSweepCollide, const QAngle &sweepAngles, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
ClearTrace( pTrace );
|
||
|
|
||
|
// Are we sweeping, or just doing a point test?
|
||
|
bool isSweeping = false;
|
||
|
|
||
|
if ( !VectorCompare( start, end ) )
|
||
|
{
|
||
|
isSweeping = true;
|
||
|
|
||
|
// Slart: Exactly one piece of code "may" want a sweeping trace, ffs
|
||
|
Log_Warning( LOG_VJolt, "Attemping an unsupported TraceCollide\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Glue variable
|
||
|
const Vector &sweepOrigin = start;
|
||
|
|
||
|
const JPH::Shape *pSweepShape = pSweepCollide->ToShape();
|
||
|
JPH::Vec3 sweepJoltPosition = SourceToJolt::Distance( sweepOrigin );
|
||
|
JPH::Quat sweepJoltRotation = SourceToJolt::Angle( sweepAngles );
|
||
|
JPH::Mat44 sweepTransform = JPH::Mat44::sRotationTranslation( sweepJoltRotation, sweepJoltPosition + sweepJoltRotation * pSweepShape->GetCenterOfMass() );
|
||
|
|
||
|
const JPH::Shape *pCollideShape = pCollide->ToShape();
|
||
|
JPH::Vec3 collideJoltPosition = SourceToJolt::Distance( collideOrigin );
|
||
|
JPH::Quat collideJoltRotation = SourceToJolt::Angle( collideAngles );
|
||
|
JPH::Mat44 collideTransform = JPH::Mat44::sRotationTranslation( collideJoltRotation, collideJoltPosition + collideJoltRotation * pCollideShape->GetCenterOfMass() );
|
||
|
//JPH::Mat44 collideTransform = JPH::Mat44::sTranslation( collideJoltPosition );
|
||
|
|
||
|
if ( !isSweeping )
|
||
|
{
|
||
|
JPH::AABox sweepAABB = pSweepShape->GetWorldSpaceBounds( sweepTransform, JPH::Vec3::sReplicate( 1.0f ) );
|
||
|
JPH::AABox collideAABB = pCollideShape->GetWorldSpaceBounds( collideTransform, JPH::Vec3::sReplicate( 1.0f ) );
|
||
|
|
||
|
// Debug trace visualizing
|
||
|
IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay();
|
||
|
if ( vjolt_trace_debug.GetBool() && pOverlay && !pTrace->allsolid )
|
||
|
{
|
||
|
Vector mins, maxs;
|
||
|
|
||
|
mins = JoltToSource::Distance( sweepAABB.mMin );
|
||
|
maxs = JoltToSource::Distance( sweepAABB.mMax );
|
||
|
pOverlay->AddBoxOverlay( vec3_origin, mins, maxs, vec3_angle, 255, 64, 64, 255, -1.0f );
|
||
|
|
||
|
mins = JoltToSource::Distance( collideAABB.mMin );
|
||
|
maxs = JoltToSource::Distance( collideAABB.mMax );
|
||
|
pOverlay->AddBoxOverlay( vec3_origin, mins, maxs, vec3_angle, 255, 64, 64, 255, -1.0f );
|
||
|
}
|
||
|
|
||
|
// Don't bother doing any work if we don't contain the thing to be tested
|
||
|
if ( !collideAABB.Contains( sweepAABB ) )
|
||
|
{
|
||
|
// TODO(Slart): Uncomment this
|
||
|
//return;
|
||
|
}
|
||
|
|
||
|
ContentsCollector_SimpleCollide collector;
|
||
|
|
||
|
JPH::CollisionDispatch::sCollideShapeVsShape(
|
||
|
pSweepShape, pCollideShape,
|
||
|
JPH::Vec3::sReplicate( 1.0f ), JPH::Vec3::sReplicate( 1.0f ),
|
||
|
sweepTransform, collideTransform,
|
||
|
JPH::SubShapeIDCreator(), JPH::SubShapeIDCreator(),
|
||
|
JPH::CollideShapeSettings(), collector );
|
||
|
|
||
|
if ( collector.intersection )
|
||
|
{
|
||
|
pTrace->allsolid = true;
|
||
|
pTrace->startsolid = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
//
|
||
|
// Where we figure out which type this trace actually is
|
||
|
//
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
|
||
|
static void TraceBase( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
VJoltAssert( pCollide && pTrace );
|
||
|
|
||
|
// Default out our trace
|
||
|
ClearTrace( pTrace );
|
||
|
|
||
|
// We can't trust Ray_t's settings because after conversion from Source > Jolt the coordinates might become tiny
|
||
|
bool isPoint = SourceToJolt::Distance( ray.m_Extents ).ReduceMin() < kMaxConvexRadius;
|
||
|
bool isCollide = !ray.m_IsSwept;
|
||
|
|
||
|
if ( isPoint )
|
||
|
{
|
||
|
if ( isCollide )
|
||
|
{
|
||
|
CollidePoint( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CastRay( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( isCollide )
|
||
|
{
|
||
|
// TODO(Slart): This should be CollideBoxVsShape, but I can't remember why it wasn't good enough...
|
||
|
CastBoxVsShape( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CastBoxVsShape( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace VJoltTrace
|
||
|
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
//
|
||
|
// IPhysicsCollision Interface
|
||
|
//
|
||
|
//-------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void JoltPhysicsCollision::TraceBox( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr )
|
||
|
{
|
||
|
Ray_t ray;
|
||
|
ray.Init( start, end, mins, maxs );
|
||
|
VJoltTrace::TraceBase( ray, MASK_ALL, nullptr, pCollide, collideOrigin, collideAngles, ptr );
|
||
|
}
|
||
|
|
||
|
void JoltPhysicsCollision::TraceBox( const Ray_t &ray, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr )
|
||
|
{
|
||
|
VJoltTrace::TraceBase( ray, MASK_ALL, nullptr, pCollide, collideOrigin, collideAngles, ptr );
|
||
|
}
|
||
|
|
||
|
void JoltPhysicsCollision::TraceBox( const Ray_t &ray, unsigned int contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr )
|
||
|
{
|
||
|
VJoltTrace::TraceBase( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, ptr );
|
||
|
}
|
||
|
|
||
|
void JoltPhysicsCollision::TraceCollide( const Vector &start, const Vector &end, const CPhysCollide *pSweepCollide, const QAngle &sweepAngles, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace )
|
||
|
{
|
||
|
VJoltTrace::CollideShapeVsShape( start, end, pSweepCollide, sweepAngles, pCollide, collideOrigin, collideAngles, pTrace );
|
||
|
}
|