Counter Strike : Global Offensive Source Code
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.

3177 lines
103 KiB

  1. //====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =====//
  2. //
  3. // Purpose:
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //===========================================================================//
  9. #include "engine/IEngineTrace.h"
  10. #include "icliententitylist.h"
  11. #include "ispatialpartitioninternal.h"
  12. #include "icliententity.h"
  13. #include "cmodel_engine.h"
  14. #include "dispcoll_common.h"
  15. #include "staticpropmgr.h"
  16. #include "server.h"
  17. #include "edict.h"
  18. #include "gl_model_private.h"
  19. #include "world.h"
  20. #include "vphysics_interface.h"
  21. #include "client_class.h"
  22. #include "server_class.h"
  23. #include "debugoverlay.h"
  24. #include "collisionutils.h"
  25. #include "tier0/vprof.h"
  26. #include "convar.h"
  27. #include "mathlib/polyhedron.h"
  28. #include "sys_dll.h"
  29. #include "vphysics/virtualmesh.h"
  30. #include "tier1/utlhashtable.h"
  31. #include "tier1/refcount.h"
  32. #include "vstdlib/jobthread.h"
  33. #include "tier0/microprofiler.h"
  34. #if !COMPILER_GCC
  35. #include <atomic>
  36. #endif
  37. // memdbgon must be the last include file in a .cpp file!!!
  38. #include "tier0/memdbgon.h"
  39. //-----------------------------------------------------------------------------
  40. // Various statistics to gather
  41. //-----------------------------------------------------------------------------
  42. enum
  43. {
  44. TRACE_STAT_COUNTER_TRACERAY = 0,
  45. TRACE_STAT_COUNTER_POINTCONTENTS,
  46. TRACE_STAT_COUNTER_ENUMERATE,
  47. NUM_TRACE_STAT_COUNTER
  48. };
  49. //-----------------------------------------------------------------------------
  50. // Used to visualize raycasts going on
  51. //-----------------------------------------------------------------------------
  52. #ifdef _DEBUG
  53. ConVar debugrayenable( "debugrayenable", "0", NULL, "Use this to enable ray testing. To reset: bind \"F1\" \"clearalloverlays; debugrayreset 0; host_framerate 66.66666667\"" );
  54. ConVar debugrayreset( "debugrayreset", "0" );
  55. ConVar debugraylimit( "debugraylimit", "500", NULL, "number of rays per frame that you have to hit before displaying them all" );
  56. static CUtlVector<Ray_t> s_FrameRays;
  57. #endif
  58. #define BENCHMARK_RAY_TEST 0
  59. #if BENCHMARK_RAY_TEST
  60. static CUtlVector<Ray_t> s_BenchmarkRays;
  61. #endif
  62. class CAsyncOcclusionQuery;
  63. //-----------------------------------------------------------------------------
  64. // Implementation of IEngineTrace
  65. //-----------------------------------------------------------------------------
  66. abstract_class CEngineTrace : public IEngineTrace
  67. {
  68. public:
  69. CEngineTrace()
  70. {
  71. m_nOcclusionTestsSuspended = 0;
  72. }
  73. // Returns the contents mask at a particular world-space position
  74. virtual int GetPointContents( const Vector &vecAbsPosition, int contentsMask, IHandleEntity** ppEntity );
  75. virtual int GetPointContents_WorldOnly( const Vector &vecAbsPosition, int contentsMask );
  76. virtual int GetPointContents_Collideable( ICollideable *pCollide, const Vector &vecAbsPosition );
  77. // Traces a ray against a particular edict
  78. virtual void ClipRayToEntity( const Ray_t &ray, unsigned int fMask, IHandleEntity *pEntity, trace_t *pTrace );
  79. // A version that simply accepts a ray (can work as a traceline or tracehull)
  80. virtual void TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace );
  81. // A version that sets up the leaf and entity lists and allows you to pass those in for collision.
  82. virtual void SetupLeafAndEntityListRay( const Ray_t &ray, ITraceListData *pTraceData );
  83. virtual void SetupLeafAndEntityListBox( const Vector &vecBoxMin, const Vector &vecBoxMax, ITraceListData *pTraceData );
  84. virtual void TraceRayAgainstLeafAndEntityList( const Ray_t &ray, ITraceListData *pTraceData, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace );
  85. // A version that sweeps a collideable through the world
  86. // abs start + abs end represents the collision origins you want to sweep the collideable through
  87. // vecAngles represents the collision angles of the collideable during the sweep
  88. virtual void SweepCollideable( ICollideable *pCollide, const Vector &vecAbsStart, const Vector &vecAbsEnd,
  89. const QAngle &vecAngles, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace );
  90. // Enumerates over all entities along a ray
  91. // If triggers == true, it enumerates all triggers along a ray
  92. virtual void EnumerateEntities( const Ray_t &ray, bool triggers, IEntityEnumerator *pEnumerator );
  93. // Same thing, but enumerate entitys within a box
  94. virtual void EnumerateEntities( const Vector &vecAbsMins, const Vector &vecAbsMaxs, IEntityEnumerator *pEnumerator );
  95. // FIXME: Different versions for client + server. Eventually we need to make these go away
  96. virtual ICollideable *HandleEntityToCollideable( IHandleEntity *pHandleEntity ) = 0;
  97. virtual ICollideable *GetWorldCollideable() = 0;
  98. // Traces a ray against a particular edict
  99. virtual void ClipRayToCollideable( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace );
  100. // HACKHACK: Temp
  101. virtual int GetStatByIndex( int index, bool bClear );
  102. //finds brushes in an AABB, prone to some false positives
  103. virtual void GetBrushesInAABB( const Vector &vMins, const Vector &vMaxs, CBrushQuery &BrushQuery, int iContentsMask, int cmodelIndex );
  104. virtual void GetBrushesInCollideable( ICollideable *pCollideable, CBrushQuery &BrushQuery );
  105. //Creates a CPhysCollide out of all displacements wholly or partially contained in the specified AABB
  106. virtual CPhysCollide* GetCollidableFromDisplacementsInAABB( const Vector& vMins, const Vector& vMaxs );
  107. virtual int GetMeshesFromDisplacementsInAABB( const Vector& vMins, const Vector& vMaxs, virtualmeshlist_t *pOutputMeshes, int iMaxOutputMeshes );
  108. // gets the number of displacements in the world
  109. virtual int GetNumDisplacements( );
  110. // gets a specific diplacement mesh
  111. virtual void GetDisplacementMesh( int nIndex, virtualmeshlist_t *pMeshTriList );
  112. //retrieve brush planes and contents, returns zero if the brush doesn't exist,
  113. //returns positive number of sides filled out if the array can hold them all, negative number of slots needed to hold info if the array is too small
  114. virtual int GetBrushInfo( int iBrush, int &ContentsOut, BrushSideInfo_t *pBrushSideInfoOut, int iBrushSideInfoArraySize );
  115. virtual bool PointOutsideWorld( const Vector &ptTest ); //Tests a point to see if it's outside any playable area
  116. // Walks bsp to find the leaf containing the specified point
  117. virtual int GetLeafContainingPoint( const Vector &ptTest );
  118. virtual ITraceListData *AllocTraceListData() { return new CTraceListData; }
  119. virtual void FreeTraceListData(ITraceListData *pTraceListData) { delete pTraceListData; }
  120. /// Used only in debugging: get/set/clear/increment the trace debug counter. See comment below for details.
  121. virtual int GetSetDebugTraceCounter( int value, DebugTraceCounterBehavior_t behavior );
  122. virtual const char *GetDebugName( IHandleEntity *pHandleEntity ) = 0;
  123. virtual bool IsFullyOccluded( int nOcclusionKey, const AABB_t &aabb1, const AABB_t &aabb2, const Vector &vShadow ) OVERRIDE;
  124. virtual void SuspendOcclusionTests() OVERRIDE{ m_nOcclusionTestsSuspended++; }
  125. virtual void ResumeOcclusionTests()OVERRIDE;
  126. virtual void FlushOcclusionQueries() OVERRIDE;
  127. private:
  128. // FIXME: Different versions for client + server. Eventually we need to make these go away
  129. virtual void SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace ) = 0;
  130. virtual ICollideable *GetCollideable( IHandleEntity *pEntity ) = 0;
  131. virtual int SpatialPartitionMask() const = 0;
  132. virtual int SpatialPartitionTriggerMask() const = 0;
  133. // Figure out point contents for entities at a particular position
  134. int EntityContents( const Vector &vecAbsPosition );
  135. // Should we perform the custom raytest?
  136. bool ShouldPerformCustomRayTest( const Ray_t& ray, ICollideable *pCollideable ) const;
  137. // Performs the custom raycast
  138. bool ClipRayToCustom( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace );
  139. // Perform vphysics trace
  140. bool ClipRayToVPhysics( const Ray_t &ray, unsigned int fMask, ICollideable *pCollideable, studiohdr_t *pStudioHdr, trace_t *pTrace );
  141. // Perform hitbox trace
  142. bool ClipRayToHitboxes( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace );
  143. // Perform bsp trace
  144. bool ClipRayToBSP( const Ray_t &ray, unsigned int fMask, ICollideable *pCollideable, trace_t *pTrace );
  145. // bbox
  146. bool ClipRayToBBox( const Ray_t &ray, unsigned int fMask, ICollideable *pCollideable, trace_t *pTrace );
  147. // OBB
  148. bool ClipRayToOBB( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace );
  149. // Clips a trace to another trace
  150. bool ClipTraceToTrace( trace_t &clipTrace, trace_t *pFinalTrace );
  151. private:
  152. int m_traceStatCounters[NUM_TRACE_STAT_COUNTER];
  153. int m_nOcclusionTestsSuspended;
  154. // Note: occlusion key MUST be evenly distributed for this to work well. Fortunately, it's just player ids, they're distributed perfectly well
  155. CUtlHashtable < int, CAsyncOcclusionQuery*, IdentityHashFunctor > m_OcclusionQueryMap;
  156. friend void RayBench( const CCommand &args );
  157. friend void RayBatchBench( const CCommand &args );
  158. };
  159. extern void FlushOcclusionQueries();
  160. class CEngineTraceServer : public CEngineTrace
  161. {
  162. private:
  163. virtual ICollideable *HandleEntityToCollideable( IHandleEntity *pEnt );
  164. virtual const char *GetDebugName( IHandleEntity *pHandleEntity );
  165. virtual void SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace );
  166. virtual int SpatialPartitionMask() const;
  167. virtual int SpatialPartitionTriggerMask() const;
  168. virtual ICollideable *GetWorldCollideable();
  169. friend void RayBench( const CCommand &args );
  170. friend void RayBatchBench( const CCommand &args );
  171. public:
  172. // IEngineTrace
  173. virtual ICollideable *GetCollideable( IHandleEntity *pEntity );
  174. };
  175. #ifndef DEDICATED
  176. class CEngineTraceClient : public CEngineTrace
  177. {
  178. private:
  179. virtual ICollideable *HandleEntityToCollideable( IHandleEntity *pEnt );
  180. virtual const char *GetDebugName( IHandleEntity *pHandleEntity );
  181. virtual void SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace );
  182. virtual int SpatialPartitionMask() const;
  183. virtual int SpatialPartitionTriggerMask() const;
  184. virtual ICollideable *GetWorldCollideable();
  185. public:
  186. // IEngineTrace
  187. virtual ICollideable *GetCollideable( IHandleEntity *pEntity );
  188. };
  189. #endif
  190. //-----------------------------------------------------------------------------
  191. // Expose CVEngineServer to the game + client DLLs
  192. //-----------------------------------------------------------------------------
  193. static CEngineTraceServer s_EngineTraceServer;
  194. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEngineTraceServer, IEngineTrace, INTERFACEVERSION_ENGINETRACE_SERVER, s_EngineTraceServer);
  195. #ifndef DEDICATED
  196. static CEngineTraceClient s_EngineTraceClient;
  197. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEngineTraceClient, IEngineTrace, INTERFACEVERSION_ENGINETRACE_CLIENT, s_EngineTraceClient);
  198. #endif
  199. //-----------------------------------------------------------------------------
  200. // Expose CVEngineServer to the engine.
  201. //-----------------------------------------------------------------------------
  202. IEngineTrace *g_pEngineTraceServer = &s_EngineTraceServer;
  203. #ifndef DEDICATED
  204. IEngineTrace *g_pEngineTraceClient = &s_EngineTraceClient;
  205. #endif
  206. //-----------------------------------------------------------------------------
  207. // Client-server neutral method of getting at collideables
  208. //-----------------------------------------------------------------------------
  209. #ifndef DEDICATED
  210. ICollideable *CEngineTraceClient::GetCollideable( IHandleEntity *pEntity )
  211. {
  212. Assert( pEntity );
  213. ICollideable *pProp = StaticPropMgr()->GetStaticProp( pEntity );
  214. if ( pProp )
  215. return pProp;
  216. IClientUnknown *pUnk = entitylist->GetClientUnknownFromHandle( pEntity->GetRefEHandle() );
  217. return pUnk->GetCollideable();
  218. }
  219. #endif
  220. ICollideable *CEngineTraceServer::GetCollideable( IHandleEntity *pEntity )
  221. {
  222. Assert( pEntity );
  223. ICollideable *pProp = StaticPropMgr()->GetStaticProp( pEntity );
  224. if ( pProp )
  225. return pProp;
  226. IServerUnknown *pNetUnknown = static_cast<IServerUnknown*>(pEntity);
  227. return pNetUnknown->GetCollideable();
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Spatial partition masks for iteration
  231. //-----------------------------------------------------------------------------
  232. #ifndef DEDICATED
  233. int CEngineTraceClient::SpatialPartitionMask() const
  234. {
  235. return PARTITION_CLIENT_SOLID_EDICTS;
  236. }
  237. #endif
  238. int CEngineTraceServer::SpatialPartitionMask() const
  239. {
  240. return PARTITION_ENGINE_SOLID_EDICTS;
  241. }
  242. #ifndef DEDICATED
  243. int CEngineTraceClient::SpatialPartitionTriggerMask() const
  244. {
  245. return PARTITION_CLIENT_TRIGGER_ENTITIES;
  246. }
  247. #endif
  248. int CEngineTraceServer::SpatialPartitionTriggerMask() const
  249. {
  250. return PARTITION_ENGINE_TRIGGER_EDICTS;
  251. }
  252. //-----------------------------------------------------------------------------
  253. // Spatial partition enumerator looking for entities that we may lie within
  254. //-----------------------------------------------------------------------------
  255. class CPointContentsEnum : public IPartitionEnumerator
  256. {
  257. public:
  258. CPointContentsEnum( CEngineTrace *pEngineTrace, const Vector &pos, int contentsMask ) : m_Contents(CONTENTS_EMPTY), m_validMask(contentsMask)
  259. {
  260. m_pEngineTrace = pEngineTrace;
  261. m_Pos = pos;
  262. m_pCollide = NULL;
  263. }
  264. static inline bool TestEntity(
  265. CEngineTrace *pEngineTrace,
  266. ICollideable *pCollide,
  267. const Vector &vPos,
  268. int validMask,
  269. int *pContents,
  270. ICollideable **pWorldCollideable )
  271. {
  272. // Deal with static props
  273. // NOTE: I could have added static props to a different list and
  274. // enumerated them separately, but that would have been less efficient
  275. if ( (validMask & CONTENTS_SOLID) && StaticPropMgr()->IsStaticProp( pCollide->GetEntityHandle() ) )
  276. {
  277. Ray_t ray;
  278. trace_t trace;
  279. ray.Init( vPos, vPos );
  280. pEngineTrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &trace );
  281. if (trace.startsolid)
  282. {
  283. // We're in a static prop; that's solid baby
  284. // Pretend we hit the world
  285. *pContents = CONTENTS_SOLID;
  286. *pWorldCollideable = pEngineTrace->GetWorldCollideable();
  287. return true;
  288. }
  289. return false;
  290. }
  291. // We only care about solid volumes
  292. if ((pCollide->GetSolidFlags() & FSOLID_VOLUME_CONTENTS) == 0)
  293. return false;
  294. model_t* pModel = (model_t*)pCollide->GetCollisionModel();
  295. if ( pModel && pModel->type == mod_brush )
  296. {
  297. Assert( pCollide->GetCollisionModelIndex() < MAX_MODELS && pCollide->GetCollisionModelIndex() >= 0 );
  298. int nHeadNode = GetModelHeadNode( pCollide );
  299. int contents = CM_TransformedPointContents( vPos, nHeadNode,
  300. pCollide->GetCollisionOrigin(), pCollide->GetCollisionAngles() );
  301. if (contents & validMask)
  302. {
  303. // Return the contents of the first thing we hit
  304. *pContents = contents;
  305. *pWorldCollideable = pCollide;
  306. return true;
  307. }
  308. }
  309. return false;
  310. }
  311. IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
  312. {
  313. ICollideable *pCollide = m_pEngineTrace->HandleEntityToCollideable( pHandleEntity );
  314. if (!pCollide)
  315. return ITERATION_CONTINUE;
  316. if ( CPointContentsEnum::TestEntity( m_pEngineTrace, pCollide, m_Pos, m_validMask, &m_Contents, &m_pCollide ) )
  317. return ITERATION_STOP;
  318. else
  319. return ITERATION_CONTINUE;
  320. }
  321. private:
  322. static int GetModelHeadNode( ICollideable *pCollide )
  323. {
  324. int modelindex = pCollide->GetCollisionModelIndex();
  325. if(modelindex >= MAX_MODELS || modelindex < 0)
  326. return -1;
  327. model_t *pModel = (model_t*)pCollide->GetCollisionModel();
  328. if(!pModel)
  329. return -1;
  330. if(cmodel_t *pCModel = CM_InlineModelNumber(modelindex-1))
  331. return pCModel->headnode;
  332. else
  333. return -1;
  334. }
  335. public:
  336. int m_Contents;
  337. ICollideable *m_pCollide;
  338. private:
  339. CEngineTrace *m_pEngineTrace;
  340. Vector m_Pos;
  341. int m_validMask;
  342. };
  343. //-----------------------------------------------------------------------------
  344. // Returns the world contents
  345. //-----------------------------------------------------------------------------
  346. int CEngineTrace::GetPointContents_WorldOnly( const Vector &vecAbsPosition, int contentsMask )
  347. {
  348. int nContents = CM_PointContents( vecAbsPosition, 0, contentsMask );
  349. return nContents;
  350. }
  351. //-----------------------------------------------------------------------------
  352. // Returns the contents mask at a particular world-space position
  353. //-----------------------------------------------------------------------------
  354. int CEngineTrace::GetPointContents( const Vector &vecAbsPosition, int contentsMask, IHandleEntity** ppEntity )
  355. {
  356. VPROF( "CEngineTrace_GetPointContents" );
  357. // VPROF_BUDGET( "CEngineTrace_GetPointContents", "CEngineTrace_GetPointContents" );
  358. m_traceStatCounters[TRACE_STAT_COUNTER_POINTCONTENTS]++;
  359. // First check the collision model
  360. int nContents = CM_PointContents( vecAbsPosition, 0, contentsMask ) & contentsMask;
  361. if ( nContents != CONTENTS_SOLID )
  362. {
  363. CPointContentsEnum contentsEnum(this, vecAbsPosition, contentsMask);
  364. SpatialPartition()->EnumerateElementsAtPoint( SpatialPartitionMask(),
  365. vecAbsPosition, false, &contentsEnum );
  366. int nEntityContents = contentsEnum.m_Contents;
  367. if ( nEntityContents != CONTENTS_EMPTY )
  368. {
  369. if (ppEntity)
  370. {
  371. *ppEntity = contentsEnum.m_pCollide->GetEntityHandle();
  372. }
  373. return nEntityContents;
  374. }
  375. }
  376. if (ppEntity)
  377. {
  378. *ppEntity = GetWorldCollideable()->GetEntityHandle();
  379. }
  380. return nContents;
  381. }
  382. int CEngineTrace::GetPointContents_Collideable( ICollideable *pCollide, const Vector &vecAbsPosition )
  383. {
  384. int contents = CONTENTS_EMPTY;
  385. ICollideable *pDummy = NULL;
  386. CPointContentsEnum::TestEntity( this, pCollide, vecAbsPosition, MASK_ALL, &contents, &pDummy );
  387. return contents;
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Should we perform the custom raytest?
  391. //-----------------------------------------------------------------------------
  392. inline bool CEngineTrace::ShouldPerformCustomRayTest( const Ray_t& ray, ICollideable *pCollideable ) const
  393. {
  394. // No model? The entity's got its own collision detector maybe
  395. // Does the entity force box or ray tests to go through its code?
  396. return( (pCollideable->GetSolid() == SOLID_CUSTOM) ||
  397. (ray.m_IsRay && (pCollideable->GetSolidFlags() & FSOLID_CUSTOMRAYTEST )) ||
  398. (!ray.m_IsRay && (pCollideable->GetSolidFlags() & FSOLID_CUSTOMBOXTEST )) );
  399. }
  400. //-----------------------------------------------------------------------------
  401. // Performs the custom raycast
  402. //-----------------------------------------------------------------------------
  403. bool CEngineTrace::ClipRayToCustom( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace )
  404. {
  405. if ( pCollideable->TestCollision( ray, fMask, *pTrace ))
  406. {
  407. return true;
  408. }
  409. return false;
  410. }
  411. //-----------------------------------------------------------------------------
  412. // Performs the hitbox raycast, returns true if the hitbox test was made
  413. //-----------------------------------------------------------------------------
  414. bool CEngineTrace::ClipRayToHitboxes( const Ray_t& ray, unsigned int fMask, ICollideable *pCollideable, trace_t* pTrace )
  415. {
  416. trace_t hitboxTrace;
  417. CM_ClearTrace( &hitboxTrace );
  418. // Keep track of the contents of what was hit initially
  419. hitboxTrace.contents = pTrace->contents;
  420. VectorAdd( ray.m_Start, ray.m_StartOffset, hitboxTrace.startpos );
  421. VectorAdd( hitboxTrace.startpos, ray.m_Delta, hitboxTrace.endpos );
  422. // At the moment, it has to be a true ray to work with hitboxes
  423. if ( !ray.m_IsRay )
  424. return false;
  425. // If the hitboxes weren't even tested, then just use the original trace
  426. if (!pCollideable->TestHitboxes( ray, fMask, hitboxTrace ))
  427. return false;
  428. // If they *were* tested and missed, clear the original trace
  429. if (!hitboxTrace.DidHit())
  430. {
  431. CM_ClearTrace( pTrace );
  432. pTrace->startpos = hitboxTrace.startpos;
  433. pTrace->endpos = hitboxTrace.endpos;
  434. }
  435. else if ( pCollideable->GetSolid() != SOLID_VPHYSICS )
  436. {
  437. // If we also hit the hitboxes, maintain fractionleftsolid +
  438. // startpos because those are reasonable enough values and the
  439. // hitbox code doesn't set those itself.
  440. Vector vecStartPos = pTrace->startpos;
  441. float flFractionLeftSolid = pTrace->fractionleftsolid;
  442. *pTrace = hitboxTrace;
  443. if (hitboxTrace.startsolid)
  444. {
  445. pTrace->startpos = vecStartPos;
  446. pTrace->fractionleftsolid = flFractionLeftSolid;
  447. }
  448. }
  449. else
  450. {
  451. // Fill out the trace hitbox details
  452. pTrace->contents = hitboxTrace.contents;
  453. pTrace->hitgroup = hitboxTrace.hitgroup;
  454. pTrace->hitbox = hitboxTrace.hitbox;
  455. pTrace->physicsbone = hitboxTrace.physicsbone;
  456. pTrace->surface = hitboxTrace.surface;
  457. Assert( pTrace->physicsbone >= 0 );
  458. // Fill out the surfaceprop details from the hitbox. Use the physics bone instead of the hitbox bone
  459. Assert(pTrace->surface.flags == SURF_HITBOX);
  460. }
  461. return true;
  462. }
  463. int CEngineTrace::GetStatByIndex( int index, bool bClear )
  464. {
  465. if ( index >= NUM_TRACE_STAT_COUNTER )
  466. return 0;
  467. int out = m_traceStatCounters[index];
  468. if ( bClear )
  469. {
  470. m_traceStatCounters[index] = 0;
  471. }
  472. return out;
  473. }
  474. class CSetupBrushQuery : public CBrushQuery
  475. {
  476. public:
  477. void Setup( int iCount, uint32 *pBrushes, int iMaxBrushSides, TraceInfo_t *pTraceInfo )
  478. {
  479. m_iCount = iCount;
  480. m_pBrushes = pBrushes;
  481. m_iMaxBrushSides = iMaxBrushSides;
  482. m_pData = pTraceInfo;
  483. m_pReleaseFunc = CSetupBrushQuery::BrushQueryReleaseFunc;
  484. }
  485. static void BrushQueryReleaseFunc( CBrushQuery *pBrushQuery )
  486. {
  487. TraceInfo_t *pTraceInfo = reinterpret_cast<TraceInfo_t *>(reinterpret_cast<CSetupBrushQuery *>(pBrushQuery)->m_pData);
  488. EndTrace( pTraceInfo );
  489. }
  490. };
  491. void CEngineTrace::GetBrushesInAABB( const Vector &vMins, const Vector &vMaxs, CBrushQuery &BrushQuery, int nContentsMask, int nCModelIndex )
  492. {
  493. BrushQuery.ReleasePrivateData();
  494. //similar to CM_BoxTraceAgainstLeafList() but tracking every brush we intersect
  495. TraceInfo_t *pTraceInfo = BeginTrace();
  496. if ( nContentsMask == CONTENTS_BRUSH_PAINT && !host_state.worldbrush->m_pSurfaceBrushList )
  497. {
  498. nContentsMask = MASK_ALL;
  499. }
  500. Vector vCenter = (vMins + vMaxs) * 0.5f;
  501. Vector vExtents = vMaxs - vCenter;
  502. CM_ClearTrace(&pTraceInfo->m_trace);
  503. // Setup global trace data. (This is nasty! I hate this.)
  504. pTraceInfo->m_bDispHit = false;
  505. pTraceInfo->m_DispStabDir.Init();
  506. pTraceInfo->m_contents = nContentsMask;
  507. VectorCopy( vCenter, pTraceInfo->m_start );
  508. VectorCopy( vCenter, pTraceInfo->m_end );
  509. VectorMultiply( vExtents, -1.0f, pTraceInfo->m_mins );
  510. VectorCopy( vExtents, pTraceInfo->m_maxs );
  511. VectorCopy( vExtents, pTraceInfo->m_extents );
  512. pTraceInfo->m_delta = vec3_origin;
  513. pTraceInfo->m_invDelta = vec3_origin;
  514. pTraceInfo->m_ispoint = false;
  515. pTraceInfo->m_isswept = false;
  516. int *pLeafList = (int *)stackalloc( pTraceInfo->m_pBSPData->numleafs * sizeof( int ) );
  517. int iNumLeafs = CM_BoxLeafnums( vMins, vMaxs, pLeafList, pTraceInfo->m_pBSPData->numleafs, NULL, nCModelIndex );
  518. TraceCounter_t *pVisitedBrushes = pTraceInfo->m_BrushCounters[0].Base();
  519. Plat_FastMemset( pVisitedBrushes, 0, pTraceInfo->m_BrushCounters[0].Count() * sizeof(TraceCounter_t) );
  520. TraceCounter_t *pKeepBrushes = pTraceInfo->m_BrushCounters[1].Base();
  521. int iKeepBrushCount = 0;
  522. int iMaxBrushSides = 0;
  523. for( int iLeaf = 0; iLeaf != iNumLeafs; ++iLeaf )
  524. {
  525. cleaf_t *pLeaf = &pTraceInfo->m_pBSPData->map_leafs[pLeafList[iLeaf]];
  526. for( int iBrushCounter = 0; iBrushCounter != pLeaf->numleafbrushes; ++iBrushCounter )
  527. {
  528. int iBrushNumber = pTraceInfo->m_pBSPData->map_leafbrushes[pLeaf->firstleafbrush + iBrushCounter];
  529. if( pVisitedBrushes[iBrushNumber] > 0 )
  530. continue;
  531. pVisitedBrushes[iBrushNumber] = 1;
  532. cbrush_t *pBrush = &pTraceInfo->m_pBSPData->map_brushes[iBrushNumber];
  533. // only collide with objects you are interested in
  534. if( !( pBrush->contents & nContentsMask ) )
  535. continue;
  536. CM_TestBoxInBrush( pTraceInfo, pBrush );
  537. if ( pTraceInfo->m_trace.allsolid )
  538. {
  539. //store the brush
  540. Assert( iKeepBrushCount < pTraceInfo->m_BrushCounters[0].Count() );
  541. pKeepBrushes[iKeepBrushCount] = iBrushNumber;
  542. ++iKeepBrushCount;
  543. int iSideCount = pBrush->IsBox() ? 6 : pBrush->numsides;
  544. if( iSideCount > iMaxBrushSides )
  545. {
  546. iMaxBrushSides = iSideCount;
  547. }
  548. pTraceInfo->m_trace.allsolid = false; //clear the flag for re-use
  549. }
  550. }
  551. }
  552. //Purposefully not ending the trace here!
  553. //The CBrushQuery type holds onto the TraceInfo_t until it's destructed by whoever called us
  554. //EndTrace( pTraceInfo );
  555. ((CSetupBrushQuery *)&BrushQuery)->Setup( iKeepBrushCount, pKeepBrushes, iMaxBrushSides, pTraceInfo );
  556. }
  557. static void GetBrushesInCollideable_r( CCollisionBSPData *pBSPData, TraceCounter_t *pVisitedBrushes, TraceCounter_t **pKeepBrushes, int node )
  558. {
  559. if ( node < 0 )
  560. {
  561. int leafIndex = -1 - node;
  562. // Add the solids in the "empty" leaf
  563. for ( int i = 0; i < pBSPData->map_leafs[leafIndex].numleafbrushes; i++ )
  564. {
  565. int brushIndex = pBSPData->map_leafbrushes[pBSPData->map_leafs[leafIndex].firstleafbrush + i];
  566. if( pVisitedBrushes[brushIndex] == 0 )
  567. {
  568. pVisitedBrushes[brushIndex] = 1;
  569. **pKeepBrushes = brushIndex;
  570. ++(*pKeepBrushes);
  571. }
  572. }
  573. }
  574. else
  575. {
  576. cnode_t *pnode = &pBSPData->map_nodes[node];
  577. GetBrushesInCollideable_r( pBSPData, pVisitedBrushes, pKeepBrushes, pnode->children[0] );
  578. GetBrushesInCollideable_r( pBSPData, pVisitedBrushes, pKeepBrushes, pnode->children[1] );
  579. }
  580. }
  581. void CEngineTrace::GetBrushesInCollideable( ICollideable *pCollideable, CBrushQuery &BrushQuery )
  582. {
  583. BrushQuery.ReleasePrivateData();
  584. //if( pCollideable->GetSolid() != SOLID_BSP )
  585. // return; //should anything other than SOLID_BSP be valid?
  586. int nModelIndex = pCollideable->GetCollisionModelIndex();
  587. cmodel_t *pCModel = CM_InlineModelNumber( nModelIndex - 1 );
  588. if( pCModel == NULL )
  589. return;
  590. int nHeadNode = pCModel->headnode;
  591. TraceInfo_t *pTraceInfo = BeginTrace();
  592. CM_ClearTrace(&pTraceInfo->m_trace);
  593. // Setup global trace data. (This is nasty! I hate this.)
  594. pTraceInfo->m_bDispHit = false;
  595. pTraceInfo->m_DispStabDir.Init();
  596. pTraceInfo->m_contents = CONTENTS_EMPTY;
  597. VectorCopy( vec3_origin, pTraceInfo->m_start );
  598. VectorCopy( vec3_origin, pTraceInfo->m_end );
  599. VectorCopy( vec3_origin, pTraceInfo->m_mins );
  600. VectorCopy( vec3_origin, pTraceInfo->m_maxs );
  601. VectorCopy( vec3_origin, pTraceInfo->m_extents );
  602. pTraceInfo->m_delta = vec3_origin;
  603. pTraceInfo->m_invDelta = vec3_origin;
  604. pTraceInfo->m_ispoint = false;
  605. pTraceInfo->m_isswept = false;
  606. for( int i = 0; i != 2; ++i )
  607. {
  608. memset( pTraceInfo->m_BrushCounters[i].Base(), 0, pTraceInfo->m_BrushCounters[i].Count() * sizeof(TraceCounter_t) );
  609. }
  610. TraceCounter_t *pKeepBrushes = pTraceInfo->m_BrushCounters[1].Base(); //will get modified by GetBrushesInCollideable_r
  611. GetBrushesInCollideable_r( pTraceInfo->m_pBSPData, pTraceInfo->m_BrushCounters[0].Base(), &pKeepBrushes, nHeadNode );
  612. int iKeepBrushCount = pKeepBrushes - pTraceInfo->m_BrushCounters[1].Base();
  613. pKeepBrushes = pTraceInfo->m_BrushCounters[1].Base();
  614. int iMaxBrushSides = 0;
  615. for( int i = 0; i != iKeepBrushCount; ++i )
  616. {
  617. cbrush_t *pBrush = &pTraceInfo->m_pBSPData->map_brushes[pKeepBrushes[i]];
  618. int iSideCount = pBrush->IsBox() ? 6 : pBrush->numsides;
  619. if( iSideCount > iMaxBrushSides )
  620. {
  621. iMaxBrushSides = iSideCount;
  622. }
  623. }
  624. //Purposefully not ending the trace here!
  625. //The CBrushQuery type holds onto the TraceInfo_t until it's destructed by whoever called us
  626. //EndTrace( pTraceInfo );
  627. ((CSetupBrushQuery *)&BrushQuery)->Setup( iKeepBrushCount, pKeepBrushes, iMaxBrushSides, pTraceInfo );
  628. }
  629. //-----------------------------------------------------------------------------
  630. // Purpose: Used to copy the collision information of all displacement surfaces in a specified box
  631. // Input : vMins - min vector of the AABB
  632. // vMaxs - max vector of the AABB
  633. // Output : CPhysCollide* the collision mesh created from all the displacements partially contained in the specified box
  634. // Note: We're not clipping to the box. Collidable may be larger than the box provided.
  635. //-----------------------------------------------------------------------------
  636. CPhysCollide* CEngineTrace::GetCollidableFromDisplacementsInAABB( const Vector& vMins, const Vector& vMaxs )
  637. {
  638. CCollisionBSPData *pBSPData = GetCollisionBSPData();
  639. int *pLeafList = (int *)stackalloc( pBSPData->numleafs * sizeof( int ) );
  640. int iLeafCount = CM_BoxLeafnums( vMins, vMaxs, pLeafList, pBSPData->numleafs, NULL );
  641. // Get all the triangles for displacement surfaces in this box, add them to a polysoup
  642. CPhysPolysoup *pDispCollideSoup = physcollision->PolysoupCreate();
  643. // Count total triangles added to this poly soup- Can't support more than 65535.
  644. int iTriCount = 0;
  645. TraceInfo_t *pTraceInfo = BeginTrace();
  646. TraceCounter_t *pCounters = pTraceInfo->GetDispCounters();
  647. int count = pTraceInfo->GetCount();
  648. // For each leaf in which the box lies, Get all displacements in that leaf and use their triangles to create the mesh
  649. for ( int i = 0; i < iLeafCount; ++i )
  650. {
  651. // Current leaf
  652. cleaf_t curLeaf = pBSPData->map_leafs[ pLeafList[i] ];
  653. // Test box against all displacements in the leaf.
  654. for( int i = 0; i < curLeaf.dispCount; i++ )
  655. {
  656. int dispIndex = pBSPData->map_dispList[curLeaf.dispListStart + i];
  657. CDispCollTree *pDispTree = &g_pDispCollTrees[dispIndex];
  658. // make sure we only check this brush once per trace/stab
  659. if ( !pTraceInfo->Visit( pDispTree->m_iCounter, count, pCounters ) )
  660. continue;
  661. // If this displacement doesn't touch our test box, don't add it to the list.
  662. if ( !IsBoxIntersectingBox( vMins, vMaxs, pDispTree->m_mins, pDispTree->m_maxs) )
  663. continue;
  664. // The the triangle mesh for this displacement surface
  665. virtualmeshlist_t meshTriList;
  666. pDispTree->GetVirtualMeshList( &meshTriList );
  667. Assert ( meshTriList.indexCount%3 == 0 );
  668. Assert ( meshTriList.indexCount != 0 );
  669. Assert ( meshTriList.indexCount/3 == meshTriList.triangleCount );
  670. // Don't allow more than 64k triangles in a collision model
  671. // TODO: Return a list of collidables? How often do we break 64k triangles?
  672. iTriCount += meshTriList.triangleCount;
  673. if ( iTriCount > 65535 )
  674. {
  675. AssertMsg ( 0, "Displacement surfaces have too many triangles to duplicate in GetCollidableFromDisplacementsInBox." );
  676. EndTrace( pTraceInfo );
  677. return NULL;
  678. }
  679. for ( int j = 0; j < meshTriList.indexCount; j+=3 )
  680. {
  681. // Don't index past the index list
  682. Assert( j+2 < meshTriList.indexCount );
  683. if ( j+2 >= meshTriList.indexCount )
  684. {
  685. EndTrace( pTraceInfo );
  686. physcollision->PolysoupDestroy( pDispCollideSoup );
  687. return NULL;
  688. }
  689. unsigned short i0 = meshTriList.indices[j+0];
  690. unsigned short i1 = meshTriList.indices[j+1];
  691. unsigned short i2 = meshTriList.indices[j+2];
  692. // Don't index past the end of the vert list
  693. Assert ( i0 < meshTriList.vertexCount && i1 < meshTriList.vertexCount && i2 < meshTriList.vertexCount );
  694. if ( i0 >= meshTriList.vertexCount || i1 >= meshTriList.vertexCount || i2 >= meshTriList.vertexCount )
  695. {
  696. EndTrace( pTraceInfo );
  697. physcollision->PolysoupDestroy( pDispCollideSoup );
  698. return NULL;
  699. }
  700. Vector &v0 = meshTriList.pVerts[ i0 ];
  701. Vector &v1 = meshTriList.pVerts[ i1 ];
  702. Vector &v2 = meshTriList.pVerts[ i2 ];
  703. Assert ( v0.IsValid() && v1.IsValid() && v2.IsValid() );
  704. // We don't need exact clipping to the box... Include any triangle that has at least one vert on the inside
  705. if ( IsPointInBox( v0, vMins, vMaxs ) || IsPointInBox( v1, vMins, vMaxs ) || IsPointInBox( v2, vMins, vMaxs ) )
  706. {
  707. // This is for collision only, so we don't need to worry about blending-- Use the first surface prop.
  708. int nProp = pDispTree->GetSurfaceProps(0);
  709. physcollision->PolysoupAddTriangle( pDispCollideSoup, v0, v1, v2, nProp );
  710. }
  711. }// triangle loop
  712. }// for each displacement in leaf
  713. }// for each leaf
  714. EndTrace( pTraceInfo );
  715. CPhysCollide* pCollide = physcollision->ConvertPolysoupToCollide ( pDispCollideSoup, false );
  716. // clean up poly soup
  717. physcollision->PolysoupDestroy( pDispCollideSoup );
  718. return pCollide;
  719. }
  720. //-----------------------------------------------------------------------------
  721. // Purpose: Used to copy the mesh information of all displacement surfaces in a specified box
  722. // Input : vMins - min vector of the AABB
  723. // vMaxs - max vector of the AABB
  724. // pOutputMeshes - A preallocated array to store results
  725. // iMaxOutputMeshes - The array size of pOutputMeshes
  726. // Output : Number of meshes written to pOutputMeshes
  727. //-----------------------------------------------------------------------------
  728. int CEngineTrace::GetMeshesFromDisplacementsInAABB( const Vector& vMins, const Vector& vMaxs, virtualmeshlist_t *pOutputMeshes, int iMaxOutputMeshes )
  729. {
  730. int iMeshesWritten = 0;
  731. CCollisionBSPData *pBSPData = GetCollisionBSPData();
  732. int *pLeafList = (int *)stackalloc( pBSPData->numleafs * sizeof( int ) );
  733. int iLeafCount = CM_BoxLeafnums( vMins, vMaxs, pLeafList, pBSPData->numleafs, NULL );
  734. TraceInfo_t *pTraceInfo = BeginTrace();
  735. TraceCounter_t *pCounters = pTraceInfo->GetDispCounters();
  736. int count = pTraceInfo->GetCount();
  737. // For each leaf in which the box lies, Get all displacements in that leaf and use their triangles to create the mesh
  738. for ( int i = 0; i < iLeafCount; ++i )
  739. {
  740. // Current leaf
  741. cleaf_t curLeaf = pBSPData->map_leafs[ pLeafList[i] ];
  742. // Test box against all displacements in the leaf.
  743. for( int i = 0; i < curLeaf.dispCount; i++ )
  744. {
  745. int dispIndex = pBSPData->map_dispList[curLeaf.dispListStart + i];
  746. CDispCollTree *pDispTree = &g_pDispCollTrees[dispIndex];
  747. // make sure we only check this brush once per trace/stab
  748. if ( !pTraceInfo->Visit( pDispTree->m_iCounter, count, pCounters ) )
  749. continue;
  750. // If this displacement doesn't touch our test box, don't add it to the list.
  751. if ( !IsBoxIntersectingBox( vMins, vMaxs, pDispTree->m_mins, pDispTree->m_maxs) )
  752. continue;
  753. // Get the triangle mesh for this displacement surface
  754. pDispTree->GetVirtualMeshList( &pOutputMeshes[iMeshesWritten] );
  755. ++iMeshesWritten;
  756. if( iMeshesWritten == iMaxOutputMeshes )
  757. {
  758. EndTrace( pTraceInfo );
  759. return iMeshesWritten;
  760. }
  761. }// for each displacement in leaf
  762. }// for each leaf
  763. EndTrace( pTraceInfo );
  764. return iMeshesWritten;
  765. }
  766. CON_COMMAND( disp_list_all_collideable, "List all collideable displacements" )
  767. {
  768. int nPhysicsCollide = 0, nHullCollide = 0, nRayCollide = 0;
  769. ConMsg( "Displacement list:\n" );
  770. for ( int i = 0; i < g_DispCollTreeCount; ++ i )
  771. {
  772. CDispCollTree *pDispCollisionTree = &g_pDispCollTrees[ i ];
  773. int nFlags = pDispCollisionTree->GetFlags();
  774. Vector vMin, vMax;
  775. pDispCollisionTree->GetBounds( vMin, vMax );
  776. Vector vCenter = ( vMin + vMax ) * 0.5f;
  777. ConMsg( "Displacement %3d, location ( % 10.2f % 10.2f % 10.2f ), collision flags: %s %s %s\n",
  778. i, vCenter.x, vCenter.y, vCenter.z,
  779. ( nFlags & CCoreDispInfo::SURF_NOPHYSICS_COLL ) ? " Physics" : "NO Physics",
  780. ( nFlags & CCoreDispInfo::SURF_NOHULL_COLL ) ? " Hull" : "NO Hull",
  781. ( nFlags & CCoreDispInfo::SURF_NORAY_COLL ) ? " Ray" : "NO Ray" );
  782. nPhysicsCollide += ( nFlags & CCoreDispInfo::SURF_NOPHYSICS_COLL ) ? 1 : 0;
  783. nHullCollide += ( nFlags & CCoreDispInfo::SURF_NOHULL_COLL ) ? 1 : 0;
  784. nRayCollide += ( nFlags & CCoreDispInfo::SURF_NORAY_COLL ) ? 1 : 0;
  785. }
  786. ConMsg( "Total displacements: %d\nCollision stats: %d with physics, %d with hull, %d with ray.\n", g_DispCollTreeCount, nPhysicsCollide, nHullCollide, nRayCollide );
  787. }
  788. int CEngineTrace::GetNumDisplacements( )
  789. {
  790. return g_DispCollTreeCount;
  791. }
  792. void CEngineTrace::GetDisplacementMesh( int nIndex, virtualmeshlist_t *pMeshTriList )
  793. {
  794. g_pDispCollTrees[ nIndex ].GetVirtualMeshList( pMeshTriList );
  795. }
  796. int CEngineTrace::GetBrushInfo( int iBrush, int &ContentsOut, BrushSideInfo_t *pBrushSideInfoOut, int iBrushSideInfoArraySize )
  797. {
  798. CCollisionBSPData *pBSPData = GetCollisionBSPData();
  799. if( iBrush < 0 || iBrush >= pBSPData->numbrushes )
  800. return 0;
  801. cbrush_t *pBrush = &pBSPData->map_brushes[iBrush];
  802. ContentsOut = pBrush->contents;
  803. if ( pBrush->IsBox() )
  804. {
  805. if( !pBrushSideInfoOut || (iBrushSideInfoArraySize < 6) )
  806. return -6;
  807. cboxbrush_t *pBox = &pBSPData->map_boxbrushes[pBrush->GetBox()];
  808. for ( int i = 0; i < 6; i++ )
  809. {
  810. V_memset( &pBrushSideInfoOut[i].plane, 0, sizeof( pBrushSideInfoOut[i].plane ) );
  811. int maskIndex = i;
  812. if ( i < 3 )
  813. {
  814. pBrushSideInfoOut[i].plane.normal[i] = 1.0f;
  815. pBrushSideInfoOut[i].plane.dist = pBox->maxs[i];
  816. maskIndex += 3;
  817. }
  818. else
  819. {
  820. pBrushSideInfoOut[i].plane.normal[i-3] = -1.0f;
  821. pBrushSideInfoOut[i].plane.dist = -pBox->mins[i-3];
  822. }
  823. pBrushSideInfoOut[i].bevel = 0;
  824. pBrushSideInfoOut[i].thin = ( pBox->thinMask & (1 << maskIndex) ) ? 1 : 0;
  825. }
  826. return 6;
  827. }
  828. else
  829. {
  830. if( !pBrushSideInfoOut || (iBrushSideInfoArraySize < pBrush->numsides) )
  831. return -pBrush->numsides;
  832. cbrushside_t *stopside = &pBSPData->map_brushsides[pBrush->firstbrushside];
  833. // 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
  834. stopside += pBrush->numsides;
  835. for( cbrushside_t *side = &pBSPData->map_brushsides[pBrush->firstbrushside]; side != stopside; ++side )
  836. {
  837. pBrushSideInfoOut->plane = *side->plane;
  838. pBrushSideInfoOut->bevel = side->bBevel;
  839. pBrushSideInfoOut->thin = side->bThin;
  840. ++pBrushSideInfoOut;
  841. }
  842. return pBrush->numsides;
  843. }
  844. }
  845. //Tests a point to see if it's outside any playable area
  846. bool CEngineTrace::PointOutsideWorld( const Vector &ptTest )
  847. {
  848. int iLeaf = CM_PointLeafnum( ptTest );
  849. Assert( iLeaf >= 0 );
  850. CCollisionBSPData *pBSPData = GetCollisionBSPData();
  851. if( pBSPData->map_leafs[iLeaf].cluster == -1 )
  852. return true;
  853. return false;
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose: Expose to the game dll a method for finding the leaf which contains a given point
  857. // Input : &vPos - Returns the leaf which contains this point
  858. // Output : int - The handle to the leaf
  859. //-----------------------------------------------------------------------------
  860. int CEngineTrace::GetLeafContainingPoint( const Vector &vPos )
  861. {
  862. return CM_PointLeafnum( vPos );
  863. }
  864. //-----------------------------------------------------------------------------
  865. // Convex info for studio + brush models
  866. //-----------------------------------------------------------------------------
  867. class CBrushConvexInfo : public IConvexInfo
  868. {
  869. public:
  870. CBrushConvexInfo()
  871. {
  872. m_pBSPData = GetCollisionBSPData();
  873. }
  874. virtual unsigned int GetContents( int convexGameData )
  875. {
  876. return m_pBSPData->map_brushes[convexGameData].contents;
  877. }
  878. private:
  879. CCollisionBSPData *m_pBSPData;
  880. };
  881. class CStudioConvexInfo : public IConvexInfo
  882. {
  883. public:
  884. CStudioConvexInfo( studiohdr_t *pStudioHdr )
  885. {
  886. m_pStudioHdr = pStudioHdr;
  887. }
  888. virtual unsigned int GetContents( int convexGameData )
  889. {
  890. if ( convexGameData == 0 )
  891. {
  892. return m_pStudioHdr->contents;
  893. }
  894. Assert( convexGameData <= m_pStudioHdr->numbones );
  895. const mstudiobone_t *pBone = m_pStudioHdr->pBone(convexGameData - 1);
  896. return pBone->contents;
  897. }
  898. private:
  899. studiohdr_t *m_pStudioHdr;
  900. };
  901. //-----------------------------------------------------------------------------
  902. // Perform vphysics trace
  903. //-----------------------------------------------------------------------------
  904. bool CEngineTrace::ClipRayToVPhysics( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, studiohdr_t *pStudioHdr, trace_t *pTrace )
  905. {
  906. if ( pEntity->GetSolid() != SOLID_VPHYSICS )
  907. return false;
  908. bool bTraced = false;
  909. // use the vphysics model for rotated brushes and vphysics simulated objects
  910. const model_t *pModel = pEntity->GetCollisionModel();
  911. if( !pModel )
  912. {
  913. IPhysicsObject *pPhysics = pEntity->GetVPhysicsObject();
  914. if ( pPhysics )
  915. {
  916. const CPhysCollide *pSolid = pPhysics->GetCollide();
  917. if ( pSolid )
  918. {
  919. physcollision->TraceBox(
  920. ray,
  921. fMask,
  922. NULL,
  923. pSolid,
  924. pEntity->GetCollisionOrigin(),
  925. pEntity->GetCollisionAngles(),
  926. pTrace );
  927. return true;
  928. }
  929. }
  930. Vector vecMins = pEntity->OBBMins( ), vecMaxs = pEntity->OBBMaxs();
  931. Warning("CEngineTrace::ClipRayToVPhysics : no model; bbox {%g,%g,%g}-{%g,%g,%g}\n", vecMins.x,vecMins.y,vecMins.z, vecMaxs.x,vecMaxs.y,vecMaxs.z) ;
  932. return false;
  933. }
  934. if ( pStudioHdr )
  935. {
  936. CStudioConvexInfo studioConvex( pStudioHdr );
  937. vcollide_t *pCollide = g_pMDLCache->GetVCollide( pModel->studio );
  938. if ( pCollide && pCollide->solidCount )
  939. {
  940. physcollision->TraceBox(
  941. ray,
  942. fMask,
  943. &studioConvex,
  944. pCollide->solids[0], // UNDONE: Support other solid indices?!?!?!? (forced zero)
  945. pEntity->GetCollisionOrigin(),
  946. pEntity->GetCollisionAngles(),
  947. pTrace );
  948. bTraced = true;
  949. }
  950. }
  951. else
  952. {
  953. Assert(pModel->type != mod_studio);
  954. // use the regular code for raytraces against brushes
  955. // do ray traces with normal code, but use vphysics to do box traces
  956. if ( !ray.m_IsRay || pModel->type != mod_brush )
  957. {
  958. int nModelIndex = pEntity->GetCollisionModelIndex();
  959. // BUGBUG: This only works when the vcollide in question is the first solid in the model
  960. vcollide_t *pCollide = CM_VCollideForModel( nModelIndex, (model_t*)pModel );
  961. if ( pCollide && pCollide->solidCount )
  962. {
  963. CBrushConvexInfo brushConvex;
  964. IConvexInfo *pConvexInfo = (pModel->type) == mod_brush ? &brushConvex : NULL;
  965. physcollision->TraceBox(
  966. ray,
  967. fMask,
  968. pConvexInfo,
  969. pCollide->solids[0], // UNDONE: Support other solid indices?!?!?!? (forced zero)
  970. pEntity->GetCollisionOrigin(),
  971. pEntity->GetCollisionAngles(),
  972. pTrace );
  973. bTraced = true;
  974. }
  975. }
  976. }
  977. return bTraced;
  978. }
  979. //-----------------------------------------------------------------------------
  980. // Perform bsp trace
  981. //-----------------------------------------------------------------------------
  982. bool CEngineTrace::ClipRayToBSP( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace )
  983. {
  984. int nModelIndex = pEntity->GetCollisionModelIndex();
  985. cmodel_t *pCModel = CM_InlineModelNumber( nModelIndex - 1 );
  986. int nHeadNode = pCModel->headnode;
  987. CM_TransformedBoxTrace( ray, nHeadNode, fMask, pEntity->GetCollisionOrigin(), pEntity->GetCollisionAngles(), *pTrace );
  988. return true;
  989. }
  990. // NOTE: Switched over to SIMD ray/box test since there is a bug we haven't hunted down yet in the scalar version
  991. bool CEngineTrace::ClipRayToBBox( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace )
  992. {
  993. extern bool IntersectRayWithBox( const Ray_t &ray, const VectorAligned &inInvDelta, const VectorAligned &inBoxMins, const VectorAligned &inBoxMaxs, trace_t *RESTRICT pTrace );
  994. if ( pEntity->GetSolid() != SOLID_BBOX )
  995. return false;
  996. // We can't use the OBBMins/Maxs unless the collision angles are world-aligned
  997. Assert( pEntity->GetCollisionAngles() == vec3_angle );
  998. VectorAligned vecAbsMins, vecAbsMaxs;
  999. VectorAligned vecInvDelta;
  1000. // NOTE: If ray.m_pWorldAxisTransform is set, then the boxes should be rotated into the root parent's space
  1001. if ( !ray.m_IsRay && ray.m_pWorldAxisTransform )
  1002. {
  1003. Ray_t ray_l;
  1004. ray_l.m_Extents = ray.m_Extents;
  1005. VectorIRotate( ray.m_Delta, *ray.m_pWorldAxisTransform, ray_l.m_Delta );
  1006. ray_l.m_StartOffset.Init();
  1007. VectorITransform( ray.m_Start, *ray.m_pWorldAxisTransform, ray_l.m_Start );
  1008. vecInvDelta = ray_l.InvDelta();
  1009. Vector localEntityOrigin;
  1010. VectorITransform( pEntity->GetCollisionOrigin(), *ray.m_pWorldAxisTransform, localEntityOrigin );
  1011. ray_l.m_IsRay = ray.m_IsRay;
  1012. ray_l.m_IsSwept = ray.m_IsSwept;
  1013. VectorAdd( localEntityOrigin, pEntity->OBBMins(), vecAbsMins );
  1014. VectorAdd( localEntityOrigin, pEntity->OBBMaxs(), vecAbsMaxs );
  1015. IntersectRayWithBox( ray_l, vecInvDelta, vecAbsMins, vecAbsMaxs, pTrace );
  1016. if ( pTrace->DidHit() )
  1017. {
  1018. Vector temp;
  1019. VectorCopy (pTrace->plane.normal, temp);
  1020. VectorRotate( temp, *ray.m_pWorldAxisTransform, pTrace->plane.normal );
  1021. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  1022. if (pTrace->fraction == 1)
  1023. {
  1024. VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos);
  1025. }
  1026. else
  1027. {
  1028. VectorMA( pTrace->startpos, pTrace->fraction, ray.m_Delta, pTrace->endpos );
  1029. }
  1030. pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal );
  1031. if ( pTrace->fractionleftsolid < 1 )
  1032. {
  1033. pTrace->startpos += ray.m_Delta * pTrace->fractionleftsolid;
  1034. }
  1035. }
  1036. else
  1037. {
  1038. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  1039. }
  1040. return true;
  1041. }
  1042. vecInvDelta = ray.InvDelta();
  1043. VectorAdd( pEntity->GetCollisionOrigin(), pEntity->OBBMins(), vecAbsMins );
  1044. VectorAdd( pEntity->GetCollisionOrigin(), pEntity->OBBMaxs(), vecAbsMaxs );
  1045. IntersectRayWithBox( ray, vecInvDelta, vecAbsMins, vecAbsMaxs, pTrace);
  1046. return true;
  1047. }
  1048. bool CEngineTrace::ClipRayToOBB( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace )
  1049. {
  1050. if ( pEntity->GetSolid() != SOLID_OBB )
  1051. return false;
  1052. // NOTE: This is busted because it doesn't compute fractionleftsolid, which at the
  1053. // moment is required for the engine trace system.
  1054. IntersectRayWithOBB( ray, pEntity->GetCollisionOrigin(), pEntity->GetCollisionAngles(),
  1055. pEntity->OBBMins(), pEntity->OBBMaxs(), DIST_EPSILON, pTrace );
  1056. return true;
  1057. }
  1058. //-----------------------------------------------------------------------------
  1059. // Main entry point for clipping rays to entities
  1060. //-----------------------------------------------------------------------------
  1061. #ifndef DEDICATED
  1062. void CEngineTraceClient::SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace )
  1063. {
  1064. if ( !pTrace->DidHit() )
  1065. return;
  1066. // FIXME: This is only necessary because of traces occurring during
  1067. // LevelInit (a suspect time to be tracing)
  1068. if (!pCollideable)
  1069. {
  1070. pTrace->m_pEnt = NULL;
  1071. return;
  1072. }
  1073. IClientUnknown *pUnk = (IClientUnknown*)pCollideable->GetEntityHandle();
  1074. if ( !StaticPropMgr()->IsStaticProp( pUnk ) )
  1075. {
  1076. pTrace->m_pEnt = (CBaseEntity*)(pUnk->GetIClientEntity());
  1077. }
  1078. else
  1079. {
  1080. // For static props, point to the world, hitbox is the prop index
  1081. pTrace->m_pEnt = (CBaseEntity*)(entitylist->GetClientEntity(0));
  1082. pTrace->hitbox = StaticPropMgr()->GetStaticPropIndex( pUnk ) + 1;
  1083. }
  1084. }
  1085. #endif
  1086. void CEngineTraceServer::SetTraceEntity( ICollideable *pCollideable, trace_t *pTrace )
  1087. {
  1088. if ( !pTrace->DidHit() )
  1089. return;
  1090. IHandleEntity *pHandleEntity = pCollideable->GetEntityHandle();
  1091. if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  1092. {
  1093. pTrace->m_pEnt = (CBaseEntity*)(pHandleEntity);
  1094. }
  1095. else
  1096. {
  1097. // For static props, point to the world, hitbox is the prop index
  1098. pTrace->m_pEnt = (CBaseEntity*)(sv.edicts->GetIServerEntity());
  1099. pTrace->hitbox = StaticPropMgr()->GetStaticPropIndex( pHandleEntity ) + 1;
  1100. }
  1101. }
  1102. //-----------------------------------------------------------------------------
  1103. // Traces a ray against a particular edict
  1104. //-----------------------------------------------------------------------------
  1105. void CEngineTrace::ClipRayToCollideable( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace )
  1106. {
  1107. CM_ClearTrace( pTrace );
  1108. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  1109. VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos );
  1110. const model_t *pModel = pEntity->GetCollisionModel();
  1111. bool bIsStudioModel = false;
  1112. studiohdr_t *pStudioHdr = NULL;
  1113. if ( pModel && pModel->type == mod_studio )
  1114. {
  1115. bIsStudioModel = true;
  1116. pStudioHdr = (studiohdr_t *)modelloader->GetExtraData( (model_t*)pModel );
  1117. // Cull if the collision mask isn't set + we're not testing hitboxes.
  1118. if ( (( fMask & CONTENTS_HITBOX ) == 0) )
  1119. {
  1120. if ( ( fMask & pStudioHdr->contents ) == 0)
  1121. return;
  1122. }
  1123. }
  1124. const matrix3x4_t *pOldTransform = ray.m_pWorldAxisTransform;
  1125. if ( pEntity->GetSolidFlags() & FSOLID_ROOT_PARENT_ALIGNED )
  1126. {
  1127. const_cast<Ray_t &>(ray).m_pWorldAxisTransform = pEntity->GetRootParentToWorldTransform();
  1128. }
  1129. bool bTraced = false;
  1130. bool bCustomPerformed = false;
  1131. if ( ShouldPerformCustomRayTest( ray, pEntity ) )
  1132. {
  1133. ClipRayToCustom( ray, fMask, pEntity, pTrace );
  1134. bTraced = true;
  1135. bCustomPerformed = true;
  1136. }
  1137. else
  1138. {
  1139. bTraced = ClipRayToVPhysics( ray, fMask, pEntity, pStudioHdr, pTrace );
  1140. }
  1141. // FIXME: Why aren't we using solid type to check what kind of collisions to test against?!?!
  1142. if ( !bTraced && pModel && pModel->type == mod_brush )
  1143. {
  1144. bTraced = ClipRayToBSP( ray, fMask, pEntity, pTrace );
  1145. }
  1146. if ( !bTraced )
  1147. {
  1148. bTraced = ClipRayToOBB( ray, fMask, pEntity, pTrace );
  1149. }
  1150. // Hitboxes..
  1151. bool bTracedHitboxes = false;
  1152. if ( bIsStudioModel && (fMask & CONTENTS_HITBOX) )
  1153. {
  1154. // Until hitboxes are no longer implemented as custom raytests,
  1155. // don't bother to do the work twice
  1156. if (!bCustomPerformed)
  1157. {
  1158. bTraced = ClipRayToHitboxes( ray, fMask, pEntity, pTrace );
  1159. if ( bTraced )
  1160. {
  1161. // Hitboxes will set the surface properties
  1162. bTracedHitboxes = true;
  1163. }
  1164. }
  1165. }
  1166. if ( !bTraced )
  1167. {
  1168. ClipRayToBBox( ray, fMask, pEntity, pTrace );
  1169. }
  1170. if ( bIsStudioModel && !bTracedHitboxes && pTrace->DidHit() && (!bCustomPerformed || pTrace->surface.surfaceProps == 0) )
  1171. {
  1172. pTrace->contents = pStudioHdr->contents;
  1173. // use the default surface properties
  1174. pTrace->surface.name = "**studio**";
  1175. pTrace->surface.flags = 0;
  1176. pTrace->surface.surfaceProps = pStudioHdr->GetSurfaceProp();
  1177. }
  1178. if (!pTrace->m_pEnt && pTrace->DidHit())
  1179. {
  1180. SetTraceEntity( pEntity, pTrace );
  1181. }
  1182. #ifdef _DEBUG
  1183. Vector vecOffset, vecEndTest;
  1184. VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset );
  1185. VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest );
  1186. // <sergiy> changing this from absolute to relative error, because the vector lengths are often over 1000
  1187. Assert( ( vecEndTest - pTrace->startpos ).Length() < 0.01f + 0.001f * vecEndTest.Length() + pTrace->startpos.Length( ) ) ;
  1188. VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest );
  1189. Assert( ( vecEndTest - pTrace->endpos ).Length() < 0.01f + 0.001f * vecEndTest.Length() + pTrace->endpos.Length( ) ) ;
  1190. #endif
  1191. const_cast<Ray_t &>(ray).m_pWorldAxisTransform = pOldTransform;
  1192. }
  1193. //-----------------------------------------------------------------------------
  1194. // Main entry point for clipping rays to entities
  1195. //-----------------------------------------------------------------------------
  1196. void CEngineTrace::ClipRayToEntity( const Ray_t &ray, unsigned int fMask, IHandleEntity *pEntity, trace_t *pTrace )
  1197. {
  1198. ClipRayToCollideable( ray, fMask, GetCollideable(pEntity), pTrace );
  1199. }
  1200. //-----------------------------------------------------------------------------
  1201. // Grabs all entities along a ray
  1202. //-----------------------------------------------------------------------------
  1203. class CEntitiesAlongRay : public IPartitionEnumerator
  1204. {
  1205. public:
  1206. CEntitiesAlongRay( ) : m_EntityHandles(0, 32) {}
  1207. void Reset()
  1208. {
  1209. m_EntityHandles.RemoveAll();
  1210. }
  1211. IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
  1212. {
  1213. m_EntityHandles.AddToTail( pHandleEntity );
  1214. return ITERATION_CONTINUE;
  1215. }
  1216. CUtlVector< IHandleEntity * > m_EntityHandles;
  1217. };
  1218. class CEntityListAlongRay : public IPartitionEnumerator
  1219. {
  1220. public:
  1221. enum { MAX_ENTITIES_ALONGRAY = 1024 };
  1222. CEntityListAlongRay()
  1223. {
  1224. m_nCount = 0;
  1225. }
  1226. void Reset()
  1227. {
  1228. m_nCount = 0;
  1229. }
  1230. int Count()
  1231. {
  1232. return m_nCount;
  1233. }
  1234. IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
  1235. {
  1236. if ( m_nCount < MAX_ENTITIES_ALONGRAY )
  1237. {
  1238. m_EntityHandles[m_nCount] = pHandleEntity;
  1239. m_nCount++;
  1240. }
  1241. else
  1242. {
  1243. DevMsg( 1, "Max entity count along ray exceeded!\n" );
  1244. }
  1245. return ITERATION_CONTINUE;
  1246. }
  1247. int m_nCount;
  1248. IHandleEntity *m_EntityHandles[MAX_ENTITIES_ALONGRAY];
  1249. };
  1250. //-----------------------------------------------------------------------------
  1251. // Makes sure the final trace is clipped to the clip trace
  1252. // Returns true if clipping occurred
  1253. //-----------------------------------------------------------------------------
  1254. bool CEngineTrace::ClipTraceToTrace( trace_t &clipTrace, trace_t *pFinalTrace )
  1255. {
  1256. if (clipTrace.allsolid || clipTrace.startsolid || (clipTrace.fraction < pFinalTrace->fraction))
  1257. {
  1258. if (pFinalTrace->startsolid)
  1259. {
  1260. float flFractionLeftSolid = pFinalTrace->fractionleftsolid;
  1261. Vector vecStartPos = pFinalTrace->startpos;
  1262. *pFinalTrace = clipTrace;
  1263. pFinalTrace->startsolid = true;
  1264. if ( flFractionLeftSolid > clipTrace.fractionleftsolid )
  1265. {
  1266. pFinalTrace->fractionleftsolid = flFractionLeftSolid;
  1267. pFinalTrace->startpos = vecStartPos;
  1268. }
  1269. }
  1270. else
  1271. {
  1272. *pFinalTrace = clipTrace;
  1273. }
  1274. return true;
  1275. }
  1276. if (clipTrace.startsolid)
  1277. {
  1278. pFinalTrace->startsolid = true;
  1279. if ( clipTrace.fractionleftsolid > pFinalTrace->fractionleftsolid )
  1280. {
  1281. pFinalTrace->fractionleftsolid = clipTrace.fractionleftsolid;
  1282. pFinalTrace->startpos = clipTrace.startpos;
  1283. }
  1284. }
  1285. return false;
  1286. }
  1287. inline bool ShouldTestStaticProp( IHandleEntity *pHandleEntity )
  1288. {
  1289. #if defined( _GAMECONSOLE )
  1290. return pHandleEntity->m_bIsStaticProp;
  1291. #else
  1292. return true;
  1293. #endif
  1294. }
  1295. //-----------------------------------------------------------------------------
  1296. // Converts a user id to a collideable + username
  1297. //-----------------------------------------------------------------------------
  1298. ICollideable *CEngineTraceServer::HandleEntityToCollideable( IHandleEntity *pHandleEntity )
  1299. {
  1300. ICollideable *pCollideable = NULL;
  1301. if ( ShouldTestStaticProp( pHandleEntity ) )
  1302. {
  1303. pCollideable = StaticPropMgr()->GetStaticProp( pHandleEntity );
  1304. if ( pCollideable )
  1305. return pCollideable;
  1306. }
  1307. IServerUnknown *pServerUnknown = static_cast<IServerUnknown*>(pHandleEntity);
  1308. if ( pServerUnknown )
  1309. {
  1310. pCollideable = pServerUnknown->GetCollideable();
  1311. }
  1312. return pCollideable;
  1313. }
  1314. const char *CEngineTraceServer::GetDebugName( IHandleEntity *pHandleEntity )
  1315. {
  1316. if ( ShouldTestStaticProp( pHandleEntity ) && StaticPropMgr()->IsStaticProp(pHandleEntity) )
  1317. return "static prop";
  1318. IServerUnknown *pServerUnknown = static_cast<IServerUnknown*>(pHandleEntity);
  1319. if ( !pServerUnknown || !pServerUnknown->GetNetworkable())
  1320. return "<null>";
  1321. return pServerUnknown->GetNetworkable()->GetClassName();
  1322. }
  1323. #ifndef DEDICATED
  1324. ICollideable *CEngineTraceClient::HandleEntityToCollideable( IHandleEntity *pHandleEntity )
  1325. {
  1326. ICollideable *pCollideable = NULL;
  1327. if ( ShouldTestStaticProp( pHandleEntity ) )
  1328. {
  1329. pCollideable = StaticPropMgr()->GetStaticProp( pHandleEntity );
  1330. if ( pCollideable )
  1331. return pCollideable;
  1332. }
  1333. IClientUnknown *pUnk = static_cast<IClientUnknown*>(pHandleEntity);
  1334. if ( pUnk )
  1335. {
  1336. pCollideable = pUnk->GetCollideable();
  1337. }
  1338. return pCollideable;
  1339. }
  1340. const char *CEngineTraceClient::GetDebugName( IHandleEntity *pHandleEntity )
  1341. {
  1342. if ( ShouldTestStaticProp( pHandleEntity ) && StaticPropMgr()->IsStaticProp(pHandleEntity) )
  1343. return "static prop";
  1344. IClientUnknown *pUnk = static_cast<IClientUnknown*>(pHandleEntity);
  1345. if ( !pUnk )
  1346. return "<null>";
  1347. IClientNetworkable *pNetwork = pUnk->GetClientNetworkable();
  1348. if (pNetwork && pNetwork->GetClientClass() )
  1349. return pNetwork->GetClientClass()->m_pNetworkName;
  1350. return "client entity";
  1351. }
  1352. #endif
  1353. //-----------------------------------------------------------------------------
  1354. // Returns the world collideable for trace setting
  1355. //-----------------------------------------------------------------------------
  1356. #ifndef DEDICATED
  1357. ICollideable *CEngineTraceClient::GetWorldCollideable()
  1358. {
  1359. IClientEntity *pUnk = entitylist->GetClientEntity( 0 );
  1360. AssertOnce( pUnk );
  1361. return pUnk ? pUnk->GetCollideable() : NULL;
  1362. }
  1363. #endif
  1364. ICollideable *CEngineTraceServer::GetWorldCollideable()
  1365. {
  1366. if (!sv.edicts)
  1367. return NULL;
  1368. return sv.edicts->GetCollideable();
  1369. }
  1370. //-----------------------------------------------------------------------------
  1371. // Debugging code to render all ray casts since the last time this call was made
  1372. //-----------------------------------------------------------------------------
  1373. void EngineTraceRenderRayCasts()
  1374. {
  1375. #if defined _DEBUG && !defined DEDICATED
  1376. if( debugrayenable.GetBool() && s_FrameRays.Count() > debugraylimit.GetInt() && !debugrayreset.GetInt() )
  1377. {
  1378. Warning( "m_FrameRays.Count() == %d\n", s_FrameRays.Count() );
  1379. debugrayreset.SetValue( 1 );
  1380. int i;
  1381. for( i = 0; i < s_FrameRays.Count(); i++ )
  1382. {
  1383. Ray_t &ray = s_FrameRays[i];
  1384. if( ray.m_Extents.x != 0.0f || ray.m_Extents.y != 0.0f || ray.m_Extents.z != 0.0f )
  1385. {
  1386. CDebugOverlay::AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 255, 0, 0, 255, true, 3600.0f );
  1387. }
  1388. else
  1389. {
  1390. CDebugOverlay::AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 255, 255, 0, 255, true, 3600.0f );
  1391. }
  1392. }
  1393. }
  1394. s_FrameRays.RemoveAll( );
  1395. #endif
  1396. }
  1397. static void ComputeRayBounds( const Ray_t &ray, Vector &mins, Vector &maxs )
  1398. {
  1399. if ( ray.m_IsRay )
  1400. {
  1401. Vector start = ray.m_Start;
  1402. for ( int i = 0; i < 3; i++ )
  1403. {
  1404. if ( ray.m_Delta[i] > 0 )
  1405. {
  1406. maxs[i] = start[i] + ray.m_Delta[i];
  1407. mins[i] = start[i];
  1408. }
  1409. else
  1410. {
  1411. maxs[i] = start[i];
  1412. mins[i] = start[i] + ray.m_Delta[i];
  1413. }
  1414. }
  1415. }
  1416. else
  1417. {
  1418. Vector start = ray.m_Start;
  1419. for ( int i = 0; i < 3; i++ )
  1420. {
  1421. if ( ray.m_Delta[i] > 0 )
  1422. {
  1423. maxs[i] = start[i] + ray.m_Delta[i] + ray.m_Extents[i];
  1424. mins[i] = start[i] - ray.m_Extents[i];
  1425. }
  1426. else
  1427. {
  1428. maxs[i] = start[i] + ray.m_Extents[i];
  1429. mins[i] = start[i] + ray.m_Delta[i] - ray.m_Extents[i];
  1430. }
  1431. }
  1432. }
  1433. }
  1434. static bool IsBoxWithinBounds( const Vector &boxMins, const Vector &boxMaxs, const Vector &boundsMins, const Vector &bounsMaxs )
  1435. {
  1436. if ( boxMaxs.x <= bounsMaxs.x && boxMins.x >= boundsMins.x &&
  1437. boxMaxs.y <= bounsMaxs.y && boxMins.y >= boundsMins.y &&
  1438. boxMaxs.z <= bounsMaxs.z && boxMins.z >= boundsMins.z )
  1439. return true;
  1440. return false;
  1441. }
  1442. bool CTraceListData::CanTraceRay( const Ray_t &ray )
  1443. {
  1444. Vector rayMins, rayMaxs;
  1445. ComputeRayBounds( ray, rayMins, rayMaxs );
  1446. return IsBoxWithinBounds( rayMins, rayMaxs, m_mins, m_maxs );
  1447. }
  1448. // implementing members of CTraceListData
  1449. IterationRetval_t CTraceListData::EnumElement( IHandleEntity *pHandleEntity )
  1450. {
  1451. ICollideable *pCollideable = m_pEngineTrace->HandleEntityToCollideable( pHandleEntity );
  1452. // Check for error condition.
  1453. if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
  1454. {
  1455. Assert( 0 );
  1456. if ( pCollideable->GetCollisionModel() )
  1457. {
  1458. Msg("%s in solid list (not solid) (%d, %04X) %.*s\n", m_pEngineTrace->GetDebugName(pHandleEntity), pCollideable->GetSolid(), pCollideable->GetSolidFlags(),
  1459. sizeof( pCollideable->GetCollisionModel()->szPathName ), pCollideable->GetCollisionModel()->szPathName );
  1460. }
  1461. else
  1462. {
  1463. Msg("%s in solid list (not solid) (%d, %04X)\n", m_pEngineTrace->GetDebugName(pHandleEntity), pCollideable->GetSolid(), pCollideable->GetSolidFlags() );
  1464. }
  1465. }
  1466. else
  1467. {
  1468. if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  1469. {
  1470. int index = m_staticPropList.AddToTail();
  1471. m_staticPropList[index].pCollideable = pCollideable;
  1472. m_staticPropList[index].pEntity = pHandleEntity;
  1473. }
  1474. else
  1475. {
  1476. int index = m_entityList.AddToTail();
  1477. m_entityList[index].pCollideable = pCollideable;
  1478. m_entityList[index].pEntity = pHandleEntity;
  1479. }
  1480. }
  1481. return ITERATION_CONTINUE;
  1482. }
  1483. //-----------------------------------------------------------------------------
  1484. // Purpose:
  1485. //-----------------------------------------------------------------------------
  1486. void CEngineTrace::SetupLeafAndEntityListRay( const Ray_t &ray, ITraceListData *pTraceData )
  1487. {
  1488. Vector mins, maxs;
  1489. ComputeRayBounds( ray, mins, maxs );
  1490. SetupLeafAndEntityListBox( mins, maxs, pTraceData );
  1491. }
  1492. //-----------------------------------------------------------------------------
  1493. // Purpose: Gives an AABB and returns a leaf and entity list.
  1494. //-----------------------------------------------------------------------------
  1495. void CEngineTrace::SetupLeafAndEntityListBox( const Vector &vecBoxMin, const Vector &vecBoxMax, ITraceListData *pTraceData )
  1496. {
  1497. VPROF("SetupLeafAndEntityListBox");
  1498. CTraceListData &traceData = *static_cast<CTraceListData *>(pTraceData);
  1499. traceData.Reset();
  1500. traceData.m_pEngineTrace = this;
  1501. // increase bounds slightly to catch exact cases
  1502. for ( int i = 0; i < 3; i++ )
  1503. {
  1504. traceData.m_mins[i] = vecBoxMin[i] - 1;
  1505. traceData.m_maxs[i] = vecBoxMax[i] + 1;
  1506. }
  1507. // Get the leaves that intersect this box.
  1508. CM_GetTraceDataForBSP( traceData.m_mins, traceData.m_maxs, traceData );
  1509. // Find all entities in the voxels that intersect this box.
  1510. SpatialPartition()->EnumerateElementsInBox( SpatialPartitionMask(), traceData.m_mins, traceData.m_maxs, false, &traceData );
  1511. }
  1512. //-----------------------------------------------------------------------------
  1513. // Purpose:
  1514. // NOTE: the fMask is redundant with the stuff below, what do I want to do???
  1515. //-----------------------------------------------------------------------------
  1516. void CEngineTrace::TraceRayAgainstLeafAndEntityList( const Ray_t &ray, ITraceListData *pTraceData,
  1517. unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace )
  1518. {
  1519. VPROF("TraceRayAgainstLeafAndEntityList");
  1520. CTraceListData &traceData = *static_cast<CTraceListData *>(pTraceData);
  1521. Vector rayMins, rayMaxs;
  1522. ComputeRayBounds( ray, rayMins, rayMaxs );
  1523. if ( !IsBoxWithinBounds( rayMins, rayMaxs, traceData.m_mins, traceData.m_maxs ) )
  1524. {
  1525. TraceRay( ray, fMask, pTraceFilter, pTrace );
  1526. return;
  1527. }
  1528. // Make sure we have some kind of trace filter.
  1529. CTraceFilterHitAll traceFilter;
  1530. if ( !pTraceFilter )
  1531. {
  1532. pTraceFilter = &traceFilter;
  1533. }
  1534. // Collide with the world.
  1535. if ( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
  1536. {
  1537. ICollideable *pCollide = GetWorldCollideable();
  1538. CM_BoxTraceAgainstLeafList( ray, traceData, fMask, *pTrace );
  1539. SetTraceEntity( pCollide, pTrace );
  1540. // Blocked by the world or early out because we only are tracing against the world.
  1541. if ( ( pTrace->fraction == 0 ) || ( pTraceFilter->GetTraceType() == TRACE_WORLD_ONLY ) )
  1542. return;
  1543. }
  1544. else
  1545. {
  1546. // Setup the trace data.
  1547. CM_ClearTrace ( pTrace );
  1548. // Set initial start and endpos. This is necessary if the world isn't traced against,
  1549. // because we may not trace against anything below.
  1550. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  1551. VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos );
  1552. }
  1553. // Save the world collision fraction.
  1554. float flWorldFraction = pTrace->fraction;
  1555. float flWorldFractionLeftSolidScale = flWorldFraction;
  1556. // Create a ray that extends only until we hit the world
  1557. // and adjust the trace accordingly
  1558. Ray_t entityRay = ray;
  1559. if ( pTrace->fraction == 0 )
  1560. {
  1561. entityRay.m_Delta.Init();
  1562. flWorldFractionLeftSolidScale = pTrace->fractionleftsolid;
  1563. pTrace->fractionleftsolid = 1.0f;
  1564. pTrace->fraction = 1.0f;
  1565. }
  1566. else
  1567. {
  1568. // Explicitly compute end so that this computation happens at the quantization of
  1569. // the output (endpos). That way we won't miss any intersections we would get
  1570. // by feeding these results back in to the tracer
  1571. // This is not the same as entityRay.m_Delta *= pTrace->fraction which happens
  1572. // at a quantization that is more precise as m_Start moves away from the origin
  1573. Vector end;
  1574. VectorMA( entityRay.m_Start, pTrace->fraction, entityRay.m_Delta, end );
  1575. VectorSubtract(end, entityRay.m_Start, entityRay.m_Delta);
  1576. // We know this is safe because pTrace->fraction != 0
  1577. pTrace->fractionleftsolid /= pTrace->fraction;
  1578. pTrace->fraction = 1.0;
  1579. }
  1580. // Collide with entities.
  1581. bool bNoStaticProps = pTraceFilter->GetTraceType() == TRACE_ENTITIES_ONLY;
  1582. bool bFilterStaticProps = pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS;
  1583. trace_t trace;
  1584. Vector mins, maxs;
  1585. if ( !bNoStaticProps )
  1586. {
  1587. int propCount = traceData.m_staticPropList.Count();
  1588. for ( int iProp = 0; iProp < propCount && !pTrace->allsolid; iProp++ )
  1589. {
  1590. IHandleEntity *pHandleEntity = traceData.m_staticPropList[iProp].pEntity;
  1591. ICollideable *pCollideable = traceData.m_staticPropList[iProp].pCollideable;
  1592. if ( bFilterStaticProps )
  1593. {
  1594. if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) )
  1595. continue;
  1596. }
  1597. pCollideable->WorldSpaceSurroundingBounds( &mins, &maxs );
  1598. if ( !IsBoxIntersectingRay( mins, maxs, entityRay, DIST_EPSILON ) )
  1599. continue;
  1600. ClipRayToCollideable( entityRay, fMask, pCollideable, &trace );
  1601. // Make sure the ray is always shorter than it currently is
  1602. ClipTraceToTrace( trace, pTrace );
  1603. }
  1604. }
  1605. int entityCount = traceData.m_entityList.Count();
  1606. for ( int iEntity = 0; iEntity < entityCount && !pTrace->allsolid; ++iEntity )
  1607. {
  1608. IHandleEntity *pHandleEntity = traceData.m_entityList[iEntity].pEntity;
  1609. ICollideable *pCollideable = traceData.m_entityList[iEntity].pCollideable;
  1610. if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) )
  1611. continue;
  1612. pCollideable->WorldSpaceSurroundingBounds( &mins, &maxs );
  1613. if ( !IsBoxIntersectingRay( mins, maxs, entityRay, DIST_EPSILON ) )
  1614. continue;
  1615. ClipRayToCollideable( entityRay, fMask, pCollideable, &trace );
  1616. // Make sure the ray is always shorter than it currently is
  1617. ClipTraceToTrace( trace, pTrace );
  1618. }
  1619. // Fix up the fractions so they are appropriate given the original unclipped-to-world ray.
  1620. pTrace->fraction *= flWorldFraction;
  1621. pTrace->fractionleftsolid *= flWorldFraction;
  1622. if ( !ray.m_IsRay )
  1623. {
  1624. // Make sure no fractionleftsolid can be used with box sweeps
  1625. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  1626. pTrace->fractionleftsolid = 0;
  1627. #ifdef _DEBUG
  1628. pTrace->fractionleftsolid = VEC_T_NAN;
  1629. #endif
  1630. }
  1631. }
  1632. #if BENCHMARK_RAY_TEST
  1633. ConVar ray_count_max("ray_count_max","8");
  1634. ConVar ray_batch_extents("ray_batch_extents","96");
  1635. ConVar ray_batch_iterations("ray_batch_iterations","20");
  1636. CON_COMMAND( ray_save, "Save the rays" )
  1637. {
  1638. int count = s_BenchmarkRays.Count();
  1639. if ( count )
  1640. {
  1641. FileHandle_t hFile = g_pFileSystem->Open("rays.bin", "wb");
  1642. if ( hFile )
  1643. {
  1644. g_pFileSystem->Write( &count, sizeof(count), hFile );
  1645. g_pFileSystem->Write( s_BenchmarkRays.Base(), sizeof(s_BenchmarkRays[0])*count, hFile );
  1646. g_pFileSystem->Close( hFile );
  1647. }
  1648. }
  1649. Msg("Saved %d rays\n", count );
  1650. }
  1651. CON_COMMAND( ray_load, "Load the rays" )
  1652. {
  1653. s_BenchmarkRays.RemoveAll();
  1654. FileHandle_t hFile = g_pFileSystem->Open("rays.bin", "rb");
  1655. if ( hFile )
  1656. {
  1657. int count = 0;
  1658. g_pFileSystem->Read( &count, sizeof(count), hFile );
  1659. if ( count )
  1660. {
  1661. s_BenchmarkRays.EnsureCount( count );
  1662. g_pFileSystem->Read( s_BenchmarkRays.Base(), sizeof(s_BenchmarkRays[0])*count, hFile );
  1663. }
  1664. g_pFileSystem->Close( hFile );
  1665. }
  1666. Msg("Loaded %d rays\n", s_BenchmarkRays.Count() );
  1667. }
  1668. CON_COMMAND( ray_clear, "Clear the current rays" )
  1669. {
  1670. s_BenchmarkRays.RemoveAll();
  1671. Msg("Reset rays!\n");
  1672. }
  1673. struct ray_batch_t
  1674. {
  1675. Vector mins;
  1676. Vector maxs;
  1677. int start;
  1678. int count;
  1679. };
  1680. CON_COMMAND_EXTERN( ray_batch_bench, RayBatchBench, "Time batches of rays" )
  1681. {
  1682. const int MAX_RAY_BATCHES = 1024;
  1683. ray_batch_t batches[MAX_RAY_BATCHES];
  1684. int batchCount = 0;
  1685. for ( int i = 0; i < s_BenchmarkRays.Count(); i++ )
  1686. {
  1687. if ( !s_BenchmarkRays[i].m_IsRay )
  1688. {
  1689. int count = 0;
  1690. Vector mins, maxs;
  1691. ClearBounds(mins, maxs);
  1692. for ( int j = i; j < s_BenchmarkRays.Count(); j++ )
  1693. {
  1694. if ( s_BenchmarkRays[j].m_IsRay )
  1695. break;
  1696. Vector tmpMins, tmpMaxs;
  1697. ComputeRayBounds( s_BenchmarkRays[j], tmpMins, tmpMaxs );
  1698. AddPointToBounds( tmpMins, mins, maxs );
  1699. AddPointToBounds( tmpMaxs, mins, maxs );
  1700. Vector ext = maxs - mins;
  1701. float maxSize = MAX(ext[0], ext[1]);
  1702. maxSize = MAX(maxSize, ext[2]);
  1703. if ( maxSize > ray_batch_extents.GetFloat() )
  1704. break;
  1705. count++;
  1706. if ( count >= ray_count_max.GetInt() )
  1707. break;
  1708. }
  1709. if ( count >= ray_count_max.GetInt() && batchCount < MAX_RAY_BATCHES )
  1710. {
  1711. batches[batchCount].count = count;
  1712. batches[batchCount].start = i;
  1713. batches[batchCount].mins = mins;
  1714. batches[batchCount].maxs = maxs;
  1715. batchCount++;
  1716. }
  1717. }
  1718. }
  1719. Msg("Testing %d batches of %d\n", batchCount, ray_count_max.GetInt() );
  1720. const int ITERATION_COUNT = ray_batch_iterations.GetInt();
  1721. float normalTime = 1;
  1722. // normal trace test
  1723. if ( 1 )
  1724. {
  1725. #if VPROF_LEVEL > 0
  1726. g_VProfCurrentProfile.Start();
  1727. g_VProfCurrentProfile.Reset();
  1728. g_VProfCurrentProfile.ResetPeaks();
  1729. #endif
  1730. double tStart = Plat_FloatTime();
  1731. trace_t trace;
  1732. for ( int jj = 0; jj < ITERATION_COUNT; jj++ )
  1733. {
  1734. for ( int kk = 0; kk < batchCount; kk++)
  1735. {
  1736. int batchEnd = batches[kk].start + batches[kk].count;
  1737. for ( int i = batches[kk].start; i < batchEnd; i++ )
  1738. {
  1739. CM_BoxTrace( s_BenchmarkRays[i], 0, MASK_SOLID, true, trace );
  1740. if ( 1 )
  1741. {
  1742. // Create a ray that extends only until we hit the world and adjust the trace accordingly
  1743. Ray_t entityRay = s_BenchmarkRays[i];
  1744. VectorScale( entityRay.m_Delta, trace.fraction, entityRay.m_Delta );
  1745. CEntityListAlongRay enumerator;
  1746. enumerator.Reset();
  1747. SpatialPartition()->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, entityRay, false, &enumerator );
  1748. trace_t tr;
  1749. ICollideable *pCollideable;
  1750. int nCount = enumerator.Count();
  1751. //float flWorldFraction = trace.fraction;
  1752. if ( 1 )
  1753. {
  1754. VPROF("IntersectStaticProps");
  1755. for ( int i = 0; i < nCount; ++i )
  1756. {
  1757. // Generate a collideable
  1758. IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i];
  1759. if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  1760. continue;
  1761. pCollideable = s_EngineTraceServer.HandleEntityToCollideable( pHandleEntity );
  1762. s_EngineTraceServer.ClipRayToCollideable( entityRay, MASK_SOLID, pCollideable, &tr );
  1763. // Make sure the ray is always shorter than it currently is
  1764. s_EngineTraceServer.ClipTraceToTrace( tr, &trace );
  1765. }
  1766. }
  1767. }
  1768. }
  1769. #if VPROF_LEVEL > 0
  1770. g_VProfCurrentProfile.MarkFrame();
  1771. #endif
  1772. }
  1773. }
  1774. double tEnd = Plat_FloatTime();
  1775. float ms = (tEnd - tStart) * 1000.0f;
  1776. if ( ms > 0 )
  1777. normalTime = ms;
  1778. #if VPROF_LEVEL > 0
  1779. g_VProfCurrentProfile.MarkFrame();
  1780. g_VProfCurrentProfile.Stop();
  1781. g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL );
  1782. #endif
  1783. Msg("NORMAL RAY TEST: %.2fms\n", ms );
  1784. }
  1785. float batchedTime = 1;
  1786. // batched trace test
  1787. if ( 1 )
  1788. {
  1789. #if VPROF_LEVEL > 0
  1790. g_VProfCurrentProfile.Start();
  1791. g_VProfCurrentProfile.Reset();
  1792. g_VProfCurrentProfile.ResetPeaks();
  1793. #endif
  1794. double tStart = Plat_FloatTime();
  1795. trace_t trace;
  1796. CTraceFilterHitAll traceFilter;
  1797. CTraceListData traceData;
  1798. for ( int jj = 0; jj < ITERATION_COUNT; jj++ )
  1799. {
  1800. for ( int kk = 0; kk < batchCount; kk++)
  1801. {
  1802. int batchEnd = batches[kk].start + batches[kk].count;
  1803. s_EngineTraceServer.SetupLeafAndEntityListBox( batches[kk].mins, batches[kk].maxs, &traceData );
  1804. traceData.m_entityList.RemoveAll(); // normal list skips all but static props, so skip them here too for comparison
  1805. for ( int i = batches[kk].start; i < batchEnd; i++ )
  1806. {
  1807. s_EngineTraceServer.TraceRayAgainstLeafAndEntityList( s_BenchmarkRays[i], &traceData, MASK_SOLID, &traceFilter, &trace );
  1808. }
  1809. #if VPROF_LEVEL > 0
  1810. g_VProfCurrentProfile.MarkFrame();
  1811. #endif
  1812. }
  1813. }
  1814. double tEnd = Plat_FloatTime();
  1815. float ms = (tEnd - tStart) * 1000.0f;
  1816. if ( ms > 0 )
  1817. batchedTime = ms;
  1818. #if VPROF_LEVEL > 0
  1819. g_VProfCurrentProfile.MarkFrame();
  1820. g_VProfCurrentProfile.Stop();
  1821. g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL );
  1822. #endif
  1823. Msg("LEAFLIST RAY TEST: %.2fms\n", ms );
  1824. }
  1825. float improvement = (normalTime - batchedTime) / normalTime;
  1826. Msg("%.1f%% improvement due to batching at %d\n", improvement*100.0f, ray_count_max.GetInt());
  1827. }
  1828. CON_COMMAND_EXTERN( ray_bench, RayBench, "Time the rays" )
  1829. {
  1830. #if VPROF_LEVEL > 0
  1831. g_VProfCurrentProfile.Start();
  1832. g_VProfCurrentProfile.Reset();
  1833. g_VProfCurrentProfile.ResetPeaks();
  1834. #endif
  1835. {
  1836. double tStart = Plat_FloatTime();
  1837. trace_t trace;
  1838. int hit = 0;
  1839. int miss = 0;
  1840. int rayVsProp = 0;
  1841. int boxVsProp = 0;
  1842. for ( int i = 0; i < s_BenchmarkRays.Count(); i++ )
  1843. {
  1844. CM_BoxTrace( s_BenchmarkRays[i], 0, MASK_SOLID, true, trace );
  1845. if ( 0 )
  1846. {
  1847. VPROF("QueryStaticProps");
  1848. // Create a ray that extends only until we hit the world and adjust the trace accordingly
  1849. Ray_t entityRay = s_BenchmarkRays[i];
  1850. VectorScale( entityRay.m_Delta, trace.fraction, entityRay.m_Delta );
  1851. CEntityListAlongRay enumerator;
  1852. enumerator.Reset();
  1853. SpatialPartition()->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, entityRay, false, &enumerator );
  1854. trace_t tr;
  1855. ICollideable *pCollideable;
  1856. int nCount = enumerator.Count();
  1857. //float flWorldFraction = trace.fraction;
  1858. if ( 0 )
  1859. {
  1860. VPROF("IntersectStaticProps");
  1861. for ( int i = 0; i < nCount; ++i )
  1862. {
  1863. // Generate a collideable
  1864. IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i];
  1865. if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  1866. continue;
  1867. if ( entityRay.m_IsRay )
  1868. rayVsProp++;
  1869. else
  1870. boxVsProp++;
  1871. pCollideable = s_EngineTraceServer.HandleEntityToCollideable( pHandleEntity );
  1872. s_EngineTraceServer.ClipRayToCollideable( entityRay, MASK_SOLID, pCollideable, &tr );
  1873. // Make sure the ray is always shorter than it currently is
  1874. s_EngineTraceServer.ClipTraceToTrace( tr, &trace );
  1875. }
  1876. }
  1877. }
  1878. if ( trace.DidHit() )
  1879. hit++;
  1880. else
  1881. miss++;
  1882. #if VPROF_LEVEL > 0
  1883. g_VProfCurrentProfile.MarkFrame();
  1884. #endif
  1885. }
  1886. double tEnd = Plat_FloatTime();
  1887. float ms = (tEnd - tStart) * 1000.0f;
  1888. int swept = 0;
  1889. int point = 0;
  1890. for ( int i = 0; i < s_BenchmarkRays.Count(); i++ )
  1891. {
  1892. swept += s_BenchmarkRays[i].m_IsSwept ? 1 : 0;
  1893. point += s_BenchmarkRays[i].m_IsRay ? 1 : 0;
  1894. }
  1895. 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 );
  1896. }
  1897. #if VPROF_LEVEL > 0
  1898. g_VProfCurrentProfile.MarkFrame();
  1899. g_VProfCurrentProfile.Stop();
  1900. g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL );
  1901. #endif
  1902. }
  1903. #endif
  1904. const int32 ALIGN16 g_ClearXYZSign[ 4 ] ALIGN16_POST = { 0x7fffffff, 0x7fffffff, 0x7fffffff, 0 };
  1905. fltx4 TestBoxAinB( const VectorAligned &ptA, const VectorAligned &extA, const VectorAligned &ptB, const VectorAligned &extB )
  1906. {
  1907. fltx4 f4ptA = LoadAlignedSIMD( &ptA ), f4extA = LoadAlignedSIMD( &extA ), f4ptB = LoadAlignedSIMD( &ptB ), f4extB = LoadAlignedSIMD( &extB );
  1908. return AndSIMD( CmpGeSIMD( f4ptA - f4extA, f4ptB - f4extB ), CmpLeSIMD( f4ptA + f4extA, f4ptB + f4extB ) );
  1909. }
  1910. // returns positive distances when box A protruding out of B; negative if A is contained inside of B
  1911. fltx4 ProtrusionBoxAoutB( const AABB_t &aabb0, const AABB_t &aabb1 )
  1912. {
  1913. fltx4 f4Min0 = LoadUnaligned3SIMD( &aabb0.m_vMinBounds ), f4Min1 = LoadUnaligned3SIMD( &aabb1.m_vMinBounds );
  1914. fltx4 f4Max0 = LoadUnaligned3SIMD( &aabb0.m_vMaxBounds ), f4Max1 = LoadUnaligned3SIMD( &aabb1.m_vMaxBounds );
  1915. return MaxSIMD( f4Min1 - f4Min0, f4Max0 - f4Max1 );
  1916. // equivalent expression:
  1917. // MaxSIMD( ( f4ptB - f4extB ) - ( f4ptA - f4extA ), ( f4ptA + f4extA ) - ( f4ptB + f4extB ) );
  1918. }
  1919. struct OcclusionStats_t
  1920. {
  1921. uint64 nTotalCalls;
  1922. uint64 nTotalOcclusions;
  1923. uint64 nNormalReuse;
  1924. uint64 nQueriesCancelled;
  1925. uint64 nWithinJitter;
  1926. uint64 nKeyNotFound;
  1927. uint64 nMovedMoreThanTolerance;
  1928. uint64 nNotCompletedInTime;
  1929. uint64 nTotalLatencyTicks;
  1930. uint64 nTotalRcpThroughputTicks;
  1931. uint64 nJobRestarts;
  1932. uint64 nJobRestartMainThreadTicks;
  1933. uint64 nVisLeavesCollected;
  1934. uint64 nVisLeavesChecked;
  1935. uint64 nVisShadowCullCalls;
  1936. uint64 nVisShadowCullsSucceeded;
  1937. CInterlockedUInt nQueries;
  1938. CInterlockedUInt nQueriesInFlight;
  1939. CInterlockedUInt nJobsInFlight;
  1940. CInterlockedUInt nJobs;
  1941. bool RegisterOcclusion( bool bOcclusion )
  1942. {
  1943. if ( bOcclusion )
  1944. nTotalOcclusions++;
  1945. return bOcclusion;
  1946. }
  1947. void Reset()
  1948. {
  1949. nTotalCalls = 0;
  1950. nTotalOcclusions = 0;
  1951. nNormalReuse = 0;
  1952. nQueriesCancelled = 0;
  1953. nWithinJitter = 0;
  1954. nKeyNotFound = 0;
  1955. nMovedMoreThanTolerance = 0;
  1956. nNotCompletedInTime = 0;
  1957. nTotalLatencyTicks = 0;
  1958. nJobRestarts = 0;
  1959. nJobRestartMainThreadTicks = 0;
  1960. nVisLeavesCollected = 0;
  1961. nVisLeavesChecked = 0;
  1962. nVisShadowCullCalls = 0;
  1963. nVisShadowCullsSucceeded = 0;
  1964. }
  1965. void Dump( bool bJitter )
  1966. {
  1967. uint64 nSubJitter = bJitter ? 0 : nWithinJitter;
  1968. uint64 nTotalCallsAdj = nTotalCalls - nSubJitter;
  1969. Msg( "%s Occlusion calls. %s (%.1f%%) calls within jitter. %u/%u queries, %u/%u jobs in flight. %d threads in pool\n",
  1970. V_pretifynum( nTotalCalls ), V_pretifynum( nWithinJitter ), ( nTotalCalls ? double( nWithinJitter ) * 100. / double( nTotalCalls ) : 100. ),
  1971. ( uint )nQueriesInFlight, ( uint )nQueries,
  1972. ( uint )nJobsInFlight, ( uint )nJobs,
  1973. g_pThreadPool->NumThreads() );
  1974. if ( nTotalCalls )
  1975. {
  1976. Msg( "Rates: %12s (%4.1f%% of %s) Occlusions\n", V_pretifynum( nTotalOcclusions ), double( nTotalOcclusions ) * 100. / double( nTotalCalls ), V_pretifynum( nTotalCalls ) );
  1977. }
  1978. if ( nTotalCallsAdj )
  1979. {
  1980. Msg( "%20s (%4.1f%% of %s) Normal Query Reuses\n", V_pretifynum( nNormalReuse ), double( nNormalReuse ) * 100. / double( nTotalCallsAdj ), V_pretifynum( nTotalCallsAdj ) );
  1981. if ( nVisLeavesCollected )
  1982. Msg( "%20s (%4.1f per call) Vis Leaves collected\n", V_pretifynum( nVisLeavesCollected ), double( nVisLeavesCollected ) / double( nTotalCallsAdj ) );
  1983. else
  1984. Msg( "No Vis Leaves collected\n" );
  1985. if ( nVisLeavesChecked )
  1986. Msg("%20s (%4.1f per call) Vis Leaves checked\n", V_pretifynum( nVisLeavesChecked ), double( nVisLeavesChecked ) / double( nTotalCallsAdj ) );
  1987. else
  1988. Msg( "No Vis Leaves checked\n" );
  1989. if ( nVisShadowCullCalls )
  1990. Msg( "%20s (%4.1f%% of %s) Vis shadows culled\n", V_pretifynum( nVisShadowCullsSucceeded ), double( nVisShadowCullsSucceeded ) * 100. / double( nVisShadowCullCalls ), V_pretifynum( nVisShadowCullCalls ) );
  1991. else
  1992. Msg( "No Vis shadows culled\n" );
  1993. if ( nWithinJitter )
  1994. Msg( "%20s (%4.1f%% of %s) Within-Jitter Reuses\n", V_pretifynum( nWithinJitter ), double( nWithinJitter ) * 100. / double( nTotalCalls ), V_pretifynum( nTotalCalls ) );
  1995. else
  1996. Msg( "No Within-Jitter Reuses\n" );
  1997. uint64 nQueuePoints = nNormalReuse + nKeyNotFound + nMovedMoreThanTolerance + nNotCompletedInTime;
  1998. if ( nQueuePoints >= nJobRestarts && nJobRestarts )
  1999. Msg( "%20s (%.1f queued queries per) Job Restarts\n", V_pretifynum( nJobRestarts ), ( double( nQueuePoints ) / double( nJobRestarts ) ) );
  2000. else
  2001. Msg( "No Jobs Restarted\n" );
  2002. }
  2003. else
  2004. {
  2005. Msg( "No untrivial occlusion calls registered\n" );
  2006. }
  2007. if ( nKeyNotFound | nMovedMoreThanTolerance | nNotCompletedInTime )
  2008. {
  2009. Msg( "Events: %12llu key not found.\n", nKeyNotFound );
  2010. if ( nMovedMoreThanTolerance )
  2011. Msg( "%20s (%4.1f%%) moved more than tolerance\n", V_pretifynum( nMovedMoreThanTolerance ), double( nMovedMoreThanTolerance ) * 100. / double( nWithinJitter + nMovedMoreThanTolerance + nNormalReuse + nNotCompletedInTime ) );
  2012. else
  2013. Msg( "None moved more than tolerance\n" );
  2014. if ( nNotCompletedInTime )
  2015. Msg( "%20s not completed on time\n", V_pretifynum( nNotCompletedInTime ) );
  2016. else
  2017. Msg( "All queries completed on time\n" );
  2018. }
  2019. else
  2020. {
  2021. Msg( "No events registered\n" );
  2022. }
  2023. if ( nNormalReuse )
  2024. {
  2025. Msg( "Ticks: %13s Query latency\n", V_pretifynum( nTotalLatencyTicks / nNormalReuse ) );
  2026. }
  2027. else
  2028. {
  2029. Msg( "Ticks: No Query Latency data\n" );
  2030. }
  2031. if ( nNormalReuse > nQueriesCancelled )
  2032. {
  2033. Msg( "%20s Query Reciprocal Throughput (%s cancels)\n", V_pretifynum( nTotalRcpThroughputTicks / ( nNormalReuse - nQueriesCancelled ) ), V_pretifynum( nQueriesCancelled ) );
  2034. }
  2035. if ( nJobRestarts && nJobRestartMainThreadTicks )
  2036. {
  2037. Msg( "%20s Job Restart\n", V_pretifynum( nJobRestartMainThreadTicks / nJobRestarts ) );
  2038. }
  2039. }
  2040. };
  2041. static OcclusionStats_t s_occlusionStats = { 0 };
  2042. class CAsyncOcclusionQuery;
  2043. CThreadFastMutex s_occlusionQueryMutex;
  2044. typedef CUtlLinkedList< CAsyncOcclusionQuery* > OcclusionQueryList_t;
  2045. OcclusionQueryList_t s_occlusionQueries; // these are the real queries in flight awaiting a job to pick them up
  2046. class COcclusionQueryJob : public CJob
  2047. {
  2048. public:
  2049. bool m_bFinished; // true when this
  2050. public:
  2051. COcclusionQueryJob() : m_bFinished( false )
  2052. {
  2053. s_occlusionStats.nJobs++;
  2054. s_occlusionStats.nJobsInFlight++;
  2055. }
  2056. virtual ~COcclusionQueryJob() OVERRIDE
  2057. {
  2058. s_occlusionStats.nJobs--;
  2059. }
  2060. virtual JobStatus_t DoExecute() OVERRIDE;
  2061. };
  2062. static COcclusionQueryJob *s_pOcclusionQueryJob = NULL; // this is the job that was last queued to consume the s_occlusionQueries queue
  2063. void SpinUpOcclusionJob()
  2064. {
  2065. if ( s_pOcclusionQueryJob )
  2066. s_pOcclusionQueryJob->Release();
  2067. s_pOcclusionQueryJob = new COcclusionQueryJob;
  2068. //s_pOcclusionQueryJob->AddRef();
  2069. uint64 nSpinUpBegin = GetTimebaseRegister();
  2070. g_pThreadPool->AddJob( s_pOcclusionQueryJob );
  2071. s_occlusionStats.nJobRestartMainThreadTicks += GetTimebaseRegister() - nSpinUpBegin;
  2072. s_occlusionStats.nJobRestarts++;
  2073. }
  2074. CON_COMMAND_F( occlusion_stats, "Occlusion statistics; [-jitter] [-reset]", FCVAR_RELEASE )
  2075. {
  2076. bool bJitter = false, bReset = false, bFlush = false;
  2077. for ( int i = 1; i < args.ArgC(); ++i )
  2078. {
  2079. if ( !V_stricmp( args[ i ], "-jitter" ) )
  2080. bJitter = true;
  2081. else if ( !V_stricmp( args[ i ], "-reset" ) )
  2082. bReset = true;
  2083. else if ( !V_stricmp( args[ i ], "-flush" ) )
  2084. bFlush = true;
  2085. }
  2086. s_occlusionStats.Dump( bJitter);
  2087. if ( bReset )
  2088. {
  2089. s_occlusionStats.Reset();
  2090. }
  2091. if ( bFlush )
  2092. {
  2093. FlushOcclusionQueries();
  2094. }
  2095. }
  2096. void OnOcclusionTestAsyncChanged( IConVar *var, const char *pOldValue, float flOldValue )
  2097. {
  2098. extern void AdjustThreadPoolThreadCount();
  2099. AdjustThreadPoolThreadCount();
  2100. }
  2101. ConVar occlusion_test_margins( "occlusion_test_margins", "36", FCVAR_RELEASE, "Amount by which the player bounding box is expanded for occlusion test. This margin should be large enough to accommodate player movement within a frame or two, and the longest weapon they might hold. Shadow does not take this into account." ); // default: 360 (max speed) / 30 ( give it a couple of frames) + however much the biggest weapon can stick out
  2102. ConVar occlusion_test_jump_margin( "occlusion_test_jump_margin", "12", FCVAR_RELEASE, "Amount by which the player bounding box is expanded up for occlusion test to account for jumping. This margin should be large enough to accommodate player movement within a frame or two. Affects both camera box and player box." ); // default: 360 (max speed) / 30 ( give it a couple of frames) + however much the biggest weapon can stick out
  2103. ConVar occlusion_test_shadow_max_distance( "occlusion_test_shadow_max_distance", "1500", FCVAR_RELEASE, "Max distance at which to consider shadows for occlusion computations" );
  2104. ConVar occlusion_test_async( "occlusion_test_async", "0", FCVAR_RELEASE, "Enable asynchronous occlusion test in another thread; may save some server tick time at the cost of synchronization overhead with the async occlusion query thread", OnOcclusionTestAsyncChanged );
  2105. ConVar occlusion_test_async_move_tolerance( "occlusion_test_async_move_tolerance", "8.25", FCVAR_CHEAT );
  2106. ConVar occlusion_test_async_jitter( "occlusion_test_async_jitter", "2", FCVAR_CHEAT );
  2107. bool IsCastingShadow( const AABB_t &aabb )
  2108. {
  2109. int nLeafArray[ 1024 ];
  2110. int nLeafCount = CM_BoxLeafnums( aabb.m_vMinBounds, aabb.m_vMaxBounds, nLeafArray, ARRAYSIZE( nLeafArray ), NULL );
  2111. s_occlusionStats.nVisLeavesCollected+=nLeafCount;
  2112. s_occlusionStats.nVisShadowCullCalls++;
  2113. for ( int n = 0; n < nLeafCount; ++n )
  2114. {
  2115. int nLeaf = nLeafArray[ n ];
  2116. mleaf_t *pLeaf = &host_state.worldbrush->leafs[ nLeaf ];
  2117. if ( pLeaf && ( pLeaf->flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) )
  2118. {
  2119. s_occlusionStats.nVisLeavesChecked += n+1;
  2120. return true;
  2121. }
  2122. }
  2123. s_occlusionStats.nVisShadowCullsSucceeded++;
  2124. s_occlusionStats.nVisLeavesChecked += nLeafCount;
  2125. return false;
  2126. }
  2127. bool IsFullyOccluded_WithShadow( const AABB_t &aabb1, const AABB_t &aabb2, const Vector &vShadow, float flExtraMoveTolerance = 0.0f )
  2128. {
  2129. VectorAligned vCenter1( aabb1.GetCenter() );
  2130. VectorAligned vHullExtents1( aabb1.GetSize() * 0.5f );
  2131. VectorAligned vCenter2( aabb2.GetCenter() );
  2132. VectorAligned vHullExtents2( aabb2.GetSize() * 0.5f );
  2133. float flHorzMargin = occlusion_test_margins.GetFloat();
  2134. float flJumpMargin = occlusion_test_jump_margin.GetFloat();
  2135. flHorzMargin += flExtraMoveTolerance;
  2136. flJumpMargin += flExtraMoveTolerance;
  2137. if ( vShadow != vec3_origin )
  2138. {
  2139. Vector vHullDist = VectorMax( vec3_origin, VectorAbs( vCenter1 - vCenter2 ) - ( vHullExtents1 + vHullExtents2 ) ); // distance between hulls..
  2140. if ( vHullDist.LengthSqr() < Sqr( occlusion_test_shadow_max_distance.GetFloat() )
  2141. && IsCastingShadow( aabb1 ) )
  2142. {
  2143. VectorAligned vShadowEnd( vCenter1 + vShadow );
  2144. OcclusionTestResults_t tr;
  2145. bool bShadowIsClose = CM_IsFullyOccluded( vCenter1, vHullExtents1, vShadowEnd, vHullExtents1, &tr );
  2146. if ( bShadowIsClose )
  2147. {
  2148. AABB_t aabbEx;
  2149. aabbEx.m_vMinBounds = VectorMin( aabb1.m_vMinBounds - Vector( flHorzMargin, flHorzMargin, 0 ), tr.vEndMin );
  2150. aabbEx.m_vMaxBounds = VectorMax( aabb1.m_vMaxBounds + Vector( flHorzMargin, flHorzMargin, flJumpMargin ), tr.vEndMax );
  2151. return CM_IsFullyOccluded( aabbEx, aabb2 ); // trace extended box
  2152. }
  2153. else
  2154. {
  2155. return false; // shadow goes too far, don't try to trace :(
  2156. }
  2157. }
  2158. }
  2159. // trace extended box, no shadow
  2160. return CM_IsFullyOccluded(
  2161. VectorAligned( vCenter1 + Vector( 0, 0, flJumpMargin * 0.5f ) ),
  2162. VectorAligned( vHullExtents1 + Vector( flHorzMargin, flHorzMargin, flJumpMargin * 0.5f ) ),
  2163. vCenter2, vHullExtents2
  2164. );
  2165. }
  2166. class ALIGN16 CAsyncOcclusionQuery : public CAlignedNewDelete< 16, CRefCounted< CRefCountServiceMT > >
  2167. {
  2168. public:
  2169. Vector m_vShadow;
  2170. AABB_t m_aabb0;
  2171. AABB_t m_aabb1;
  2172. uint64 m_nTicksLatency; // this is garbage on architectures that don't have coherent rdtsc on multiple threads. Otherwise, it's the start tick when !m_bCompleted and latency of this query in tick if m_bCompleted
  2173. uint64 m_nTicksRcpThroughput;
  2174. bool m_bCancel;
  2175. bool m_bResult;
  2176. bool m_bCompleted;
  2177. public:
  2178. CAsyncOcclusionQuery( const AABB_t &aabb0, const AABB_t &aabb1, const Vector &vShadow )
  2179. {
  2180. s_occlusionStats.nQueriesInFlight++;
  2181. s_occlusionStats.nQueries++;
  2182. Init( aabb0, aabb1, vShadow );
  2183. }
  2184. virtual ~CAsyncOcclusionQuery() OVERRIDE
  2185. {
  2186. if ( !m_bCompleted )
  2187. s_occlusionStats.nQueriesInFlight--;
  2188. s_occlusionStats.nQueries--;
  2189. }
  2190. void Init( const AABB_t &aabb0, const AABB_t &aabb1, const Vector &vShadow )
  2191. {
  2192. m_aabb0 = aabb0;
  2193. m_aabb1 = aabb1;
  2194. m_vShadow = vShadow;
  2195. m_bCancel= false;
  2196. m_bResult =false;
  2197. m_bCompleted = false;
  2198. m_nTicksLatency = GetTimebaseRegister();
  2199. m_nTicksRcpThroughput = 0;
  2200. }
  2201. void DoExecute()
  2202. {
  2203. uint64 nTicksStarted = GetTimebaseRegister();
  2204. if ( !m_bCancel )
  2205. {
  2206. #if COMPILER_GCC
  2207. __sync_synchronize();
  2208. #else
  2209. std::atomic_thread_fence( std::memory_order_acquire );
  2210. #endif
  2211. m_bResult = IsFullyOccluded_WithShadow( m_aabb0, m_aabb1, m_vShadow, occlusion_test_async_move_tolerance.GetFloat() );
  2212. }
  2213. uint64 nTicksEnded = GetTimebaseRegister();
  2214. m_nTicksRcpThroughput = m_bCancel ? 0 : nTicksEnded - nTicksStarted;
  2215. m_nTicksLatency = nTicksEnded - m_nTicksLatency;
  2216. #if COMPILER_GCC
  2217. __sync_synchronize();
  2218. #else
  2219. std::atomic_thread_fence( std::memory_order_release );
  2220. #endif
  2221. s_occlusionStats.nQueriesInFlight--;
  2222. m_bCompleted = true;
  2223. }
  2224. fltx4 GetManhattanDistance( const AABB_t &aabb0, const AABB_t &aabb1 )
  2225. {
  2226. return SetWToZeroSIMD( MaxSIMD( ProtrusionBoxAoutB( aabb0, m_aabb0 ), ProtrusionBoxAoutB( aabb1, m_aabb1 ) ) );
  2227. }
  2228. void Cancel()
  2229. {
  2230. m_bCancel = true;
  2231. }
  2232. void Queue( int nOcclusionTestsSuspended )
  2233. {
  2234. Assert( !m_bCancel );
  2235. Assert( !m_bCompleted );
  2236. AddRef();
  2237. {
  2238. CAutoLockT< CThreadFastMutex > autoLock( s_occlusionQueryMutex );
  2239. s_occlusionQueries.AddToTail( this );
  2240. if ( s_pOcclusionQueryJob )
  2241. {
  2242. if ( !s_pOcclusionQueryJob->m_bFinished )
  2243. return;
  2244. }
  2245. }
  2246. if ( !nOcclusionTestsSuspended || occlusion_test_async.GetInt() >= 2 )
  2247. SpinUpOcclusionJob();
  2248. }
  2249. } ALIGN16_POST;
  2250. JobStatus_t COcclusionQueryJob::DoExecute()
  2251. {
  2252. for ( ;; )
  2253. {
  2254. CAsyncOcclusionQuery *pQuery;
  2255. {
  2256. CAutoLockT< CThreadFastMutex > autoLock( s_occlusionQueryMutex );
  2257. OcclusionQueryList_t::IndexLocalType_t nHead = s_occlusionQueries.Head( );
  2258. if ( nHead == s_occlusionQueries.InvalidIndex() )
  2259. {
  2260. m_bFinished = true;
  2261. break;
  2262. }
  2263. else
  2264. {
  2265. pQuery = s_occlusionQueries.Element( nHead );
  2266. s_occlusionQueries.Remove( nHead );
  2267. }
  2268. }
  2269. pQuery->DoExecute();
  2270. pQuery->Release();
  2271. }
  2272. s_occlusionStats.nJobsInFlight--;
  2273. return JOB_OK;
  2274. }
  2275. void CEngineTrace::FlushOcclusionQueries()
  2276. {
  2277. // cancel all queries in flight: take them away from the jobs consuming them
  2278. {
  2279. CAutoLockT< CThreadFastMutex > autoLock( s_occlusionQueryMutex );
  2280. for ( ;;)
  2281. {
  2282. OcclusionQueryList_t::IndexLocalType_t nHead = s_occlusionQueries.Head( );
  2283. if ( nHead == s_occlusionQueries.InvalidIndex() )
  2284. {
  2285. break;
  2286. }
  2287. else
  2288. {
  2289. CAsyncOcclusionQuery *pQuery = s_occlusionQueries.Element( nHead );
  2290. s_occlusionQueries.Remove( nHead );
  2291. pQuery->Release();
  2292. }
  2293. }
  2294. }
  2295. // also, release all jobs currently locked by
  2296. for ( UtlHashHandle_t it = m_OcclusionQueryMap.FirstHandle(); it != m_OcclusionQueryMap.InvalidHandle(); it = m_OcclusionQueryMap.RemoveAndAdvance( it ) )
  2297. {
  2298. m_OcclusionQueryMap.Element( it )->Release();
  2299. }
  2300. m_OcclusionQueryMap.Purge();
  2301. if ( s_pOcclusionQueryJob )
  2302. {
  2303. s_pOcclusionQueryJob->Release();
  2304. s_pOcclusionQueryJob = NULL;
  2305. }
  2306. }
  2307. void FlushOcclusionQueries()
  2308. {
  2309. s_EngineTraceServer.FlushOcclusionQueries();
  2310. #ifndef DEDICATED
  2311. s_EngineTraceClient.FlushOcclusionQueries();
  2312. #endif
  2313. }
  2314. void CEngineTrace::ResumeOcclusionTests()
  2315. {
  2316. if ( !--m_nOcclusionTestsSuspended && s_occlusionQueries.Head() != s_occlusionQueries.InvalidIndex() )
  2317. {
  2318. // We're out of suspension and we have some jobs queued up. Execute them.
  2319. SpinUpOcclusionJob();
  2320. }
  2321. }
  2322. bool CEngineTrace::IsFullyOccluded( int nOcclusionKey, const AABB_t &aabb0, const AABB_t &aabb1, const Vector &vShadow )
  2323. {
  2324. s_occlusionStats.nTotalCalls++;
  2325. if ( !occlusion_test_async.GetInt() || nOcclusionKey < 0 )
  2326. return s_occlusionStats.RegisterOcclusion( IsFullyOccluded_WithShadow( aabb0, aabb1, vShadow ) );
  2327. // first, try to find the previous frame version of this job
  2328. UtlHashHandle_t hFind = m_OcclusionQueryMap.Find( nOcclusionKey );
  2329. if ( hFind != m_OcclusionQueryMap.InvalidHandle() )
  2330. {
  2331. CAsyncOcclusionQuery* pQuery = m_OcclusionQueryMap[ hFind ];
  2332. fltx4 f4ManhattanError = pQuery->GetManhattanDistance( aabb0, aabb1 );
  2333. if ( IsAllGreaterThanOrEq( ReplicateX4( occlusion_test_async_move_tolerance.GetFloat() ), f4ManhattanError ) )
  2334. {
  2335. if ( pQuery->m_bCompleted )
  2336. {
  2337. #if COMPILER_GCC
  2338. __sync_synchronize();
  2339. #else
  2340. std::atomic_thread_fence( std::memory_order_acquire );
  2341. #endif
  2342. bool bIsOccluded = pQuery->m_bResult;
  2343. s_occlusionStats.RegisterOcclusion( bIsOccluded );
  2344. // Optimal case: we can use the results of this job because it's a strict superset of this query and it's completed
  2345. if ( IsAllGreaterThanOrEq( ReplicateX4( occlusion_test_async_jitter.GetFloat() ), f4ManhattanError ) )
  2346. {
  2347. s_occlusionStats.nWithinJitter++;
  2348. // we don't need to restart this query, it's perfectly fine within the jitter margin
  2349. }
  2350. else
  2351. {
  2352. s_occlusionStats.nNormalReuse++;
  2353. s_occlusionStats.nTotalLatencyTicks += pQuery->m_nTicksLatency;
  2354. if ( pQuery->m_nTicksRcpThroughput )
  2355. s_occlusionStats.nTotalRcpThroughputTicks += pQuery->m_nTicksRcpThroughput;
  2356. else
  2357. s_occlusionStats.nQueriesCancelled++;
  2358. // the query was within the margins, but we need to restart it. This will hopefully be much more common than any of the error modes below
  2359. s_occlusionStats.nQueriesInFlight++; // reusing the same queue
  2360. pQuery->Init( aabb0, aabb1, vShadow );
  2361. pQuery->Queue( m_nOcclusionTestsSuspended);
  2362. }
  2363. return bIsOccluded;
  2364. }
  2365. else
  2366. {
  2367. s_occlusionStats.nNotCompletedInTime++;
  2368. }
  2369. }
  2370. else
  2371. {
  2372. s_occlusionStats.nMovedMoreThanTolerance++;
  2373. }
  2374. // for whatever reason, the query didn't work out... try to cancel it, and queue a new one
  2375. pQuery->Cancel();
  2376. pQuery->Release();
  2377. CAsyncOcclusionQuery* pNewQuery = new CAsyncOcclusionQuery( aabb0, aabb1, vShadow );
  2378. pNewQuery->Queue( m_nOcclusionTestsSuspended );
  2379. m_OcclusionQueryMap[ hFind ] = pNewQuery;
  2380. }
  2381. else
  2382. {
  2383. s_occlusionStats.nKeyNotFound++;
  2384. CAsyncOcclusionQuery* pNewQuery = new CAsyncOcclusionQuery( aabb0, aabb1, vShadow );
  2385. pNewQuery->Queue( m_nOcclusionTestsSuspended );
  2386. m_OcclusionQueryMap.Insert( nOcclusionKey, pNewQuery );
  2387. }
  2388. // we queued the new query, but we still don't know whether the boxes are occlude
  2389. // we may return false here to be safe and save some CPU, or we could run the query synchronously
  2390. return s_occlusionStats.RegisterOcclusion( IsFullyOccluded_WithShadow( aabb0, aabb1, vShadow ) );
  2391. }
  2392. //-----------------------------------------------------------------------------
  2393. // A version that simply accepts a ray (can work as a traceline or tracehull)
  2394. //-----------------------------------------------------------------------------
  2395. void CEngineTrace::TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace )
  2396. {
  2397. // check ray extents for bugs
  2398. Assert(ray.m_Extents.x >=0 && ray.m_Extents.y >= 0 && ray.m_Extents.z >= 0);
  2399. #if defined _DEBUG && !defined DEDICATED
  2400. if( debugrayenable.GetBool() )
  2401. {
  2402. s_FrameRays.AddToTail( ray );
  2403. }
  2404. #endif
  2405. #if BENCHMARK_RAY_TEST
  2406. if( s_BenchmarkRays.Count() < 15000 )
  2407. {
  2408. s_BenchmarkRays.EnsureCapacity(15000);
  2409. s_BenchmarkRays.AddToTail( ray );
  2410. }
  2411. #endif
  2412. VPROF_INCREMENT_COUNTER( "TraceRay", 1 );
  2413. m_traceStatCounters[TRACE_STAT_COUNTER_TRACERAY]++;
  2414. // VPROF_BUDGET( "CEngineTrace::TraceRay", "Ray/Hull Trace" );
  2415. CTraceFilterHitAll traceFilter;
  2416. if ( !pTraceFilter )
  2417. {
  2418. pTraceFilter = &traceFilter;
  2419. }
  2420. CM_ClearTrace( pTrace );
  2421. // Collide with the world.
  2422. if ( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
  2423. {
  2424. ICollideable *pCollide = GetWorldCollideable();
  2425. Assert( pCollide );
  2426. // Make sure the world entity is unrotated
  2427. // FIXME: BAH! The !pCollide test here is because of
  2428. // CStaticProp::PrecacheLighting.. it's occurring too early
  2429. // need to fix that later
  2430. // Commenting this check out because Abs queries are not valid at the moment and we can't easily set them valid from Engine.dll,
  2431. // So having this assert enabled causes another assert to fire just for checking the origin / angles when abs queries are not valid.
  2432. //Assert(!pCollide || pCollide->GetCollisionOrigin() == vec3_origin );
  2433. //Assert(!pCollide || pCollide->GetCollisionAngles() == vec3_angle );
  2434. CM_BoxTrace( ray, 0, fMask, true, *pTrace );
  2435. SetTraceEntity( pCollide, pTrace );
  2436. // inside world, no need to check being inside anything else
  2437. if ( pTrace->startsolid )
  2438. return;
  2439. // Early out if we only trace against the world
  2440. if ( pTraceFilter->GetTraceType() == TRACE_WORLD_ONLY )
  2441. return;
  2442. }
  2443. else
  2444. {
  2445. // Set initial start + endpos, necessary if the world isn't traced against
  2446. // because we may not trace against *anything* below.
  2447. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  2448. VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos );
  2449. }
  2450. // Save the world collision fraction.
  2451. float flWorldFraction = pTrace->fraction;
  2452. float flWorldFractionLeftSolidScale = flWorldFraction;
  2453. // Create a ray that extends only until we hit the world
  2454. // and adjust the trace accordingly
  2455. Ray_t entityRay = ray;
  2456. if ( pTrace->fraction == 0 )
  2457. {
  2458. entityRay.m_Delta.Init();
  2459. flWorldFractionLeftSolidScale = pTrace->fractionleftsolid;
  2460. pTrace->fractionleftsolid = 1.0f;
  2461. pTrace->fraction = 1.0f;
  2462. }
  2463. else
  2464. {
  2465. // Explicitly compute end so that this computation happens at the quantization of
  2466. // the output (endpos). That way we won't miss any intersections we would get
  2467. // by feeding these results back in to the tracer
  2468. // This is not the same as entityRay.m_Delta *= pTrace->fraction which happens
  2469. // at a quantization that is more precise as m_Start moves away from the origin
  2470. Vector end;
  2471. VectorMA( entityRay.m_Start, pTrace->fraction, entityRay.m_Delta, end );
  2472. VectorSubtract(end, entityRay.m_Start, entityRay.m_Delta);
  2473. // We know this is safe because pTrace->fraction != 0
  2474. pTrace->fractionleftsolid /= pTrace->fraction;
  2475. pTrace->fraction = 1.0;
  2476. }
  2477. // Collide with entities along the ray
  2478. // FIXME: Hitbox code causes this to be re-entrant for the IK stuff.
  2479. // If we could eliminate that, this could be static and therefore
  2480. // not have to reallocate memory all the time
  2481. CEntityListAlongRay enumerator;
  2482. enumerator.Reset();
  2483. SpatialPartition()->EnumerateElementsAlongRay( SpatialPartitionMask(), entityRay, false, &enumerator );
  2484. bool bNoStaticProps = pTraceFilter->GetTraceType() == TRACE_ENTITIES_ONLY;
  2485. bool bFilterStaticProps = pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS;
  2486. trace_t tr;
  2487. ICollideable *pCollideable;
  2488. int nCount = enumerator.Count();
  2489. for ( int i = 0; i < nCount; ++i )
  2490. {
  2491. // Generate a collideable
  2492. IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i];
  2493. pCollideable = HandleEntityToCollideable( pHandleEntity );
  2494. // Check for error condition
  2495. if ( IsPC() && IsDebug() && !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
  2496. {
  2497. Assert( 0 );
  2498. Msg( "%s in solid list (not solid)\n", GetDebugName(pHandleEntity) );
  2499. continue;
  2500. }
  2501. if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  2502. {
  2503. if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) )
  2504. continue;
  2505. }
  2506. else
  2507. {
  2508. // FIXME: Could remove this check here by
  2509. // using a different spatial partition mask. Look into it
  2510. // if we want more speedups here.
  2511. if ( bNoStaticProps )
  2512. continue;
  2513. if ( bFilterStaticProps )
  2514. {
  2515. if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) )
  2516. continue;
  2517. }
  2518. }
  2519. ClipRayToCollideable( entityRay, fMask, pCollideable, &tr );
  2520. // Make sure the ray is always shorter than it currently is
  2521. ClipTraceToTrace( tr, pTrace );
  2522. // Stop if we're in allsolid
  2523. if (pTrace->allsolid)
  2524. break;
  2525. }
  2526. // Fix up the fractions so they are appropriate given the original
  2527. // unclipped-to-world ray
  2528. pTrace->fraction *= flWorldFraction;
  2529. pTrace->fractionleftsolid *= flWorldFractionLeftSolidScale;
  2530. #ifdef _DEBUG
  2531. Vector vecOffset, vecEndTest;
  2532. VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset );
  2533. VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest );
  2534. Assert( VectorsAreEqual( vecEndTest, pTrace->startpos, 0.1f ) );
  2535. VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest );
  2536. Assert( VectorsAreEqual( vecEndTest, pTrace->endpos, 0.1f ) );
  2537. // Assert( !ray.m_IsRay || pTrace->allsolid || pTrace->fraction >= pTrace->fractionleftsolid );
  2538. #endif
  2539. if ( !ray.m_IsRay )
  2540. {
  2541. // Make sure no fractionleftsolid can be used with box sweeps
  2542. VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
  2543. pTrace->fractionleftsolid = 0;
  2544. #ifdef _DEBUG
  2545. pTrace->fractionleftsolid = VEC_T_NAN;
  2546. #endif
  2547. }
  2548. }
  2549. //-----------------------------------------------------------------------------
  2550. // A version that sweeps a collideable through the world
  2551. //-----------------------------------------------------------------------------
  2552. void CEngineTrace::SweepCollideable( ICollideable *pCollide,
  2553. const Vector &vecAbsStart, const Vector &vecAbsEnd, const QAngle &vecAngles,
  2554. unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace )
  2555. {
  2556. Ray_t ray;
  2557. Assert( vecAngles == vec3_angle );
  2558. ray.Init( vecAbsStart, vecAbsEnd, pCollide->OBBMins(), pCollide->OBBMaxs() );
  2559. if ( pCollide->GetSolidFlags() & FSOLID_ROOT_PARENT_ALIGNED )
  2560. {
  2561. ray.m_pWorldAxisTransform = pCollide->GetRootParentToWorldTransform();
  2562. }
  2563. TraceRay( ray, fMask, pTraceFilter, pTrace );
  2564. }
  2565. //-----------------------------------------------------------------------------
  2566. // Lets clients know about all edicts along a ray
  2567. //-----------------------------------------------------------------------------
  2568. class CEnumerationFilter : public IPartitionEnumerator
  2569. {
  2570. public:
  2571. CEnumerationFilter( CEngineTrace *pEngineTrace, IEntityEnumerator* pEnumerator ) :
  2572. m_pEngineTrace(pEngineTrace), m_pEnumerator(pEnumerator) {}
  2573. IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
  2574. {
  2575. // Don't enumerate static props
  2576. if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  2577. return ITERATION_CONTINUE;
  2578. if ( !m_pEnumerator->EnumEntity( pHandleEntity ) )
  2579. {
  2580. return ITERATION_STOP;
  2581. }
  2582. return ITERATION_CONTINUE;
  2583. }
  2584. private:
  2585. IEntityEnumerator* m_pEnumerator;
  2586. CEngineTrace *m_pEngineTrace;
  2587. };
  2588. //-----------------------------------------------------------------------------
  2589. // Enumerates over all entities along a ray
  2590. // If triggers == true, it enumerates all triggers along a ray
  2591. //-----------------------------------------------------------------------------
  2592. void CEngineTrace::EnumerateEntities( const Ray_t &ray, bool bTriggers, IEntityEnumerator *pEnumerator )
  2593. {
  2594. m_traceStatCounters[TRACE_STAT_COUNTER_ENUMERATE]++;
  2595. // FIXME: If we store CBaseHandles directly in the spatial partition, this method
  2596. // basically becomes obsolete. The spatial partition can be queried directly.
  2597. CEnumerationFilter enumerator( this, pEnumerator );
  2598. int fMask = !bTriggers ? SpatialPartitionMask() : SpatialPartitionTriggerMask();
  2599. // NOTE: Triggers currently don't exist on the client
  2600. if (fMask)
  2601. {
  2602. SpatialPartition()->EnumerateElementsAlongRay( fMask, ray, false, &enumerator );
  2603. }
  2604. }
  2605. //-----------------------------------------------------------------------------
  2606. // Lets clients know about all entities in a box
  2607. //-----------------------------------------------------------------------------
  2608. void CEngineTrace::EnumerateEntities( const Vector &vecAbsMins, const Vector &vecAbsMaxs, IEntityEnumerator *pEnumerator )
  2609. {
  2610. m_traceStatCounters[TRACE_STAT_COUNTER_ENUMERATE]++;
  2611. // FIXME: If we store CBaseHandles directly in the spatial partition, this method
  2612. // basically becomes obsolete. The spatial partition can be queried directly.
  2613. CEnumerationFilter enumerator( this, pEnumerator );
  2614. SpatialPartition()->EnumerateElementsInBox( SpatialPartitionMask(),
  2615. vecAbsMins, vecAbsMaxs, false, &enumerator );
  2616. }
  2617. class CEntList : public IEntityEnumerator
  2618. {
  2619. public:
  2620. virtual bool EnumEntity( IHandleEntity *pHandleEntity )
  2621. {
  2622. IServerUnknown *pNetEntity = static_cast<IServerUnknown*>(pHandleEntity);
  2623. ICollideable *pCollide = pNetEntity->GetCollideable();
  2624. if ( !pCollide )
  2625. return true;
  2626. Vector vecCenter;
  2627. VectorMA( MainViewOrigin(), 100.0f, MainViewForward(), vecCenter );
  2628. float flDist = (vecCenter - pCollide->GetCollisionOrigin()).LengthSqr();
  2629. if (flDist < m_flClosestDist)
  2630. {
  2631. m_flClosestDist = flDist;
  2632. m_pClosest = pCollide;
  2633. }
  2634. return true;
  2635. }
  2636. ICollideable *m_pClosest;
  2637. float m_flClosestDist;
  2638. };
  2639. // create a macro that is true if we are allowed to debug traces during thinks, and compiles out to nothing otherwise.
  2640. #ifndef _PS3
  2641. #include "engine/thinktracecounter.h"
  2642. #endif
  2643. /// Used only in debugging: get/set/clear/increment the trace debug counter. See comment below for details.
  2644. int CEngineTrace::GetSetDebugTraceCounter( int value, DebugTraceCounterBehavior_t behavior )
  2645. {
  2646. #ifdef THINK_TRACE_COUNTER_COMPILED
  2647. extern CTHREADLOCALINT g_DebugTracesRemainingBeforeTrap;
  2648. if ( DEBUG_THINK_TRACE_COUNTER_ALLOWED() )
  2649. {
  2650. const int retval = g_DebugTracesRemainingBeforeTrap;
  2651. switch ( behavior )
  2652. {
  2653. case kTRACE_COUNTER_SET:
  2654. {
  2655. g_DebugTracesRemainingBeforeTrap = value;
  2656. break;
  2657. }
  2658. case kTRACE_COUNTER_INC:
  2659. {
  2660. g_DebugTracesRemainingBeforeTrap = value + g_DebugTracesRemainingBeforeTrap;
  2661. break;
  2662. }
  2663. }
  2664. return retval;
  2665. }
  2666. else
  2667. {
  2668. return 0;
  2669. }
  2670. #else
  2671. return 0;
  2672. #endif
  2673. }
  2674. #ifdef _DEBUG
  2675. //-----------------------------------------------------------------------------
  2676. // A method to test out sweeps
  2677. //-----------------------------------------------------------------------------
  2678. CON_COMMAND( test_sweepaabb, "method to test out sweeps" )
  2679. {
  2680. Vector vecStartPoint;
  2681. VectorMA( MainViewOrigin(), 50.0f, MainViewForward(), vecStartPoint );
  2682. Vector endPoint;
  2683. VectorMA( MainViewOrigin(), COORD_EXTENT * 1.74f, MainViewForward(), endPoint );
  2684. Ray_t ray;
  2685. ray.Init( vecStartPoint, endPoint );
  2686. trace_t tr;
  2687. // CTraceFilterHitAll traceFilter;
  2688. // g_pEngineTraceServer->TraceRay( ray, MASK_ALL, &traceFilter, &tr );
  2689. CEntList list;
  2690. list.m_pClosest = NULL;
  2691. list.m_flClosestDist = FLT_MAX;
  2692. g_pEngineTraceServer->EnumerateEntities( MainViewOrigin() - Vector( 200, 200, 200 ), MainViewOrigin() + Vector( 200, 200, 200 ), &list );
  2693. if ( !list.m_pClosest )
  2694. return;
  2695. // Visualize the intersection test
  2696. ICollideable *pCollide = list.m_pClosest;
  2697. if ( pCollide->GetCollisionOrigin() == vec3_origin )
  2698. return;
  2699. QAngle test( 0, 45, 0 );
  2700. #ifndef DEDICATED
  2701. CDebugOverlay::AddBoxOverlay( pCollide->GetCollisionOrigin(),
  2702. pCollide->OBBMins(), pCollide->OBBMaxs(),
  2703. test /*pCollide->GetCollisionAngles()*/, 0, 0, 255, 128, 5.0f );
  2704. #endif
  2705. VectorMA( MainViewOrigin(), 200.0f, MainViewForward(), endPoint );
  2706. ray.Init( vecStartPoint, endPoint, Vector( -10, -20, -10 ), Vector( 30, 30, 20 ) );
  2707. bool bIntersect = IntersectRayWithOBB( ray, pCollide->GetCollisionOrigin(), test, pCollide->OBBMins(),
  2708. pCollide->OBBMaxs(), 0.0f, &tr );
  2709. unsigned char r, g, b, a;
  2710. b = 0;
  2711. a = 255;
  2712. r = bIntersect ? 255 : 0;
  2713. g = bIntersect ? 0 : 255;
  2714. #ifndef DEDICATED
  2715. CDebugOverlay::AddSweptBoxOverlay( tr.startpos, tr.endpos,
  2716. Vector( -10, -20, -10 ), Vector( 30, 30, 20 ), vec3_angle, r, g, b, a, 5.0 );
  2717. #endif
  2718. }
  2719. #endif