summaryrefslogtreecommitdiff
path: root/engine/enginetrace.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/enginetrace.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'engine/enginetrace.cpp')
-rw-r--r--engine/enginetrace.cpp2010
1 files changed, 2010 insertions, 0 deletions
diff --git a/engine/enginetrace.cpp b/engine/enginetrace.cpp
new file mode 100644
index 0000000..54ac243
--- /dev/null
+++ b/engine/enginetrace.cpp
@@ -0,0 +1,2010 @@
+//========= 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