cstrike15_src/game/shared/portal/portal_util_shared.cpp
2025-06-04 03:22:50 +02:00

3473 lines
127 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include <algorithm>
#include "portal_util_shared.h"
#include "portal_base2d_shared.h"
#include "portal_shareddefs.h"
#include "portal_collideable_enumerator.h"
#include "beam_shared.h"
#include "CollisionUtils.h"
#include "util_shared.h"
#include "portal_mp_gamerules.h"
#include "coordsize.h"
#ifndef CLIENT_DLL
#include "Util.h"
#include "NDebugOverlay.h"
#include "env_debughistory.h"
#include "world.h"
#else
#include "c_portal_player.h"
#include "c_prop_portal.h"
#include "materialsystem/imaterialvar.h"
#include "c_world.h"
#endif
#include "PortalSimulation.h"
#include "CegClientWrapper.h"
bool g_bAllowForcePortalTrace = false;
bool g_bForcePortalTrace = false;
bool g_bBulletPortalTrace = false;
// paint convars
ConVar sv_paint_detection_sphere_radius( "sv_paint_detection_sphere_radius", "16.f", FCVAR_REPLICATED | FCVAR_CHEAT, "The radius of the query sphere used to find the color of a light map at a contact point in world space." );
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." );
ConVar portal_trace_shrink_ray_each_query("portal_trace_shrink_ray_each_query", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar portal_beamtrace_optimization ("portal_beamtrace_optimization", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
// FIXME: Bring this back for DLC2
//ConVar reflect_paint_vertical_snap( "reflect_paint_vertical_snap", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
extern ConVar portal_clone_displacements;
const float COS_PI_OVER_SIX = 0.86602540378443864676372317075294f; // cos( 30 degrees ) in radians
//#define PORTAL_TRACE_LOGGING
#if defined PORTAL_TRACE_LOGGING
enum eTraceType
{
BRUSHES,
DISP,
HOLYWALL_BRUSHES,
HOLYWALL_TUBE,
HOLYWALL_TRANSLATED_BRUSHES,
ENTITIES,
STATIC_PROPS,
REMOTE_STATIC_PROPS,
TRACE_TYPE_COUNT,
};
class CPortalTraceLogger
{
public:
CPortalTraceLogger::CPortalTraceLogger():m_nTotalTraces(0), m_nTotalKept(0)
{
for (int i=0; i<TRACE_TYPE_COUNT;++i)
{
m_nCallCounts[i] = 0;
m_nKeptCounts[i] = 0;
}
}
void LogTrace( eTraceType type ) { m_nCallCounts[type]++; m_nTotalTraces++; }
void LogTypeKept( eTraceType type ) { m_nKeptCounts[type]++; m_nTotalKept++; }
void Display()
{
char row = 20;
for ( int i = 0; i < TRACE_TYPE_COUNT; ++i )
{
engine->Con_NPrintf( row++, "Calls(%d): %d [%f.2]", i, m_nCallCounts[i], 100.0f * ((float)m_nCallCounts[i]/(float)m_nTotalTraces) );
engine->Con_NPrintf( row++, "Keeps(%d): %d [%f.2]", i, m_nKeptCounts[i], 100.0f * ((float)m_nKeptCounts[i]/(float)m_nTotalKept) );
}
}
uint32 m_nCallCounts[TRACE_TYPE_COUNT];
uint32 m_nKeptCounts[TRACE_TYPE_COUNT];
uint64 m_nTotalTraces;
uint64 m_nTotalKept;
};
static CPortalTraceLogger s_TraceLogger;
CON_COMMAND( dump_portal_trace_log, "Spew current trace data to the console" )
{
for ( int i = 0; i < TRACE_TYPE_COUNT; ++i )
{
Msg( "Calls(%d): %d [%f.2]\n", i, s_TraceLogger.m_nCallCounts[i], 100.0f * ((float)s_TraceLogger.m_nCallCounts[i]/(float)s_TraceLogger.m_nTotalTraces) );
Msg( "Keeps(%d): %d [%f.2]\n", i, s_TraceLogger.m_nKeptCounts[i], 100.0f * ((float)s_TraceLogger.m_nKeptCounts[i]/(float)s_TraceLogger.m_nTotalKept) );
}
}
#endif // PORTAL_TRACE_LOGGING
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& 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 uint GetRequiredTriggerFlags() const { return m_pWrappedCollideable->GetRequiredTriggerFlags(); }
virtual IPhysicsObject *GetVPhysicsObject() const { return m_pWrappedCollideable->GetVPhysicsObject(); }
//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;
}
#if defined ( CLIENT_DLL )
static const Color s_defaultPortalColors[2] = { Color( 64, 160, 255, 255 ), Color( 255, 160, 32, 255 ) };
Color UTIL_Portal_Color( int iPortal, int iTeamNumber /*= 0*/ )
{
switch ( iPortal )
{
case 0:
// GRAVITY BEAM
return Color( 242, 202, 167, 255 );
case 1:
case 2:
{
// PORTAL 1 or 2
if ( GameRules()->IsMultiplayer() && !((CPortalMPGameRules *)g_pGameRules)->Is2GunsCoOp() )
{
Assert( TEAM_BLUE == TEAM_RED + 1 );
if ( ( iTeamNumber == TEAM_RED ) || ( iTeamNumber == TEAM_BLUE ) )
{
const Vector &vColor = C_Prop_Portal::m_Materials.m_coopPlayerPortalColors[ 1 - ( iTeamNumber - TEAM_RED ) ][ iPortal - 1 ];
return Color( vColor[0] * 255, vColor[1] * 255, vColor[2] * 255 );
}
else
{
return s_defaultPortalColors[ iPortal - 1 ];
}
}
else
{
// Single player
const Vector &vColor = C_Prop_Portal::m_Materials.m_singlePlayerPortalColors[ iPortal - 1 ];
return Color( vColor[0] * 255, vColor[1] * 255, vColor[2] * 255 );
}
}
break;
}
Assert( 0 );
return Color( 255, 255, 255, 255 );
}
Color UTIL_Portal_Color_Particles( int iPortal, int iTeamNumber /*= 0*/ )
{
if ( GameRules()->IsMultiplayer() && !((CPortalMPGameRules *)g_pGameRules)->Is2GunsCoOp() )
return UTIL_Portal_Color( iPortal, iTeamNumber );
else
return (iPortal - 1)?( Color( 233, 78, 2, 255 ) ):( Color( 0, 60, 255, 255 ) );
}
#endif
void UTIL_Portal_Trace_Filter( CTraceFilterSimpleClassnameList *traceFilterPortalShot )
{
traceFilterPortalShot->AddClassnameToIgnore( "prop_physics" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_weighted_cube" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_monster_box" );
traceFilterPortalShot->AddClassnameToIgnore( "func_physbox" );
traceFilterPortalShot->AddClassnameToIgnore( "npc_portal_turret_floor" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_energy_ball" );
traceFilterPortalShot->AddClassnameToIgnore( "npc_security_camera" );
traceFilterPortalShot->AddClassnameToIgnore( "simple_physics_prop" );
traceFilterPortalShot->AddClassnameToIgnore( "simple_physics_brush" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_ragdoll" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_glados_core" );
traceFilterPortalShot->AddClassnameToIgnore( "player" );
traceFilterPortalShot->AddClassnameToIgnore( "Player" );
traceFilterPortalShot->AddClassnameToIgnore( "projected_wall_entity" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_paint_bomb" );
traceFilterPortalShot->AddClassnameToIgnore( "prop_exploding_futbol" );
traceFilterPortalShot->AddClassnameToIgnore( "npc_personality_core" );
}
CPortal_Base2D* UTIL_Portal_FirstAlongRay( const Ray_t &ray, float &fMustBeCloserThan, CPortal_Base2D **pSearchArray, int iSearchArrayCount )
{
CPortal_Base2D *pIntersectedPortal = NULL;
for( int i = 0; i != iSearchArrayCount; ++i )
{
CPortal_Base2D *pTempPortal = pSearchArray[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;
}
CPortal_Base2D* UTIL_Portal_FirstAlongRay( const Ray_t &ray, float &fMustBeCloserThan )
{
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
if( iPortalCount != 0 )
{
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
return UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan, pPortals, iPortalCount );
}
return NULL;
}
bool UTIL_Portal_TraceRay_Bullets( const CPortal_Base2D *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 );
// If the ray isn't going into the front of the portal, just use the real trace
if ( pPortal->m_vForward.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;
}
CPortal_Base2D* 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;
CPortal_Base2D *pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan );
*pfFraction = fMustBeCloserThan; //will be real trace distance if it didn't hit a portal
return pIntersectedPortal;
}
void UTIL_Portal_TraceRay_With( const CPortal_Base2D *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 );
float flDot = -1.0f;
if ( ray.m_IsSwept )
{
flDot = vDirection.Dot( pPortal->m_vForward );
}
// 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, CPortal_Base2D** pOutLocal, CPortal_Base2D** pOutRemote )
{
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
if( iPortalCount == 0 )
{
if( pOutLocal )
*pOutLocal = NULL;
if( pOutRemote )
*pOutRemote = NULL;
return false;
}
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
CPortal_Base2D *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
CPortal_Base2D **pBoxIntersectsPortals = (CPortal_Base2D **)stackalloc( sizeof(CPortal_Base2D *) * iPortalCount );
int iBoxIntersectsPortalsCount = 0;
for( int i = 0; i != iPortalCount; ++i )
{
CPortal_Base2D *pTempPortal = pPortals[i];
if( (pTempPortal->IsActive()) &&
(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->m_ptOrigin).LengthSqr();
for( int i = 1; i != iBoxIntersectsPortalsCount; ++i )
{
float fDist = (ptRayEndPoint - pBoxIntersectsPortals[i]->m_ptOrigin).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
//-----------------------------------------------------------------------------
CPortal_Base2D* UTIL_Portal_TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
{
float fMustBeCloserThan = 2.0f;
CPortal_Base2D *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;
}
}
CPortal_Base2D* 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 );
}
extern ConVar sv_portal_new_player_trace;
//-----------------------------------------------------------------------------
// 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
//-----------------------------------------------------------------------------
//extern bool g_bSpewTraceStuck;
void UTIL_Portal_TraceRay_PreTraceChanges( const CPortal_Base2D *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall );
void UTIL_Portal_TraceRay( const CPortal_Base2D *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
{
if( !sv_portal_new_player_trace.GetBool() )
{
//it's not funny anymore, go back
return UTIL_Portal_TraceRay_PreTraceChanges( pPortal, ray, fMask, pTraceFilter, pTrace, bTraceHolyWall );
}
#if defined ( PORTAL_TRACE_LOGGING )
int keptType = -1;
#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;
}
UTIL_ClearTrace( *pTrace );
pTrace->startpos = ray.m_Start + ray.m_StartOffset;
pTrace->endpos = pTrace->startpos + ray.m_Delta;
// We shrink the ray when we hit something so subsequent traces can early-out on hits that would be further away
Ray_t queryRay = ray;
trace_t TempTrace;
int counter;
const CPortalSimulator &portalSimulator = pPortal->m_PortalSimulator;
const PS_InternalData_t &portalSimulatorData = portalSimulator.GetInternalData();
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.IsRayInPortalHole( queryRay ) != RIPHR_NOT_TOUCHING_HOLE) );
bool bCopyBackBrushTraceData = false;
// Traces vs world
if( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
{
if( sv_portal_trace_vs_world.GetBool() )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( portalSimulatorData.Simulation.Static.World.Brushes.BrushSets ); ++iBrushSet )
{
const PS_SD_Static_BrushSet_t *pBrushSet = &portalSimulatorData.Simulation.Static.World.Brushes.BrushSets[iBrushSet];
if( ((pBrushSet->iSolidMask & fMask) != 0) && pBrushSet->pCollideable &&
physcollision->TraceBoxAA( queryRay, pBrushSet->pCollideable, &TempTrace ) )
{
bCopyBackBrushTraceData = true;
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser) )
{
*pTrace = TempTrace;
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
}
if ( pTrace->DidHit() && portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
#if defined ( PORTAL_TRACE_LOGGING )
keptType = BRUSHES;
s_TraceLogger.LogTrace( BRUSHES );
#endif
}
}
}
if( bTraceStaticProps )
{
bool bFilterStaticProps = (pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS);
//local clipped static props
{
int iLocalStaticCount = portalSimulatorData.Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
if( iLocalStaticCount != 0 && portalSimulatorData.Simulation.Static.World.StaticProps.bCollisionExists )
{
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = portalSimulatorData.Simulation.Static.World.StaticProps.ClippedRepresentations.Base();
const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount;
do
{
if( ( !bFilterStaticProps || pTraceFilter->ShouldHitEntity( pCurrentProp->pSourceProp, fMask ) ) &&
physcollision->TraceBoxAA( queryRay, pCurrentProp->pCollide, &TempTrace ) )
{
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( STATIC_PROPS );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser) )
{
*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;
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = STATIC_PROPS;
#endif
}
}
++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->GetInternalData().Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
if( iLocalStaticCount != 0 )
{
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.StaticProps.ClippedRepresentations.Base();
const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount;
Vector vTransform = portalSimulatorData.Placement.ptaap_LinkedToThis.ptShrinkAlignedOrigin;
QAngle qTransform = portalSimulatorData.Placement.ptaap_LinkedToThis.qAngleTransform;
do
{
if ( !bFilterStaticProps || pTraceFilter->ShouldHitEntity( pCurrentProp->pSourceProp, fMask ) )
{
physcollision->TraceBox( queryRay, pCurrentProp->pCollide, vTransform, qTransform, &TempTrace );
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( REMOTE_STATIC_PROPS );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser ) )
{
*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;
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = REMOTE_STATIC_PROPS;
#endif
}
}
++pCurrentProp;
}
while( pCurrentProp != pStop );
}
}
}
}
if( portalSimulatorData.Simulation.Static.World.Displacements.pCollideable && sv_portal_trace_vs_world.GetBool() && portal_clone_displacements.GetBool() )
{
physcollision->TraceBox( queryRay, portalSimulatorData.Simulation.Static.World.Displacements.pCollideable, vec3_origin, vec3_angle, &TempTrace );
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( DISP );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser ) )
{
*pTrace = TempTrace;
bCopyBackBrushTraceData = true;
//pTrace->dispFlags |= DISPSURF_FLAG_SURFACE | DISPSURF_FLAG_WALKABLE;
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = DISP;
#endif
}
}
if( bTraceHolyWall )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( portalSimulatorData.Simulation.Static.Wall.Local.Brushes.BrushSets ); ++iBrushSet )
{
const PS_SD_Static_BrushSet_t *pBrushSet = &portalSimulatorData.Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet];
if( ((pBrushSet->iSolidMask & fMask) != 0) && pBrushSet->pCollideable &&
physcollision->TraceBoxAA( queryRay, pBrushSet->pCollideable, &TempTrace ) )
{
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( HOLYWALL_BRUSHES );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser) )
{
*pTrace = TempTrace;
bCopyBackBrushTraceData = true;
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = HOLYWALL_BRUSHES;
#endif
}
}
}
if( portalSimulatorData.Simulation.Static.Wall.Local.Tube.pCollideable )
{
physcollision->TraceBoxAA( queryRay, portalSimulatorData.Simulation.Static.Wall.Local.Tube.pCollideable, &TempTrace );
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( HOLYWALL_TUBE );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser) )
{
*pTrace = TempTrace;
bCopyBackBrushTraceData = true;
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = HOLYWALL_TUBE;
#endif
}
}
//if( portalSimulatorData.Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() )
if( bTraceTransformedGeometry )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets ); ++iBrushSet )
{
const PS_SD_Static_BrushSet_t *pBrushSet = &pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet];
if( ((pBrushSet->iSolidMask & fMask) != 0) && pBrushSet->pCollideable )
{
physcollision->TraceBox( queryRay, pBrushSet->pCollideable, portalSimulatorData.Placement.ptaap_LinkedToThis.ptOriginTransform, portalSimulatorData.Placement.ptaap_LinkedToThis.qAngleTransform, &TempTrace );
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( HOLYWALL_TRANSLATED_BRUSHES );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser ) )
{
*pTrace = TempTrace;
bCopyBackBrushTraceData = true;
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
#if defined ( PORTAL_TRACE_LOGGING )
keptType = HOLYWALL_TRANSLATED_BRUSHES;
#endif
}
}
}
}
}
if( bCopyBackBrushTraceData )
{
pTrace->surface = portalSimulatorData.Simulation.Static.SurfaceProperties.surface;
pTrace->contents = portalSimulatorData.Simulation.Static.SurfaceProperties.contents;
pTrace->m_pEnt = portalSimulatorData.Simulation.Static.SurfaceProperties.pEntity;
bCopyBackBrushTraceData = false;
}
}
// Traces vs entities
if( pTraceFilter->GetTraceType() != TRACE_WORLD_ONLY )
{
//solid entities
CPortalCollideableEnumerator enumerator( pPortal );
int PartitionMask;
#if defined( CLIENT_DLL )
PartitionMask = PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_STATIC_PROPS;
#else
PartitionMask = PARTITION_ENGINE_SOLID_EDICTS | PARTITION_ENGINE_STATIC_PROPS;
#endif
::partition->EnumerateElementsAlongRay( PartitionMask, queryRay, 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;
}
CBaseEntity *pEnumeratedEntity = EntityFromEntityHandle( enumerator.m_pHandles[counter] );
//If we have a carved representation of this entity, trace against that instead of the real thing
CPhysCollide *pCarvedCollide = portalSimulator.IsEntityCarvedByPortal( pEnumeratedEntity ) ? portalSimulator.GetCollideForCarvedEntity( pEnumeratedEntity ) : NULL;
if( pCarvedCollide != NULL )
{
ICollideable *pUncarvedCollideable = pEnumeratedEntity->GetCollideable();
physcollision->TraceBox( queryRay, pCarvedCollide, pUncarvedCollideable->GetCollisionOrigin(), pUncarvedCollideable->GetCollisionAngles(), &TempTrace );
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( ENTITIES );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser ) )
{
//copy the trace data from the carved trace
*pTrace = TempTrace;
//then trace against the real thing for surface data.
//TODO: There's got to be a way to store this off and look it up intelligently. But I can't seem to find surface info without a trace, making the results only valid for that trace.
enginetrace->ClipRayToEntity( queryRay, fMask, enumerator.m_pHandles[counter], &TempTrace );
pTrace->contents = TempTrace.contents;
pTrace->surface = TempTrace.surface;
pTrace->m_pEnt = TempTrace.m_pEnt;
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = ENTITIES;
#endif
}
}
else
{
enginetrace->ClipRayToEntity( queryRay, fMask, enumerator.m_pHandles[counter], &TempTrace );
#if defined ( PORTAL_TRACE_LOGGING )
s_TraceLogger.LogTrace( ENTITIES );
#endif
bool bIsCloser = (portal_trace_shrink_ray_each_query.GetBool() ) ? (TempTrace.DidHit()) : (TempTrace.fraction < pTrace->fraction);
if( (TempTrace.startsolid && !pTrace->startsolid) || (queryRay.m_IsSwept && bIsCloser ) )
{
*pTrace = TempTrace;
Assert( pTrace->startsolid || (pTrace->fraction == 1.0f) || (pTrace->plane.normal.LengthSqr() > 0.5f) );
if ( portal_trace_shrink_ray_each_query.GetBool() )
{
queryRay.m_Delta *= pTrace->fraction;
}
#if defined ( PORTAL_TRACE_LOGGING )
keptType = ENTITIES;
#endif
}
}
}
}
#if defined ( PORTAL_TRACE_LOGGING )
if ( pTrace->DidHit() && keptType >= 0 && keptType < TRACE_TYPE_COUNT )
s_TraceLogger.LogTypeKept( (eTraceType)keptType );
s_TraceLogger.Display();
#endif
}
void UTIL_Portal_TraceRay( const CPortal_Base2D *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 CPortal_Base2D *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
{
// 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;
CPortal_Base2D *pLinkedPortal = (CPortal_Base2D*)(pPortal->m_hLinkedPortal.Get());
if( (pLinkedPortal == NULL) || (portalSimulator.IsRayInPortalHole( ray ) == RIPHR_NOT_TOUCHING_HOLE) )
{
memset( pTrace, 0, sizeof(trace_t));
pTrace->fraction = 1.0f;
pTrace->fractionleftsolid = 0;
pTrace->contents = pPortal->m_PortalSimulator.GetInternalData().Simulation.Static.SurfaceProperties.contents;
pTrace->surface = pPortal->m_PortalSimulator.GetInternalData().Simulation.Static.SurfaceProperties.surface;
pTrace->m_pEnt = pPortal->m_PortalSimulator.GetInternalData().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 CPortal_Base2D *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 );
}
int UTIL_Portal_EntitiesAlongRayComplex( int *entSegmentIndices, int *segCount, int maxEntities, ComplexPortalTrace_t *pResultSegmentArray, int maxSegments, const Ray_t& ray, ICountedPartitionEnumerator* pEnum, ITraceFilter* pTraceFilter, int fStopTraceContents )
{
if( !pEnum )
return 0;
CTraceFilterHitAll dummyFilter;
if( !pTraceFilter )
pTraceFilter = &dummyFilter;
ComplexPortalTrace_t dummySegmentResults[16];
if( !pResultSegmentArray )
{
pResultSegmentArray = dummySegmentResults;
maxSegments = ARRAYSIZE( dummySegmentResults );
}
// Run a complex trace that hits only the world to get all the segments to trace with
const int segmentCount = UTIL_Portal_ComplexTraceRay( ray, fStopTraceContents, pTraceFilter, pResultSegmentArray, maxSegments );
if( segCount )
*segCount = segmentCount;
for( int i = 0; i < segmentCount && pEnum->GetCount() < maxEntities; ++i )
{
// Enumerate all entities along the ray
Ray_t segmentRay;
segmentRay.Init( pResultSegmentArray[i].trSegment.startpos, pResultSegmentArray[i].trSegment.endpos - DIST_EPSILON * pResultSegmentArray[i].trSegment.plane.normal );
//NDebugOverlay::Line( segmentRay.m_Start, segmentRay.m_Start + segmentRay.m_Delta, 0, 255, 0, false, 5 );
const int oldEnumCount = pEnum->GetCount();
#ifdef GAME_DLL
const int PARTITION_ALL_SERVER_EDICTS = PARTITION_ENGINE_NON_STATIC_EDICTS | PARTITION_ENGINE_STATIC_PROPS | PARTITION_ENGINE_SOLID_EDICTS | PARTITION_ENGINE_TRIGGER_EDICTS;
if( segmentRay.m_Delta.IsZeroFast() )
::partition->EnumerateElementsAtPoint( PARTITION_ALL_SERVER_EDICTS, segmentRay.m_Start, false, pEnum );
else
::partition->EnumerateElementsAlongRay( PARTITION_ALL_SERVER_EDICTS, segmentRay, false, pEnum );
#else
if( segmentRay.m_Delta.IsZeroFast() )
::partition->EnumerateElementsAtPoint( PARTITION_ALL_CLIENT_EDICTS, segmentRay.m_Start, false, pEnum );
else
::partition->EnumerateElementsAlongRay( PARTITION_ALL_CLIENT_EDICTS, segmentRay, false, pEnum );
#endif
const int newEnumCount = pEnum->GetCount();
const int remainingEnts = MAX( 0, maxEntities - newEnumCount );
const int rayEnumCount = MIN( remainingEnts, newEnumCount - oldEnumCount );
if( entSegmentIndices )
{
for( int j = 0; j < rayEnumCount; ++j, ++entSegmentIndices )
{
*entSegmentIndices = i;
}
}
}
// Add whatever stopped the trace
CBaseEntity* pLastTraceEntity = segmentCount ? pResultSegmentArray[segmentCount - 1].trSegment.m_pEnt : NULL;
if( pLastTraceEntity && pEnum->GetCount() < maxEntities )
{
const int oldCount = pEnum->GetCount();
pEnum->EnumElement( pLastTraceEntity );
if( pEnum->GetCount() == oldCount + 1 )
{
if( entSegmentIndices )
*entSegmentIndices = segmentCount - 1;
}
}
return pEnum->GetCount();
}
struct CollideableTraceData_t
{
Vector vecAbsStart;
Vector vecAbsEnd;
CPhysCollide *pEntCollide;
QAngle qCollisionAngle;
};
void TraceFunc_Collideable( void *pData, CPhysCollide *pCollide, const Vector &vOrigin, const QAngle &qAngle, trace_t *pTrace )
{
physcollision->TraceCollide( ((CollideableTraceData_t *)pData)->vecAbsStart, ((CollideableTraceData_t *)pData)->vecAbsEnd, ((CollideableTraceData_t *)pData)->pEntCollide, ((CollideableTraceData_t *)pData)->qCollisionAngle,
pCollide, vOrigin, qAngle, pTrace );
}
void TraceFunc_Ray( void *pData, CPhysCollide *pCollide, const Vector &vOrigin, const QAngle &qAngle, trace_t *pTrace )
{
physcollision->TraceBox( *(Ray_t *)pData, MASK_ALL, NULL, pCollide, vOrigin, qAngle, pTrace );
}
//-----------------------------------------------------------------------------
// 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 )
{
if ( !pEntity || !pEntity->CollisionProp() )
{
Assert ( 0 );
return;
}
#ifdef CLIENT_DLL
Assert( pEntity->IsPlayer() );
CPortalSimulator *pPortalSimulator = NULL;
if( pEntity->IsPlayer() )
{
C_Portal_Base2D *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();
// BUG: sweep collideable requires no angles to be passed in
enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, vec3_angle, 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->CollisionProp()->OBBSize() * 0.5 + Vector ( flTolerance, flTolerance, flTolerance );
Vector vColCenter = realTrace.endpos + ( pEntity->CollisionProp()->OBBMins() + pEntity->CollisionProp()->OBBMaxs() ) * 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
{
CPhysCollide *pTempPhysCollide = (sv_portal_new_player_trace.GetBool() && (pEntity->GetSolid() == SOLID_BBOX)) ? NULL : physcollision->BBoxToCollide( pCollision->OBBMins(), pCollision->OBBMaxs() );
//CPhysCollide *pTempPhysCollide = physcollision->BBoxToCollide( pCollision->OBBMins(), pCollision->OBBMaxs() );
CPhysCollide *pEntPhysCollide = pTempPhysCollide; //HACKHACK: At some point we should investigate into getting the real CPhysCollide
void (*pTraceFunc)( void *, CPhysCollide *, const Vector &, const QAngle &, trace_t * );
void *pTraceData;
Ray_t entRay;
entRay.Init( vecAbsStart, vecAbsEnd, pCollision->OBBMins(), pCollision->OBBMaxs() );
CollideableTraceData_t entCollideableData;
if( pEntPhysCollide )
{
pTraceFunc = TraceFunc_Collideable;
pTraceData = &entCollideableData;
entCollideableData.pEntCollide = pEntPhysCollide;
entCollideableData.qCollisionAngle = qCollisionAngles;
entCollideableData.vecAbsStart = vecAbsStart;
entCollideableData.vecAbsEnd = vecAbsEnd;
}
else
{
pTraceFunc = TraceFunc_Ray;
pTraceData = &entRay;
}
CPortalSimulator *pLinkedPortalSimulator = pPortalSimulator->GetLinkedPortalSimulator();
#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;
CPortal_Base2D *pFirstPortal = UTIL_Portal_FirstAlongRay( entRay, fFirstPortalFraction );
if ( !pFirstPortal )
*pTrace = realTrace;
else
{
if ( pFirstPortal->m_vForward.Dot( realTrace.endpos - pFirstPortal->m_ptOrigin ) > 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( sv_portal_trace_vs_world.GetBool() )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( pPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets ); ++iBrushSet )
{
if( ((pPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].iSolidMask & mask) != 0) &&
pPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable )
{
pTraceFunc( pTraceData, pPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable, vec3_origin, vec3_angle, &tempTrace );
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
{
*pTrace = tempTrace;
}
}
}
}
if( pPortalSimulator->GetInternalData().Simulation.Static.World.Displacements.pCollideable &&
sv_portal_trace_vs_world.GetBool() &&
portal_clone_displacements.GetBool() )
{
pTraceFunc( pTraceData, pPortalSimulator->GetInternalData().Simulation.Static.World.Displacements.pCollideable, vec3_origin, vec3_angle, &tempTrace );
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
{
*pTrace = tempTrace;
}
}
//if( pPortalSimulator->GetInternalData().Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable &&
if( pLinkedPortalSimulator &&
sv_portal_trace_vs_world.GetBool() &&
sv_portal_trace_vs_holywall.GetBool() )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets ); ++iBrushSet )
{
if( ((pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].iSolidMask & mask) != 0) &&
pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable )
{
pTraceFunc( pTraceData, pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable, pPortalSimulator->GetInternalData().Placement.ptaap_LinkedToThis.ptOriginTransform, pPortalSimulator->GetInternalData().Placement.ptaap_LinkedToThis.qAngleTransform, &tempTrace );
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
{
*pTrace = tempTrace;
}
}
}
}
//if( pPortalSimulator->GetInternalData().Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable &&
if( pLinkedPortalSimulator &&
pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Displacements.pCollideable &&
sv_portal_trace_vs_world.GetBool() &&
sv_portal_trace_vs_holywall.GetBool() &&
portal_clone_displacements.GetBool() )
{
pTraceFunc( pTraceData, pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Displacements.pCollideable, pPortalSimulator->GetInternalData().Placement.ptaap_LinkedToThis.ptOriginTransform, pPortalSimulator->GetInternalData().Placement.ptaap_LinkedToThis.qAngleTransform, &tempTrace );
if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) )
{
*pTrace = tempTrace;
}
}
if ( sv_portal_trace_vs_holywall.GetBool() )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( pPortalSimulator->GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets ); ++iBrushSet )
{
if( ((pPortalSimulator->GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet].iSolidMask & mask) != 0) &&
pPortalSimulator->GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet].pCollideable )
{
pTraceFunc( pTraceData, pPortalSimulator->GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet].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->GetInternalData().Simulation.Static.Wall.Local.Tube.pCollideable &&
sv_portal_trace_vs_holywall.GetBool() )
{
pTraceFunc( pTraceData, pPortalSimulator->GetInternalData().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->GetInternalData().Simulation.Static.SurfaceProperties.surface;
pTrace->contents = pPortalSimulator->GetInternalData().Simulation.Static.SurfaceProperties.contents;
pTrace->m_pEnt = pPortalSimulator->GetInternalData().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->GetInternalData().Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
if( iLocalStaticCount != 0 && pPortalSimulator->GetInternalData().Simulation.Static.World.StaticProps.bCollisionExists )
{
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pPortalSimulator->GetInternalData().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 ) )
{
pTraceFunc( pTraceData, 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->GetInternalData().Placement.matThisToLinked;
transformedCollideable.m_matInvTransform = pLinkedPortalSimulator->GetInternalData().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
}
}
}
if( pTrace->fraction == 1.0f )
{
memset( pTrace, 0, sizeof( trace_t ) );
pTrace->fraction = 1.0f;
pTrace->startpos = vecAbsStart;
pTrace->endpos = vecAbsEnd;
}
if( pTempPhysCollide )
{
physcollision->DestroyCollide( pTempPhysCollide );
}
}
}
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;
}
}
struct IntersectionCachedData_t
{
Vector vCenter;
VPlane plane;
Vector vUp, vRight;
float fRadiusSquare;
};
inline CPortal_Base2D *UTIL_Portal_ClipTraceToFirstTransformingPortal( trace_t &tr, const Vector &vRayNormalizedDelta, const float fRayRadius, CPortal_Base2D **pPortals, IntersectionCachedData_t *pIntersectionData, const int iPortalCount )
{
Vector vIntersection;
float fIntersectionScale = FLT_MAX; //a pseudo-distance. Expressed as a scale of the existing trace length (which we never actually compute)
CPortal_Base2D *pIntersectionPortal = NULL;
//Portal-Ray intersection has the following axioms that allow for some cheap checks up front
//intersection requires the ray to start in front of the portal plane and end behind it
//intersection requires the ray to intersect a quad on the portal plane
bool bFullTrace = tr.fraction == 1.0f;
for( int i = 0; i != iPortalCount; ++i )
{
float fStartDot = pIntersectionData[i].plane.DistTo( tr.startpos );
float fEndDot = pIntersectionData[i].plane.DistTo( tr.endpos );
if( fEndDot >= fStartDot ) //ray would be coming out of portal instead of going in (or didn't move at all)
continue;
if( (fStartDot + fRayRadius) < 0.0f )
continue; //ray started wholly behind the portal
if( bFullTrace && (fEndDot > (1.0f / 4096.0f)) )
continue; //ray reached it's destination before the center crossed the portal plane
if( (fEndDot - fRayRadius) - (1.0f / 4096.0f) > 0.01f )
continue; //ray ended wholly in front of the portal
//compute portal distance to the (infinite) ray
Vector vRayStartToPortal = pIntersectionData[i].vCenter - tr.startpos;
Vector vProjected = vRayNormalizedDelta * vRayStartToPortal.Dot( vRayNormalizedDelta );
Vector vRayClosestToPortal = vRayStartToPortal - vProjected;
if( vRayClosestToPortal.LengthSqr() > pIntersectionData[i].fRadiusSquare )
continue; //portal quad is too far away for the ray's center to intersect it. This doesn't take ray extents into account because we're assuming a successful teleportation requires at least the center of the ray to intersect
//If we're here. The ray at least grazes the portal. An intersection between the ray and this portal is very probable
//NOTE: For rays with extents, it's possible that the end position stops short of the portal because it hit the wall behind the portal.
float fRayIntersectionScale = fStartDot / (fStartDot - fEndDot); //how far (relative to the trace length) along the ray normal do we go before hitting the plane
if( fRayIntersectionScale > fIntersectionScale ) //already have something closer
continue;
if( (fRayIntersectionScale * tr.fraction) > 1.0f )
continue; //in the case of a ray with extents, some of the code above assumes a trace that stops short *would have* gone through, this is the actual check to verify that is the truth
Vector vPlaneIntersection = (fRayIntersectionScale * tr.endpos) + ( (1.0f - fRayIntersectionScale) * tr.startpos ); //barycentric
//Vector vPlaneIntersection = tr.startpos + (vRayNormalizedDelta * fRayIntersectionLength);
#ifdef DBGFLAG_ASSERT
float fIntersectionPlaneDist = fabs( pIntersectionData[i].plane.DistTo( vPlaneIntersection ) );
AssertOnce( fIntersectionPlaneDist <= (1.0f/256.0f) ); //arbitrary small float that's a power of 2. FLT_MIN is too small
#endif
Vector vPortalSpacePlaneIntersection = vPlaneIntersection - pIntersectionData[i].vCenter; //move our calculation space to portal centric to make the following math simpler
if( fabs( pIntersectionData[i].vRight.Dot( vPortalSpacePlaneIntersection ) ) > pPortals[i]->GetHalfWidth() )
continue; //ray center outside of portal quad
if( fabs( pIntersectionData[i].vUp.Dot( vPortalSpacePlaneIntersection ) ) > pPortals[i]->GetHalfHeight() )
continue; //ray center outside of portal quad
//HIT! You sank my portalship. Don't know if the entire ray intersects, but at least the center does
//which is good enough for now. TODO: Test forwardmost corners as well?
AssertMsgOnce( fRayRadius < 20.0f, "This code is currently designed to assume the traced rays have small extents" );
fIntersectionScale = fRayIntersectionScale;
vIntersection = vPlaneIntersection;
pIntersectionPortal = pPortals[i];
}
if( pIntersectionPortal != NULL )
{
//clip the trace to closest
tr.endpos = vIntersection;
tr.fraction *= fIntersectionScale;
tr.m_pEnt = pIntersectionPortal;
return pIntersectionPortal;
}
return NULL;
}
int UTIL_Portal_ComplexTraceRay( const Ray_t &ray, unsigned int mask, ITraceFilter *pTraceFilter, ComplexPortalTrace_t *pResultSegmentArray, int iMaxSegments )
{
if( (pResultSegmentArray == NULL) || (iMaxSegments <= 0) )
return 0;
if( !ray.m_IsSwept )
{
Assert( 0 ); //shame on you for wasting our time
return 0;
}
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
CPortal_Base2D **pPortals = pPortals = (CPortal_Base2D **)stackalloc( sizeof( CPortal_Base2D * ) * iPortalCount );
if( iPortalCount != 0 )
{
//we only care about active/linked portals, so reduce the list to that set
CPortal_Base2D **pAllPortals = CPortal_Base2D_Shared::AllPortals.Base();
int iWriteIndex = 0;
for( int i = 0; i != iPortalCount; ++i )
{
if( pAllPortals[i]->IsActivedAndLinked() )
{
pPortals[iWriteIndex++] = pAllPortals[i];
}
}
iPortalCount = iWriteIndex;
}
pResultSegmentArray[0].pSegmentStartPortal = NULL;
if( iPortalCount == 0 )
{
//trivial case where it's impossible that it hit any portals
pResultSegmentArray[0].pSegmentEndPortal = NULL;
pResultSegmentArray[0].vNormalizedDelta = ray.m_Delta.Normalized();
enginetrace->TraceRay( ray, mask, pTraceFilter, &pResultSegmentArray[0].trSegment );
return 1;
}
//cache/compute some data to speed up further tests
IntersectionCachedData_t *pIntersectionData = (IntersectionCachedData_t *)stackalloc( sizeof( IntersectionCachedData_t ) * iPortalCount );
for( int i = 0; i != iPortalCount; ++i )
{
pIntersectionData[i].vCenter = pPortals[i]->m_ptOrigin;
pIntersectionData[i].plane.Init( pPortals[i]->m_plane_Origin.normal, pPortals[i]->m_plane_Origin.dist );
pIntersectionData[i].vRight = pPortals[i]->m_vRight;
pIntersectionData[i].vUp = pPortals[i]->m_vUp;
float x,y;
x = pPortals[i]->GetHalfWidth();
y = pPortals[i]->GetHalfHeight();
pIntersectionData[i].fRadiusSquare = ( (x * x) + (y * y) );
}
float fRayRadius = ray.m_Extents.Length();
float fRemainingLength = ray.m_Delta.Length(); //the ray will continually get shorter as segments use up the original delta
Vector vRayNormalizedDelta = ray.m_Delta * (1.0f / fRemainingLength);
Ray_t workRay = ray;
pResultSegmentArray[0].vNormalizedDelta = vRayNormalizedDelta;
enginetrace->TraceRay( workRay, mask, pTraceFilter, &pResultSegmentArray[0].trSegment );
pResultSegmentArray[0].pSegmentEndPortal = UTIL_Portal_ClipTraceToFirstTransformingPortal( pResultSegmentArray[0].trSegment, vRayNormalizedDelta, fRayRadius, pPortals, pIntersectionData, iPortalCount );
for( int iSegmentIndex = 1; iSegmentIndex < iMaxSegments; ++iSegmentIndex )
{
CPortal_Base2D *pTransformPortal = pResultSegmentArray[iSegmentIndex - 1].pSegmentEndPortal;
if( pTransformPortal == NULL )
return iSegmentIndex;
fRemainingLength -= (fRemainingLength * pResultSegmentArray[iSegmentIndex - 1].trSegment.fraction);
if( fRemainingLength <= 0.0f )
return iSegmentIndex;
const VMatrix &transformMatrix = pTransformPortal->MatrixThisToLinked();
vRayNormalizedDelta = transformMatrix.ApplyRotation( vRayNormalizedDelta );
workRay.m_Start = (transformMatrix * pResultSegmentArray[iSegmentIndex - 1].trSegment.endpos) + (vRayNormalizedDelta * 0.5f); //NOTE: if extents are non-zero. It's possible this will start in solid
workRay.m_Delta = vRayNormalizedDelta * fRemainingLength;
pResultSegmentArray[iSegmentIndex].vNormalizedDelta = vRayNormalizedDelta;
pResultSegmentArray[iSegmentIndex].pSegmentStartPortal = pTransformPortal->m_hLinkedPortal.Get();
enginetrace->TraceRay( workRay, mask, pTraceFilter, &pResultSegmentArray[iSegmentIndex].trSegment );
pResultSegmentArray[iSegmentIndex].pSegmentEndPortal = UTIL_Portal_ClipTraceToFirstTransformingPortal( pResultSegmentArray[iSegmentIndex].trSegment, vRayNormalizedDelta, fRayRadius, pPortals, pIntersectionData, iPortalCount );
}
return iMaxSegments;
}
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 );
}
ConVar portal_triangles_overlap("portal_triangles_overlap", "0.1f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY);
void UTIL_Portal_Triangles( const Vector &ptPortalCenter, const QAngle &qPortalAngles, float fHalfWidth, float fHalfHeight, Vector pvTri1[ 3 ], Vector pvTri2[ 3 ] )
{
// Get points to make triangles
Vector vRight, vUp;
AngleVectors( qPortalAngles, NULL, &vRight, &vUp );
Vector vTopEdge = vUp * fHalfHeight;
Vector vBottomEdge = -vTopEdge;
Vector vRightEdge = vRight * fHalfWidth;
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
float flOverlap = portal_triangles_overlap.GetFloat();
if ( flOverlap != 0.f )
{
pvTri1[ 0 ] = vTopRight + flOverlap * vRight;
pvTri1[ 1 ] = vTopLeft;
pvTri1[ 2 ] = vBottomLeft - flOverlap * vUp;
pvTri2[ 0 ] = vTopRight + flOverlap * vUp;
pvTri2[ 1 ] = vBottomLeft - flOverlap * vRight;
pvTri2[ 2 ] = vBottomRight;
}
else
{
pvTri1[ 0 ] = vTopRight;
pvTri1[ 1 ] = vTopLeft;
pvTri1[ 2 ] = vBottomLeft;
pvTri2[ 0 ] = vTopRight;
pvTri2[ 1 ] = vBottomLeft;
pvTri2[ 2 ] = vBottomRight;
}
}
void UTIL_Portal_Triangles( const CPortal_Base2D *pPortal, Vector pvTri1[ 3 ], Vector pvTri2[ 3 ] )
{
UTIL_Portal_Triangles( pPortal->m_ptOrigin, pPortal->m_qAbsAngle, pPortal->GetHalfWidth(), pPortal->GetHalfHeight(), pvTri1, pvTri2 );
}
float UTIL_Portal_DistanceThroughPortal( const CPortal_Base2D *pPortal, const Vector &vPoint1, const Vector &vPoint2 )
{
return FastSqrt( UTIL_Portal_DistanceThroughPortalSqr( pPortal, vPoint1, vPoint2 ) );
}
float UTIL_Portal_DistanceThroughPortalSqr( const CPortal_Base2D *pPortal, const Vector &vPoint1, const Vector &vPoint2 )
{
if ( !pPortal || !pPortal->IsActive() )
return -1.0f;
CPortal_Base2D *pPortalLinked = pPortal->m_hLinkedPortal;
if ( !pPortalLinked || !pPortalLinked->IsActive() )
return -1.0f;
return vPoint1.DistToSqr( pPortal->m_ptOrigin ) + ((Vector)pPortalLinked->m_ptOrigin).DistToSqr( vPoint2 );
}
float UTIL_Portal_ShortestDistance( const Vector &vPoint1, const Vector &vPoint2, CPortal_Base2D **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, CPortal_Base2D **pShortestDistPortal_Out /*= NULL*/, bool bRequireStraightLine /*= false*/ )
{
float fMinDist = vPoint1.DistToSqr( vPoint2 );
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
if( iPortalCount == 0 )
{
if( pShortestDistPortal_Out )
*pShortestDistPortal_Out = NULL;
return fMinDist;
}
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
CPortal_Base2D *pShortestDistPortal = NULL;
for( int i = 0; i != iPortalCount; ++i )
{
CPortal_Base2D *pTempPortal = pPortals[i];
if( pTempPortal->IsActive() )
{
CPortal_Base2D *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 ptLinkedCenter = pLinkedPortal->m_ptOrigin;
Vector vCenterToIntersection = ptPlaneIntersection - ptLinkedCenter;
float fRight = pLinkedPortal->m_vRight.Dot( vCenterToIntersection );
float fUp = pLinkedPortal->m_vUp.Dot( vCenterToIntersection );
float fAbsRight = fabs( fRight );
float fAbsUp = fabs( fUp );
if( (fAbsRight > pTempPortal->GetHalfWidth()) ||
(fAbsUp > pTempPortal->GetHalfHeight()) )
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 > pTempPortal->GetHalfWidth() )
{
fNormalizer = fAbsRight/pTempPortal->GetHalfWidth();
if( fAbsUp > pTempPortal->GetHalfHeight() )
{
float fUpNormalizer = fAbsUp/pTempPortal->GetHalfHeight();
if( fUpNormalizer > fNormalizer )
fNormalizer = fUpNormalizer;
}
}
else
{
fNormalizer = fAbsUp/pTempPortal->GetHalfHeight();
}
vCenterToIntersection *= (1.0f/fNormalizer);
ptPlaneIntersection = ptLinkedCenter + vCenterToIntersection;
float fWrapDist = vPoint1Transformed.DistToSqr( ptPlaneIntersection ) + vPoint2.DistToSqr( ptPlaneIntersection );
if( fWrapDist < fMinDist )
{
fMinDist = fWrapDist;
pShortestDistPortal = pTempPortal;
*pShortestDistPortal_Out = pShortestDistPortal;
}
}
else
{
//it's a straight shot from point 1 to 2 through the portal
fMinDist = fDirectDist;
pShortestDistPortal = pTempPortal;
*pShortestDistPortal_Out = pShortestDistPortal;
}
}
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->m_ptOrigin ) + ((Vector)pLinkedPortal->m_ptOrigin).DistToSqr( vPoint2 );
if( fWrapDist < fMinDist )
{
fMinDist = fWrapDist;
pShortestDistPortal = pTempPortal;
*pShortestDistPortal_Out = pShortestDistPortal;
}
}
}
}
}
}
return fMinDist;
}
void UTIL_Portal_VectorToGlobalTransforms( const Vector &vPoint, CUtlVector< Vector > *utlVecPositions )
{
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
if( iPortalCount == 0 )
{
return;
}
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
for( int i = 0; i != iPortalCount; ++i )
{
CPortal_Base2D *pTempPortal = pPortals[i];
if( pTempPortal->IsActive() )
{
CPortal_Base2D *pLinkedPortal = pTempPortal->m_hLinkedPortal.Get();
if( pLinkedPortal != NULL )
{
VMatrix matrixFromPortal;
MatrixInverseTR( pTempPortal->MatrixThisToLinked(), matrixFromPortal );
Vector vPoint1Transformed = matrixFromPortal * vPoint;
utlVecPositions->AddToTail( vPoint1Transformed );
// vPositions[i][0] = vPoint1Transformed[0];
// vPositions[i][1] = vPoint1Transformed[1];
// vPositions[i][2] = vPoint1Transformed[2];
}
}
}
}
void UTIL_Portal_AABB( const CPortal_Base2D *pPortal, Vector &vMin, Vector &vMax )
{
Vector vOrigin = pPortal->m_ptOrigin;
QAngle qAngles = pPortal->m_qAbsAngle;
Vector vOBBForward;
Vector vOBBRight;
Vector vOBBUp;
AngleVectors( qAngles, &vOBBForward, &vOBBRight, &vOBBUp );
//scale the extents to usable sizes
vOBBForward *= PORTAL_HALF_DEPTH;
vOBBRight *= pPortal->GetHalfWidth();
vOBBUp *= pPortal->GetHalfHeight();
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 CPortal_Base2D *pPortal )
{
if ( !pPortal || !pPortal->IsActive() )
{
return -1.0f;
}
// Discount rays not coming from the front of the portal
float fDot = DotProduct( pPortal->m_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 CPortal_Base2D *pPortal, const Ray_t &ray, trace_t *pTrace )
{
return IntersectRayWithOBB( ray, pPortal->m_ptOrigin, pPortal->m_qAbsAngle, pPortal->GetLocalMins(), pPortal->GetLocalMaxs(), 0.0f, pTrace );
}
bool UTIL_IntersectRayWithPortalOBBAsAABB( const CPortal_Base2D *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 fPortalHalfWidth, float fPortalHalfHeight, float flTolerance )
{
Vector pvTri1[ 3 ], pvTri2[ 3 ];
UTIL_Portal_Triangles( ptPortalCenter, qPortalAngles, fPortalHalfWidth, fPortalHalfHeight, 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 CPortal_Base2D *pPortal, float flTolerance )
{
if( pPortal == NULL )
return false;
return UTIL_IsBoxIntersectingPortal( vecBoxCenter, vecBoxExtents, pPortal->m_ptOrigin, pPortal->m_qAbsAngle, pPortal->GetHalfWidth(), pPortal->GetHalfHeight(), flTolerance );
}
CPortal_Base2D *UTIL_IntersectEntityExtentsWithPortal( const CBaseEntity *pEntity )
{
int iPortalCount = CPortal_Base2D_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;
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
for( int i = 0; i != iPortalCount; ++i )
{
CPortal_Base2D *pTempPortal = pPortals[i];
if( pTempPortal->IsActive() &&
(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, float fHalfWidth, float fHalfHeight, 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, fHalfWidth, fHalfHeight, 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 CPortal_Base2D *pPortal, int r, int g, int b, int a, bool noDepthTest, float duration )
{
#ifndef CLIENT_DLL
UTIL_Portal_NDebugOverlay( pPortal->m_ptOrigin, pPortal->m_qAbsAngle, pPortal->GetHalfWidth(), pPortal->GetHalfHeight(), r, g, b, a, noDepthTest, duration );
#endif //#ifndef CLIENT_DLL
}
struct FindClosestPassableSpace_TraceAdapter_Portal_t : public FindClosestPassableSpace_TraceAdapter_t
{
const CPortal_Base2D *pPortal;
};
static void PortalTraceFunc( const Ray_t &ray, trace_t *pResult, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter )
{
UTIL_Portal_TraceRay( ((FindClosestPassableSpace_TraceAdapter_Portal_t *)pTraceAdapter)->pPortal, ray, pTraceAdapter->fMask, pTraceAdapter->pTraceFilter, pResult, true );
}
static bool PortalPointOutsideWorldFunc( const Vector &vTest, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter )
{
trace_t tr;
Ray_t testRay;
testRay.Init( vTest, vTest );
CTraceFilterWorldOnly traceFilter;
UTIL_Portal_TraceRay( ((FindClosestPassableSpace_TraceAdapter_Portal_t *)pTraceAdapter)->pPortal, testRay, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
return tr.startsolid;
}
bool UTIL_FindClosestPassableSpace_InPortal( const CPortal_Base2D *pPortal, const Vector &vCenter, const Vector &vExtents, const Vector &vIndecisivePush, ITraceFilter *pTraceFilter, unsigned int fMask, unsigned int iIterations, Vector &vCenterOut )
{
FindClosestPassableSpace_TraceAdapter_Portal_t adapter;
adapter.pTraceFunc = PortalTraceFunc;
adapter.pPointOutsideWorldFunc = PortalPointOutsideWorldFunc;
adapter.pTraceFilter = pTraceFilter;
adapter.fMask = fMask;
adapter.pPortal = pPortal;
return UTIL_FindClosestPassableSpace( vCenter, vExtents, vIndecisivePush, iIterations, vCenterOut, FL_AXIS_DIRECTION_NONE, &adapter );
}
struct FindClosestPassableSpace_TraceAdapter_Portal_StayInFront_t : FindClosestPassableSpace_TraceAdapter_Portal_t
{
VPlane shiftedPlane;//we only want the center of the starting box to stay in front, not every trace. So we create a plane of solidity that is not necessarily coplanar with the portal quad
Vector vExtentSigns; //when testing planar distance we need to use the extent closest to the solidity plane, multiply the ray extents by these to get that point.
};
static const Ray_t *AdjustRayToPlane( const Ray_t &originalRay, Ray_t &alterableRay, const Vector &vExtentSigns, const VPlane &Plane, float &fDeltaScale )
{
Vector vExtentShift;
vExtentShift.x = originalRay.m_Extents.x * vExtentSigns.x;
vExtentShift.y = originalRay.m_Extents.y * vExtentSigns.y;
vExtentShift.z = originalRay.m_Extents.z * vExtentSigns.z;
float fExtentShiftDist = Plane.m_Normal.Dot( vExtentShift );
if( (Plane.DistTo( originalRay.m_Start ) + fExtentShiftDist) < 0.0f )
{
//start solid
NULL;
}
else if( (Plane.DistTo( originalRay.m_Start + originalRay.m_Delta ) + fExtentShiftDist) < 0.0f )
{
//end will be behind shifted plane, shorten the delta now, then expand the results on the tail end
alterableRay = originalRay;
float fEndDist = (Plane.DistTo( originalRay.m_Start + originalRay.m_Delta ) + fExtentShiftDist);
float fDeltaLength = originalRay.m_Delta.Length();
Vector vDeltaNormalized = originalRay.m_Delta / fDeltaLength;
float fNewDeltaLength = fDeltaLength - (Plane.m_Normal.Dot( vDeltaNormalized ) * fEndDist);
alterableRay.m_Delta = vDeltaNormalized * fNewDeltaLength;
fDeltaScale = fNewDeltaLength / fDeltaLength;
return &alterableRay;
}
else
{
fDeltaScale = 1.0f;
}
return &originalRay;
}
static void PortalTraceFunc_CenterMustStayInFront( const Ray_t &ray, trace_t *pResult, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter )
{
const FindClosestPassableSpace_TraceAdapter_Portal_StayInFront_t *pCastedData = (const FindClosestPassableSpace_TraceAdapter_Portal_StayInFront_t *)pTraceAdapter;
float fDeltaScale;
Ray_t alterableRay;
const Ray_t *pUseRay = AdjustRayToPlane( ray, alterableRay, pCastedData->vExtentSigns, pCastedData->shiftedPlane, fDeltaScale );
if( pUseRay == NULL )
{
//start solid
UTIL_ClearTrace( *pResult );
pResult->startsolid = true;
pResult->fraction = 0.0f;
pResult->allsolid = true; //TODO: bother calculating if it leaves solid?
return;
}
UTIL_Portal_TraceRay( pCastedData->pPortal, *pUseRay, pTraceAdapter->fMask, pTraceAdapter->pTraceFilter, pResult, true );
if( pUseRay == &alterableRay )
{
//fixup any abnormalities from using a shortened ray
if( !pResult->DidHit() )
{
pResult->m_pEnt = (CPortal_Base2D *)pCastedData->pPortal;
pResult->plane = pCastedData->pPortal->m_plane_Origin;
pResult->plane.dist = pCastedData->shiftedPlane.m_Dist;
}
pResult->fraction *= fDeltaScale;
pResult->fractionleftsolid *= fDeltaScale;
}
}
static bool PortalPointOutsideWorldFunc_CenterMustStayInFront( const Vector &vTest, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter )
{
const FindClosestPassableSpace_TraceAdapter_Portal_StayInFront_t *pCastedData = (const FindClosestPassableSpace_TraceAdapter_Portal_StayInFront_t *)pTraceAdapter;
if( pCastedData->shiftedPlane.DistTo( vTest ) < 0.0f )
return true;
return PortalPointOutsideWorldFunc( vTest, pTraceAdapter );
}
bool UTIL_FindClosestPassableSpace_InPortal_CenterMustStayInFront( const CPortal_Base2D *pPortal, const Vector &vCenter, const Vector &vExtents, const Vector &vIndecisivePush, ITraceFilter *pTraceFilter, unsigned int fMask, unsigned int iIterations, Vector &vCenterOut )
{
FindClosestPassableSpace_TraceAdapter_Portal_StayInFront_t adapter;
adapter.pTraceFunc = PortalTraceFunc_CenterMustStayInFront;
adapter.pPointOutsideWorldFunc = PortalPointOutsideWorldFunc_CenterMustStayInFront;
adapter.pTraceFilter = pTraceFilter;
adapter.fMask = fMask;
adapter.pPortal = pPortal;
adapter.vExtentSigns.x = -Sign( pPortal->m_plane_Origin.normal.x );
adapter.vExtentSigns.y = -Sign( pPortal->m_plane_Origin.normal.y );
adapter.vExtentSigns.z = -Sign( pPortal->m_plane_Origin.normal.z );
//when caclulating the shift plane, all we need to be sure of is that the most penetrating extent is coplanar with the shift plane when the center would be coplanar with the original plane
Vector vCoplanarExtent;
vCoplanarExtent.x = vExtents.x * adapter.vExtentSigns.x;
vCoplanarExtent.y = vExtents.y * adapter.vExtentSigns.y;
vCoplanarExtent.z = vExtents.z * adapter.vExtentSigns.z;
adapter.shiftedPlane.m_Normal = pPortal->m_plane_Origin.normal;
adapter.shiftedPlane.m_Dist = pPortal->m_plane_Origin.dist + (pPortal->m_plane_Origin.normal.Dot( vCoplanarExtent ) ); //the dot is known to be negative, shifting the plane back so the extent is coplanar with it.
return UTIL_FindClosestPassableSpace( vCenter, vExtents, vIndecisivePush, iIterations, vCenterOut, FL_AXIS_DIRECTION_NONE, &adapter );
}
struct FindClosestPassableSpace_TraceAdapter_Engine_StayInFront_t : FindClosestPassableSpace_TraceAdapter_t
{
VPlane shiftedPlane;//we only want the center of the starting box to stay in front, not every trace. So we create a plane of solidity that is not necessarily coplanar with the portal quad
Vector vExtentSigns; //when testing planar distance we need to use the extent closest to the solidity plane, multiply the ray extents by these to get that point.
};
static void EngineTraceFunc_CenterMustStayInFront( const Ray_t &ray, trace_t *pResult, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter )
{
const FindClosestPassableSpace_TraceAdapter_Engine_StayInFront_t *pCastedData = (const FindClosestPassableSpace_TraceAdapter_Engine_StayInFront_t *)pTraceAdapter;
float fDeltaScale;
Ray_t alterableRay;
const Ray_t *pUseRay = AdjustRayToPlane( ray, alterableRay, pCastedData->vExtentSigns, pCastedData->shiftedPlane, fDeltaScale );
if( pUseRay == NULL )
{
//start solid
UTIL_ClearTrace( *pResult );
pResult->startsolid = true;
pResult->fraction = 0.0f;
pResult->allsolid = true; //TODO: bother calculating if it leaves solid?
return;
}
enginetrace->TraceRay( *pUseRay, pTraceAdapter->fMask, pTraceAdapter->pTraceFilter, pResult );
if( pUseRay == &alterableRay )
{
//fixup any abnormalities from using a shortened ray
if( !pResult->DidHit() )
{
#ifdef GAME_DLL
pResult->m_pEnt = GetWorldEntity();
#else
pResult->m_pEnt = GetClientWorldEntity();
#endif
pResult->plane.normal = pCastedData->shiftedPlane.m_Normal;
pResult->plane.dist = pCastedData->shiftedPlane.m_Dist;
}
pResult->fraction *= fDeltaScale;
pResult->fractionleftsolid *= fDeltaScale;
}
}
static bool EnginePointOutsideWorldFunc_CenterMustStayInFront( const Vector &vTest, FindClosestPassableSpace_TraceAdapter_t *pTraceAdapter )
{
const FindClosestPassableSpace_TraceAdapter_Engine_StayInFront_t *pCastedData = (const FindClosestPassableSpace_TraceAdapter_Engine_StayInFront_t *)pTraceAdapter;
if( pCastedData->shiftedPlane.DistTo( vTest ) < 0.0f )
return true;
return enginetrace->PointOutsideWorld( vTest );
}
bool UTIL_FindClosestPassableSpace_CenterMustStayInFrontOfPlane( const Vector &vCenter, const Vector &vExtents, const Vector &vIndecisivePush, ITraceFilter *pTraceFilter, unsigned int fMask, unsigned int iIterations, Vector &vCenterOut, const VPlane &stayInFrontOfPlane )
{
FindClosestPassableSpace_TraceAdapter_Engine_StayInFront_t adapter;
adapter.pTraceFunc = EngineTraceFunc_CenterMustStayInFront;
adapter.pPointOutsideWorldFunc = EnginePointOutsideWorldFunc_CenterMustStayInFront;
adapter.pTraceFilter = pTraceFilter;
adapter.fMask = fMask;
adapter.vExtentSigns.x = -Sign( stayInFrontOfPlane.m_Normal.x );
adapter.vExtentSigns.y = -Sign( stayInFrontOfPlane.m_Normal.y );
adapter.vExtentSigns.z = -Sign( stayInFrontOfPlane.m_Normal.z );
//when caclulating the shift plane, all we need to be sure of is that the most penetrating extent is coplanar with the shift plane when the center would be coplanar with the original plane
Vector vCoplanarExtent;
vCoplanarExtent.x = vExtents.x * adapter.vExtentSigns.x;
vCoplanarExtent.y = vExtents.y * adapter.vExtentSigns.y;
vCoplanarExtent.z = vExtents.z * adapter.vExtentSigns.z;
adapter.shiftedPlane.m_Normal = stayInFrontOfPlane.m_Normal;
adapter.shiftedPlane.m_Dist = stayInFrontOfPlane.m_Dist + (stayInFrontOfPlane.m_Normal.Dot( vCoplanarExtent ) ); //the dot is known to be negative, shifting the plane back so the extent is coplanar with it.
return UTIL_FindClosestPassableSpace( vCenter, vExtents, vIndecisivePush, iIterations, vCenterOut, FL_AXIS_DIRECTION_NONE, &adapter );
}
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( IsGameConsole() || iFailCount != 100, "FindClosestPassableSpace() failure." );
return false;
}
CPortal_Base2D *UTIL_PointIsOnPortalQuad( const Vector vPoint, float fOnPlaneEpsilon, CPortal_Base2D * const *pPortalsToCheck, int iArraySize )
{
if( pPortalsToCheck == NULL )
return NULL;
if( iArraySize == 0 )
return NULL;
CPortal_Base2D **pPlanarCandidates = (CPortal_Base2D **)stackalloc( sizeof( CPortal_Base2D * ) * iArraySize );
int iPlanarCandidateCount = 0;
for( int i = 0; i != iArraySize; ++i )
{
if( fabs( pPortalsToCheck[i]->m_plane_Origin.normal.Dot( vPoint ) - pPortalsToCheck[i]->m_plane_Origin.dist ) < fOnPlaneEpsilon )
{
pPlanarCandidates[iPlanarCandidateCount] = pPortalsToCheck[i];
++iPlanarCandidateCount;
}
}
if( iPlanarCandidateCount == 0 )
return NULL;
for( int i = 0; i != iPlanarCandidateCount; ++i )
{
Vector vDiff = vPoint - pPlanarCandidates[i]->m_ptOrigin;
if( (fabs( vDiff.Dot( pPlanarCandidates[i]->m_vRight ) ) < pPlanarCandidates[i]->GetHalfWidth()) &&
(fabs( vDiff.Dot( pPlanarCandidates[i]->m_vUp ) ) < pPlanarCandidates[i]->GetHalfHeight()) )
{
return pPlanarCandidates[i];
}
}
return NULL;
}
bool UTIL_Portal_EntityIsInPortalHole( const CPortal_Base2D *pPortal, const CBaseEntity *pEntity )
{
const 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;
return OBBHasFullyContainedIntersectionWithQuad( vForward, vRight, vUp, ptOBBCenter,
pPortal->m_vForward, pPortal->m_vForward.Dot( pPortal->m_ptOrigin ), pPortal->m_ptOrigin,
pPortal->m_vRight, pPortal->GetHalfWidth() + 1.0f, pPortal->m_vUp, pPortal->GetHalfHeight() + 1.0f );
}
const Vector UTIL_ProjectPointOntoPlane( const Vector& point, const cplane_t& plane )
{
return point - (DotProduct( point, plane.normal ) - plane.dist) * plane.normal;
}
bool UTIL_PointIsNearPortal( const Vector& point, const CPortal_Base2D* pPortal2D, float planeDist, float radiusReduction )
{
AssertMsg( pPortal2D != NULL, "Null pointers are bad, and you should feel bad." );
const cplane_t& portalPlane = pPortal2D->m_plane_Origin;
Vector transformedPt, origin;
pPortal2D->WorldToEntitySpace( point, &transformedPt );
pPortal2D->WorldToEntitySpace( UTIL_ProjectPointOntoPlane( pPortal2D->WorldSpaceCenter(), portalPlane ), &origin );
AssertMsg( pPortal2D->GetHalfWidth() > radiusReduction && pPortal2D->GetHalfHeight() > radiusReduction, "Reduction of the box is too high." );
const float halfWidth = pPortal2D->GetHalfWidth() - radiusReduction;
const float halfHeight = pPortal2D->GetHalfHeight() - radiusReduction;
const Vector boxMin( origin[0] - planeDist, origin[1] - halfWidth, origin[2] - halfHeight );
const Vector boxMax( origin[0] + planeDist, origin[1] + halfWidth, origin[2] + halfHeight );
return (transformedPt[0] >= boxMin[0] && transformedPt[0] <= boxMax[0]) &&
(transformedPt[1] >= boxMin[1] && transformedPt[1] <= boxMax[1]) &&
(transformedPt[2] >= boxMin[2] && transformedPt[2] <= boxMax[2]);
}
bool UTIL_IsEntityMovingOrRotating( CBaseEntity* pEntity )
{
Vector vLinearVelocity;
AngularImpulse vAngularVelocity;
IPhysicsObject *pEntityPhysicsObject = pEntity->VPhysicsGetObject();
#ifdef GAME_DLL
if ( pEntityPhysicsObject )
{
pEntityPhysicsObject->GetVelocity( &vLinearVelocity, &vAngularVelocity );
}
else
{
pEntity->GetVelocity( &vLinearVelocity, &vAngularVelocity );
}
#else
vLinearVelocity = pEntity->GetAbsVelocity();
// vAngularVelocity = vec3_origin; //TODO: Find client equivalent of server code above for angular impulse
QAngleToAngularImpulse( pEntity->GetLocalAngularVelocity(), vAngularVelocity );
#endif
if ( pEntity->GetAbsVelocity() != vec3_origin )
return true;
//ugh, func_brush attached to an animating entity doesn't give the entity a velocity. Check implicit velocity
if( pEntityPhysicsObject && pEntityPhysicsObject->IsMoveable() )
{
Vector vOtherImplicitVelocity;
pEntityPhysicsObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
if( vOtherImplicitVelocity.LengthSqr() > 1e-1 )
return true;
}
#ifdef GAME_DLL
CBaseEntity *pCheck = pEntity;
while( pCheck )
{
if( pCheck->GetLocalAngularVelocity() != vec3_angle )
return true;
pCheck = pCheck->GetParent();
}
#endif
// don't check for the speed, and check for position offset from when the portal is placed instead.
/*if ( vLinearVelocity.LengthSqr() > 1e-1 || vAngularVelocity.LengthSqr() > 1e-1 )
return true;*/
return false;
}
#ifdef CLIENT_DLL
void UTIL_TransformInterpolatedAngle( CInterpolatedVar< QAngle > &qInterped, matrix3x4_t matTransform, float fUpToTime )
{
int iHead = qInterped.GetHead();
if( !qInterped.IsValidIndex( iHead ) )
return;
#ifdef _DEBUG
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 < fUpToTime )
*pCurrent = TransformAnglesToWorldSpace( *pCurrent, matTransform );
iCurrent = qInterped.GetNext( iCurrent );
if( iCurrent == iHead )
break;
}
//qInterped.Interpolate( gpGlobals->curtime );
}
void UTIL_TransformInterpolatedPosition( CInterpolatedVar< Vector > &vInterped, const VMatrix& matTransform, float fUpToTime )
{
int iHead = vInterped.GetHead();
if( !vInterped.IsValidIndex( iHead ) )
return;
#ifdef _DEBUG
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 < fUpToTime )
{
*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
bool IsIn180( float f )
{
return f >= 0.f && f <= 180.f;
}
bool IsInNeg180( float f )
{
return f < 0.f && f >= -180.f;
}
float DistTo180( float f )
{
if ( IsIn180( f ) )
{
return 180.f - f;
}
else if ( IsInNeg180( f ) )
{
return -180 - f;
}
else
{
Assert("We are out of bound [-180,180]");
return 0.f;
}
}
void UTIL_NormalizedAngleDiff( const QAngle& start, const QAngle& end, QAngle* result )
{
if ( result )
{
QAngle angles;
for ( int i=0; i<3; ++i )
{
float a = start[ i ];
float b = end[ i ];
float distAto180 = DistTo180( a );
float distBto180 = DistTo180( b );
bool bUse180 = ( fabs( distAto180 ) + fabs( distBto180 ) ) < ( fabs( a ) + fabs( b ) );
bool bDiffSign = Sign( a ) * Sign( b ) < 0;
if ( bDiffSign )
{
if ( bUse180 )
{
angles[ i ] = distBto180 - distAto180;
}
else
{
angles[ i ] = a - b;
}
}
else
{
angles[ i ] = a - b;
}
}
*result = -angles;
}
}
//it turns out that using MatrixInverseTR() is theoretically correct. But we need to ensure that these matrices match exactly on the client/server.
//And computing inverses screws that up just enough (differences of ~0.00005 in the translation some times) to matter. So we compute each from scratch every time
#if defined( CLIENT_DLL )
void UTIL_Portal_ComputeMatrix_ForReal( CPortalRenderable_FlatBasic *pLocalPortal, CPortalRenderable_FlatBasic *pRemotePortal )
#else
void UTIL_Portal_ComputeMatrix_ForReal( CPortal_Base2D *pLocalPortal, CPortal_Base2D *pRemotePortal )
#endif
{
VMatrix worldToLocal_Rotated;
worldToLocal_Rotated.m[0][0] = -pLocalPortal->m_vForward.x;
worldToLocal_Rotated.m[0][1] = -pLocalPortal->m_vForward.y;
worldToLocal_Rotated.m[0][2] = -pLocalPortal->m_vForward.z;
worldToLocal_Rotated.m[0][3] = ((Vector)pLocalPortal->m_ptOrigin).Dot( pLocalPortal->m_vForward );
worldToLocal_Rotated.m[1][0] = pLocalPortal->m_vRight.x;
worldToLocal_Rotated.m[1][1] = pLocalPortal->m_vRight.y;
worldToLocal_Rotated.m[1][2] = pLocalPortal->m_vRight.z;
worldToLocal_Rotated.m[1][3] = -((Vector)pLocalPortal->m_ptOrigin).Dot( pLocalPortal->m_vRight );
worldToLocal_Rotated.m[2][0] = pLocalPortal->m_vUp.x;
worldToLocal_Rotated.m[2][1] = pLocalPortal->m_vUp.y;
worldToLocal_Rotated.m[2][2] = pLocalPortal->m_vUp.z;
worldToLocal_Rotated.m[2][3] = -((Vector)pLocalPortal->m_ptOrigin).Dot( pLocalPortal->m_vUp );
worldToLocal_Rotated.m[3][0] = 0.0f;
worldToLocal_Rotated.m[3][1] = 0.0f;
worldToLocal_Rotated.m[3][2] = 0.0f;
worldToLocal_Rotated.m[3][3] = 1.0f;
VMatrix remoteToWorld( pRemotePortal->m_vForward, -pRemotePortal->m_vRight, pRemotePortal->m_vUp );
remoteToWorld.SetTranslation( pRemotePortal->m_ptOrigin );
//final
pLocalPortal->m_matrixThisToLinked = remoteToWorld * worldToLocal_Rotated;
}
//MUST be a shared function to prevent floating point precision weirdness in the 100,000th decimal place between client/server that we're attributing to differing register usage.
#if defined( CLIENT_DLL )
void UTIL_Portal_ComputeMatrix( CPortalRenderable_FlatBasic *pLocalPortal, CPortalRenderable_FlatBasic *pRemotePortal )
#else
void UTIL_Portal_ComputeMatrix( CPortal_Base2D *pLocalPortal, CPortal_Base2D *pRemotePortal )
#endif
{
if ( pRemotePortal != NULL )
{
UTIL_Portal_ComputeMatrix_ForReal( pLocalPortal, pRemotePortal );
UTIL_Portal_ComputeMatrix_ForReal( pRemotePortal, pLocalPortal );
}
else
{
pLocalPortal->m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
}
}
CEG_NOINLINE bool UTIL_IsPaintableSurface( const csurface_t& surface )
{
CEG_GCV_PRE();
static const unsigned short CEG_SURF_NO_PAINT_FLAG = CEG_GET_CONSTANT_VALUE( SurfNoPaintFlag );
CEG_GCV_POST();
return !( surface.flags & CEG_SURF_NO_PAINT_FLAG );
}
float UTIL_PaintBrushEntity( CBaseEntity* pBrushEntity, const Vector& contactPoint, PaintPowerType power, float flPaintRadius, float flAlphaPercent )
{
if ( !pBrushEntity )
{
// HACK HACK: Fix it for real Bank! :)
return 0.0f;
}
if ( !pBrushEntity->IsBSPModel() )
{
return 0.0f;
}
Vector vEntitySpaceContactPoint;
pBrushEntity->WorldToEntitySpace( contactPoint, &vEntitySpaceContactPoint );
if ( !engine->SpherePaintSurface( pBrushEntity->GetModel(), vEntitySpaceContactPoint, power, flPaintRadius, flAlphaPercent ) )
return 0.0f;
return flPaintRadius;
}
PaintPowerType UTIL_Paint_TracePower( CBaseEntity* pBrushEntity, const Vector& contactPoint, const Vector& vContactNormal )
{
if ( !pBrushEntity->IsBSPModel() )
{
return NO_POWER;
}
CUtlVector<BYTE> color;
// Transform contact point from world to entity space
Vector vEntitySpaceContactPoint;
pBrushEntity->WorldToEntitySpace( contactPoint, &vEntitySpaceContactPoint );
// transform contact normal
Vector vTransformedContactNormal;
VectorRotate( vContactNormal, -pBrushEntity->GetAbsAngles(), vTransformedContactNormal );
engine->SphereTracePaintSurface( pBrushEntity->GetModel(), vEntitySpaceContactPoint, vTransformedContactNormal, sv_paint_detection_sphere_radius.GetFloat(), color );
return MapColorToPower( color );
}
bool UTIL_Paint_Reflect( const trace_t& tr, Vector& vStart, Vector& vDir, PaintPowerType reflectPower /* = REFLECT_POWER */ )
{
// check for reflect paint
if ( engine->HasPaintmap() && tr.m_pEnt && tr.m_pEnt->IsBSPModel() )
{
PaintPowerType power = UTIL_Paint_TracePower( tr.m_pEnt, tr.endpos, tr.plane.normal );
if ( power == reflectPower )
{
Vector vecIn = tr.endpos - tr.startpos;
if ( DotProduct( vecIn.Normalized(), tr.plane.normal ) > -0.99f )
{
Vector vecReflect = vecIn - 2 * DotProduct( vecIn, tr.plane.normal ) * tr.plane.normal;
vStart = tr.endpos;
vDir = vecReflect.Normalized();
// FIXME: Bring this back for DLC2
//if ( reflect_paint_vertical_snap.GetBool() )
{
float flDot = DotProduct( tr.plane.normal, Vector(0.f,0.f,1.f) );
if ( vDir.z > 0.1 && flDot > 0 && flDot < 1 )
{
vDir = Vector( 0.f, 0.f, 1.f );
}
else if( vDir.z < -0.1f && flDot > 0 && flDot < 1 )
{
vDir = Vector( 0.f, 0.f, -1.f );
}
}
return true;
}
}
}
return false;
}
//Extends a radius through portals. For various radius effects like explosions and pushing
void ExtendRadiusThroughPortals( const Vector &vecOrigin, const QAngle &vecAngles, float flRadius, PortalRadiusExtensionVector &portalRadiusExtensions )
{
//Always add the original point
PortalRadiusExtension_t extension;
extension.pPortalFrom = NULL;
extension.pPortalTo = NULL;
extension.vecOrigin = vecOrigin;
extension.vecAngles = vecAngles;
portalRadiusExtensions.AddToTail( extension );
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
if( iPortalCount != 0 )
{
CPortal_Base2D **pAllPortals = CPortal_Base2D_Shared::AllPortals.Base();
CUtlVector<CPortal_Base2D*> testedPortals; //The portals that have already been tested
for( int i = 0; i != iPortalCount; ++i )
{
CPortal_Base2D *pPortal = pAllPortals[i];
//If this portal exists and is linked
if( !pPortal || !pPortal->IsActivedAndLinked() )
{
continue;
}
//Check if this portal has already been tested
int iTestedPortalsCount = testedPortals.Count();
bool bTestThroughPortal = true;
for( int j = 0; j < iTestedPortalsCount; ++j )
{
//If either this portal or it's linked partner has been tested, then don't test it again
if( testedPortals[j] == pPortal || testedPortals[j] == pPortal->GetLinkedPortal() )
{
bTestThroughPortal = false;
break;
}
}
//If we should test through this portal
if( bTestThroughPortal )
{
//If this portal is near our radius
float flDistSqr = pPortal->GetAbsOrigin().DistToSqr( vecOrigin );
if( flDistSqr <= flRadius * flRadius )
{
//Check which portal is facing the origin.
Vector vecOriginForward;
AngleVectors( vecAngles, &vecOriginForward );
Vector vecPortal1ToOrigin = pPortal->GetAbsOrigin() - vecOrigin;
VectorNormalize( vecPortal1ToOrigin );
Vector vecPortal2ToOrigin = pPortal->m_hLinkedPortal.Get()->GetAbsOrigin() - vecOrigin;
VectorNormalize( vecPortal2ToOrigin );
float flDot1 = 1.0f - abs( DotProduct( vecPortal1ToOrigin, vecOriginForward ) );
float flDot2 = 1.0f - abs( DotProduct( vecPortal2ToOrigin, vecOriginForward ) );
//Use the portal that is facing the origin for the transformations
if( flDot2 < flDot1 )
{
pPortal = pPortal->m_hLinkedPortal.Get();
}
VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
//Transform the center to the other side of the portal
Vector vecTransformedOrigin;
UTIL_Portal_PointTransform( matThisToLinked, vecOrigin, vecTransformedOrigin );
//Transform the angles to the other side of the portal
QAngle vecTransformedAngles;
UTIL_Portal_AngleTransform( matThisToLinked, vecAngles, vecTransformedAngles );
//Extend the radius through this portal
PortalRadiusExtension_t extension;
extension.pPortalFrom = pPortal;
extension.pPortalTo = pPortal->m_hLinkedPortal.Get();
extension.vecOrigin = vecTransformedOrigin;
extension.vecAngles = vecTransformedAngles;
portalRadiusExtensions.AddToTail( extension );
//Add this portal to the list of tested portals
testedPortals.AddToTail( pPortal );
}
}
}// For all the portals in the level
} //If there are portals in the level
}
void UTIL_Portal_TraceRay_PreTraceChanges( const CPortal_Base2D *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall )
{
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.IsRayInPortalHole( ray ) != RIPHR_NOT_TOUCHING_HOLE) );
bool bCopyBackBrushTraceData = false;
// Traces vs world
if( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
{
//trace_t RealTrace;
//enginetrace->TraceRay( ray, fMask, pTraceFilter, &RealTrace );
if( sv_portal_trace_vs_world.GetBool() )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( portalSimulator.GetInternalData().Simulation.Static.World.Brushes.BrushSets ); ++iBrushSet )
{
if( ((portalSimulator.GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].iSolidMask & fMask) != 0) &&
portalSimulator.GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable )
{
physcollision->TraceBoxAA( ray, portalSimulator.GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable, &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.GetInternalData().Simulation.Static.World.Displacements.pCollideable && sv_portal_trace_vs_world.GetBool() && portal_clone_displacements.GetBool() )
{
physcollision->TraceBoxAA( ray, portalSimulator.GetInternalData().Simulation.Static.World.Displacements.pCollideable, &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( bTraceHolyWall )
{
if( portalSimulator.GetInternalData().Simulation.Static.Wall.Local.Tube.pCollideable )
{
physcollision->TraceBoxAA( ray, portalSimulator.GetInternalData().Simulation.Static.Wall.Local.Tube.pCollideable, &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;
}
}
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( portalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets ); ++iBrushSet )
{
if( ((portalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet].iSolidMask & fMask) != 0) &&
portalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet].pCollideable )
{
physcollision->TraceBoxAA( ray, portalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[iBrushSet].pCollideable, &TempTrace );
if( (TempTrace.fraction < pTrace->fraction) )
{
*pTrace = TempTrace;
bCopyBackBrushTraceData = true;
}
}
}
//if( portalSimulator.GetInternalData().Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() )
if( bTraceTransformedGeometry )
{
for( int iBrushSet = 0; iBrushSet != ARRAYSIZE( pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets ); ++iBrushSet )
{
if( ((pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].iSolidMask & fMask) != 0) &&
pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable )
{
physcollision->TraceBox( ray, pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.Brushes.BrushSets[iBrushSet].pCollideable, portalSimulator.GetInternalData().Placement.ptaap_LinkedToThis.ptOriginTransform, portalSimulator.GetInternalData().Placement.ptaap_LinkedToThis.qAngleTransform, &TempTrace );
if( (TempTrace.fraction < pTrace->fraction) )
{
*pTrace = TempTrace;
bCopyBackBrushTraceData = true;
}
}
}
}
}
if( bCopyBackBrushTraceData )
{
pTrace->surface = portalSimulator.GetInternalData().Simulation.Static.SurfaceProperties.surface;
pTrace->contents = portalSimulator.GetInternalData().Simulation.Static.SurfaceProperties.contents;
pTrace->m_pEnt = portalSimulator.GetInternalData().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 );
int PartitionMask;
#if defined( CLIENT_DLL )
PartitionMask = PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_STATIC_PROPS;
#else
PartitionMask = PARTITION_ENGINE_SOLID_EDICTS | PARTITION_ENGINE_STATIC_PROPS;
#endif
::partition->EnumerateElementsAlongRay( PartitionMask, 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;
}
CBaseEntity *pEnumeratedEntity = EntityFromEntityHandle( enumerator.m_pHandles[counter] );;
//If we have a carved representation of this entity, trace against that instead of the real thing
CPhysCollide *pCarvedCollide = portalSimulator.IsEntityCarvedByPortal( pEnumeratedEntity ) ? portalSimulator.GetCollideForCarvedEntity( pEnumeratedEntity ) : NULL;
if( pCarvedCollide != NULL )
{
ICollideable *pUncarvedCollideable = pEnumeratedEntity->GetCollideable();
physcollision->TraceBox( ray, pCarvedCollide, pUncarvedCollideable->GetCollisionOrigin(), pUncarvedCollideable->GetCollisionAngles(), &TempTrace );
if( (TempTrace.fraction < pTrace->fraction) )
{
//copy the trace data from the carved trace
*pTrace = TempTrace;
//then trace against the real thing for surface data.
//TODO: There's got to be a way to store this off and look it up intelligently. But I can't seem to find surface info without a trace, making the results only valid for that trace.
enginetrace->ClipRayToEntity( ray, fMask, enumerator.m_pHandles[counter], &TempTrace );
pTrace->contents = TempTrace.contents;
pTrace->surface = TempTrace.surface;
pTrace->m_pEnt = TempTrace.m_pEnt;
}
}
else
{
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.GetInternalData().Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
if( iLocalStaticCount != 0 && portalSimulator.GetInternalData().Simulation.Static.World.StaticProps.bCollisionExists )
{
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = portalSimulator.GetInternalData().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->GetInternalData().Simulation.Static.World.StaticProps.ClippedRepresentations.Count();
if( iLocalStaticCount != 0 )
{
const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pLinkedPortalSimulator->GetInternalData().Simulation.Static.World.StaticProps.ClippedRepresentations.Base();
const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount;
Vector vTransform = portalSimulator.GetInternalData().Placement.ptaap_LinkedToThis.ptOriginTransform;
QAngle qTransform = portalSimulator.GetInternalData().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.GetInternalData().Simulation.Static.SurfaceProperties.contents;
pTrace->surface = portalSimulator.GetInternalData().Simulation.Static.SurfaceProperties.surface;
pTrace->m_pEnt = portalSimulator.GetInternalData().Simulation.Static.SurfaceProperties.pEntity;
}
}
void UTIL_Portal_Laser_Prevent_Tilting( Vector& vDirection )
{
if ( fabs( vDirection.z ) < 0.1 )
{
vDirection.z = 0.f;
vDirection.NormalizeInPlace();
}
}
void UTIL_DebugOverlay_Polyhedron( const CPolyhedron *pPolyhedron, int red, int green, int blue, bool noDepthTest, float flDuration, const matrix3x4_t *pTransform )
{
const Vector *pPoints;
if( pTransform )
{
Vector *pPointsTransform;
pPoints = pPointsTransform = (Vector *)stackalloc( sizeof( Vector ) * pPolyhedron->iVertexCount );
for( int i = 0; i != pPolyhedron->iVertexCount; ++i )
{
VectorTransform( pPolyhedron->pVertices[i], *pTransform, pPointsTransform[i] );
}
}
else
{
pPoints = pPolyhedron->pVertices;
}
for( int i = 0; i != pPolyhedron->iLineCount; ++i )
{
NDebugOverlay::Line( pPoints[pPolyhedron->pLines[i].iPointIndices[0]], pPoints[pPolyhedron->pLines[i].iPointIndices[1]], red, green, blue, noDepthTest, flDuration );
}
}
void UTIL_DebugOverlay_CPhysCollide( const CPhysCollide *pCollide, int red, int green, int blue, bool noDepthTest, float flDuration, const matrix3x4_t *pTransform )
{
Vector *outVerts;
int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts );
int triCount = vertCount / 3;
const Vector *pPoints;
if( pTransform )
{
Vector *pPointsTransform;
pPoints = pPointsTransform = (Vector *)stackalloc( sizeof( Vector ) * vertCount );
for( int i = 0; i != vertCount; ++i )
{
VectorTransform( outVerts[i], *pTransform, pPointsTransform[i] );
}
}
else
{
pPoints = outVerts;
}
for( int i = 0; i != triCount; ++i )
{
int iStartVert = (i * 3);
NDebugOverlay::Line( pPoints[iStartVert], pPoints[iStartVert + 1], red, green, blue, noDepthTest, flDuration );
NDebugOverlay::Line( pPoints[iStartVert + 1], pPoints[iStartVert + 2], red, green, blue, noDepthTest, flDuration );
NDebugOverlay::Line( pPoints[iStartVert + 2], pPoints[iStartVert], red, green, blue, noDepthTest, flDuration );
}
physcollision->DestroyDebugMesh( vertCount, outVerts );
}
bool UTIL_IsCollideableIntersectingPhysCollide( ICollideable *pCollideable, const CPhysCollide *pCollide, const Vector &vPhysCollideOrigin, const QAngle &qPhysCollideAngles )
{
vcollide_t *pVCollide = modelinfo->GetVCollide( pCollideable->GetCollisionModel() );
trace_t Trace;
if( pVCollide != NULL )
{
Vector ptEntityPosition = pCollideable->GetCollisionOrigin();
QAngle qEntityAngles = pCollideable->GetCollisionAngles();
for( int i = 0; i != pVCollide->solidCount; ++i )
{
physcollision->TraceCollide( ptEntityPosition, ptEntityPosition, pVCollide->solids[i], qEntityAngles, pCollide, vPhysCollideOrigin, qPhysCollideAngles, &Trace );
if( Trace.startsolid )
return true;
}
}
else
{
//use AABB
Vector vMins, vMaxs, ptCenter;
pCollideable->WorldSpaceSurroundingBounds( &vMins, &vMaxs );
ptCenter = (vMins + vMaxs) * 0.5f;
vMins -= ptCenter;
vMaxs -= ptCenter;
physcollision->TraceBox( ptCenter, ptCenter, vMins, vMaxs, pCollide, vPhysCollideOrigin, qPhysCollideAngles, &Trace );
return Trace.startsolid;
}
return false;
}
CBasePlayer* UTIL_OtherPlayer( CBasePlayer const* pPlayer )
{
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer* pOtherPlayer = UTIL_PlayerByIndex( i );
if ( pOtherPlayer != NULL && pOtherPlayer != pPlayer )
return pOtherPlayer;
}
return NULL;
}
#ifdef GAME_DLL
CBasePlayer* UTIL_OtherConnectedPlayer( CBasePlayer const* pPlayer )
{
CBasePlayer *pOtherPlayer = UTIL_OtherPlayer( pPlayer );
if ( pOtherPlayer && pOtherPlayer->IsConnected() )
{
return pOtherPlayer;
}
return NULL;
}
bool CBrushEntityList::EnumEntity( IHandleEntity *pHandleEntity )
{
if ( !pHandleEntity )
return true;
CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
if ( !pEntity )
return true;
if ( pEntity->IsBSPModel() )
{
m_BrushEntitiesToPaint.AddToTail( pEntity );
}
return true;
}
void UTIL_FindBrushEntitiesInSphere( CBrushEntityList& brushEnum, const Vector& vCenter, float flRadius )
{
Vector vExtents = flRadius * Vector( 1.f, 1.f, 1.f );
enginetrace->EnumerateEntities( vCenter - vExtents, vCenter + vExtents, &brushEnum );
}
#endif