You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2010 lines
63 KiB
2010 lines
63 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "engine/IEngineTrace.h" |
|
#include "icliententitylist.h" |
|
#include "ispatialpartitioninternal.h" |
|
#include "icliententity.h" |
|
#include "cmodel_engine.h" |
|
#include "dispcoll_common.h" |
|
#include "staticpropmgr.h" |
|
#include "server.h" |
|
#include "edict.h" |
|
#include "gl_model_private.h" |
|
#include "world.h" |
|
#include "vphysics_interface.h" |
|
#include "client_class.h" |
|
#include "server_class.h" |
|
#include "debugoverlay.h" |
|
#include "collisionutils.h" |
|
#include "tier0/vprof.h" |
|
#include "convar.h" |
|
#include "mathlib/polyhedron.h" |
|
#include "sys_dll.h" |
|
#include "vphysics/virtualmesh.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Various statistics to gather |
|
//----------------------------------------------------------------------------- |
|
enum |
|
{ |
|
TRACE_STAT_COUNTER_TRACERAY = 0, |
|
TRACE_STAT_COUNTER_POINTCONTENTS, |
|
TRACE_STAT_COUNTER_ENUMERATE, |
|
NUM_TRACE_STAT_COUNTER |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to visualize raycasts going on |
|
//----------------------------------------------------------------------------- |
|
#ifdef _DEBUG |
|
|
|
ConVar debugrayenable( "debugrayenable", "0", NULL, "Use this to enable ray testing. To reset: bind \"F1\" \"clearalloverlays; debugrayreset 0; host_framerate 66.66666667\"" ); |
|
ConVar debugrayreset( "debugrayreset", "0" ); |
|
ConVar debugraylimit( "debugraylimit", "500", NULL, "number of rays per frame that you have to hit before displaying them all" ); |
|
static CUtlVector<Ray_t> s_FrameRays; |
|
#endif |
|
|
|
#define BENCHMARK_RAY_TEST 0 |
|
|
|
#if BENCHMARK_RAY_TEST |
|
static CUtlVector<Ray_t> s_BenchmarkRays; |
|
#endif |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Implementation of IEngineTrace |
|
//----------------------------------------------------------------------------- |
|
abstract_class CEngineTrace : public IEngineTrace |
|
{ |
|
public: |
|
CEngineTrace() { m_pRootMoveParent = NULL; } |
|
// Returns the contents mask at a particular world-space position |
|
virtual int GetPointContents( const Vector &vecAbsPosition, IHandleEntity** ppEntity ); |
|
|
|
virtual int GetPointContents_Collideable( ICollideable *pCollide, const Vector &vecAbsPosition ); |
|
|
|
// Traces a ray against a particular edict |
|
virtual void ClipRayToEntity( const Ray_t &ray, unsigned int fMask, IHandleEntity *pEntity, trace_t *pTrace ); |
|
|
|
// A version that simply accepts a ray (can work as a traceline or tracehull) |
|
virtual void TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ); |
|
|
|
// A version that sets up the leaf and entity lists and allows you to pass those in for collision. |
|
virtual void SetupLeafAndEntityListRay( const Ray_t &ray, CTraceListData &traceData ); |
|
virtual void SetupLeafAndEntityListBox( const Vector &vecBoxMin, const Vector &vecBoxMax, CTraceListData &traceData ); |
|
virtual void TraceRayAgainstLeafAndEntityList( const Ray_t &ray, CTraceListData &traceData, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ); |
|
|
|
// A version that sweeps a collideable through the world |
|
// abs start + abs end represents the collision origins you want to sweep the collideable through |
|
// vecAngles represents the collision angles of the collideable during the sweep |
|
virtual void SweepCollideable( ICollideable *pCollide, const Vector &vecAbsStart, const Vector &vecAbsEnd, |
|
const QAngle &vecAngles, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ); |
|
|
|
// Enumerates over all entities along a ray |
|
// If triggers == true, it enumerates all triggers along a ray |
|
virtual void EnumerateEntities( const Ray_t &ray, bool triggers, IEntityEnumerator *pEnumerator ); |
|
|
|
// Same thing, but enumerate entitys within a box |
|
virtual void EnumerateEntities( const Vector &vecAbsMins, const Vector &vecAbsMaxs, IEntityEnumerator *pEnumerator ); |
|
|
|
// FIXME: Different versions for client + server. Eventually we need to make these go away |
|
virtual void HandleEntityToCollideable( IHandleEntity *pHandleEntity, ICollideable **ppCollide, const char **ppDebugName ) = 0; |
|
virtual ICollideable *GetWorldCollideable() = 0; |
|
|
|
// Traces a ray against a particular edict |
|
virtual void ClipRayToCollideable( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace ); |
|
|
|
// HACKHACK: Temp |
|
virtual int GetStatByIndex( int index, bool bClear ); |
|
|
|
//finds brushes in an AABB, prone to some false positives |
|
virtual void GetBrushesInAABB( const Vector &vMins, const Vector &vMaxs, CUtlVector<int> *pOutput, int iContentsMask = 0xFFFFFFFF ); |
|
|
|
//Creates a CPhysCollide out of all displacements wholly or partially contained in the specified AABB |
|
virtual CPhysCollide* GetCollidableFromDisplacementsInAABB( const Vector& vMins, const Vector& vMaxs ); |
|
|
|
//retrieve brush planes and contents, returns true if data is being returned in the output pointers, false if the brush doesn't exist |
|
virtual bool GetBrushInfo( int iBrush, CUtlVector<Vector4D> *pPlanesOut, int *pContentsOut ); |
|
|
|
virtual bool PointOutsideWorld( const Vector &ptTest ); //Tests a point to see if it's outside any playable area |
|
|
|
|
|
// Walks bsp to find the leaf containing the specified point |
|
virtual int GetLeafContainingPoint( const Vector &ptTest ); |
|
|
|
private: |
|
// FIXME: Different versions for client + server. Eventually we need to make these go away |
|
virtual void SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace ) = 0; |
|
virtual ICollideable *GetCollideable( IHandleEntity *pEntity ) = 0; |
|
virtual int SpatialPartitionMask() const = 0; |
|
virtual int SpatialPartitionTriggerMask() const = 0; |
|
|
|
// Figure out point contents for entities at a particular position |
|
int EntityContents( const Vector &vecAbsPosition ); |
|
|
|
// Should we perform the custom raytest? |
|
bool ShouldPerformCustomRayTest( const Ray_t& ray, ICollideable *pCollideable ) const; |
|
|
|
// Performs the custom raycast |
|
bool ClipRayToCustom( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace ); |
|
|
|
// Perform vphysics trace |
|
bool ClipRayToVPhysics( const Ray_t &ray, unsigned int fMask, ICollideable *pCollideable, studiohdr_t *pStudioHdr, trace_t *pTrace ); |
|
|
|
// Perform hitbox trace |
|
bool ClipRayToHitboxes( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace ); |
|
|
|
// Perform bsp trace |
|
bool ClipRayToBSP( const Ray_t &ray, unsigned int fMask, ICollideable *pCollideable, trace_t *pTrace ); |
|
|
|
// bbox |
|
bool ClipRayToBBox( const Ray_t &ray, unsigned int fMask, ICollideable *pCollideable, trace_t *pTrace ); |
|
|
|
// OBB |
|
bool ClipRayToOBB( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace ); |
|
|
|
// Clips a trace to another trace |
|
bool ClipTraceToTrace( trace_t &clipTrace, trace_t *pFinalTrace ); |
|
private: |
|
int m_traceStatCounters[NUM_TRACE_STAT_COUNTER]; |
|
const matrix3x4_t *m_pRootMoveParent; |
|
friend void RayBench( const CCommand &args ); |
|
|
|
}; |
|
|
|
class CEngineTraceServer : public CEngineTrace |
|
{ |
|
private: |
|
virtual void HandleEntityToCollideable( IHandleEntity *pEnt, ICollideable **ppCollide, const char **ppDebugName ); |
|
virtual void SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace ); |
|
virtual int SpatialPartitionMask() const; |
|
virtual int SpatialPartitionTriggerMask() const; |
|
virtual ICollideable *GetWorldCollideable(); |
|
friend void RayBench( const CCommand &args ); |
|
public: |
|
// IEngineTrace |
|
virtual ICollideable *GetCollideable( IHandleEntity *pEntity ); |
|
}; |
|
|
|
#ifndef SWDS |
|
class CEngineTraceClient : public CEngineTrace |
|
{ |
|
private: |
|
virtual void HandleEntityToCollideable( IHandleEntity *pEnt, ICollideable **ppCollide, const char **ppDebugName ); |
|
virtual void SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace ); |
|
virtual int SpatialPartitionMask() const; |
|
virtual int SpatialPartitionTriggerMask() const; |
|
virtual ICollideable *GetWorldCollideable(); |
|
public: |
|
// IEngineTrace |
|
virtual ICollideable *GetCollideable( IHandleEntity *pEntity ); |
|
}; |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Expose CVEngineServer to the game + client DLLs |
|
//----------------------------------------------------------------------------- |
|
static CEngineTraceServer s_EngineTraceServer; |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEngineTraceServer, IEngineTrace, INTERFACEVERSION_ENGINETRACE_SERVER, s_EngineTraceServer); |
|
|
|
#ifndef SWDS |
|
static CEngineTraceClient s_EngineTraceClient; |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEngineTraceClient, IEngineTrace, INTERFACEVERSION_ENGINETRACE_CLIENT, s_EngineTraceClient); |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Expose CVEngineServer to the engine. |
|
//----------------------------------------------------------------------------- |
|
IEngineTrace *g_pEngineTraceServer = &s_EngineTraceServer; |
|
#ifndef SWDS |
|
IEngineTrace *g_pEngineTraceClient = &s_EngineTraceClient; |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Client-server neutral method of getting at collideables |
|
//----------------------------------------------------------------------------- |
|
#ifndef SWDS |
|
ICollideable *CEngineTraceClient::GetCollideable( IHandleEntity *pEntity ) |
|
{ |
|
Assert( pEntity ); |
|
|
|
ICollideable *pProp = StaticPropMgr()->GetStaticProp( pEntity ); |
|
if ( pProp ) |
|
return pProp; |
|
|
|
IClientUnknown *pUnk = entitylist->GetClientUnknownFromHandle( pEntity->GetRefEHandle() ); |
|
return pUnk->GetCollideable(); |
|
} |
|
#endif |
|
|
|
ICollideable *CEngineTraceServer::GetCollideable( IHandleEntity *pEntity ) |
|
{ |
|
Assert( pEntity ); |
|
|
|
ICollideable *pProp = StaticPropMgr()->GetStaticProp( pEntity ); |
|
if ( pProp ) |
|
return pProp; |
|
|
|
IServerUnknown *pNetUnknown = static_cast<IServerUnknown*>(pEntity); |
|
return pNetUnknown->GetCollideable(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Spatial partition masks for iteration |
|
//----------------------------------------------------------------------------- |
|
#ifndef SWDS |
|
int CEngineTraceClient::SpatialPartitionMask() const |
|
{ |
|
return PARTITION_CLIENT_SOLID_EDICTS; |
|
} |
|
#endif |
|
|
|
int CEngineTraceServer::SpatialPartitionMask() const |
|
{ |
|
return PARTITION_ENGINE_SOLID_EDICTS; |
|
} |
|
|
|
#ifndef SWDS |
|
int CEngineTraceClient::SpatialPartitionTriggerMask() const |
|
{ |
|
return 0; |
|
} |
|
#endif |
|
|
|
int CEngineTraceServer::SpatialPartitionTriggerMask() const |
|
{ |
|
return PARTITION_ENGINE_TRIGGER_EDICTS; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Spatial partition enumerator looking for entities that we may lie within |
|
//----------------------------------------------------------------------------- |
|
class CPointContentsEnum : public IPartitionEnumerator |
|
{ |
|
public: |
|
CPointContentsEnum( CEngineTrace *pEngineTrace, const Vector &pos ) : m_Contents(CONTENTS_EMPTY) |
|
{ |
|
m_pEngineTrace = pEngineTrace; |
|
m_Pos = pos; |
|
m_pCollide = NULL; |
|
} |
|
|
|
static inline bool TestEntity( |
|
CEngineTrace *pEngineTrace, |
|
ICollideable *pCollide, |
|
const Vector &vPos, |
|
int *pContents, |
|
ICollideable **pWorldCollideable ) |
|
{ |
|
// Deal with static props |
|
// NOTE: I could have added static props to a different list and |
|
// enumerated them separately, but that would have been less efficient |
|
if ( StaticPropMgr()->IsStaticProp( pCollide->GetEntityHandle() ) ) |
|
{ |
|
Ray_t ray; |
|
trace_t trace; |
|
ray.Init( vPos, vPos ); |
|
pEngineTrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &trace ); |
|
if (trace.startsolid) |
|
{ |
|
// We're in a static prop; that's solid baby |
|
// Pretend we hit the world |
|
*pContents = CONTENTS_SOLID; |
|
*pWorldCollideable = pEngineTrace->GetWorldCollideable(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
// We only care about solid volumes |
|
if ((pCollide->GetSolidFlags() & FSOLID_VOLUME_CONTENTS) == 0) |
|
return false; |
|
|
|
model_t* pModel = (model_t*)pCollide->GetCollisionModel(); |
|
if ( pModel && pModel->type == mod_brush ) |
|
{ |
|
Assert( pCollide->GetCollisionModelIndex() < MAX_MODELS && pCollide->GetCollisionModelIndex() >= 0 ); |
|
int nHeadNode = GetModelHeadNode( pCollide ); |
|
int contents = CM_TransformedPointContents( vPos, nHeadNode, |
|
pCollide->GetCollisionOrigin(), pCollide->GetCollisionAngles() ); |
|
|
|
if (contents != CONTENTS_EMPTY) |
|
{ |
|
// Return the contents of the first thing we hit |
|
*pContents = contents; |
|
*pWorldCollideable = pCollide; |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) |
|
{ |
|
ICollideable *pCollide; |
|
const char *pDbgName; |
|
m_pEngineTrace->HandleEntityToCollideable( pHandleEntity, &pCollide, &pDbgName ); |
|
if (!pCollide) |
|
return ITERATION_CONTINUE; |
|
|
|
if ( CPointContentsEnum::TestEntity( m_pEngineTrace, pCollide, m_Pos, &m_Contents, &m_pCollide ) ) |
|
return ITERATION_STOP; |
|
else |
|
return ITERATION_CONTINUE; |
|
} |
|
|
|
private: |
|
static int GetModelHeadNode( ICollideable *pCollide ) |
|
{ |
|
int modelindex = pCollide->GetCollisionModelIndex(); |
|
if(modelindex >= MAX_MODELS || modelindex < 0) |
|
return -1; |
|
|
|
model_t *pModel = (model_t*)pCollide->GetCollisionModel(); |
|
if(!pModel) |
|
return -1; |
|
|
|
if(cmodel_t *pCModel = CM_InlineModelNumber(modelindex-1)) |
|
return pCModel->headnode; |
|
else |
|
return -1; |
|
} |
|
|
|
public: |
|
int m_Contents; |
|
ICollideable *m_pCollide; |
|
|
|
private: |
|
CEngineTrace *m_pEngineTrace; |
|
Vector m_Pos; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the contents mask at a particular world-space position |
|
//----------------------------------------------------------------------------- |
|
int CEngineTrace::GetPointContents( const Vector &vecAbsPosition, IHandleEntity** ppEntity ) |
|
{ |
|
VPROF( "CEngineTrace_GetPointContents" ); |
|
// VPROF_BUDGET( "CEngineTrace_GetPointContents", "CEngineTrace_GetPointContents" ); |
|
|
|
m_traceStatCounters[TRACE_STAT_COUNTER_POINTCONTENTS]++; |
|
// First check the collision model |
|
int nContents = CM_PointContents( vecAbsPosition, 0 ); |
|
if ( nContents & MASK_CURRENT ) |
|
{ |
|
nContents = CONTENTS_WATER; |
|
} |
|
|
|
if ( nContents != CONTENTS_SOLID ) |
|
{ |
|
CPointContentsEnum contentsEnum(this, vecAbsPosition); |
|
SpatialPartition()->EnumerateElementsAtPoint( SpatialPartitionMask(), |
|
vecAbsPosition, false, &contentsEnum ); |
|
|
|
int nEntityContents = contentsEnum.m_Contents; |
|
if ( nEntityContents & MASK_CURRENT ) |
|
nContents = CONTENTS_WATER; |
|
if ( nEntityContents != CONTENTS_EMPTY ) |
|
{ |
|
if (ppEntity) |
|
{ |
|
*ppEntity = contentsEnum.m_pCollide->GetEntityHandle(); |
|
} |
|
|
|
return nEntityContents; |
|
} |
|
} |
|
|
|
if (ppEntity) |
|
{ |
|
*ppEntity = GetWorldCollideable()->GetEntityHandle(); |
|
} |
|
|
|
return nContents; |
|
} |
|
|
|
|
|
int CEngineTrace::GetPointContents_Collideable( ICollideable *pCollide, const Vector &vecAbsPosition ) |
|
{ |
|
int contents = CONTENTS_EMPTY; |
|
ICollideable *pDummy; |
|
CPointContentsEnum::TestEntity( this, pCollide, vecAbsPosition, &contents, &pDummy ); |
|
return contents; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Should we perform the custom raytest? |
|
//----------------------------------------------------------------------------- |
|
inline bool CEngineTrace::ShouldPerformCustomRayTest( const Ray_t& ray, ICollideable *pCollideable ) const |
|
{ |
|
// No model? The entity's got its own collision detector maybe |
|
// Does the entity force box or ray tests to go through its code? |
|
return( (pCollideable->GetSolid() == SOLID_CUSTOM) || |
|
(ray.m_IsRay && (pCollideable->GetSolidFlags() & FSOLID_CUSTOMRAYTEST )) || |
|
(!ray.m_IsRay && (pCollideable->GetSolidFlags() & FSOLID_CUSTOMBOXTEST )) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Performs the custom raycast |
|
//----------------------------------------------------------------------------- |
|
bool CEngineTrace::ClipRayToCustom( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace ) |
|
{ |
|
if ( pCollideable->TestCollision( ray, fMask, *pTrace )) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Performs the hitbox raycast, returns true if the hitbox test was made |
|
//----------------------------------------------------------------------------- |
|
bool CEngineTrace::ClipRayToHitboxes( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace ) |
|
{ |
|
trace_t hitboxTrace; |
|
CM_ClearTrace( &hitboxTrace ); |
|
|
|
// Keep track of the contents of what was hit initially |
|
hitboxTrace.contents = pTrace->contents; |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, hitboxTrace.startpos ); |
|
VectorAdd( hitboxTrace.startpos, ray.m_Delta, hitboxTrace.endpos ); |
|
|
|
// At the moment, it has to be a true ray to work with hitboxes |
|
if ( !ray.m_IsRay ) |
|
return false; |
|
|
|
// If the hitboxes weren't even tested, then just use the original trace |
|
if (!pCollideable->TestHitboxes( ray, fMask, hitboxTrace )) |
|
return false; |
|
|
|
// If they *were* tested and missed, clear the original trace |
|
if (!hitboxTrace.DidHit()) |
|
{ |
|
CM_ClearTrace( pTrace ); |
|
pTrace->startpos = hitboxTrace.startpos; |
|
pTrace->endpos = hitboxTrace.endpos; |
|
} |
|
else if ( pCollideable->GetSolid() != SOLID_VPHYSICS ) |
|
{ |
|
// If we also hit the hitboxes, maintain fractionleftsolid + |
|
// startpos because those are reasonable enough values and the |
|
// hitbox code doesn't set those itself. |
|
Vector vecStartPos = pTrace->startpos; |
|
float flFractionLeftSolid = pTrace->fractionleftsolid; |
|
|
|
*pTrace = hitboxTrace; |
|
|
|
if (hitboxTrace.startsolid) |
|
{ |
|
pTrace->startpos = vecStartPos; |
|
pTrace->fractionleftsolid = flFractionLeftSolid; |
|
} |
|
} |
|
else |
|
{ |
|
// Fill out the trace hitbox details |
|
pTrace->contents = hitboxTrace.contents; |
|
pTrace->hitgroup = hitboxTrace.hitgroup; |
|
pTrace->hitbox = hitboxTrace.hitbox; |
|
pTrace->physicsbone = hitboxTrace.physicsbone; |
|
pTrace->surface = hitboxTrace.surface; |
|
Assert( pTrace->physicsbone >= 0 ); |
|
// Fill out the surfaceprop details from the hitbox. Use the physics bone instead of the hitbox bone |
|
Assert(pTrace->surface.flags == SURF_HITBOX); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int CEngineTrace::GetStatByIndex( int index, bool bClear ) |
|
{ |
|
if ( index >= NUM_TRACE_STAT_COUNTER ) |
|
return 0; |
|
int out = m_traceStatCounters[index]; |
|
if ( bClear ) |
|
{ |
|
m_traceStatCounters[index] = 0; |
|
} |
|
return out; |
|
} |
|
|
|
|
|
|
|
static void FASTCALL GetBrushesInAABB_ParseLeaf( const Vector *pExtents, CCollisionBSPData *pBSPData, cleaf_t *pLeaf, CUtlVector<int> *pOutput, int iContentsMask, int *pCounters ) |
|
{ |
|
for( unsigned int i = 0; i != pLeaf->numleafbrushes; ++i ) |
|
{ |
|
int iBrushNumber = pBSPData->map_leafbrushes[pLeaf->firstleafbrush + i]; |
|
cbrush_t *pBrush = &pBSPData->map_brushes[iBrushNumber]; |
|
|
|
if( pCounters[iBrushNumber] ) |
|
continue; |
|
|
|
pCounters[iBrushNumber] = 1; |
|
|
|
if( (pBrush->contents & iContentsMask) == 0 ) |
|
continue; |
|
|
|
if ( pBrush->IsBox() ) |
|
{ |
|
cboxbrush_t *pBox = &pBSPData->map_boxbrushes[pBrush->GetBox()]; |
|
if ( IsBoxIntersectingBox(pBox->mins, pBox->maxs, pExtents[0], pExtents[7]) ) |
|
{ |
|
pOutput->AddToTail(iBrushNumber); |
|
} |
|
} |
|
else |
|
{ |
|
unsigned int j; |
|
for( j = 0; j != pBrush->numsides; ++j ) |
|
{ |
|
cplane_t *pPlane = pBSPData->map_brushsides[pBrush->firstbrushside + j].plane; |
|
|
|
if( (pExtents[pPlane->signbits].Dot( pPlane->normal ) - pPlane->dist) > 0.0f ) |
|
break; //the bounding box extent that was most likely to be encapsulated by the plane is outside the halfspace, brush not in bbox |
|
} |
|
|
|
if( j == pBrush->numsides ) |
|
pOutput->AddToTail( iBrushNumber ); //brush was most likely in bbox |
|
} |
|
} |
|
} |
|
|
|
|
|
void CEngineTrace::GetBrushesInAABB( const Vector &vMins, const Vector &vMaxs, CUtlVector<int> *pOutput, int iContentsMask ) |
|
{ |
|
if( pOutput == NULL ) return; |
|
CCollisionBSPData *pBSPData = GetCollisionBSPData(); |
|
|
|
Vector ptBBoxExtents[8]; //for fast plane checking |
|
for( int i = 0; i != 8; ++i ) |
|
{ |
|
//set these up to be opposite that of cplane_t's signbits for it's normal |
|
ptBBoxExtents[i].x = (i & (1<<0)) ? (vMaxs.x) : (vMins.x); |
|
ptBBoxExtents[i].y = (i & (1<<1)) ? (vMaxs.y) : (vMins.y); |
|
ptBBoxExtents[i].z = (i & (1<<2)) ? (vMaxs.z) : (vMins.z); |
|
} |
|
|
|
int *pLeafList = (int *)stackalloc( pBSPData->numleafs * 2 * sizeof( int ) ); // *2 just in case |
|
int iNumLeafs = CM_BoxLeafnums( vMins, vMaxs, pLeafList, pBSPData->numleafs * 2, NULL ); |
|
|
|
CUtlVector<int> counters; |
|
counters.SetSize( pBSPData->numbrushes ); |
|
memset( counters.Base(), 0, pBSPData->numbrushes * sizeof(int) ); |
|
for( int i = 0; i != iNumLeafs; ++i ) |
|
GetBrushesInAABB_ParseLeaf( ptBBoxExtents, pBSPData, &pBSPData->map_leafs[pLeafList[i]], pOutput, iContentsMask, counters.Base() ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Used to copy the collision information of all displacement surfaces in a specified box |
|
// Input : vMins - min vector of the AABB |
|
// vMaxs - max vector of the AABB |
|
// Output : CPhysCollide* the collision mesh created from all the displacements partially contained in the specified box |
|
// Note: We're not clipping to the box. Collidable may be larger than the box provided. |
|
//----------------------------------------------------------------------------- |
|
CPhysCollide* CEngineTrace::GetCollidableFromDisplacementsInAABB( const Vector& vMins, const Vector& vMaxs ) |
|
{ |
|
CCollisionBSPData *pBSPData = GetCollisionBSPData(); |
|
|
|
int *pLeafList = (int *)stackalloc( pBSPData->numleafs * sizeof( int ) ); |
|
int iLeafCount = CM_BoxLeafnums( vMins, vMaxs, pLeafList, pBSPData->numleafs, NULL ); |
|
|
|
// Get all the triangles for displacement surfaces in this box, add them to a polysoup |
|
CPhysPolysoup *pDispCollideSoup = physcollision->PolysoupCreate(); |
|
|
|
// Count total triangles added to this poly soup- Can't support more than 65435. |
|
int iTriCount = 0; |
|
|
|
TraceInfo_t *pTraceInfo = BeginTrace(); |
|
|
|
TraceCounter_t *pCounters = pTraceInfo->GetDispCounters(); |
|
int count = pTraceInfo->GetCount(); |
|
|
|
// For each leaf in which the box lies, Get all displacements in that leaf and use their triangles to create the mesh |
|
for ( int i = 0; i < iLeafCount; ++i ) |
|
{ |
|
// Current leaf |
|
cleaf_t curLeaf = pBSPData->map_leafs[ pLeafList[i] ]; |
|
|
|
// Test box against all displacements in the leaf. |
|
for( int k = 0; k < curLeaf.dispCount; k++ ) |
|
{ |
|
int dispIndex = pBSPData->map_dispList[curLeaf.dispListStart + k]; |
|
CDispCollTree *pDispTree = &g_pDispCollTrees[dispIndex]; |
|
|
|
// make sure we only check this brush once per trace/stab |
|
if ( !pTraceInfo->Visit( pDispTree->m_iCounter, count, pCounters ) ) |
|
continue; |
|
|
|
// If this displacement doesn't touch our test box, don't add it to the list. |
|
if ( !IsBoxIntersectingBox( vMins, vMaxs, pDispTree->m_mins, pDispTree->m_maxs) ) |
|
continue; |
|
|
|
// The the triangle mesh for this displacement surface |
|
virtualmeshlist_t meshTriList; |
|
pDispTree->GetVirtualMeshList( &meshTriList ); |
|
|
|
Assert ( meshTriList.indexCount%3 == 0 ); |
|
Assert ( meshTriList.indexCount != 0 ); |
|
Assert ( meshTriList.indexCount/3 == meshTriList.triangleCount ); |
|
|
|
// Don't allow more than 64k triangles in a collision model |
|
// TODO: Return a list of collidables? How often do we break 64k triangles? |
|
iTriCount += meshTriList.triangleCount; |
|
if ( iTriCount > 65535 ) |
|
{ |
|
AssertMsg ( 0, "Displacement surfaces have too many triangles to duplicate in GetCollidableFromDisplacementsInBox." ); |
|
EndTrace( pTraceInfo ); |
|
return NULL; |
|
} |
|
|
|
for ( int j = 0; j < meshTriList.indexCount; j+=3 ) |
|
{ |
|
// Don't index past the index list |
|
Assert( j+2 < meshTriList.indexCount ); |
|
|
|
if ( j+2 >= meshTriList.indexCount ) |
|
{ |
|
EndTrace( pTraceInfo ); |
|
physcollision->PolysoupDestroy( pDispCollideSoup ); |
|
return NULL; |
|
} |
|
|
|
unsigned short i0 = meshTriList.indices[j+0]; |
|
unsigned short i1 = meshTriList.indices[j+1]; |
|
unsigned short i2 = meshTriList.indices[j+2]; |
|
|
|
// Don't index past the end of the vert list |
|
Assert ( i0 < meshTriList.vertexCount && i1 < meshTriList.vertexCount && i2 < meshTriList.vertexCount ); |
|
|
|
if ( i0 >= meshTriList.vertexCount || i1 >= meshTriList.vertexCount || i2 >= meshTriList.vertexCount ) |
|
{ |
|
EndTrace( pTraceInfo ); |
|
physcollision->PolysoupDestroy( pDispCollideSoup ); |
|
return NULL; |
|
} |
|
|
|
Vector v0 = meshTriList.pVerts[ i0 ]; |
|
Vector v1 = meshTriList.pVerts[ i1 ]; |
|
Vector v2 = meshTriList.pVerts[ i2 ]; |
|
|
|
Assert ( v0.IsValid() && v1.IsValid() && v2.IsValid() ); |
|
|
|
// We don't need exact clipping to the box... Include any triangle that has at least one vert on the inside |
|
if ( IsPointInBox( v0, vMins, vMaxs ) || IsPointInBox( v1, vMins, vMaxs ) || IsPointInBox( v2, vMins, vMaxs ) ) |
|
{ |
|
// This is for collision only, so we don't need to worry about blending-- Use the first surface prop. |
|
int nProp = pDispTree->GetSurfaceProps(0); |
|
physcollision->PolysoupAddTriangle( pDispCollideSoup, v0, v1, v2, nProp ); |
|
} |
|
|
|
}// triangle loop |
|
|
|
}// for each displacement in leaf |
|
|
|
}// for each leaf |
|
|
|
EndTrace( pTraceInfo ); |
|
|
|
CPhysCollide* pCollide = physcollision->ConvertPolysoupToCollide ( pDispCollideSoup, false ); |
|
|
|
// clean up poly soup |
|
physcollision->PolysoupDestroy( pDispCollideSoup ); |
|
|
|
return pCollide; |
|
} |
|
|
|
bool CEngineTrace::GetBrushInfo( int iBrush, CUtlVector<Vector4D> *pPlanesOut, int *pContentsOut ) |
|
{ |
|
CCollisionBSPData *pBSPData = GetCollisionBSPData(); |
|
|
|
if( iBrush < 0 || iBrush >= pBSPData->numbrushes ) |
|
return false; |
|
|
|
cbrush_t *pBrush = &pBSPData->map_brushes[iBrush]; |
|
|
|
if( pPlanesOut ) |
|
{ |
|
pPlanesOut->RemoveAll(); |
|
Vector4D p; |
|
if ( pBrush->IsBox() ) |
|
{ |
|
cboxbrush_t *pBox = &pBSPData->map_boxbrushes[pBrush->GetBox()]; |
|
|
|
for ( int i = 0; i < 6; i++ ) |
|
{ |
|
p.Init(0,0,0,0); |
|
if ( i < 3 ) |
|
{ |
|
p[i] = 1.0f; |
|
p[3] = pBox->maxs[i]; |
|
} |
|
else |
|
{ |
|
p[i-3] = -1.0f; |
|
p[3] = -pBox->mins[i-3]; |
|
} |
|
pPlanesOut->AddToTail( p ); |
|
} |
|
} |
|
else |
|
{ |
|
cbrushside_t *stopside = &pBSPData->map_brushsides[pBrush->firstbrushside]; |
|
// Note: Don't do this in the [] since the final one on the last brushside will be past the end of the array end by one index |
|
stopside += pBrush->numsides; |
|
for( cbrushside_t *side = &pBSPData->map_brushsides[pBrush->firstbrushside]; side != stopside; ++side ) |
|
{ |
|
Vector4D pVec( side->plane->normal.x, side->plane->normal.y, side->plane->normal.z, side->plane->dist ); |
|
pPlanesOut->AddToTail( pVec ); |
|
} |
|
} |
|
} |
|
|
|
if( pContentsOut ) |
|
*pContentsOut = pBrush->contents; |
|
|
|
return true; |
|
} |
|
|
|
//Tests a point to see if it's outside any playable area |
|
bool CEngineTrace::PointOutsideWorld( const Vector &ptTest ) |
|
{ |
|
int iLeaf = CM_PointLeafnum( ptTest ); |
|
Assert( iLeaf >= 0 ); |
|
|
|
CCollisionBSPData *pBSPData = GetCollisionBSPData(); |
|
|
|
if( pBSPData->map_leafs[iLeaf].cluster == -1 ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Expose to the game dll a method for finding the leaf which contains a given point |
|
// Input : &vPos - Returns the leaf which contains this point |
|
// Output : int - The handle to the leaf |
|
//----------------------------------------------------------------------------- |
|
int CEngineTrace::GetLeafContainingPoint( const Vector &vPos ) |
|
{ |
|
return CM_PointLeafnum( vPos ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Convex info for studio + brush models |
|
//----------------------------------------------------------------------------- |
|
class CBrushConvexInfo : public IConvexInfo |
|
{ |
|
public: |
|
CBrushConvexInfo() |
|
{ |
|
m_pBSPData = GetCollisionBSPData(); |
|
} |
|
virtual unsigned int GetContents( int convexGameData ) |
|
{ |
|
return m_pBSPData->map_brushes[convexGameData].contents; |
|
} |
|
|
|
private: |
|
CCollisionBSPData *m_pBSPData; |
|
}; |
|
|
|
class CStudioConvexInfo : public IConvexInfo |
|
{ |
|
public: |
|
CStudioConvexInfo( studiohdr_t *pStudioHdr ) |
|
{ |
|
m_pStudioHdr = pStudioHdr; |
|
} |
|
|
|
virtual unsigned int GetContents( int convexGameData ) |
|
{ |
|
if ( convexGameData == 0 ) |
|
{ |
|
return m_pStudioHdr->contents; |
|
} |
|
|
|
Assert( convexGameData <= m_pStudioHdr->numbones ); |
|
mstudiobone_t *pBone = m_pStudioHdr->pBone(convexGameData - 1); |
|
return pBone->contents; |
|
} |
|
|
|
private: |
|
studiohdr_t *m_pStudioHdr; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Perform vphysics trace |
|
//----------------------------------------------------------------------------- |
|
bool CEngineTrace::ClipRayToVPhysics( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, studiohdr_t *pStudioHdr, trace_t *pTrace ) |
|
{ |
|
if ( pEntity->GetSolid() != SOLID_VPHYSICS ) |
|
return false; |
|
|
|
bool bTraced = false; |
|
|
|
// use the vphysics model for rotated brushes and vphysics simulated objects |
|
const model_t *pModel = pEntity->GetCollisionModel(); |
|
|
|
if ( !pModel ) |
|
return false; |
|
|
|
if ( pStudioHdr ) |
|
{ |
|
CStudioConvexInfo studioConvex( pStudioHdr ); |
|
vcollide_t *pCollide = g_pMDLCache->GetVCollide( pModel->studio ); |
|
if ( pCollide && pCollide->solidCount ) |
|
{ |
|
physcollision->TraceBox( |
|
ray, |
|
fMask, |
|
&studioConvex, |
|
pCollide->solids[0], // UNDONE: Support other solid indices?!?!?!? (forced zero) |
|
pEntity->GetCollisionOrigin(), |
|
pEntity->GetCollisionAngles(), |
|
pTrace ); |
|
bTraced = true; |
|
} |
|
} |
|
else |
|
{ |
|
Assert(pModel->type != mod_studio); |
|
// use the regular code for raytraces against brushes |
|
// do ray traces with normal code, but use vphysics to do box traces |
|
if ( !ray.m_IsRay || pModel->type != mod_brush ) |
|
{ |
|
int nModelIndex = pEntity->GetCollisionModelIndex(); |
|
|
|
// BUGBUG: This only works when the vcollide in question is the first solid in the model |
|
vcollide_t *pCollide = CM_VCollideForModel( nModelIndex, (model_t*)pModel ); |
|
|
|
if ( pCollide && pCollide->solidCount ) |
|
{ |
|
CBrushConvexInfo brushConvex; |
|
|
|
IConvexInfo *pConvexInfo = (pModel->type) == mod_brush ? &brushConvex : NULL; |
|
physcollision->TraceBox( |
|
ray, |
|
fMask, |
|
pConvexInfo, |
|
pCollide->solids[0], // UNDONE: Support other solid indices?!?!?!? (forced zero) |
|
pEntity->GetCollisionOrigin(), |
|
pEntity->GetCollisionAngles(), |
|
pTrace ); |
|
bTraced = true; |
|
} |
|
} |
|
} |
|
|
|
return bTraced; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Perform bsp trace |
|
//----------------------------------------------------------------------------- |
|
bool CEngineTrace::ClipRayToBSP( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace ) |
|
{ |
|
int nModelIndex = pEntity->GetCollisionModelIndex(); |
|
cmodel_t *pCModel = CM_InlineModelNumber( nModelIndex - 1 ); |
|
int nHeadNode = pCModel->headnode; |
|
|
|
CM_TransformedBoxTrace( ray, nHeadNode, fMask, pEntity->GetCollisionOrigin(), pEntity->GetCollisionAngles(), *pTrace ); |
|
return true; |
|
} |
|
|
|
|
|
// NOTE: Switched over to SIMD ray/box test since there is a bug we haven't hunted down yet in the scalar version |
|
bool CEngineTrace::ClipRayToBBox( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace ) |
|
{ |
|
extern bool IntersectRayWithBox( const Ray_t &ray, const VectorAligned &inInvDelta, const VectorAligned &inBoxMins, const VectorAligned &inBoxMaxs, trace_t *RESTRICT pTrace ); |
|
|
|
if ( pEntity->GetSolid() != SOLID_BBOX ) |
|
return false; |
|
|
|
// We can't use the OBBMins/Maxs unless the collision angles are world-aligned |
|
Assert( pEntity->GetCollisionAngles() == vec3_angle ); |
|
|
|
VectorAligned vecAbsMins, vecAbsMaxs; |
|
VectorAligned vecInvDelta; |
|
// NOTE: If m_pRootMoveParent is set, then the boxes should be rotated into the root parent's space |
|
if ( !ray.m_IsRay && m_pRootMoveParent ) |
|
{ |
|
Ray_t ray_l; |
|
|
|
ray_l.m_Extents = ray.m_Extents; |
|
|
|
VectorIRotate( ray.m_Delta, *m_pRootMoveParent, ray_l.m_Delta ); |
|
ray_l.m_StartOffset.Init(); |
|
VectorITransform( ray.m_Start, *m_pRootMoveParent, ray_l.m_Start ); |
|
|
|
vecInvDelta = ray_l.InvDelta(); |
|
Vector localEntityOrigin; |
|
VectorITransform( pEntity->GetCollisionOrigin(), *m_pRootMoveParent, localEntityOrigin ); |
|
ray_l.m_IsRay = ray.m_IsRay; |
|
ray_l.m_IsSwept = ray.m_IsSwept; |
|
|
|
VectorAdd( localEntityOrigin, pEntity->OBBMins(), vecAbsMins ); |
|
VectorAdd( localEntityOrigin, pEntity->OBBMaxs(), vecAbsMaxs ); |
|
IntersectRayWithBox( ray_l, vecInvDelta, vecAbsMins, vecAbsMaxs, pTrace ); |
|
|
|
if ( pTrace->DidHit() ) |
|
{ |
|
Vector temp; |
|
VectorCopy (pTrace->plane.normal, temp); |
|
VectorRotate( temp, *m_pRootMoveParent, pTrace->plane.normal ); |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
|
|
if (pTrace->fraction == 1) |
|
{ |
|
VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos); |
|
} |
|
else |
|
{ |
|
VectorMA( pTrace->startpos, pTrace->fraction, ray.m_Delta, pTrace->endpos ); |
|
} |
|
pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal ); |
|
if ( pTrace->fractionleftsolid < 1 ) |
|
{ |
|
pTrace->startpos += ray.m_Delta * pTrace->fractionleftsolid; |
|
} |
|
} |
|
else |
|
{ |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
vecInvDelta = ray.InvDelta(); |
|
VectorAdd( pEntity->GetCollisionOrigin(), pEntity->OBBMins(), vecAbsMins ); |
|
VectorAdd( pEntity->GetCollisionOrigin(), pEntity->OBBMaxs(), vecAbsMaxs ); |
|
IntersectRayWithBox( ray, vecInvDelta, vecAbsMins, vecAbsMaxs, pTrace); |
|
return true; |
|
} |
|
|
|
bool CEngineTrace::ClipRayToOBB( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace ) |
|
{ |
|
if ( pEntity->GetSolid() != SOLID_OBB ) |
|
return false; |
|
|
|
// NOTE: This is busted because it doesn't compute fractionleftsolid, which at the |
|
// moment is required for the engine trace system. |
|
IntersectRayWithOBB( ray, pEntity->GetCollisionOrigin(), pEntity->GetCollisionAngles(), |
|
pEntity->OBBMins(), pEntity->OBBMaxs(), DIST_EPSILON, pTrace ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Main entry point for clipping rays to entities |
|
//----------------------------------------------------------------------------- |
|
#ifndef SWDS |
|
void CEngineTraceClient::SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace ) |
|
{ |
|
if ( !pTrace->DidHit() ) |
|
return; |
|
|
|
// FIXME: This is only necessary because of traces occurring during |
|
// LevelInit (a suspect time to be tracing) |
|
if (!pCollideable) |
|
{ |
|
pTrace->m_pEnt = NULL; |
|
return; |
|
} |
|
|
|
IClientUnknown *pUnk = (IClientUnknown*)pCollideable->GetEntityHandle(); |
|
if ( !StaticPropMgr()->IsStaticProp( pUnk ) ) |
|
{ |
|
pTrace->m_pEnt = (CBaseEntity*)(pUnk->GetIClientEntity()); |
|
} |
|
else |
|
{ |
|
// For static props, point to the world, hitbox is the prop index |
|
pTrace->m_pEnt = (CBaseEntity*)(entitylist->GetClientEntity(0)); |
|
pTrace->hitbox = StaticPropMgr()->GetStaticPropIndex( pUnk ) + 1; |
|
} |
|
} |
|
#endif |
|
|
|
void CEngineTraceServer::SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace ) |
|
{ |
|
if ( !pTrace->DidHit() ) |
|
return; |
|
|
|
IHandleEntity *pHandleEntity = pCollideable->GetEntityHandle(); |
|
if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ) |
|
{ |
|
pTrace->m_pEnt = (CBaseEntity*)(pHandleEntity); |
|
} |
|
else |
|
{ |
|
// For static props, point to the world, hitbox is the prop index |
|
pTrace->m_pEnt = (CBaseEntity*)(sv.edicts->GetIServerEntity()); |
|
pTrace->hitbox = StaticPropMgr()->GetStaticPropIndex( pHandleEntity ) + 1; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Traces a ray against a particular edict |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::ClipRayToCollideable( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace ) |
|
{ |
|
CM_ClearTrace( pTrace ); |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos ); |
|
|
|
const model_t *pModel = pEntity->GetCollisionModel(); |
|
bool bIsStudioModel = false; |
|
studiohdr_t *pStudioHdr = NULL; |
|
if ( pModel && pModel->type == mod_studio ) |
|
{ |
|
bIsStudioModel = true; |
|
pStudioHdr = (studiohdr_t *)modelloader->GetExtraData( (model_t*)pModel ); |
|
// Cull if the collision mask isn't set + we're not testing hitboxes. |
|
if ( (( fMask & CONTENTS_HITBOX ) == 0) ) |
|
{ |
|
if ( ( fMask & pStudioHdr->contents ) == 0) |
|
return; |
|
} |
|
} |
|
|
|
const matrix3x4_t *pOldRoot = m_pRootMoveParent; |
|
if ( pEntity->GetSolidFlags() & FSOLID_ROOT_PARENT_ALIGNED ) |
|
{ |
|
m_pRootMoveParent = pEntity->GetRootParentToWorldTransform(); |
|
} |
|
bool bTraced = false; |
|
bool bCustomPerformed = false; |
|
if ( ShouldPerformCustomRayTest( ray, pEntity ) ) |
|
{ |
|
ClipRayToCustom( ray, fMask, pEntity, pTrace ); |
|
bTraced = true; |
|
bCustomPerformed = true; |
|
} |
|
else |
|
{ |
|
bTraced = ClipRayToVPhysics( ray, fMask, pEntity, pStudioHdr, pTrace ); |
|
} |
|
|
|
// FIXME: Why aren't we using solid type to check what kind of collisions to test against?!?! |
|
if ( !bTraced && pModel && pModel->type == mod_brush ) |
|
{ |
|
bTraced = ClipRayToBSP( ray, fMask, pEntity, pTrace ); |
|
} |
|
|
|
if ( !bTraced ) |
|
{ |
|
bTraced = ClipRayToOBB( ray, fMask, pEntity, pTrace ); |
|
} |
|
|
|
// Hitboxes.. |
|
bool bTracedHitboxes = false; |
|
if ( bIsStudioModel && (fMask & CONTENTS_HITBOX) ) |
|
{ |
|
// Until hitboxes are no longer implemented as custom raytests, |
|
// don't bother to do the work twice |
|
if (!bCustomPerformed) |
|
{ |
|
bTraced = ClipRayToHitboxes( ray, fMask, pEntity, pTrace ); |
|
if ( bTraced ) |
|
{ |
|
// Hitboxes will set the surface properties |
|
bTracedHitboxes = true; |
|
} |
|
} |
|
} |
|
|
|
if ( !bTraced ) |
|
{ |
|
ClipRayToBBox( ray, fMask, pEntity, pTrace ); |
|
} |
|
|
|
if ( bIsStudioModel && !bTracedHitboxes && pTrace->DidHit() && (!bCustomPerformed || pTrace->surface.surfaceProps == 0) ) |
|
{ |
|
pTrace->contents = pStudioHdr->contents; |
|
// use the default surface properties |
|
pTrace->surface.name = "**studio**"; |
|
pTrace->surface.flags = 0; |
|
pTrace->surface.surfaceProps = physprop->GetSurfaceIndex( pStudioHdr->pszSurfaceProp() ); |
|
} |
|
|
|
if (!pTrace->m_pEnt && pTrace->DidHit()) |
|
{ |
|
SetTraceEntity( pEntity, pTrace ); |
|
} |
|
|
|
#ifdef _DEBUG |
|
Vector vecOffset, vecEndTest; |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset ); |
|
VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest ); |
|
Assert( VectorsAreEqual( vecEndTest, pTrace->startpos, 0.1f ) ); |
|
VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest ); |
|
Assert( VectorsAreEqual( vecEndTest, pTrace->endpos, 0.1f ) ); |
|
#endif |
|
m_pRootMoveParent = pOldRoot; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Main entry point for clipping rays to entities |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::ClipRayToEntity( const Ray_t &ray, unsigned int fMask, IHandleEntity *pEntity, trace_t *pTrace ) |
|
{ |
|
ClipRayToCollideable( ray, fMask, GetCollideable(pEntity), pTrace ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Grabs all entities along a ray |
|
//----------------------------------------------------------------------------- |
|
class CEntitiesAlongRay : public IPartitionEnumerator |
|
{ |
|
public: |
|
CEntitiesAlongRay( ) : m_EntityHandles(0, 32) {} |
|
|
|
void Reset() |
|
{ |
|
m_EntityHandles.RemoveAll(); |
|
} |
|
|
|
IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) |
|
{ |
|
m_EntityHandles.AddToTail( pHandleEntity ); |
|
return ITERATION_CONTINUE; |
|
} |
|
|
|
CUtlVector< IHandleEntity * > m_EntityHandles; |
|
}; |
|
|
|
class CEntityListAlongRay : public IPartitionEnumerator |
|
{ |
|
public: |
|
|
|
enum { MAX_ENTITIES_ALONGRAY = 1024 }; |
|
|
|
CEntityListAlongRay() |
|
{ |
|
m_nCount = 0; |
|
} |
|
|
|
void Reset() |
|
{ |
|
m_nCount = 0; |
|
} |
|
|
|
int Count() |
|
{ |
|
return m_nCount; |
|
} |
|
|
|
IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) |
|
{ |
|
if ( m_nCount < MAX_ENTITIES_ALONGRAY ) |
|
{ |
|
m_EntityHandles[m_nCount] = pHandleEntity; |
|
m_nCount++; |
|
} |
|
else |
|
{ |
|
DevMsg( 1, "Max entity count along ray exceeded!\n" ); |
|
} |
|
|
|
return ITERATION_CONTINUE; |
|
} |
|
|
|
int m_nCount; |
|
IHandleEntity *m_EntityHandles[MAX_ENTITIES_ALONGRAY]; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Makes sure the final trace is clipped to the clip trace |
|
// Returns true if clipping occurred |
|
//----------------------------------------------------------------------------- |
|
bool CEngineTrace::ClipTraceToTrace( trace_t &clipTrace, trace_t *pFinalTrace ) |
|
{ |
|
if (clipTrace.allsolid || clipTrace.startsolid || (clipTrace.fraction < pFinalTrace->fraction)) |
|
{ |
|
if (pFinalTrace->startsolid) |
|
{ |
|
float flFractionLeftSolid = pFinalTrace->fractionleftsolid; |
|
Vector vecStartPos = pFinalTrace->startpos; |
|
|
|
*pFinalTrace = clipTrace; |
|
pFinalTrace->startsolid = true; |
|
|
|
if ( flFractionLeftSolid > clipTrace.fractionleftsolid ) |
|
{ |
|
pFinalTrace->fractionleftsolid = flFractionLeftSolid; |
|
pFinalTrace->startpos = vecStartPos; |
|
} |
|
} |
|
else |
|
{ |
|
*pFinalTrace = clipTrace; |
|
} |
|
return true; |
|
} |
|
|
|
if (clipTrace.startsolid) |
|
{ |
|
pFinalTrace->startsolid = true; |
|
|
|
if ( clipTrace.fractionleftsolid > pFinalTrace->fractionleftsolid ) |
|
{ |
|
pFinalTrace->fractionleftsolid = clipTrace.fractionleftsolid; |
|
pFinalTrace->startpos = clipTrace.startpos; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Converts a user id to a collideable + username |
|
//----------------------------------------------------------------------------- |
|
void CEngineTraceServer::HandleEntityToCollideable( IHandleEntity *pHandleEntity, ICollideable **ppCollide, const char **ppDebugName ) |
|
{ |
|
*ppCollide = StaticPropMgr()->GetStaticProp( pHandleEntity ); |
|
if ( *ppCollide ) |
|
{ |
|
*ppDebugName = "static prop"; |
|
return; |
|
} |
|
|
|
IServerUnknown *pServerUnknown = static_cast<IServerUnknown*>(pHandleEntity); |
|
if ( !pServerUnknown || ! pServerUnknown->GetNetworkable()) |
|
{ |
|
*ppCollide = NULL; |
|
*ppDebugName = "<null>"; |
|
return; |
|
} |
|
|
|
*ppCollide = pServerUnknown->GetCollideable(); |
|
*ppDebugName = pServerUnknown->GetNetworkable()->GetClassName(); |
|
} |
|
|
|
#ifndef SWDS |
|
void CEngineTraceClient::HandleEntityToCollideable( IHandleEntity *pHandleEntity, ICollideable **ppCollide, const char **ppDebugName ) |
|
{ |
|
*ppCollide = StaticPropMgr()->GetStaticProp( pHandleEntity ); |
|
if ( *ppCollide ) |
|
{ |
|
*ppDebugName = "static prop"; |
|
return; |
|
} |
|
|
|
IClientUnknown *pUnk = static_cast<IClientUnknown*>(pHandleEntity); |
|
if ( !pUnk ) |
|
{ |
|
*ppCollide = NULL; |
|
*ppDebugName = "<null>"; |
|
return; |
|
} |
|
|
|
*ppCollide = pUnk->GetCollideable(); |
|
*ppDebugName = "client entity"; |
|
IClientNetworkable *pNetwork = pUnk->GetClientNetworkable(); |
|
if (pNetwork) |
|
{ |
|
if (pNetwork->GetClientClass()) |
|
{ |
|
*ppDebugName = pNetwork->GetClientClass()->m_pNetworkName; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the world collideable for trace setting |
|
//----------------------------------------------------------------------------- |
|
#ifndef SWDS |
|
ICollideable *CEngineTraceClient::GetWorldCollideable() |
|
{ |
|
IClientEntity *pUnk = entitylist->GetClientEntity( 0 ); |
|
AssertOnce( pUnk ); |
|
return pUnk ? pUnk->GetCollideable() : NULL; |
|
} |
|
#endif |
|
|
|
ICollideable *CEngineTraceServer::GetWorldCollideable() |
|
{ |
|
if (!sv.edicts) |
|
return NULL; |
|
return sv.edicts->GetCollideable(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Debugging code to render all ray casts since the last time this call was made |
|
//----------------------------------------------------------------------------- |
|
void EngineTraceRenderRayCasts() |
|
{ |
|
#if defined _DEBUG && !defined SWDS |
|
if( debugrayenable.GetBool() && s_FrameRays.Count() > debugraylimit.GetInt() && !debugrayreset.GetInt() ) |
|
{ |
|
Warning( "m_FrameRays.Count() == %d\n", s_FrameRays.Count() ); |
|
debugrayreset.SetValue( 1 ); |
|
int i; |
|
for( i = 0; i < s_FrameRays.Count(); i++ ) |
|
{ |
|
Ray_t &ray = s_FrameRays[i]; |
|
if( ray.m_Extents.x != 0.0f || ray.m_Extents.y != 0.0f || ray.m_Extents.z != 0.0f ) |
|
{ |
|
CDebugOverlay::AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 255, 0, 0, 255, true, 3600.0f ); |
|
} |
|
else |
|
{ |
|
CDebugOverlay::AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 255, 255, 0, 255, true, 3600.0f ); |
|
} |
|
} |
|
} |
|
|
|
s_FrameRays.RemoveAll( ); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::SetupLeafAndEntityListRay( const Ray_t &ray, CTraceListData &traceData ) |
|
{ |
|
if ( !ray.m_IsSwept ) |
|
{ |
|
Vector vecMin, vecMax; |
|
VectorSubtract( ray.m_Start, ray.m_Extents, vecMin ); |
|
VectorAdd( ray.m_Start, ray.m_Extents, vecMax ); |
|
SetupLeafAndEntityListBox( vecMin, vecMax, traceData ); |
|
return; |
|
} |
|
|
|
// Get the leaves that intersect the ray. |
|
traceData.LeafCountReset(); |
|
CM_RayLeafnums( ray, traceData.m_aLeafList.Base(), traceData.LeafCountMax(), traceData.m_nLeafCount ); |
|
|
|
// Find all the entities in the voxels that intersect this ray. |
|
traceData.EntityCountReset(); |
|
SpatialPartition()->EnumerateElementsAlongRay( SpatialPartitionMask(), ray, false, &traceData ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gives an AABB and returns a leaf and entity list. |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::SetupLeafAndEntityListBox( const Vector &vecBoxMin, const Vector &vecBoxMax, CTraceListData &traceData ) |
|
{ |
|
// Get the leaves that intersect this box. |
|
int iTopNode = -1; |
|
traceData.LeafCountReset(); |
|
traceData.m_nLeafCount = CM_BoxLeafnums( vecBoxMin, vecBoxMax, traceData.m_aLeafList.Base(), traceData.LeafCountMax(), &iTopNode ); |
|
|
|
// Find all entities in the voxels that intersect this box. |
|
traceData.EntityCountReset(); |
|
SpatialPartition()->EnumerateElementsInBox( SpatialPartitionMask(), vecBoxMin, vecBoxMax, false, &traceData ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// NOTE: the fMask is redundant with the stuff below, what do I want to do??? |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::TraceRayAgainstLeafAndEntityList( const Ray_t &ray, CTraceListData &traceData, |
|
unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ) |
|
{ |
|
// Setup the trace data. |
|
CM_ClearTrace ( pTrace ); |
|
|
|
// Make sure we have some kind of trace filter. |
|
CTraceFilterHitAll traceFilter; |
|
if ( !pTraceFilter ) |
|
{ |
|
pTraceFilter = &traceFilter; |
|
} |
|
|
|
// Collide with the world. |
|
if ( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY ) |
|
{ |
|
ICollideable *pCollide = GetWorldCollideable(); |
|
|
|
// Make sure the world entity is unrotated |
|
// FIXME: BAH! The !pCollide test here is because of |
|
// CStaticProp::PrecacheLighting.. it's occurring too early |
|
// need to fix that later |
|
Assert( !pCollide || pCollide->GetCollisionOrigin() == vec3_origin ); |
|
Assert( !pCollide || pCollide->GetCollisionAngles() == vec3_angle ); |
|
|
|
CM_BoxTraceAgainstLeafList( ray, traceData.m_aLeafList.Base(), traceData.LeafCount(), fMask, true, *pTrace ); |
|
SetTraceEntity( pCollide, pTrace ); |
|
|
|
// Blocked by the world or early out because we only are tracing against the world. |
|
if ( ( pTrace->fraction == 0 ) || ( pTraceFilter->GetTraceType() == TRACE_WORLD_ONLY ) ) |
|
return; |
|
} |
|
else |
|
{ |
|
// Set initial start and endpos. This is necessary if the world isn't traced against, |
|
// because we may not trace against anything below. |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos ); |
|
} |
|
|
|
// Save the world collision fraction. |
|
float flWorldFraction = pTrace->fraction; |
|
|
|
// Create a ray that extends only until we hit the world and adjust the trace accordingly |
|
Ray_t entityRay = ray; |
|
VectorScale( entityRay.m_Delta, pTrace->fraction, entityRay.m_Delta ); |
|
|
|
// We know this is safe because if pTrace->fraction == 0, we would have exited above. |
|
pTrace->fractionleftsolid /= pTrace->fraction; |
|
pTrace->fraction = 1.0; |
|
|
|
// Collide with entities. |
|
bool bNoStaticProps = pTraceFilter->GetTraceType() == TRACE_ENTITIES_ONLY; |
|
bool bFilterStaticProps = pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS; |
|
|
|
trace_t trace; |
|
ICollideable *pCollideable; |
|
const char *pDebugName; |
|
for ( int iEntity = 0; iEntity < traceData.m_nEntityCount; ++iEntity ) |
|
{ |
|
// Generate a collideable. |
|
IHandleEntity *pHandleEntity = traceData.m_aEntityList[iEntity]; |
|
HandleEntityToCollideable( pHandleEntity, &pCollideable, &pDebugName ); |
|
|
|
// Check for error condition. |
|
if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) ) |
|
{ |
|
Assert( 0 ); |
|
Msg("%s in solid list (not solid)\n", pDebugName ); |
|
continue; |
|
} |
|
|
|
if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ) |
|
{ |
|
if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) ) |
|
continue; |
|
} |
|
else |
|
{ |
|
// FIXME: Could remove this check here by |
|
// using a different spatial partition mask. Look into it |
|
// if we want more speedups here. |
|
if ( bNoStaticProps ) |
|
continue; |
|
|
|
if ( bFilterStaticProps ) |
|
{ |
|
if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) ) |
|
continue; |
|
} |
|
} |
|
|
|
ClipRayToCollideable( entityRay, fMask, pCollideable, &trace ); |
|
|
|
// Make sure the ray is always shorter than it currently is |
|
ClipTraceToTrace( trace, pTrace ); |
|
|
|
// Stop if we're in allsolid |
|
if ( pTrace->allsolid ) |
|
break; |
|
} |
|
|
|
// Fix up the fractions so they are appropriate given the original unclipped-to-world ray. |
|
pTrace->fraction *= flWorldFraction; |
|
pTrace->fractionleftsolid *= flWorldFraction; |
|
|
|
if ( !ray.m_IsRay ) |
|
{ |
|
// Make sure no fractionleftsolid can be used with box sweeps. |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
pTrace->fractionleftsolid = 0; |
|
} |
|
} |
|
|
|
#if BENCHMARK_RAY_TEST |
|
|
|
CON_COMMAND( ray_save, "Save the rays" ) |
|
{ |
|
int count = s_BenchmarkRays.Count(); |
|
if ( count ) |
|
{ |
|
FileHandle_t hFile = g_pFileSystem->Open("rays.bin", "wb"); |
|
if ( hFile ) |
|
{ |
|
g_pFileSystem->Write( &count, sizeof(count), hFile ); |
|
g_pFileSystem->Write( s_BenchmarkRays.Base(), sizeof(s_BenchmarkRays[0])*count, hFile ); |
|
g_pFileSystem->Close( hFile ); |
|
} |
|
} |
|
|
|
Msg("Saved %d rays\n", count ); |
|
} |
|
|
|
CON_COMMAND( ray_load, "Load the rays" ) |
|
{ |
|
s_BenchmarkRays.RemoveAll(); |
|
FileHandle_t hFile = g_pFileSystem->Open("rays.bin", "rb"); |
|
if ( hFile ) |
|
{ |
|
int count = 0; |
|
g_pFileSystem->Read( &count, sizeof(count), hFile ); |
|
if ( count ) |
|
{ |
|
s_BenchmarkRays.EnsureCount( count ); |
|
g_pFileSystem->Read( s_BenchmarkRays.Base(), sizeof(s_BenchmarkRays[0])*count, hFile ); |
|
} |
|
g_pFileSystem->Close( hFile ); |
|
} |
|
|
|
Msg("Loaded %d rays\n", s_BenchmarkRays.Count() ); |
|
} |
|
|
|
CON_COMMAND( ray_clear, "Clear the current rays" ) |
|
{ |
|
s_BenchmarkRays.RemoveAll(); |
|
Msg("Reset rays!\n"); |
|
} |
|
|
|
|
|
CON_COMMAND_EXTERN( ray_bench, RayBench, "Time the rays" ) |
|
{ |
|
#if VPROF_LEVEL > 0 |
|
g_VProfCurrentProfile.Start(); |
|
g_VProfCurrentProfile.Reset(); |
|
g_VProfCurrentProfile.ResetPeaks(); |
|
#endif |
|
{ |
|
double tStart = Plat_FloatTime(); |
|
trace_t trace; |
|
int hit = 0; |
|
int miss = 0; |
|
int rayVsProp = 0; |
|
int boxVsProp = 0; |
|
for ( int i = 0; i < s_BenchmarkRays.Count(); i++ ) |
|
{ |
|
CM_BoxTrace( s_BenchmarkRays[i], 0, MASK_SOLID, true, trace ); |
|
if ( 0 ) |
|
{ |
|
VPROF("QueryStaticProps"); |
|
// Create a ray that extends only until we hit the world and adjust the trace accordingly |
|
Ray_t entityRay = s_BenchmarkRays[i]; |
|
VectorScale( entityRay.m_Delta, trace.fraction, entityRay.m_Delta ); |
|
CEntityListAlongRay enumerator; |
|
enumerator.Reset(); |
|
SpatialPartition()->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, entityRay, false, &enumerator ); |
|
trace_t tr; |
|
ICollideable *pCollideable; |
|
int nCount = enumerator.Count(); |
|
const char *pDebugName = NULL; |
|
//float flWorldFraction = trace.fraction; |
|
if ( 0 ) |
|
{ |
|
|
|
VPROF("IntersectStaticProps"); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
// Generate a collideable |
|
IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i]; |
|
|
|
if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ) |
|
continue; |
|
if ( entityRay.m_IsRay ) |
|
rayVsProp++; |
|
else |
|
boxVsProp++; |
|
s_EngineTraceServer.HandleEntityToCollideable( pHandleEntity, &pCollideable, &pDebugName ); |
|
s_EngineTraceServer.ClipRayToCollideable( entityRay, MASK_SOLID, pCollideable, &tr ); |
|
|
|
// Make sure the ray is always shorter than it currently is |
|
s_EngineTraceServer.ClipTraceToTrace( tr, &trace ); |
|
} |
|
} |
|
} |
|
if ( trace.DidHit() ) |
|
hit++; |
|
else |
|
miss++; |
|
#if VPROF_LEVEL > 0 |
|
g_VProfCurrentProfile.MarkFrame(); |
|
#endif |
|
} |
|
double tEnd = Plat_FloatTime(); |
|
float ms = (tEnd - tStart) * 1000.0f; |
|
int swept = 0; |
|
int point = 0; |
|
for ( int i = 0; i < s_BenchmarkRays.Count(); i++ ) |
|
{ |
|
swept += s_BenchmarkRays[i].m_IsSwept ? 1 : 0; |
|
point += s_BenchmarkRays[i].m_IsRay ? 1 : 0; |
|
} |
|
Msg("RAY TEST: %d hits, %d misses, %.2fms (%d rays, %d sweeps) (%d ray/prop, %d box/prop)\n", hit, miss, ms, point, swept, rayVsProp, boxVsProp ); |
|
} |
|
#if VPROF_LEVEL > 0 |
|
g_VProfCurrentProfile.MarkFrame(); |
|
g_VProfCurrentProfile.Stop(); |
|
g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL ); |
|
#endif |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// A version that simply accepts a ray (can work as a traceline or tracehull) |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ) |
|
{ |
|
#if defined _DEBUG && !defined SWDS |
|
if( debugrayenable.GetBool() ) |
|
{ |
|
s_FrameRays.AddToTail( ray ); |
|
} |
|
#endif |
|
|
|
#if BENCHMARK_RAY_TEST |
|
if( s_BenchmarkRays.Count() < 15000 ) |
|
{ |
|
s_BenchmarkRays.EnsureCapacity(15000); |
|
s_BenchmarkRays.AddToTail( ray ); |
|
} |
|
#endif |
|
|
|
tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "%s:%d", __FUNCTION__, __LINE__ ); |
|
VPROF_INCREMENT_COUNTER( "TraceRay", 1 ); |
|
m_traceStatCounters[TRACE_STAT_COUNTER_TRACERAY]++; |
|
// VPROF_BUDGET( "CEngineTrace::TraceRay", "Ray/Hull Trace" ); |
|
|
|
CTraceFilterHitAll traceFilter; |
|
if ( !pTraceFilter ) |
|
{ |
|
pTraceFilter = &traceFilter; |
|
} |
|
|
|
CM_ClearTrace( pTrace ); |
|
|
|
// Collide with the world. |
|
if ( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY ) |
|
{ |
|
ICollideable *pCollide = GetWorldCollideable(); |
|
Assert( pCollide ); |
|
|
|
// Make sure the world entity is unrotated |
|
// FIXME: BAH! The !pCollide test here is because of |
|
// CStaticProp::PrecacheLighting.. it's occurring too early |
|
// need to fix that later |
|
Assert(!pCollide || pCollide->GetCollisionOrigin() == vec3_origin ); |
|
Assert(!pCollide || pCollide->GetCollisionAngles() == vec3_angle ); |
|
|
|
CM_BoxTrace( ray, 0, fMask, true, *pTrace ); |
|
SetTraceEntity( pCollide, pTrace ); |
|
|
|
// inside world, no need to check being inside anything else |
|
if ( pTrace->startsolid ) |
|
return; |
|
|
|
// Early out if we only trace against the world |
|
if ( pTraceFilter->GetTraceType() == TRACE_WORLD_ONLY ) |
|
return; |
|
} |
|
else |
|
{ |
|
// Set initial start + endpos, necessary if the world isn't traced against |
|
// because we may not trace against *anything* below. |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos ); |
|
} |
|
|
|
// Save the world collision fraction. |
|
float flWorldFraction = pTrace->fraction; |
|
float flWorldFractionLeftSolidScale = flWorldFraction; |
|
|
|
// Create a ray that extends only until we hit the world |
|
// and adjust the trace accordingly |
|
Ray_t entityRay = ray; |
|
|
|
if ( pTrace->fraction == 0 ) |
|
{ |
|
entityRay.m_Delta.Init(); |
|
flWorldFractionLeftSolidScale = pTrace->fractionleftsolid; |
|
pTrace->fractionleftsolid = 1.0f; |
|
pTrace->fraction = 1.0f; |
|
} |
|
else |
|
{ |
|
// Explicitly compute end so that this computation happens at the quantization of |
|
// the output (endpos). That way we won't miss any intersections we would get |
|
// by feeding these results back in to the tracer |
|
// This is not the same as entityRay.m_Delta *= pTrace->fraction which happens |
|
// at a quantization that is more precise as m_Start moves away from the origin |
|
Vector end; |
|
VectorMA( entityRay.m_Start, pTrace->fraction, entityRay.m_Delta, end ); |
|
VectorSubtract(end, entityRay.m_Start, entityRay.m_Delta); |
|
// We know this is safe because pTrace->fraction != 0 |
|
pTrace->fractionleftsolid /= pTrace->fraction; |
|
pTrace->fraction = 1.0; |
|
} |
|
|
|
// Collide with entities along the ray |
|
// FIXME: Hitbox code causes this to be re-entrant for the IK stuff. |
|
// If we could eliminate that, this could be static and therefore |
|
// not have to reallocate memory all the time |
|
CEntityListAlongRay enumerator; |
|
enumerator.Reset(); |
|
SpatialPartition()->EnumerateElementsAlongRay( SpatialPartitionMask(), entityRay, false, &enumerator ); |
|
|
|
bool bNoStaticProps = pTraceFilter->GetTraceType() == TRACE_ENTITIES_ONLY; |
|
bool bFilterStaticProps = pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS; |
|
|
|
trace_t tr; |
|
ICollideable *pCollideable; |
|
const char *pDebugName; |
|
int nCount = enumerator.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
// Generate a collideable |
|
IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i]; |
|
HandleEntityToCollideable( pHandleEntity, &pCollideable, &pDebugName ); |
|
|
|
// Check for error condition |
|
if ( IsPC() && IsDebug() && !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) ) |
|
{ |
|
Assert( 0 ); |
|
Msg( "%s in solid list (not solid)\n", pDebugName ); |
|
continue; |
|
} |
|
|
|
if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ) |
|
{ |
|
if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) ) |
|
continue; |
|
} |
|
else |
|
{ |
|
// FIXME: Could remove this check here by |
|
// using a different spatial partition mask. Look into it |
|
// if we want more speedups here. |
|
if ( bNoStaticProps ) |
|
continue; |
|
|
|
if ( bFilterStaticProps ) |
|
{ |
|
if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) ) |
|
continue; |
|
} |
|
} |
|
|
|
ClipRayToCollideable( entityRay, fMask, pCollideable, &tr ); |
|
|
|
// Make sure the ray is always shorter than it currently is |
|
ClipTraceToTrace( tr, pTrace ); |
|
|
|
// Stop if we're in allsolid |
|
if (pTrace->allsolid) |
|
break; |
|
} |
|
|
|
// Fix up the fractions so they are appropriate given the original |
|
// unclipped-to-world ray |
|
pTrace->fraction *= flWorldFraction; |
|
pTrace->fractionleftsolid *= flWorldFractionLeftSolidScale; |
|
|
|
#ifdef _DEBUG |
|
Vector vecOffset, vecEndTest; |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset ); |
|
VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest ); |
|
Assert( VectorsAreEqual( vecEndTest, pTrace->startpos, 0.1f ) ); |
|
VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest ); |
|
Assert( VectorsAreEqual( vecEndTest, pTrace->endpos, 0.1f ) ); |
|
// Assert( !ray.m_IsRay || pTrace->allsolid || pTrace->fraction >= pTrace->fractionleftsolid ); |
|
#endif |
|
|
|
if ( !ray.m_IsRay ) |
|
{ |
|
// Make sure no fractionleftsolid can be used with box sweeps |
|
VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); |
|
pTrace->fractionleftsolid = 0; |
|
|
|
#ifdef _DEBUG |
|
pTrace->fractionleftsolid = VEC_T_NAN; |
|
#endif |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// A version that sweeps a collideable through the world |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::SweepCollideable( ICollideable *pCollide, |
|
const Vector &vecAbsStart, const Vector &vecAbsEnd, const QAngle &vecAngles, |
|
unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ) |
|
{ |
|
const matrix3x4_t *pOldRoot = m_pRootMoveParent; |
|
Ray_t ray; |
|
Assert( vecAngles == vec3_angle ); |
|
if ( pCollide->GetSolidFlags() & FSOLID_ROOT_PARENT_ALIGNED ) |
|
{ |
|
m_pRootMoveParent = pCollide->GetRootParentToWorldTransform(); |
|
} |
|
ray.Init( vecAbsStart, vecAbsEnd, pCollide->OBBMins(), pCollide->OBBMaxs() ); |
|
TraceRay( ray, fMask, pTraceFilter, pTrace ); |
|
m_pRootMoveParent = pOldRoot; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Lets clients know about all edicts along a ray |
|
//----------------------------------------------------------------------------- |
|
class CEnumerationFilter : public IPartitionEnumerator |
|
{ |
|
public: |
|
CEnumerationFilter( CEngineTrace *pEngineTrace, IEntityEnumerator* pEnumerator ) : |
|
m_pEngineTrace(pEngineTrace), m_pEnumerator(pEnumerator) {} |
|
|
|
IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) |
|
{ |
|
// Don't enumerate static props |
|
if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) ) |
|
return ITERATION_CONTINUE; |
|
|
|
if ( !m_pEnumerator->EnumEntity( pHandleEntity ) ) |
|
{ |
|
return ITERATION_STOP; |
|
} |
|
|
|
return ITERATION_CONTINUE; |
|
} |
|
|
|
private: |
|
IEntityEnumerator* m_pEnumerator; |
|
CEngineTrace *m_pEngineTrace; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Enumerates over all entities along a ray |
|
// If triggers == true, it enumerates all triggers along a ray |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::EnumerateEntities( const Ray_t &ray, bool bTriggers, IEntityEnumerator *pEnumerator ) |
|
{ |
|
m_traceStatCounters[TRACE_STAT_COUNTER_ENUMERATE]++; |
|
// FIXME: If we store CBaseHandles directly in the spatial partition, this method |
|
// basically becomes obsolete. The spatial partition can be queried directly. |
|
CEnumerationFilter enumerator( this, pEnumerator ); |
|
|
|
int fMask = !bTriggers ? SpatialPartitionMask() : SpatialPartitionTriggerMask(); |
|
|
|
// NOTE: Triggers currently don't exist on the client |
|
if (fMask) |
|
{ |
|
SpatialPartition()->EnumerateElementsAlongRay( fMask, ray, false, &enumerator ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Lets clients know about all entities in a box |
|
//----------------------------------------------------------------------------- |
|
void CEngineTrace::EnumerateEntities( const Vector &vecAbsMins, const Vector &vecAbsMaxs, IEntityEnumerator *pEnumerator ) |
|
{ |
|
m_traceStatCounters[TRACE_STAT_COUNTER_ENUMERATE]++; |
|
// FIXME: If we store CBaseHandles directly in the spatial partition, this method |
|
// basically becomes obsolete. The spatial partition can be queried directly. |
|
CEnumerationFilter enumerator( this, pEnumerator ); |
|
SpatialPartition()->EnumerateElementsInBox( SpatialPartitionMask(), |
|
vecAbsMins, vecAbsMaxs, false, &enumerator ); |
|
} |
|
|
|
|
|
class CEntList : public IEntityEnumerator |
|
{ |
|
public: |
|
virtual bool EnumEntity( IHandleEntity *pHandleEntity ) |
|
{ |
|
IServerUnknown *pNetEntity = static_cast<IServerUnknown*>(pHandleEntity); |
|
ICollideable *pCollide = pNetEntity->GetCollideable(); |
|
if ( !pCollide ) |
|
return true; |
|
|
|
Vector vecCenter; |
|
VectorMA( MainViewOrigin(), 100.0f, MainViewForward(), vecCenter ); |
|
float flDist = (vecCenter - pCollide->GetCollisionOrigin()).LengthSqr(); |
|
if (flDist < m_flClosestDist) |
|
{ |
|
m_flClosestDist = flDist; |
|
m_pClosest = pCollide; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
ICollideable *m_pClosest; |
|
float m_flClosestDist; |
|
|
|
|
|
}; |
|
|
|
#ifdef _DEBUG |
|
|
|
//----------------------------------------------------------------------------- |
|
// A method to test out sweeps |
|
//----------------------------------------------------------------------------- |
|
CON_COMMAND( test_sweepaabb, "method to test out sweeps" ) |
|
{ |
|
Vector vecStartPoint; |
|
VectorMA( MainViewOrigin(), 50.0f, MainViewForward(), vecStartPoint ); |
|
|
|
Vector endPoint; |
|
VectorMA( MainViewOrigin(), COORD_EXTENT * 1.74f, MainViewForward(), endPoint ); |
|
|
|
Ray_t ray; |
|
ray.Init( vecStartPoint, endPoint ); |
|
|
|
trace_t tr; |
|
// CTraceFilterHitAll traceFilter; |
|
// g_pEngineTraceServer->TraceRay( ray, MASK_ALL, &traceFilter, &tr ); |
|
|
|
CEntList list; |
|
list.m_pClosest = NULL; |
|
list.m_flClosestDist = FLT_MAX; |
|
g_pEngineTraceServer->EnumerateEntities( MainViewOrigin() - Vector( 200, 200, 200 ), MainViewOrigin() + Vector( 200, 200, 200 ), &list ); |
|
|
|
if ( !list.m_pClosest ) |
|
return; |
|
|
|
// Visualize the intersection test |
|
ICollideable *pCollide = list.m_pClosest; |
|
if ( pCollide->GetCollisionOrigin() == vec3_origin ) |
|
return; |
|
|
|
QAngle test( 0, 45, 0 ); |
|
#ifndef SWDS |
|
CDebugOverlay::AddBoxOverlay( pCollide->GetCollisionOrigin(), |
|
pCollide->OBBMins(), pCollide->OBBMaxs(), |
|
test /*pCollide->GetCollisionAngles()*/, 0, 0, 255, 128, 5.0f ); |
|
#endif |
|
|
|
VectorMA( MainViewOrigin(), 200.0f, MainViewForward(), endPoint ); |
|
ray.Init( vecStartPoint, endPoint, Vector( -10, -20, -10 ), Vector( 30, 30, 20 ) ); |
|
|
|
bool bIntersect = IntersectRayWithOBB( ray, pCollide->GetCollisionOrigin(), test, pCollide->OBBMins(), |
|
pCollide->OBBMaxs(), 0.0f, &tr ); |
|
unsigned char r, g, b, a; |
|
b = 0; |
|
a = 255; |
|
r = bIntersect ? 255 : 0; |
|
g = bIntersect ? 0 : 255; |
|
|
|
#ifndef SWDS |
|
CDebugOverlay::AddSweptBoxOverlay( tr.startpos, tr.endpos, |
|
Vector( -10, -20, -10 ), Vector( 30, 30, 20 ), vec3_angle, r, g, b, a, 5.0 ); |
|
#endif |
|
} |
|
|
|
|
|
#endif
|
|
|