You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
8.2 KiB
259 lines
8.2 KiB
//========= Copyright © Valve Corporation, All rights reserved. ============//
|
|
#include "capsule.h"
|
|
#include "trace.h"
|
|
//#include "body.h"
|
|
//#include "mass.h"
|
|
|
|
//#include "distance.h"
|
|
//#include "gjk.h"
|
|
//#include "sat.h"
|
|
|
|
#define NUM_STACKS 8
|
|
#define NUM_SLICES 16
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Local utilities
|
|
//--------------------------------------------------------------------------------------------------
|
|
struct CapsuleCast2D_t
|
|
{
|
|
float m_flCapsule, m_flRay;
|
|
};
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
static void CastCapsuleRay2DCoaxialInternal( CapsuleCast2D_t &out, float mx, float dx, float h, float e )
|
|
{
|
|
Assert( e >= 0 );
|
|
float mxProj = mx + e; // m.x - (-e)
|
|
if( mxProj < 0 )
|
|
{
|
|
// ray starts before the capsule cap
|
|
out.m_flCapsule = 0;
|
|
if( dx >= -mxProj ) // otherwise, ending before capsule starts: FLT_MAX
|
|
{
|
|
out.m_flRay = -mxProj / dx;
|
|
}
|
|
}
|
|
else if( mx < h + e ) // otherwise, starting after capsule ends : FLT_MAX
|
|
{
|
|
out.m_flCapsule = Clamp( mx, 0.0f, h );
|
|
out.m_flRay = 0;
|
|
}
|
|
else
|
|
{
|
|
// ray starts after the capsule cap
|
|
out.m_flCapsule = h;
|
|
float mxEnd = mx - ( h + e );
|
|
if( -dx >= mxEnd )
|
|
{
|
|
out.m_flRay = mxEnd / -dx;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
static void CastCapsuleRay2DParallelInternal( CapsuleCast2D_t &out, const Vector2D &m, float dx, float h, float rr )
|
|
{
|
|
float e2 = rr - Sqr( m.y );
|
|
if( e2 > 0 ) // otherwise, going parallel and outside : FLT_MAX
|
|
{
|
|
// going parallel and inside the infinite slab at level m.y, left to right
|
|
float e = sqrtf( e2 ); // -e..h+e is the extent
|
|
CastCapsuleRay2DCoaxialInternal( out, m.x, dx, h, e );
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Intersect 2D ray with 2D capsule; capsule has radius r, length h, it starts at (0,0) and ends at (h,0)
|
|
// ray goes from m, delta d
|
|
// return: time of hit
|
|
static void CastCapsuleRay2DInternal( CapsuleCast2D_t &out, const Vector2D &m, const Vector2D &d, float h, float rr )
|
|
{
|
|
Assert( rr >= 0 );
|
|
Assert( d.y > -FLT_EPSILON );
|
|
Assert( d.y != 0.0f ); // otherwise it's going parallel
|
|
float my2 = Sqr( m.y );
|
|
out.m_flCapsule = Clamp( m.x, 0.0f, h );
|
|
|
|
// Easy case we'll have to check a few times if we delay: are we starting in solid?
|
|
// same idea as with box-box distance: cut out x=0..h, capsule becomes a circle, find distance to circle
|
|
// I'm sure there's more elegant way to handle it
|
|
if( Sqr( m.x - out.m_flCapsule ) + my2 < rr )
|
|
{
|
|
out.m_flRay = 0; // start-in-solid
|
|
return;
|
|
}
|
|
// well, we don't start inside the capsule. Good to know
|
|
float r = sqrtf( rr ), dd = Sqr( d.x ) + Sqr( d.y ), ddInv = 1.0f / dd, dymy = d.y * m.y;
|
|
|
|
// first, intersect the ray with the rectangle
|
|
|
|
float t = ( -r - m.y ) / d.y, t0 = fpmax( 0, t ), s0 = m.x + d.x * t0;
|
|
|
|
// solutions: -b0±sqrt(b0^2-c0) , -b1±sqrt(b1^2-c1) with ± controlled by d.x sign
|
|
// since we know we go left-bottom to right-top, we can just choose the circle we wanna hit
|
|
// since we know we don't start-in-solid, we know the first root (if any) will be t>=0
|
|
float mxh;
|
|
if( s0 < 0 )
|
|
{
|
|
// we're entering through the left cap
|
|
// if we hit, we hit left circle
|
|
out.m_flCapsule = 0;
|
|
mxh = m.x;
|
|
}
|
|
else if( s0 < h )
|
|
{
|
|
// we're entering through the side of the capsule
|
|
out.m_flCapsule = s0;
|
|
if( t >= 0 ) // only if we didn't enter before ray started; otherwise, since we didn't start-in-solid, we don't hit capsule at all
|
|
{
|
|
out.m_flRay = t; // the caller will sort out if it's >1 or not
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
out.m_flCapsule = h;
|
|
mxh = m.x - h;
|
|
}
|
|
|
|
float b = ( d.x * mxh + dymy ) * ddInv, c = ( mxh * mxh + my2 - rr ) * ddInv, D = b * b - c;
|
|
if( D >= 0 )
|
|
{
|
|
float tc = -b - sqrtf( D );
|
|
Assert( tc - t >= -1e-4f ); // the ray should really enter the circle after it entered the stripe of halfspaces
|
|
// if tc < 0, we entered capsule before ray began; since we didn't start-in-solid, it means we don't hit the capsule at all
|
|
if( tc >= 0 )
|
|
{
|
|
out.m_flRay = tc;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void CastCapsuleShortRay( CShapeCastResult &out, const Vector &sUnit, float sLen, const Vector &m, const Vector &vRayStart, const Vector vCenter[], float flRadius )
|
|
{
|
|
// the ray is too short, just compute the distance to the capsule and compare with radius
|
|
// if we really need both high precision and stability, we need to compute distance to capsule from both ends of the ray: the capsule curvature is very low in the vicinity of the ray and is o(d^2) effect
|
|
float flProjOnCapsule = DotProduct( sUnit, m );
|
|
Vector vDistance;
|
|
if( flProjOnCapsule < 0 )
|
|
{
|
|
vDistance = m;
|
|
}
|
|
else if( flProjOnCapsule > sLen )
|
|
{
|
|
vDistance = vRayStart - vCenter[ 1 ];
|
|
|
|
}
|
|
else
|
|
{
|
|
vDistance = m - vCenter[ 0 ] * flProjOnCapsule ;
|
|
}
|
|
|
|
float flDistFromCapsuleSqr = vDistance.LengthSqr();
|
|
|
|
if( flDistFromCapsuleSqr > flRadius )
|
|
{
|
|
// the ray is outside of the capsule
|
|
out.m_bStartInSolid = false;
|
|
out.m_flHitTime = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
out.m_bStartInSolid = true;
|
|
out.m_flHitTime = 0;
|
|
out.m_vHitNormal = flDistFromCapsuleSqr > 1e-8f ? vDistance / sqrtf( flDistFromCapsuleSqr ) : VectorPerpendicularToVector( sUnit );
|
|
out.m_vHitPoint = vRayStart;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CastSphereRay( CShapeCastResult& out, const Vector &m, const Vector& p, const Vector& d, float flRadius );
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
void CastCapsuleRay( CShapeCastResult& out, const Vector& vRayStart, const Vector& vRayDelta, const Vector vCenter[], float flRadius )
|
|
{
|
|
Vector m = vRayStart - vCenter[0], s = vCenter[1] - vCenter[0];
|
|
float sLen = s.Length();
|
|
|
|
if( flRadius < 1e-5f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( sLen < 1e-3f ) // note: we should filter out 0-length capsules somewhere outside of this function
|
|
{
|
|
CastSphereRay( out, m, vRayStart, vRayDelta, flRadius );
|
|
return;
|
|
}
|
|
Vector sUnit = s / sLen;
|
|
float dLen = vRayDelta.Length();
|
|
if( dLen > 1e-4f )
|
|
{
|
|
Vector dUnit = vRayDelta / dLen;
|
|
Vector z = CrossProduct( sUnit, dUnit );
|
|
float zLenSqr = z.LengthSqr();
|
|
float dsUnit = DotProduct( vRayDelta, sUnit );
|
|
|
|
CapsuleCast2D_t cast;
|
|
cast.m_flRay = FLT_MAX;
|
|
|
|
if( zLenSqr > 256*256 * FLT_EPSILON * FLT_EPSILON ) // the tolerance here is found experimentally, with the target of achieving minimal orthogonality of 1e-3 between z^s and z^d
|
|
{
|
|
float zLen = sqrtf( zLenSqr );
|
|
Vector zUnit = z / zLen;
|
|
#ifdef _DEBUG
|
|
// z must be orthogonal to capsule and ray (it's a cross product of the two); if it's not, we need to handle this case as parallel
|
|
float flOrthogonality[2] = { DotProduct( zUnit, s ), DotProduct( zUnit, vRayDelta ) };
|
|
Assert( fabsf( flOrthogonality[0] ) < 1e-3f * MAX( 1, MAX( zLen, sLen ) ) && fabsf( flOrthogonality[1] ) < 1e-3f * MAX( 1, MAX( zLen, dLen ) ) );
|
|
#endif
|
|
float mzUnit = DotProduct( m, zUnit ), rr = Sqr( flRadius ) - Sqr( mzUnit );
|
|
if( rr <= 0 )
|
|
{
|
|
out.m_flHitTime = FLT_MAX;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Vector yUnit = CrossProduct( zUnit, sUnit );
|
|
Vector2D mProj( DotProduct( m, sUnit ), DotProduct( m, yUnit ) );
|
|
float dyUnit = DotProduct( vRayDelta, yUnit );
|
|
CastCapsuleRay2DInternal( cast, mProj, Vector2D( dsUnit, dyUnit ), sLen, rr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// they're parallel..
|
|
float msUnit = DotProduct( m, sUnit );
|
|
Vector zAlt = m - sUnit * msUnit;
|
|
float zAltLenSqr = zAlt.LengthSqr();
|
|
if( zAltLenSqr < FLT_EPSILON * FLT_EPSILON )
|
|
{
|
|
// ray and capsule are coaxial...
|
|
CastCapsuleRay2DCoaxialInternal( cast, msUnit, dsUnit, sLen, flRadius ); // note: we're passing radius!
|
|
}
|
|
else
|
|
{
|
|
// ray and capsule are parallel
|
|
Vector zUnit = zAlt / sqrtf( zAltLenSqr ), yUnit = CrossProduct( zUnit, sUnit );
|
|
CastCapsuleRay2DParallelInternal( cast, Vector2D( DotProduct( m, sUnit ), DotProduct( m, yUnit ) ), dsUnit, sLen, Sqr( flRadius ) - zAltLenSqr ); // r^2 may be negative here - it'll just return no hit
|
|
}
|
|
}
|
|
|
|
Assert( cast.m_flRay >= 0 );
|
|
out.m_flHitTime = cast.m_flRay;
|
|
out.m_vHitPoint = vRayStart + vRayDelta * cast.m_flRay;
|
|
out.m_vHitNormal = ( out.m_vHitPoint - ( vCenter[0] + sUnit * cast.m_flCapsule ) ).Normalized();
|
|
}
|
|
else
|
|
{
|
|
CastCapsuleShortRay( out, sUnit, sLen, m, vRayStart, vCenter, flRadius );
|
|
}
|
|
}
|
|
|