mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-23 06:36:54 +00:00
1775 lines
63 KiB
C++
1775 lines
63 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "portal_util_shared.h"
|
|
#include "prop_portal_shared.h"
|
|
#include "portal_shareddefs.h"
|
|
#include "portal_collideable_enumerator.h"
|
|
#include "beam_shared.h"
|
|
#include "collisionutils.h"
|
|
#include "util_shared.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "util.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "env_debughistory.h"
|
|
#else
|
|
#include "c_portal_player.h"
|
|
#endif
|
|
#include "PortalSimulation.h"
|
|
|
|
bool g_bAllowForcePortalTrace = false;
|
|
bool g_bForcePortalTrace = false;
|
|
bool g_bBulletPortalTrace = false;
|
|
|
|
ConVar sv_portal_trace_vs_world ("sv_portal_trace_vs_world", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment world geometry" );
|
|
ConVar sv_portal_trace_vs_displacements ("sv_portal_trace_vs_displacements", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment displacement geometry" );
|
|
ConVar sv_portal_trace_vs_holywall ("sv_portal_trace_vs_holywall", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment carved wall" );
|
|
ConVar sv_portal_trace_vs_staticprops ("sv_portal_trace_vs_staticprops", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment static prop geometry" );
|
|
ConVar sv_use_find_closest_passable_space ("sv_use_find_closest_passable_space", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Enables heavy-handed player teleporting stuck fix code." );
|
|
ConVar sv_use_transformed_collideables("sv_use_transformed_collideables", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Disables traces against remote portal moving entities using transforms to bring them into local space." );
|
|
class CTransformedCollideable : public ICollideable //wraps an existing collideable, but transforms everything that pertains to world space by another transform
|
|
{
|
|
public:
|
|
VMatrix m_matTransform; //the transformation we apply to the wrapped collideable
|
|
VMatrix m_matInvTransform; //cached inverse of m_matTransform
|
|
|
|
ICollideable *m_pWrappedCollideable; //the collideable we're transforming without it knowing
|
|
|
|
struct CTC_ReferenceVars_t
|
|
{
|
|
Vector m_vCollisionOrigin;
|
|
QAngle m_qCollisionAngles;
|
|
matrix3x4_t m_matCollisionToWorldTransform;
|
|
matrix3x4_t m_matRootParentToWorldTransform;
|
|
};
|
|
|
|
mutable CTC_ReferenceVars_t m_ReferencedVars; //when returning a const reference, it needs to point to something, so here we go
|
|
|
|
//abstract functions which require no transforms, just pass them along to the wrapped collideable
|
|
virtual IHandleEntity *GetEntityHandle() { return m_pWrappedCollideable->GetEntityHandle(); }
|
|
virtual const Vector& OBBMinsPreScaled() const { return m_pWrappedCollideable->OBBMinsPreScaled(); }
|
|
virtual const Vector& OBBMaxsPreScaled() const { return m_pWrappedCollideable->OBBMaxsPreScaled(); }
|
|
virtual const Vector& OBBMins() const { return m_pWrappedCollideable->OBBMins(); }
|
|
virtual const Vector& OBBMaxs() const { return m_pWrappedCollideable->OBBMaxs(); }
|
|
virtual int GetCollisionModelIndex() { return m_pWrappedCollideable->GetCollisionModelIndex(); }
|
|
virtual const model_t* GetCollisionModel() { return m_pWrappedCollideable->GetCollisionModel(); }
|
|
virtual SolidType_t GetSolid() const { return m_pWrappedCollideable->GetSolid(); }
|
|
virtual int GetSolidFlags() const { return m_pWrappedCollideable->GetSolidFlags(); }
|
|
virtual IClientUnknown* GetIClientUnknown() { return m_pWrappedCollideable->GetIClientUnknown(); }
|
|
virtual int GetCollisionGroup() const { return m_pWrappedCollideable->GetCollisionGroup(); }
|
|
virtual bool ShouldTouchTrigger( int triggerSolidFlags ) const { return m_pWrappedCollideable->ShouldTouchTrigger(triggerSolidFlags); }
|
|
|
|
//slightly trickier functions
|
|
virtual void WorldSpaceTriggerBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) const;
|
|
virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
|
|
virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
|
|
virtual const Vector& GetCollisionOrigin() const;
|
|
virtual const QAngle& GetCollisionAngles() const;
|
|
virtual const matrix3x4_t& CollisionToWorldTransform() const;
|
|
virtual void WorldSpaceSurroundingBounds( Vector *pVecMins, Vector *pVecMaxs );
|
|
virtual const matrix3x4_t *GetRootParentToWorldTransform() const;
|
|
};
|
|
|
|
void CTransformedCollideable::WorldSpaceTriggerBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) const
|
|
{
|
|
m_pWrappedCollideable->WorldSpaceTriggerBounds( pVecWorldMins, pVecWorldMaxs );
|
|
|
|
if( pVecWorldMins )
|
|
*pVecWorldMins = m_matTransform * (*pVecWorldMins);
|
|
|
|
if( pVecWorldMaxs )
|
|
*pVecWorldMaxs = m_matTransform * (*pVecWorldMaxs);
|
|
}
|
|
|
|
bool CTransformedCollideable::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
//TODO: Transform the ray by inverse matTransform and transform the trace results by matTransform? AABB Errors arise by transforming the ray.
|
|
return m_pWrappedCollideable->TestCollision( ray, fContentsMask, tr );
|
|
}
|
|
|
|
bool CTransformedCollideable::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
//TODO: Transform the ray by inverse matTransform and transform the trace results by matTransform? AABB Errors arise by transforming the ray.
|
|
return m_pWrappedCollideable->TestHitboxes( ray, fContentsMask, tr );
|
|
}
|
|
|
|
const Vector& CTransformedCollideable::GetCollisionOrigin() const
|
|
{
|
|
m_ReferencedVars.m_vCollisionOrigin = m_matTransform * m_pWrappedCollideable->GetCollisionOrigin();
|
|
return m_ReferencedVars.m_vCollisionOrigin;
|
|
}
|
|
|
|
const QAngle& CTransformedCollideable::GetCollisionAngles() const
|
|
{
|
|
m_ReferencedVars.m_qCollisionAngles = TransformAnglesToWorldSpace( m_pWrappedCollideable->GetCollisionAngles(), m_matTransform.As3x4() );
|
|
return m_ReferencedVars.m_qCollisionAngles;
|
|
}
|
|
|
|
const matrix3x4_t& CTransformedCollideable::CollisionToWorldTransform() const
|
|
{
|
|
//1-2 order correct?
|
|
ConcatTransforms( m_matTransform.As3x4(), m_pWrappedCollideable->CollisionToWorldTransform(), m_ReferencedVars.m_matCollisionToWorldTransform );
|
|
return m_ReferencedVars.m_matCollisionToWorldTransform;
|
|
}
|
|
|
|
void CTransformedCollideable::WorldSpaceSurroundingBounds( Vector *pVecMins, Vector *pVecMaxs )
|
|
{
|
|
if( (pVecMins == NULL) && (pVecMaxs == NULL) )
|
|
return;
|
|
|
|
Vector vMins, vMaxs;
|
|
m_pWrappedCollideable->WorldSpaceSurroundingBounds( &vMins, &vMaxs );
|
|
|
|
TransformAABB( m_matTransform.As3x4(), vMins, vMaxs, vMins, vMaxs );
|
|
|
|
if( pVecMins )
|
|
*pVecMins = vMins;
|
|
if( pVecMaxs )
|
|
*pVecMaxs = vMaxs;
|
|
}
|
|
|
|
const matrix3x4_t* CTransformedCollideable::GetRootParentToWorldTransform() const
|
|
{
|
|
const matrix3x4_t *pWrappedVersion = m_pWrappedCollideable->GetRootParentToWorldTransform();
|
|
if( pWrappedVersion == NULL )
|
|
return NULL;
|
|
|
|
ConcatTransforms( m_matTransform.As3x4(), *pWrappedVersion, m_ReferencedVars.m_matRootParentToWorldTransform );
|
|
return &m_ReferencedVars.m_matRootParentToWorldTransform;
|
|
}
|
|
|
|
Color UTIL_Portal_Color( int iPortal )
|
|
{
|
|
switch ( iPortal )
|
|
{
|
|
case 0:
|
|
// GRAVITY BEAM
|
|
return Color( 242, 202, 167, 255 );
|
|
|
|
case 1:
|
|
// PORTAL 1
|
|
return Color( 64, 160, 255, 255 );
|
|
|
|
case 2:
|
|
// PORTAL 2
|
|
return Color( 255, 160, 32, 255 );
|
|
}
|
|
|
|
return Color( 255, 255, 255, 255 );
|
|
}
|
|
|
|
void UTIL_Portal_Trace_Filter( CTraceFilterSimpleClassnameList *traceFilterPortalShot )
|
|
{
|
|
traceFilterPortalShot->AddClassnameToIgnore( "prop_physics" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "func_physbox" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "npc_portal_turret_floor" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "prop_energy_ball" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "npc_security_camera" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "player" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "simple_physics_prop" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "simple_physics_brush" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "prop_ragdoll" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "prop_glados_core" );
|
|
traceFilterPortalShot->AddClassnameToIgnore( "updateitem2" );
|
|
}
|
|
|
|
|
|
CProp_Portal* UTIL_Portal_FirstAlongRay( const Ray_t &ray, float &fMustBeCloserThan )
|
|
{
|
|
CProp_Portal *pIntersectedPortal = NULL;
|
|
|
|
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
|
|
if( iPortalCount != 0 )
|
|
{
|
|
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
|
|
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CProp_Portal *pTempPortal = pPortals[i];
|
|
if( pTempPortal->IsActivedAndLinked() )
|
|
{
|
|
float fIntersection = UTIL_IntersectRayWithPortal( ray, pTempPortal );
|
|
if( fIntersection >= 0.0f && fIntersection < fMustBeCloserThan )
|
|
{
|
|
//within range, now check directionality
|
|
if( pTempPortal->m_plane_Origin.normal.Dot( ray.m_Delta ) < 0.0f )
|
|
{
|
|
//qualifies for consideration, now it just has to compete for closest
|
|
pIntersectedPortal = pTempPortal;
|
|
fMustBeCloserThan = fIntersection;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return pIntersectedPortal;
|
|
}
|
|
|
|
|
|
bool UTIL_Portal_TraceRay_Bullets( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
if( !pPortal || !pPortal->IsActivedAndLinked() )
|
|
{
|
|
//not in a portal environment, use regular traces
|
|
enginetrace->TraceRay( ray, fMask, pTraceFilter, pTrace );
|
|
return false;
|
|
}
|
|
|
|
trace_t trReal;
|
|
|
|
enginetrace->TraceRay( ray, fMask, pTraceFilter, &trReal );
|
|
|
|
Vector vRayNormal = ray.m_Delta;
|
|
VectorNormalize( vRayNormal );
|
|
|
|
Vector vPortalForward;
|
|
pPortal->GetVectors( &vPortalForward, 0, 0 );
|
|
|
|
// If the ray isn't going into the front of the portal, just use the real trace
|
|
if ( vPortalForward.Dot( vRayNormal ) > 0.0f )
|
|
{
|
|
*pTrace = trReal;
|
|
return false;
|
|
}
|
|
|
|
// If the real trace collides before the portal plane, just use the real trace
|
|
float fPortalFraction = UTIL_IntersectRayWithPortal( ray, pPortal );
|
|
|
|
if ( fPortalFraction == -1.0f || trReal.fraction + 0.0001f < fPortalFraction )
|
|
{
|
|
// Didn't intersect or the real trace intersected closer
|
|
*pTrace = trReal;
|
|
return false;
|
|
}
|
|
|
|
Ray_t rayPostPortal;
|
|
rayPostPortal = ray;
|
|
rayPostPortal.m_Start = ray.m_Start + ray.m_Delta * fPortalFraction;
|
|
rayPostPortal.m_Delta = ray.m_Delta * ( 1.0f - fPortalFraction );
|
|
|
|
VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
|
|
|
|
Ray_t rayTransformed;
|
|
UTIL_Portal_RayTransform( matThisToLinked, rayPostPortal, rayTransformed );
|
|
|
|
// After a bullet traces through a portal it can hit the player that fired it
|
|
CTraceFilterSimple *pSimpleFilter = dynamic_cast<CTraceFilterSimple*>(pTraceFilter);
|
|
const IHandleEntity *pPassEntity = NULL;
|
|
if ( pSimpleFilter )
|
|
{
|
|
pPassEntity = pSimpleFilter->GetPassEntity();
|
|
pSimpleFilter->SetPassEntity( 0 );
|
|
}
|
|
|
|
trace_t trPostPortal;
|
|
enginetrace->TraceRay( rayTransformed, fMask, pTraceFilter, &trPostPortal );
|
|
|
|
if ( pSimpleFilter )
|
|
{
|
|
pSimpleFilter->SetPassEntity( pPassEntity );
|
|
}
|
|
|
|
//trPostPortal.startpos = ray.m_Start;
|
|
UTIL_Portal_PointTransform( matThisToLinked, ray.m_Start, trPostPortal.startpos );
|
|
trPostPortal.fraction = trPostPortal.fraction * ( 1.0f - fPortalFraction ) + fPortalFraction;
|
|
|
|
*pTrace = trPostPortal;
|
|
|
|
return true;
|
|
}
|
|
|
|
CProp_Portal* UTIL_Portal_TraceRay_Beam( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, float *pfFraction )
|
|
{
|
|
// Do a regular trace
|
|
trace_t tr;
|
|
UTIL_TraceLine( ray.m_Start, ray.m_Start + ray.m_Delta, fMask, pTraceFilter, &tr );
|
|
float fMustBeCloserThan = tr.fraction + 0.0001f;
|
|
|
|
CProp_Portal *pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan );
|
|
|
|
*pfFraction = fMustBeCloserThan; //will be real trace distance if it didn't hit a portal
|
|
return pIntersectedPortal;
|
|
}
|
|
|
|
|
|
bool UTIL_Portal_Trace_Beam( const CBeam *pBeam, Vector &vecStart, Vector &vecEnd, Vector &vecIntersectionStart, Vector &vecIntersectionEnd, ITraceFilter *pTraceFilter )
|
|
{
|
|
vecStart = pBeam->GetAbsStartPos();
|
|
vecEnd = pBeam->GetAbsEndPos();
|
|
|
|
// Trace to see if we've intersected a portal
|
|
float fEndFraction;
|
|
Ray_t rayBeam;
|
|
|
|
bool bIsReversed = ( pBeam->GetBeamFlags() & FBEAM_REVERSED ) != 0x0;
|
|
|
|
if ( !bIsReversed )
|
|
rayBeam.Init( vecStart, vecEnd );
|
|
else
|
|
rayBeam.Init( vecEnd, vecStart );
|
|
|
|
CProp_Portal *pPortal = UTIL_Portal_TraceRay_Beam( rayBeam, MASK_SHOT, pTraceFilter, &fEndFraction );
|
|
|
|
// If we intersected a portal we need to modify the start and end points to match the actual trace through portal drawing extents
|
|
if ( !pPortal )
|
|
return false;
|
|
|
|
// Modify the start and end points to match the actual trace through portal drawing extents
|
|
vecStart = rayBeam.m_Start;
|
|
|
|
Vector vecIntersection = rayBeam.m_Start + rayBeam.m_Delta * fEndFraction;
|
|
|
|
int iNumLoops = 0;
|
|
|
|
// Loop through the portals (at most 16 times)
|
|
while ( pPortal && iNumLoops < 16 )
|
|
{
|
|
// Get the point that we hit a portal or wall
|
|
vecIntersectionStart = vecIntersection;
|
|
|
|
VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
|
|
|
|
// Get the transformed positions of the sub beam in the other portal's space
|
|
UTIL_Portal_PointTransform( matThisToLinked, vecIntersectionStart, vecIntersectionEnd );
|
|
UTIL_Portal_PointTransform( matThisToLinked, rayBeam.m_Start + rayBeam.m_Delta, vecEnd );
|
|
|
|
CTraceFilterSkipClassname traceFilter( pPortal->m_hLinkedPortal, "prop_energy_ball", COLLISION_GROUP_NONE );
|
|
|
|
rayBeam.Init( vecIntersectionEnd, vecEnd );
|
|
pPortal = UTIL_Portal_TraceRay_Beam( rayBeam, MASK_SHOT, &traceFilter, &fEndFraction );
|
|
vecIntersection = rayBeam.m_Start + rayBeam.m_Delta * fEndFraction;
|
|
|
|
++iNumLoops;
|
|
}
|
|
|
|
vecEnd = vecIntersection;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void UTIL_Portal_TraceRay_With( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
//check to see if the player is theoretically in a portal environment
|
|
if( !pPortal || !pPortal->m_PortalSimulator.IsReadyToSimulate() )
|
|
{
|
|
//not in a portal environment, use regular traces
|
|
enginetrace->TraceRay( ray, fMask, pTraceFilter, pTrace );
|
|
}
|
|
else
|
|
{
|
|
|
|
trace_t RealTrace;
|
|
enginetrace->TraceRay( ray, fMask, pTraceFilter, &RealTrace );
|
|
|
|
trace_t PortalTrace;
|
|
UTIL_Portal_TraceRay( pPortal, ray, fMask, pTraceFilter, &PortalTrace, bTraceHolyWall );
|
|
|
|
if( !g_bForcePortalTrace && !RealTrace.startsolid && PortalTrace.fraction <= RealTrace.fraction )
|
|
{
|
|
*pTrace = RealTrace;
|
|
return;
|
|
}
|
|
|
|
if ( g_bAllowForcePortalTrace )
|
|
{
|
|
g_bForcePortalTrace = true;
|
|
}
|
|
|
|
*pTrace = PortalTrace;
|
|
|
|
// If this ray has a delta, make sure its towards the portal before we try to trace across portals
|
|
Vector vDirection = ray.m_Delta;
|
|
VectorNormalize( vDirection );
|
|
Vector vPortalForward;
|
|
pPortal->GetVectors( &vPortalForward, 0, 0 );
|
|
|
|
float flDot = -1.0f;
|
|
if ( ray.m_IsSwept )
|
|
{
|
|
flDot = vDirection.Dot( vPortalForward );
|
|
}
|
|
|
|
// TODO: Translate extents of rays properly, tracing extruded box rays across portals causes collision bugs
|
|
// Until this is fixed, we'll only test true rays across portals
|
|
if ( flDot < 0.0f && /*PortalTrace.fraction == 1.0f &&*/ ray.m_IsRay)
|
|
{
|
|
// Check if we're hitting stuff on the other side of the portal
|
|
trace_t PortalLinkedTrace;
|
|
UTIL_PortalLinked_TraceRay( pPortal, ray, fMask, pTraceFilter, &PortalLinkedTrace, bTraceHolyWall );
|
|
|
|
if ( PortalLinkedTrace.fraction < pTrace->fraction )
|
|
{
|
|
// Only collide with the cross-portal objects if this trace crossed a portal
|
|
if ( UTIL_DidTraceTouchPortals( ray, PortalLinkedTrace ) )
|
|
{
|
|
*pTrace = PortalLinkedTrace;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pTrace->fraction < 1.0f )
|
|
{
|
|
pTrace->contents = RealTrace.contents;
|
|
pTrace->surface = RealTrace.surface;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Tests if a ray touches the surface of any portals
|
|
// Input : ray - the ray to be tested against portal surfaces
|
|
// trace - a filled-in trace corresponding to the parameter ray
|
|
// Output : bool - false if the 'ray' parameter failed to hit any portal surface
|
|
// pOutLocal - the portal touched (if any)
|
|
// pOutRemote - the portal linked to the portal touched
|
|
//-----------------------------------------------------------------------------
|
|
bool UTIL_DidTraceTouchPortals( const Ray_t& ray, const trace_t& trace, CProp_Portal** pOutLocal, CProp_Portal** pOutRemote )
|
|
{
|
|
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
|
|
if( iPortalCount == 0 )
|
|
{
|
|
if( pOutLocal )
|
|
*pOutLocal = NULL;
|
|
|
|
if( pOutRemote )
|
|
*pOutRemote = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
|
|
CProp_Portal *pIntersectedPortal = NULL;
|
|
|
|
if( ray.m_IsSwept )
|
|
{
|
|
float fMustBeCloserThan = trace.fraction + 0.0001f;
|
|
|
|
pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan );
|
|
}
|
|
|
|
if( (pIntersectedPortal == NULL) && !ray.m_IsRay )
|
|
{
|
|
//haven't hit anything yet, try again with box tests
|
|
|
|
Vector ptRayEndPoint = trace.endpos - ray.m_StartOffset; // The trace added the start offset to the end position, so remove it for the box test
|
|
CProp_Portal **pBoxIntersectsPortals = (CProp_Portal **)stackalloc( sizeof(CProp_Portal *) * iPortalCount );
|
|
int iBoxIntersectsPortalsCount = 0;
|
|
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CProp_Portal *pTempPortal = pPortals[i];
|
|
if( (pTempPortal->m_bActivated) &&
|
|
(pTempPortal->m_hLinkedPortal.Get() != NULL) )
|
|
{
|
|
if( UTIL_IsBoxIntersectingPortal( ptRayEndPoint, ray.m_Extents, pTempPortal, 0.00f ) )
|
|
{
|
|
pBoxIntersectsPortals[iBoxIntersectsPortalsCount] = pTempPortal;
|
|
++iBoxIntersectsPortalsCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( iBoxIntersectsPortalsCount > 0 )
|
|
{
|
|
pIntersectedPortal = pBoxIntersectsPortals[0];
|
|
|
|
if( iBoxIntersectsPortalsCount > 1 )
|
|
{
|
|
//hit more than one, use the closest
|
|
float fDistToBeat = (ptRayEndPoint - pIntersectedPortal->GetAbsOrigin()).LengthSqr();
|
|
|
|
for( int i = 1; i != iBoxIntersectsPortalsCount; ++i )
|
|
{
|
|
float fDist = (ptRayEndPoint - pBoxIntersectsPortals[i]->GetAbsOrigin()).LengthSqr();
|
|
if( fDist < fDistToBeat )
|
|
{
|
|
pIntersectedPortal = pBoxIntersectsPortals[i];
|
|
fDistToBeat = fDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pIntersectedPortal == NULL )
|
|
{
|
|
if( pOutLocal )
|
|
*pOutLocal = NULL;
|
|
|
|
if( pOutRemote )
|
|
*pOutRemote = NULL;
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Record the touched portals and return
|
|
if( pOutLocal )
|
|
*pOutLocal = pIntersectedPortal;
|
|
|
|
if( pOutRemote )
|
|
*pOutRemote = pIntersectedPortal->m_hLinkedPortal.Get();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Redirects the trace to either a trace that uses portal environments, or if a
|
|
// global boolean is set, trace with a special bullets trace.
|
|
// NOTE: UTIL_Portal_TraceRay_With will use the default world trace if it gets a NULL portal pointer
|
|
// Input : &ray - the ray to use to trace
|
|
// fMask - collision mask
|
|
// *pTraceFilter - customizable filter on the trace
|
|
// *pTrace - trace struct to fill with output info
|
|
//-----------------------------------------------------------------------------
|
|
CProp_Portal* UTIL_Portal_TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
float fMustBeCloserThan = 2.0f;
|
|
CProp_Portal *pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan );
|
|
|
|
if ( g_bBulletPortalTrace )
|
|
{
|
|
if ( UTIL_Portal_TraceRay_Bullets( pIntersectedPortal, ray, fMask, pTraceFilter, pTrace, bTraceHolyWall ) )
|
|
return pIntersectedPortal;
|
|
|
|
// Bullet didn't actually go through portal
|
|
return NULL;
|
|
|
|
}
|
|
else
|
|
{
|
|
UTIL_Portal_TraceRay_With( pIntersectedPortal, ray, fMask, pTraceFilter, pTrace, bTraceHolyWall );
|
|
return pIntersectedPortal;
|
|
}
|
|
}
|
|
|
|
CProp_Portal* UTIL_Portal_TraceRay( const Ray_t &ray, unsigned int fMask, const IHandleEntity *ignore, int collisionGroup, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
CTraceFilterSimple traceFilter( ignore, collisionGroup );
|
|
return UTIL_Portal_TraceRay( ray, fMask, &traceFilter, pTrace, bTraceHolyWall );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: This version of traceray only traces against the portal environment of the specified portal.
|
|
// Input : *pPortal - the portal whose physics we will trace against
|
|
// &ray - the ray to trace with
|
|
// fMask - collision mask
|
|
// *pTraceFilter - customizable filter to determine what it hits
|
|
// *pTrace - the trace struct to fill in with results
|
|
// bTraceHolyWall - if this trace is to test against the 'holy wall' geometry
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_Portal_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
Assert( (GameRules() == NULL) || GameRules()->IsMultiplayer() );
|
|
#endif
|
|
Assert( pPortal->m_PortalSimulator.IsReadyToSimulate() ); //a trace shouldn't make it down this far if the portal is incapable of changing the results of the trace
|
|
|
|
CTraceFilterHitAll traceFilterHitAll;
|
|
if ( !pTraceFilter )
|
|
{
|
|
pTraceFilter = &traceFilterHitAll;
|
|
}
|
|
|
|
pTrace->fraction = 2.0f;
|
|
pTrace->startsolid = true;
|
|
pTrace->allsolid = true;
|
|
|
|
trace_t TempTrace;
|
|
int counter;
|
|
|
|
const CPortalSimulator &portalSimulator = pPortal->m_PortalSimulator;
|
|
CPortalSimulator *pLinkedPortalSimulator = portalSimulator.GetLinkedPortalSimulator();
|
|
|
|
//bool bTraceDisplacements = sv_portal_trace_vs_displacements.GetBool();
|
|
bool bTraceStaticProps = sv_portal_trace_vs_staticprops.GetBool();
|
|
if( sv_portal_trace_vs_holywall.GetBool() == false )
|
|
bTraceHolyWall = false;
|
|
|
|
bool bTraceTransformedGeometry = ( (pLinkedPortalSimulator != NULL) && bTraceHolyWall && portalSimulator.RayIsInPortalHole( ray ) );
|
|
|
|
bool bCopyBackBrushTraceData = false;
|
|
|
|
|
|
|
|
// Traces vs world
|
|
if( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
|
|
{
|
|
//trace_t RealTrace;
|
|
//enginetrace->TraceRay( ray, fMask, pTraceFilter, &RealTrace );
|
|
if( portalSimulator.m_DataAccess.Simulation.Static.World.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() )
|
|
{
|
|
physcollision->TraceBox( ray, portalSimulator.m_DataAccess.Simulation.Static.World.Brushes.pCollideable, vec3_origin, vec3_angle, pTrace );
|
|
bCopyBackBrushTraceData = true;
|
|
}
|
|
|
|
if( bTraceHolyWall )
|
|
{
|
|
if( portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable )
|
|
{
|
|
physcollision->TraceBox( ray, portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable, vec3_origin, vec3_angle, &TempTrace );
|
|
|
|
if( (TempTrace.startsolid == false) && (TempTrace.fraction < pTrace->fraction) ) //never allow something to be stuck in the tube, it's more of a last-resort guide than a real collideable
|
|
{
|
|
*pTrace = TempTrace;
|
|
bCopyBackBrushTraceData = true;
|
|
}
|
|
}
|
|
|
|
if( portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable )
|
|
{
|
|
physcollision->TraceBox( ray, portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &TempTrace );
|
|
if( (TempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = TempTrace;
|
|
bCopyBackBrushTraceData = true;
|
|
}
|
|
}
|
|
|
|
//if( portalSimulator.m_DataAccess.Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() )
|
|
if( bTraceTransformedGeometry && pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable )
|
|
{
|
|
physcollision->TraceBox( ray, pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform, portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform, &TempTrace );
|
|
if( (TempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = TempTrace;
|
|
bCopyBackBrushTraceData = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bCopyBackBrushTraceData )
|
|
{
|
|
pTrace->surface = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.surface;
|
|
pTrace->contents = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.contents;
|
|
pTrace->m_pEnt = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.pEntity;
|
|
|
|
bCopyBackBrushTraceData = false;
|
|
}
|
|
}
|
|
|
|
// Traces vs entities
|
|
if( pTraceFilter->GetTraceType() != TRACE_WORLD_ONLY )
|
|
{
|
|
bool bFilterStaticProps = (pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS);
|
|
|
|
//solid entities
|
|
CPortalCollideableEnumerator enumerator( pPortal );
|
|
partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS | PARTITION_ENGINE_STATIC_PROPS, ray, false, &enumerator );
|
|
for( counter = 0; counter != enumerator.m_iHandleCount; ++counter )
|
|
{
|
|
if( staticpropmgr->IsStaticProp( enumerator.m_pHandles[counter] ) )
|
|
{
|
|
//if( bFilterStaticProps && !pTraceFilter->ShouldHitEntity( enumerator.m_pHandles[counter], fMask ) )
|
|
continue; //static props are handled separately, with clipped versions
|
|
}
|
|
else if ( !pTraceFilter->ShouldHitEntity( enumerator.m_pHandles[counter], fMask ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
enginetrace->ClipRayToEntity( ray, fMask, enumerator.m_pHandles[counter], &TempTrace );
|
|
if( (TempTrace.fraction < pTrace->fraction) )
|
|
*pTrace = TempTrace;
|
|
}
|
|
|
|
|
|
|
|
|
|
if( bTraceStaticProps )
|
|
{
|
|
//local clipped static props
|
|
{
|
|
int iLocalStaticCount = portalSimulator.m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
|
|
if( iLocalStaticCount != 0 && portalSimulator.m_DataAccess.Simulation.Static.World.StaticProps.bCollisionExists )
|
|
{
|
|
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = portalSimulator.m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Base();
|
|
const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount;
|
|
Vector vTransform = vec3_origin;
|
|
QAngle qTransform = vec3_angle;
|
|
|
|
do
|
|
{
|
|
if( (!bFilterStaticProps) || pTraceFilter->ShouldHitEntity( pCurrentProp->pSourceProp, fMask ) )
|
|
{
|
|
physcollision->TraceBox( ray, pCurrentProp->pCollide, vTransform, qTransform, &TempTrace );
|
|
if( (TempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = TempTrace;
|
|
pTrace->surface.flags = pCurrentProp->iTraceSurfaceFlags;
|
|
pTrace->surface.surfaceProps = pCurrentProp->iTraceSurfaceProps;
|
|
pTrace->surface.name = pCurrentProp->szTraceSurfaceName;
|
|
pTrace->contents = pCurrentProp->iTraceContents;
|
|
pTrace->m_pEnt = pCurrentProp->pTraceEntity;
|
|
}
|
|
}
|
|
|
|
++pCurrentProp;
|
|
}
|
|
while( pCurrentProp != pStop );
|
|
}
|
|
}
|
|
|
|
if( bTraceHolyWall )
|
|
{
|
|
//remote clipped static props transformed into our wall space
|
|
if( bTraceTransformedGeometry && (pTraceFilter->GetTraceType() != TRACE_WORLD_ONLY) && sv_portal_trace_vs_staticprops.GetBool() )
|
|
{
|
|
int iLocalStaticCount = pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
|
|
if( iLocalStaticCount != 0 )
|
|
{
|
|
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Base();
|
|
const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount;
|
|
Vector vTransform = portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform;
|
|
QAngle qTransform = portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform;
|
|
|
|
do
|
|
{
|
|
if( (!bFilterStaticProps) || pTraceFilter->ShouldHitEntity( pCurrentProp->pSourceProp, fMask ) )
|
|
{
|
|
physcollision->TraceBox( ray, pCurrentProp->pCollide, vTransform, qTransform, &TempTrace );
|
|
if( (TempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = TempTrace;
|
|
pTrace->surface.flags = pCurrentProp->iTraceSurfaceFlags;
|
|
pTrace->surface.surfaceProps = pCurrentProp->iTraceSurfaceProps;
|
|
pTrace->surface.name = pCurrentProp->szTraceSurfaceName;
|
|
pTrace->contents = pCurrentProp->iTraceContents;
|
|
pTrace->m_pEnt = pCurrentProp->pTraceEntity;
|
|
}
|
|
}
|
|
|
|
++pCurrentProp;
|
|
}
|
|
while( pCurrentProp != pStop );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pTrace->fraction > 1.0f ) //this should only happen if there was absolutely nothing to trace against
|
|
{
|
|
//AssertMsg( 0, "Nothing to trace against" );
|
|
memset( pTrace, 0, sizeof( trace_t ) );
|
|
pTrace->fraction = 1.0f;
|
|
pTrace->startpos = ray.m_Start - ray.m_StartOffset;
|
|
pTrace->endpos = pTrace->startpos + ray.m_Delta;
|
|
}
|
|
else if ( pTrace->fraction < 0 )
|
|
{
|
|
// For all brush traces, use the 'portal backbrush' surface surface contents
|
|
// BUGBUG: Doing this is a great solution because brushes near a portal
|
|
// will have their contents and surface properties homogenized to the brush the portal ray hit.
|
|
pTrace->contents = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.contents;
|
|
pTrace->surface = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.surface;
|
|
pTrace->m_pEnt = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.pEntity;
|
|
}
|
|
}
|
|
|
|
void UTIL_Portal_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, const IHandleEntity *ignore, int collisionGroup, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
CTraceFilterSimple traceFilter( ignore, collisionGroup );
|
|
UTIL_Portal_TraceRay( pPortal, ray, fMask, &traceFilter, pTrace, bTraceHolyWall );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Trace a ray 'past' a portal's surface, hitting objects in the linked portal's collision environment
|
|
// Input : *pPortal - The portal being traced 'through'
|
|
// &ray - The ray being traced
|
|
// fMask - trace mask to cull results
|
|
// *pTraceFilter - trace filter to cull results
|
|
// *pTrace - Empty trace to return the result (value will be overwritten)
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_PortalLinked_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
Assert( (GameRules() == NULL) || GameRules()->IsMultiplayer() );
|
|
#endif
|
|
// Transform the specified ray to the remote portal's space
|
|
Ray_t rayTransformed;
|
|
UTIL_Portal_RayTransform( pPortal->MatrixThisToLinked(), ray, rayTransformed );
|
|
|
|
AssertMsg ( ray.m_IsRay, "Ray with extents across portal tracing not implemented!" );
|
|
|
|
const CPortalSimulator &portalSimulator = pPortal->m_PortalSimulator;
|
|
CProp_Portal *pLinkedPortal = (CProp_Portal*)(pPortal->m_hLinkedPortal.Get());
|
|
if( (pLinkedPortal == NULL) || (portalSimulator.RayIsInPortalHole( ray ) == false) )
|
|
{
|
|
memset( pTrace, 0, sizeof(trace_t));
|
|
pTrace->fraction = 1.0f;
|
|
pTrace->fractionleftsolid = 0;
|
|
|
|
pTrace->contents = pPortal->m_PortalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.contents;
|
|
pTrace->surface = pPortal->m_PortalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.surface;
|
|
pTrace->m_pEnt = pPortal->m_PortalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.pEntity;
|
|
return;
|
|
}
|
|
UTIL_Portal_TraceRay( pLinkedPortal, rayTransformed, fMask, pTraceFilter, pTrace, bTraceHolyWall );
|
|
|
|
// Transform the ray's start, end and plane back into this portal's space,
|
|
// because we react to the collision as it is displayed, and the image is displayed with this local portal's orientation.
|
|
VMatrix matLinkedToThis = pLinkedPortal->MatrixThisToLinked();
|
|
UTIL_Portal_PointTransform( matLinkedToThis, pTrace->startpos, pTrace->startpos );
|
|
UTIL_Portal_PointTransform( matLinkedToThis, pTrace->endpos, pTrace->endpos );
|
|
UTIL_Portal_PlaneTransform( matLinkedToThis, pTrace->plane, pTrace->plane );
|
|
}
|
|
|
|
void UTIL_PortalLinked_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, const IHandleEntity *ignore, int collisionGroup, trace_t *pTrace, bool bTraceHolyWall )
|
|
{
|
|
CTraceFilterSimple traceFilter( ignore, collisionGroup );
|
|
UTIL_PortalLinked_TraceRay( pPortal, ray, fMask, &traceFilter, pTrace, bTraceHolyWall );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: A version of trace entity which detects portals and translates the trace through portals
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_Portal_TraceEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd,
|
|
unsigned int mask, ITraceFilter *pFilter, trace_t *pTrace )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
Assert( (GameRules() == NULL) || GameRules()->IsMultiplayer() );
|
|
Assert( pEntity->IsPlayer() );
|
|
|
|
CPortalSimulator *pPortalSimulator = NULL;
|
|
if( pEntity->IsPlayer() )
|
|
{
|
|
C_Prop_Portal *pPortal = ((C_Portal_Player *)pEntity)->m_hPortalEnvironment.Get();
|
|
if( pPortal )
|
|
pPortalSimulator = &pPortal->m_PortalSimulator;
|
|
}
|
|
#else
|
|
CPortalSimulator *pPortalSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity );
|
|
#endif
|
|
|
|
memset( pTrace, 0, sizeof(trace_t));
|
|
pTrace->fraction = 1.0f;
|
|
pTrace->fractionleftsolid = 0;
|
|
|
|
ICollideable* pCollision = enginetrace->GetCollideable( pEntity );
|
|
|
|
// If main is simulating this object, trace as UTIL_TraceEntity would
|
|
trace_t realTrace;
|
|
QAngle qCollisionAngles = pCollision->GetCollisionAngles();
|
|
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, qCollisionAngles, mask, pFilter, &realTrace );
|
|
|
|
// For the below box test, we need to add the tolerance onto the extents, because the underlying
|
|
// box on plane side test doesn't use the parameter tolerance.
|
|
float flTolerance = 0.1f;
|
|
Vector vEntExtents = pEntity->WorldAlignSize() * 0.5 + Vector ( flTolerance, flTolerance, flTolerance );
|
|
Vector vColCenter = realTrace.endpos + ( pEntity->WorldAlignMaxs() + pEntity->WorldAlignMins() ) * 0.5f;
|
|
|
|
// If this entity is not simulated in a portal environment, trace as normal
|
|
if( pPortalSimulator == NULL )
|
|
{
|
|
// If main is simulating this object, trace as UTIL_TraceEntity would
|
|
*pTrace = realTrace;
|
|
}
|
|
else
|
|
{
|
|
CPortalSimulator *pLinkedPortalSimulator = pPortalSimulator->GetLinkedPortalSimulator();
|
|
|
|
Ray_t entRay;
|
|
entRay.Init( vecAbsStart, vecAbsEnd, pCollision->OBBMins(), pCollision->OBBMaxs() );
|
|
|
|
#if 0 // this trace for brush ents made sense at one time, but it's 'overcolliding' during portal transitions (bugzilla#25)
|
|
if( realTrace.m_pEnt && (realTrace.m_pEnt->GetMoveType() != MOVETYPE_NONE) ) //started by hitting something moving which wouldn't be detected in the following traces
|
|
{
|
|
float fFirstPortalFraction = 2.0f;
|
|
CProp_Portal *pFirstPortal = UTIL_Portal_FirstAlongRay( entRay, fFirstPortalFraction );
|
|
|
|
if ( !pFirstPortal )
|
|
*pTrace = realTrace;
|
|
else
|
|
{
|
|
Vector vFirstPortalForward;
|
|
pFirstPortal->GetVectors( &vFirstPortalForward, NULL, NULL );
|
|
if ( vFirstPortalForward.Dot( realTrace.endpos - pFirstPortal->GetAbsOrigin() ) > 0.0f )
|
|
*pTrace = realTrace;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// We require both environments to be active in order to trace against them
|
|
Assert ( pCollision );
|
|
if ( !pCollision )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// World, displacements and holy wall are stored in separate collideables
|
|
// Traces against each and keep the closest intersection (if any)
|
|
trace_t tempTrace;
|
|
|
|
// Hit the world
|
|
if ( pFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
|
|
{
|
|
if( pPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable &&
|
|
sv_portal_trace_vs_world.GetBool() )
|
|
{
|
|
//physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles,
|
|
// pPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace );
|
|
|
|
physcollision->TraceBox( entRay, MASK_ALL, NULL, pPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace );
|
|
|
|
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = tempTrace;
|
|
}
|
|
}
|
|
|
|
//if( pPortalSimulator->m_DataAccess.Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable &&
|
|
if( pLinkedPortalSimulator &&
|
|
pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable &&
|
|
sv_portal_trace_vs_world.GetBool() &&
|
|
sv_portal_trace_vs_holywall.GetBool() )
|
|
{
|
|
//physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles,
|
|
// pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform, &tempTrace );
|
|
|
|
physcollision->TraceBox( entRay, MASK_ALL, NULL, pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform, &tempTrace );
|
|
|
|
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = tempTrace;
|
|
}
|
|
}
|
|
|
|
if ( pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable &&
|
|
sv_portal_trace_vs_holywall.GetBool() )
|
|
{
|
|
//physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles,
|
|
// pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace );
|
|
|
|
physcollision->TraceBox( entRay, MASK_ALL, NULL, pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace );
|
|
|
|
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
if( tempTrace.fraction == 0.0f )
|
|
tempTrace.startsolid = true;
|
|
|
|
if( tempTrace.fractionleftsolid == 1.0f )
|
|
tempTrace.allsolid = true;
|
|
|
|
*pTrace = tempTrace;
|
|
}
|
|
}
|
|
|
|
if ( pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable &&
|
|
sv_portal_trace_vs_holywall.GetBool() )
|
|
{
|
|
//physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles,
|
|
// pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable, vec3_origin, vec3_angle, &tempTrace );
|
|
|
|
physcollision->TraceBox( entRay, MASK_ALL, NULL, pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable, vec3_origin, vec3_angle, &tempTrace );
|
|
|
|
if( (tempTrace.startsolid == false) && (tempTrace.fraction < pTrace->fraction) ) //never allow something to be stuck in the tube, it's more of a last-resort guide than a real collideable
|
|
{
|
|
*pTrace = tempTrace;
|
|
}
|
|
}
|
|
|
|
// For all brush traces, use the 'portal backbrush' surface surface contents
|
|
// BUGBUG: Doing this is a great solution because brushes near a portal
|
|
// will have their contents and surface properties homogenized to the brush the portal ray hit.
|
|
if ( pTrace->startsolid || (pTrace->fraction < 1.0f) )
|
|
{
|
|
pTrace->surface = pPortalSimulator->m_DataAccess.Simulation.Static.SurfaceProperties.surface;
|
|
pTrace->contents = pPortalSimulator->m_DataAccess.Simulation.Static.SurfaceProperties.contents;
|
|
pTrace->m_pEnt = pPortalSimulator->m_DataAccess.Simulation.Static.SurfaceProperties.pEntity;
|
|
}
|
|
}
|
|
|
|
// Trace vs entities
|
|
if ( pFilter->GetTraceType() != TRACE_WORLD_ONLY )
|
|
{
|
|
if( sv_portal_trace_vs_staticprops.GetBool() && (pFilter->GetTraceType() != TRACE_ENTITIES_ONLY) )
|
|
{
|
|
bool bFilterStaticProps = (pFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS);
|
|
|
|
//local clipped static props
|
|
{
|
|
int iLocalStaticCount = pPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
|
|
if( iLocalStaticCount != 0 && pPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.bCollisionExists )
|
|
{
|
|
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Base();
|
|
const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount;
|
|
Vector vTransform = vec3_origin;
|
|
QAngle qTransform = vec3_angle;
|
|
|
|
do
|
|
{
|
|
if( (!bFilterStaticProps) || pFilter->ShouldHitEntity( pCurrentProp->pSourceProp, mask ) )
|
|
{
|
|
//physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles,
|
|
// pCurrentProp->pCollide, vTransform, qTransform, &tempTrace );
|
|
|
|
physcollision->TraceBox( entRay, MASK_ALL, NULL, pCurrentProp->pCollide, vTransform, qTransform, &tempTrace );
|
|
|
|
if( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = tempTrace;
|
|
pTrace->surface.flags = pCurrentProp->iTraceSurfaceFlags;
|
|
pTrace->surface.surfaceProps = pCurrentProp->iTraceSurfaceProps;
|
|
pTrace->surface.name = pCurrentProp->szTraceSurfaceName;
|
|
pTrace->contents = pCurrentProp->iTraceContents;
|
|
pTrace->m_pEnt = pCurrentProp->pTraceEntity;
|
|
}
|
|
}
|
|
|
|
++pCurrentProp;
|
|
}
|
|
while( pCurrentProp != pStop );
|
|
}
|
|
}
|
|
|
|
if( pLinkedPortalSimulator && pPortalSimulator->EntityIsInPortalHole( pEntity ) )
|
|
{
|
|
|
|
#ifndef CLIENT_DLL
|
|
if( sv_use_transformed_collideables.GetBool() ) //if this never gets turned off, it should be removed before release
|
|
{
|
|
//moving entities near the remote portal
|
|
CBaseEntity *pEnts[1024];
|
|
int iEntCount = pLinkedPortalSimulator->GetMoveableOwnedEntities( pEnts, 1024 );
|
|
|
|
CTransformedCollideable transformedCollideable;
|
|
transformedCollideable.m_matTransform = pLinkedPortalSimulator->m_DataAccess.Placement.matThisToLinked;
|
|
transformedCollideable.m_matInvTransform = pLinkedPortalSimulator->m_DataAccess.Placement.matLinkedToThis;
|
|
for( int i = 0; i != iEntCount; ++i )
|
|
{
|
|
CBaseEntity *pRemoteEntity = pEnts[i];
|
|
if( pRemoteEntity->GetSolid() == SOLID_NONE )
|
|
continue;
|
|
|
|
transformedCollideable.m_pWrappedCollideable = pRemoteEntity->GetCollideable();
|
|
Assert( transformedCollideable.m_pWrappedCollideable != NULL );
|
|
|
|
//enginetrace->ClipRayToCollideable( entRay, mask, &transformedCollideable, pTrace );
|
|
|
|
enginetrace->ClipRayToCollideable( entRay, mask, &transformedCollideable, &tempTrace );
|
|
if( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
|
|
{
|
|
*pTrace = tempTrace;
|
|
}
|
|
}
|
|
}
|
|
#endif //#ifndef CLIENT_DLL
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pTrace->fraction == 1.0f )
|
|
{
|
|
memset( pTrace, 0, sizeof( trace_t ) );
|
|
pTrace->fraction = 1.0f;
|
|
pTrace->startpos = vecAbsStart;
|
|
pTrace->endpos = vecAbsEnd;
|
|
}
|
|
//#endif
|
|
}
|
|
}
|
|
|
|
void UTIL_Portal_PointTransform( const VMatrix matThisToLinked, const Vector &ptSource, Vector &ptTransformed )
|
|
{
|
|
ptTransformed = matThisToLinked * ptSource;
|
|
}
|
|
|
|
void UTIL_Portal_VectorTransform( const VMatrix matThisToLinked, const Vector &vSource, Vector &vTransformed )
|
|
{
|
|
vTransformed = matThisToLinked.ApplyRotation( vSource );
|
|
}
|
|
|
|
void UTIL_Portal_AngleTransform( const VMatrix matThisToLinked, const QAngle &qSource, QAngle &qTransformed )
|
|
{
|
|
qTransformed = TransformAnglesToWorldSpace( qSource, matThisToLinked.As3x4() );
|
|
}
|
|
|
|
void UTIL_Portal_RayTransform( const VMatrix matThisToLinked, const Ray_t &raySource, Ray_t &rayTransformed )
|
|
{
|
|
rayTransformed = raySource;
|
|
|
|
UTIL_Portal_PointTransform( matThisToLinked, raySource.m_Start, rayTransformed.m_Start );
|
|
UTIL_Portal_VectorTransform( matThisToLinked, raySource.m_StartOffset, rayTransformed.m_StartOffset );
|
|
UTIL_Portal_VectorTransform( matThisToLinked, raySource.m_Delta, rayTransformed.m_Delta );
|
|
|
|
//BUGBUG: Extents are axis aligned, so rotating it won't necessarily give us what we're expecting
|
|
UTIL_Portal_VectorTransform( matThisToLinked, raySource.m_Extents, rayTransformed.m_Extents );
|
|
|
|
//HACKHACK: Negative extents hang in traces, make each positive because we rotated it above
|
|
if ( rayTransformed.m_Extents.x < 0.0f )
|
|
{
|
|
rayTransformed.m_Extents.x = -rayTransformed.m_Extents.x;
|
|
}
|
|
if ( rayTransformed.m_Extents.y < 0.0f )
|
|
{
|
|
rayTransformed.m_Extents.y = -rayTransformed.m_Extents.y;
|
|
}
|
|
if ( rayTransformed.m_Extents.z < 0.0f )
|
|
{
|
|
rayTransformed.m_Extents.z = -rayTransformed.m_Extents.z;
|
|
}
|
|
|
|
}
|
|
|
|
void UTIL_Portal_PlaneTransform( const VMatrix matThisToLinked, const cplane_t &planeSource, cplane_t &planeTransformed )
|
|
{
|
|
planeTransformed = planeSource;
|
|
|
|
Vector vTrans;
|
|
UTIL_Portal_VectorTransform( matThisToLinked, planeSource.normal, planeTransformed.normal );
|
|
planeTransformed.dist = planeSource.dist * DotProduct( planeTransformed.normal, planeTransformed.normal );
|
|
planeTransformed.dist += DotProduct( planeTransformed.normal, matThisToLinked.GetTranslation( vTrans ) );
|
|
}
|
|
|
|
void UTIL_Portal_PlaneTransform( const VMatrix matThisToLinked, const VPlane &planeSource, VPlane &planeTransformed )
|
|
{
|
|
Vector vTranformedNormal;
|
|
float fTransformedDist;
|
|
|
|
Vector vTrans;
|
|
UTIL_Portal_VectorTransform( matThisToLinked, planeSource.m_Normal, vTranformedNormal );
|
|
fTransformedDist = planeSource.m_Dist * DotProduct( vTranformedNormal, vTranformedNormal );
|
|
fTransformedDist += DotProduct( vTranformedNormal, matThisToLinked.GetTranslation( vTrans ) );
|
|
|
|
planeTransformed.Init( vTranformedNormal, fTransformedDist );
|
|
}
|
|
|
|
void UTIL_Portal_Triangles( const Vector &ptPortalCenter, const QAngle &qPortalAngles, Vector pvTri1[ 3 ], Vector pvTri2[ 3 ] )
|
|
{
|
|
// Get points to make triangles
|
|
Vector vRight, vUp;
|
|
AngleVectors( qPortalAngles, NULL, &vRight, &vUp );
|
|
|
|
Vector vTopEdge = vUp * PORTAL_HALF_HEIGHT;
|
|
Vector vBottomEdge = -vTopEdge;
|
|
Vector vRightEdge = vRight * PORTAL_HALF_WIDTH;
|
|
Vector vLeftEdge = -vRightEdge;
|
|
|
|
Vector vTopLeft = ptPortalCenter + vTopEdge + vLeftEdge;
|
|
Vector vTopRight = ptPortalCenter + vTopEdge + vRightEdge;
|
|
Vector vBottomLeft = ptPortalCenter + vBottomEdge + vLeftEdge;
|
|
Vector vBottomRight = ptPortalCenter + vBottomEdge + vRightEdge;
|
|
|
|
// Make triangles
|
|
pvTri1[ 0 ] = vTopRight;
|
|
pvTri1[ 1 ] = vTopLeft;
|
|
pvTri1[ 2 ] = vBottomLeft;
|
|
|
|
pvTri2[ 0 ] = vTopRight;
|
|
pvTri2[ 1 ] = vBottomLeft;
|
|
pvTri2[ 2 ] = vBottomRight;
|
|
}
|
|
|
|
void UTIL_Portal_Triangles( const CProp_Portal *pPortal, Vector pvTri1[ 3 ], Vector pvTri2[ 3 ] )
|
|
{
|
|
UTIL_Portal_Triangles( pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), pvTri1, pvTri2 );
|
|
}
|
|
|
|
float UTIL_Portal_DistanceThroughPortal( const CProp_Portal *pPortal, const Vector &vPoint1, const Vector &vPoint2 )
|
|
{
|
|
return FastSqrt( UTIL_Portal_DistanceThroughPortalSqr( pPortal, vPoint1, vPoint2 ) );
|
|
}
|
|
|
|
float UTIL_Portal_DistanceThroughPortalSqr( const CProp_Portal *pPortal, const Vector &vPoint1, const Vector &vPoint2 )
|
|
{
|
|
if ( !pPortal || !pPortal->m_bActivated )
|
|
return -1.0f;
|
|
|
|
CProp_Portal *pPortalLinked = pPortal->m_hLinkedPortal;
|
|
if ( !pPortalLinked || !pPortalLinked->m_bActivated )
|
|
return -1.0f;
|
|
|
|
return vPoint1.DistToSqr( pPortal->GetAbsOrigin() ) + pPortalLinked->GetAbsOrigin().DistToSqr( vPoint2 );
|
|
}
|
|
|
|
float UTIL_Portal_ShortestDistance( const Vector &vPoint1, const Vector &vPoint2, CProp_Portal **pShortestDistPortal_Out /*= NULL*/, bool bRequireStraightLine /*= false*/ )
|
|
{
|
|
return FastSqrt( UTIL_Portal_ShortestDistanceSqr( vPoint1, vPoint2, pShortestDistPortal_Out, bRequireStraightLine ) );
|
|
}
|
|
|
|
float UTIL_Portal_ShortestDistanceSqr( const Vector &vPoint1, const Vector &vPoint2, CProp_Portal **pShortestDistPortal_Out /*= NULL*/, bool bRequireStraightLine /*= false*/ )
|
|
{
|
|
float fMinDist = vPoint1.DistToSqr( vPoint2 );
|
|
|
|
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
|
|
if( iPortalCount == 0 )
|
|
{
|
|
if( pShortestDistPortal_Out )
|
|
*pShortestDistPortal_Out = NULL;
|
|
|
|
return fMinDist;
|
|
}
|
|
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
|
|
CProp_Portal *pShortestDistPortal = NULL;
|
|
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CProp_Portal *pTempPortal = pPortals[i];
|
|
if( pTempPortal->m_bActivated )
|
|
{
|
|
CProp_Portal *pLinkedPortal = pTempPortal->m_hLinkedPortal.Get();
|
|
if( pLinkedPortal != NULL )
|
|
{
|
|
Vector vPoint1Transformed = pTempPortal->MatrixThisToLinked() * vPoint1;
|
|
|
|
float fDirectDist = vPoint1Transformed.DistToSqr( vPoint2 );
|
|
if( fDirectDist < fMinDist )
|
|
{
|
|
//worth investigating further
|
|
//find out if it's a straight line through the portal, or if we have to wrap around a corner
|
|
float fPoint1TransformedDist = pLinkedPortal->m_plane_Origin.normal.Dot( vPoint1Transformed ) - pLinkedPortal->m_plane_Origin.dist;
|
|
float fPoint2Dist = pLinkedPortal->m_plane_Origin.normal.Dot( vPoint2 ) - pLinkedPortal->m_plane_Origin.dist;
|
|
|
|
bool bStraightLine = true;
|
|
if( (fPoint1TransformedDist > 0.0f) || (fPoint2Dist < 0.0f) ) //straight line through portal impossible, part of the line has to backtrack to get to the portal surface
|
|
bStraightLine = false;
|
|
|
|
if( bStraightLine ) //if we're not already doing some crazy wrapping, find an intersection point
|
|
{
|
|
float fTotalDist = fPoint2Dist - fPoint1TransformedDist; //fPoint1TransformedDist is known to be negative
|
|
Vector ptPlaneIntersection;
|
|
|
|
if( fTotalDist != 0.0f )
|
|
{
|
|
float fInvTotalDist = 1.0f / fTotalDist;
|
|
ptPlaneIntersection = (vPoint1Transformed * (fPoint2Dist * fInvTotalDist)) + (vPoint2 * ((-fPoint1TransformedDist) * fInvTotalDist));
|
|
}
|
|
else
|
|
{
|
|
ptPlaneIntersection = vPoint1Transformed;
|
|
}
|
|
|
|
Vector vRight, vUp;
|
|
pLinkedPortal->GetVectors( NULL, &vRight, &vUp );
|
|
|
|
Vector ptLinkedCenter = pLinkedPortal->GetAbsOrigin();
|
|
Vector vCenterToIntersection = ptPlaneIntersection - ptLinkedCenter;
|
|
float fRight = vRight.Dot( vCenterToIntersection );
|
|
float fUp = vUp.Dot( vCenterToIntersection );
|
|
|
|
float fAbsRight = fabs( fRight );
|
|
float fAbsUp = fabs( fUp );
|
|
if( (fAbsRight > PORTAL_HALF_WIDTH) ||
|
|
(fAbsUp > PORTAL_HALF_HEIGHT) )
|
|
bStraightLine = false;
|
|
|
|
if( bStraightLine == false )
|
|
{
|
|
if( bRequireStraightLine )
|
|
continue;
|
|
|
|
//find the offending extent and shorten both extents to bring it into the portal quad
|
|
float fNormalizer;
|
|
if( fAbsRight > PORTAL_HALF_WIDTH )
|
|
{
|
|
fNormalizer = fAbsRight/PORTAL_HALF_WIDTH;
|
|
|
|
if( fAbsUp > PORTAL_HALF_HEIGHT )
|
|
{
|
|
float fUpNormalizer = fAbsUp/PORTAL_HALF_HEIGHT;
|
|
if( fUpNormalizer > fNormalizer )
|
|
fNormalizer = fUpNormalizer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fNormalizer = fAbsUp/PORTAL_HALF_HEIGHT;
|
|
}
|
|
|
|
vCenterToIntersection *= (1.0f/fNormalizer);
|
|
ptPlaneIntersection = ptLinkedCenter + vCenterToIntersection;
|
|
|
|
float fWrapDist = vPoint1Transformed.DistToSqr( ptPlaneIntersection ) + vPoint2.DistToSqr( ptPlaneIntersection );
|
|
if( fWrapDist < fMinDist )
|
|
{
|
|
fMinDist = fWrapDist;
|
|
pShortestDistPortal = pTempPortal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//it's a straight shot from point 1 to 2 through the portal
|
|
fMinDist = fDirectDist;
|
|
pShortestDistPortal = pTempPortal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( bRequireStraightLine )
|
|
continue;
|
|
|
|
//do some crazy wrapped line intersection algorithm
|
|
|
|
//for now, just do the cheap and easy solution
|
|
float fWrapDist = vPoint1.DistToSqr( pTempPortal->GetAbsOrigin() ) + pLinkedPortal->GetAbsOrigin().DistToSqr( vPoint2 );
|
|
if( fWrapDist < fMinDist )
|
|
{
|
|
fMinDist = fWrapDist;
|
|
pShortestDistPortal = pTempPortal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fMinDist;
|
|
}
|
|
|
|
void UTIL_Portal_AABB( const CProp_Portal *pPortal, Vector &vMin, Vector &vMax )
|
|
{
|
|
Vector vOrigin = pPortal->GetAbsOrigin();
|
|
QAngle qAngles = pPortal->GetAbsAngles();
|
|
|
|
Vector vOBBForward;
|
|
Vector vOBBRight;
|
|
Vector vOBBUp;
|
|
|
|
AngleVectors( qAngles, &vOBBForward, &vOBBRight, &vOBBUp );
|
|
|
|
//scale the extents to usable sizes
|
|
vOBBForward *= PORTAL_HALF_DEPTH;
|
|
vOBBRight *= PORTAL_HALF_WIDTH;
|
|
vOBBUp *= PORTAL_HALF_HEIGHT;
|
|
|
|
vOrigin -= vOBBForward + vOBBRight + vOBBUp;
|
|
|
|
vOBBForward *= 2.0f;
|
|
vOBBRight *= 2.0f;
|
|
vOBBUp *= 2.0f;
|
|
|
|
vMin = vMax = vOrigin;
|
|
|
|
for( int i = 1; i != 8; ++i )
|
|
{
|
|
Vector ptTest = vOrigin;
|
|
if( i & (1 << 0) ) ptTest += vOBBForward;
|
|
if( i & (1 << 1) ) ptTest += vOBBRight;
|
|
if( i & (1 << 2) ) ptTest += vOBBUp;
|
|
|
|
if( ptTest.x < vMin.x ) vMin.x = ptTest.x;
|
|
if( ptTest.y < vMin.y ) vMin.y = ptTest.y;
|
|
if( ptTest.z < vMin.z ) vMin.z = ptTest.z;
|
|
if( ptTest.x > vMax.x ) vMax.x = ptTest.x;
|
|
if( ptTest.y > vMax.y ) vMax.y = ptTest.y;
|
|
if( ptTest.z > vMax.z ) vMax.z = ptTest.z;
|
|
}
|
|
}
|
|
|
|
float UTIL_IntersectRayWithPortal( const Ray_t &ray, const CProp_Portal *pPortal )
|
|
{
|
|
if ( !pPortal || !pPortal->m_bActivated )
|
|
{
|
|
return -1.0f;
|
|
}
|
|
|
|
Vector vForward;
|
|
pPortal->GetVectors( &vForward, NULL, NULL );
|
|
|
|
// Discount rays not coming from the front of the portal
|
|
float fDot = DotProduct( vForward, ray.m_Delta );
|
|
if ( fDot > 0.0f )
|
|
{
|
|
return -1.0f;
|
|
}
|
|
|
|
Vector pvTri1[ 3 ], pvTri2[ 3 ];
|
|
|
|
UTIL_Portal_Triangles( pPortal, pvTri1, pvTri2 );
|
|
|
|
float fT;
|
|
|
|
// Test triangle 1
|
|
fT = IntersectRayWithTriangle( ray, pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], false );
|
|
|
|
// If there was an intersection return the T
|
|
if ( fT >= 0.0f )
|
|
return fT;
|
|
|
|
// Return the result of collision with the other face triangle
|
|
return IntersectRayWithTriangle( ray, pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], false );
|
|
}
|
|
|
|
bool UTIL_IntersectRayWithPortalOBB( const CProp_Portal *pPortal, const Ray_t &ray, trace_t *pTrace )
|
|
{
|
|
return IntersectRayWithOBB( ray, pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs, 0.0f, pTrace );
|
|
}
|
|
|
|
bool UTIL_IntersectRayWithPortalOBBAsAABB( const CProp_Portal *pPortal, const Ray_t &ray, trace_t *pTrace )
|
|
{
|
|
Vector vAABBMins, vAABBMaxs;
|
|
|
|
UTIL_Portal_AABB( pPortal, vAABBMins, vAABBMaxs );
|
|
|
|
return IntersectRayWithBox( ray, vAABBMins, vAABBMaxs, 0.0f, pTrace );
|
|
}
|
|
|
|
bool UTIL_IsBoxIntersectingPortal( const Vector &vecBoxCenter, const Vector &vecBoxExtents, const Vector &ptPortalCenter, const QAngle &qPortalAngles, float flTolerance )
|
|
{
|
|
Vector pvTri1[ 3 ], pvTri2[ 3 ];
|
|
|
|
UTIL_Portal_Triangles( ptPortalCenter, qPortalAngles, pvTri1, pvTri2 );
|
|
|
|
cplane_t plane;
|
|
|
|
ComputeTrianglePlane( pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], plane.normal, plane.dist );
|
|
plane.type = PLANE_ANYZ;
|
|
plane.signbits = SignbitsForPlane( &plane );
|
|
|
|
if ( IsBoxIntersectingTriangle( vecBoxCenter, vecBoxExtents, pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], plane, flTolerance ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
ComputeTrianglePlane( pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], plane.normal, plane.dist );
|
|
plane.type = PLANE_ANYZ;
|
|
plane.signbits = SignbitsForPlane( &plane );
|
|
|
|
return IsBoxIntersectingTriangle( vecBoxCenter, vecBoxExtents, pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], plane, flTolerance );
|
|
}
|
|
|
|
bool UTIL_IsBoxIntersectingPortal( const Vector &vecBoxCenter, const Vector &vecBoxExtents, const CProp_Portal *pPortal, float flTolerance )
|
|
{
|
|
if( pPortal == NULL )
|
|
return false;
|
|
|
|
return UTIL_IsBoxIntersectingPortal( vecBoxCenter, vecBoxExtents, pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), flTolerance );
|
|
}
|
|
|
|
CProp_Portal *UTIL_IntersectEntityExtentsWithPortal( const CBaseEntity *pEntity )
|
|
{
|
|
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
|
|
if( iPortalCount == 0 )
|
|
return NULL;
|
|
|
|
Vector vMin, vMax;
|
|
pEntity->CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
|
|
Vector ptCenter = ( vMin + vMax ) * 0.5f;
|
|
Vector vExtents = ( vMax - vMin ) * 0.5f;
|
|
|
|
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CProp_Portal *pTempPortal = pPortals[i];
|
|
if( pTempPortal->m_bActivated &&
|
|
(pTempPortal->m_hLinkedPortal.Get() != NULL) &&
|
|
UTIL_IsBoxIntersectingPortal( ptCenter, vExtents, pTempPortal ) )
|
|
{
|
|
return pPortals[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void UTIL_Portal_NDebugOverlay( const Vector &ptPortalCenter, const QAngle &qPortalAngles, int r, int g, int b, int a, bool noDepthTest, float duration )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
Vector pvTri1[ 3 ], pvTri2[ 3 ];
|
|
|
|
UTIL_Portal_Triangles( ptPortalCenter, qPortalAngles, pvTri1, pvTri2 );
|
|
|
|
NDebugOverlay::Triangle( pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], r, g, b, a, noDepthTest, duration );
|
|
NDebugOverlay::Triangle( pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], r, g, b, a, noDepthTest, duration );
|
|
#endif //#ifndef CLIENT_DLL
|
|
}
|
|
|
|
void UTIL_Portal_NDebugOverlay( const CProp_Portal *pPortal, int r, int g, int b, int a, bool noDepthTest, float duration )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
UTIL_Portal_NDebugOverlay( pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), r, g, b, a, noDepthTest, duration );
|
|
#endif //#ifndef CLIENT_DLL
|
|
}
|
|
|
|
|
|
bool FindClosestPassableSpace( CBaseEntity *pEntity, const Vector &vIndecisivePush, unsigned int fMask ) //assumes the object is already in a mostly passable space
|
|
{
|
|
if ( sv_use_find_closest_passable_space.GetBool() == false )
|
|
return true;
|
|
|
|
// Don't ever do this to entities with a move parent
|
|
if ( pEntity->GetMoveParent() )
|
|
return true;
|
|
|
|
#ifndef CLIENT_DLL
|
|
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "RUNNING FIND CLOSEST PASSABLE SPACE on %s..\n", pEntity->GetDebugName() ) );
|
|
#endif
|
|
|
|
Vector ptExtents[8]; //ordering is going to be like 3 bits, where 0 is a min on the related axis, and 1 is a max on the same axis, axis order x y z
|
|
|
|
float fExtentsValidation[8]; //some points are more valid than others, and this is our measure
|
|
|
|
|
|
Vector vEntityMaxs;// = pEntity->WorldAlignMaxs();
|
|
Vector vEntityMins;// = pEntity->WorldAlignMins();
|
|
CCollisionProperty *pEntityCollision = pEntity->CollisionProp();
|
|
pEntityCollision->WorldSpaceAABB( &vEntityMins, &vEntityMaxs );
|
|
|
|
|
|
|
|
Vector ptEntityCenter = ((vEntityMins + vEntityMaxs) / 2.0f);
|
|
vEntityMins -= ptEntityCenter;
|
|
vEntityMaxs -= ptEntityCenter;
|
|
|
|
Vector ptEntityOriginalCenter = ptEntityCenter;
|
|
|
|
ptEntityCenter.z += 0.001f; //to satisfy m_IsSwept on first pass
|
|
|
|
int iEntityCollisionGroup = pEntity->GetCollisionGroup();
|
|
|
|
trace_t traces[2];
|
|
Ray_t entRay;
|
|
//entRay.Init( ptEntityCenter, ptEntityCenter, vEntityMins, vEntityMaxs );
|
|
entRay.m_Extents = vEntityMaxs;
|
|
entRay.m_IsRay = false;
|
|
entRay.m_IsSwept = true;
|
|
entRay.m_StartOffset = vec3_origin;
|
|
|
|
Vector vOriginalExtents = vEntityMaxs;
|
|
|
|
Vector vGrowSize = vEntityMaxs / 101.0f;
|
|
vEntityMaxs -= vGrowSize;
|
|
vEntityMins += vGrowSize;
|
|
|
|
|
|
Ray_t testRay;
|
|
testRay.m_Extents = vGrowSize;
|
|
testRay.m_IsRay = false;
|
|
testRay.m_IsSwept = true;
|
|
testRay.m_StartOffset = vec3_origin;
|
|
|
|
|
|
|
|
unsigned int iFailCount;
|
|
for( iFailCount = 0; iFailCount != 100; ++iFailCount )
|
|
{
|
|
entRay.m_Start = ptEntityCenter;
|
|
entRay.m_Delta = ptEntityOriginalCenter - ptEntityCenter;
|
|
|
|
UTIL_TraceRay( entRay, fMask, pEntity, iEntityCollisionGroup, &traces[0] );
|
|
if( traces[0].startsolid == false )
|
|
{
|
|
Vector vNewPos = traces[0].endpos + (pEntity->GetAbsOrigin() - ptEntityOriginalCenter);
|
|
#ifdef CLIENT_DLL
|
|
pEntity->SetAbsOrigin( vNewPos );
|
|
#else
|
|
pEntity->Teleport( &vNewPos, NULL, NULL );
|
|
#endif
|
|
return true; //current placement worked
|
|
}
|
|
|
|
bool bExtentInvalid[8];
|
|
for( int i = 0; i != 8; ++i )
|
|
{
|
|
fExtentsValidation[i] = 0.0f;
|
|
ptExtents[i] = ptEntityCenter;
|
|
ptExtents[i].x += ((i & (1<<0)) ? vEntityMaxs.x : vEntityMins.x);
|
|
ptExtents[i].y += ((i & (1<<1)) ? vEntityMaxs.y : vEntityMins.y);
|
|
ptExtents[i].z += ((i & (1<<2)) ? vEntityMaxs.z : vEntityMins.z);
|
|
|
|
bExtentInvalid[i] = enginetrace->PointOutsideWorld( ptExtents[i] );
|
|
}
|
|
|
|
unsigned int counter, counter2;
|
|
for( counter = 0; counter != 7; ++counter )
|
|
{
|
|
for( counter2 = counter + 1; counter2 != 8; ++counter2 )
|
|
{
|
|
|
|
testRay.m_Delta = ptExtents[counter2] - ptExtents[counter];
|
|
|
|
if( bExtentInvalid[counter] )
|
|
traces[0].startsolid = true;
|
|
else
|
|
{
|
|
testRay.m_Start = ptExtents[counter];
|
|
UTIL_TraceRay( testRay, fMask, pEntity, iEntityCollisionGroup, &traces[0] );
|
|
}
|
|
|
|
if( bExtentInvalid[counter2] )
|
|
traces[1].startsolid = true;
|
|
else
|
|
{
|
|
testRay.m_Start = ptExtents[counter2];
|
|
testRay.m_Delta = -testRay.m_Delta;
|
|
UTIL_TraceRay( testRay, fMask, pEntity, iEntityCollisionGroup, &traces[1] );
|
|
}
|
|
|
|
float fDistance = testRay.m_Delta.Length();
|
|
|
|
for( int i = 0; i != 2; ++i )
|
|
{
|
|
int iExtent = (i==0)?(counter):(counter2);
|
|
|
|
if( traces[i].startsolid )
|
|
{
|
|
fExtentsValidation[iExtent] -= 100.0f;
|
|
}
|
|
else
|
|
{
|
|
fExtentsValidation[iExtent] += traces[i].fraction * fDistance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector vNewOriginDirection( 0.0f, 0.0f, 0.0f );
|
|
float fTotalValidation = 0.0f;
|
|
for( counter = 0; counter != 8; ++counter )
|
|
{
|
|
if( fExtentsValidation[counter] > 0.0f )
|
|
{
|
|
vNewOriginDirection += (ptExtents[counter] - ptEntityCenter) * fExtentsValidation[counter];
|
|
fTotalValidation += fExtentsValidation[counter];
|
|
}
|
|
}
|
|
|
|
if( fTotalValidation != 0.0f )
|
|
{
|
|
ptEntityCenter += (vNewOriginDirection / fTotalValidation);
|
|
|
|
//increase sizing
|
|
testRay.m_Extents += vGrowSize;
|
|
vEntityMaxs -= vGrowSize;
|
|
vEntityMins = -vEntityMaxs;
|
|
}
|
|
else
|
|
{
|
|
//no point was valid, apply the indecisive vector
|
|
ptEntityCenter += vIndecisivePush;
|
|
|
|
//reset sizing
|
|
testRay.m_Extents = vGrowSize;
|
|
vEntityMaxs = vOriginalExtents;
|
|
vEntityMins = -vEntityMaxs;
|
|
}
|
|
}
|
|
|
|
// X360TBD: Hits in portal devtest
|
|
AssertMsg( IsX360() || iFailCount != 100, "FindClosestPassableSpace() failure." );
|
|
return false;
|
|
}
|
|
|
|
bool UTIL_Portal_EntityIsInPortalHole( const CProp_Portal *pPortal, CBaseEntity *pEntity )
|
|
{
|
|
CCollisionProperty *pCollisionProp = pEntity->CollisionProp();
|
|
Vector vMins = pCollisionProp->OBBMins();
|
|
Vector vMaxs = pCollisionProp->OBBMaxs();
|
|
Vector vForward, vUp, vRight;
|
|
AngleVectors( pCollisionProp->GetCollisionAngles(), &vForward, &vRight, &vUp );
|
|
Vector ptOrigin = pEntity->GetAbsOrigin();
|
|
|
|
Vector ptOBBCenter = pEntity->GetAbsOrigin() + (vMins + vMaxs * 0.5f);
|
|
Vector vExtents = (vMaxs - vMins) * 0.5f;
|
|
|
|
vForward *= vExtents.x;
|
|
vRight *= vExtents.y;
|
|
vUp *= vExtents.z;
|
|
|
|
Vector vPortalForward, vPortalRight, vPortalUp;
|
|
pPortal->GetVectors( &vPortalForward, &vPortalRight, &vPortalUp );
|
|
Vector ptPortalCenter = pPortal->GetAbsOrigin();
|
|
|
|
return OBBHasFullyContainedIntersectionWithQuad( vForward, vRight, vUp, ptOBBCenter,
|
|
vPortalForward, vPortalForward.Dot( ptPortalCenter ), ptPortalCenter,
|
|
vPortalRight, PORTAL_HALF_WIDTH + 1.0f, vPortalUp, PORTAL_HALF_HEIGHT + 1.0f );
|
|
}
|
|
|
|
|
|
#ifdef CLIENT_DLL
|
|
void UTIL_TransformInterpolatedAngle( CInterpolatedVar< QAngle > &qInterped, matrix3x4_t matTransform, bool bSkipNewest )
|
|
{
|
|
int iHead = qInterped.GetHead();
|
|
if( !qInterped.IsValidIndex( iHead ) )
|
|
return;
|
|
|
|
#ifdef DBGFLAG_ASSERT
|
|
float fHeadTime;
|
|
qInterped.GetHistoryValue( iHead, fHeadTime );
|
|
#endif
|
|
|
|
float fTime;
|
|
QAngle *pCurrent;
|
|
int iCurrent;
|
|
|
|
if( bSkipNewest )
|
|
iCurrent = qInterped.GetNext( iHead );
|
|
else
|
|
iCurrent = iHead;
|
|
|
|
while( (pCurrent = qInterped.GetHistoryValue( iCurrent, fTime )) != NULL )
|
|
{
|
|
Assert( (fTime <= fHeadTime) || (iCurrent == iHead) ); //asserting that head is always newest
|
|
|
|
if( fTime < gpGlobals->curtime )
|
|
*pCurrent = TransformAnglesToWorldSpace( *pCurrent, matTransform );
|
|
|
|
iCurrent = qInterped.GetNext( iCurrent );
|
|
if( iCurrent == iHead )
|
|
break;
|
|
}
|
|
|
|
qInterped.Interpolate( gpGlobals->curtime );
|
|
}
|
|
|
|
void UTIL_TransformInterpolatedPosition( CInterpolatedVar< Vector > &vInterped, VMatrix matTransform, bool bSkipNewest )
|
|
{
|
|
int iHead = vInterped.GetHead();
|
|
if( !vInterped.IsValidIndex( iHead ) )
|
|
return;
|
|
|
|
#ifdef DBGFLAG_ASSERT
|
|
float fHeadTime;
|
|
vInterped.GetHistoryValue( iHead, fHeadTime );
|
|
#endif
|
|
|
|
float fTime;
|
|
Vector *pCurrent;
|
|
int iCurrent;
|
|
|
|
if( bSkipNewest )
|
|
iCurrent = vInterped.GetNext( iHead );
|
|
else
|
|
iCurrent = iHead;
|
|
|
|
while( (pCurrent = vInterped.GetHistoryValue( iCurrent, fTime )) != NULL )
|
|
{
|
|
Assert( (fTime <= fHeadTime) || (iCurrent == iHead) );
|
|
|
|
if( fTime < gpGlobals->curtime )
|
|
*pCurrent = matTransform * (*pCurrent);
|
|
|
|
iCurrent = vInterped.GetNext( iCurrent );
|
|
if( iCurrent == iHead )
|
|
break;
|
|
}
|
|
|
|
vInterped.Interpolate( gpGlobals->curtime );
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifndef CLIENT_DLL
|
|
|
|
void CC_Debug_FixMyPosition( void )
|
|
{
|
|
CBaseEntity *pPlayer = UTIL_GetCommandClient();
|
|
|
|
FindClosestPassableSpace( pPlayer, vec3_origin );
|
|
}
|
|
|
|
static ConCommand debug_fixmyposition("debug_fixmyposition", CC_Debug_FixMyPosition, "Runs FindsClosestPassableSpace() on player.", FCVAR_CHEAT );
|
|
#endif
|