Team Fortress 2 Source Code as on 22/4/2020
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.

625 lines
17 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: particle system definitions
  4. //
  5. //===========================================================================//
  6. #include "cbase.h"
  7. #include "particles/particles.h"
  8. #include "baseparticleentity.h"
  9. #include "entityparticletrail_shared.h"
  10. #include "collisionutils.h"
  11. #if defined( CLIENT_DLL )
  12. #include "c_pixel_visibility.h"
  13. #endif
  14. #ifdef TF_CLIENT_DLL
  15. #include "tf_shareddefs.h"
  16. #endif
  17. #ifdef GAME_DLL
  18. #include "ai_utils.h"
  19. #endif
  20. // memdbgon must be the last include file in a .cpp file!!!
  21. #include "tier0/memdbgon.h"
  22. //-----------------------------------------------------------------------------
  23. // Interface to allow the particle system to call back into the game code
  24. //-----------------------------------------------------------------------------
  25. class CParticleSystemQuery : public CBaseAppSystem< IParticleSystemQuery >
  26. {
  27. public:
  28. // Inherited from IParticleSystemQuery
  29. virtual void GetLightingAtPoint( const Vector& vecOrigin, Color &cTint );
  30. virtual void TraceLine( const Vector& vecAbsStart,
  31. const Vector& vecAbsEnd, unsigned int mask,
  32. const IHandleEntity *ignore,
  33. int collisionGroup, CBaseTrace *ptr );
  34. virtual bool MovePointInsideControllingObject( CParticleCollection *pParticles,
  35. void *pObject,
  36. Vector *pPnt );
  37. virtual void GetRandomPointsOnControllingObjectHitBox(
  38. CParticleCollection *pParticles,
  39. int nControlPointNumber,
  40. int nNumPtsOut,
  41. float flBBoxScale,
  42. int nNumTrysToGetAPointInsideTheModel,
  43. Vector *pPntsOut,
  44. Vector vecDirectionalBias,
  45. Vector *pHitBoxRelativeCoordOut,
  46. int *pHitBoxIndexOut
  47. );
  48. virtual int GetCollisionGroupFromName( const char *pszCollisionGroupName );
  49. virtual int GetControllingObjectHitBoxInfo(
  50. CParticleCollection *pParticles,
  51. int nControlPointNumber,
  52. int nBufSize, // # of output slots available
  53. ModelHitBoxInfo_t *pHitBoxOutputBuffer );
  54. virtual bool IsPointInControllingObjectHitBox(
  55. CParticleCollection *pParticles,
  56. int nControlPointNumber, Vector vecPos, bool bBBoxOnly );
  57. virtual Vector GetLocalPlayerPos( void );
  58. virtual void GetLocalPlayerEyeVectors( Vector *pForward, Vector *pRight = NULL, Vector *pUp = NULL );
  59. virtual float GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale );
  60. virtual void SetUpLightingEnvironment( const Vector& pos );
  61. };
  62. static CParticleSystemQuery s_ParticleSystemQuery;
  63. IParticleSystemQuery *g_pParticleSystemQuery = &s_ParticleSystemQuery;
  64. //-----------------------------------------------------------------------------
  65. // Exposes the interface (so tools can get at it)
  66. //-----------------------------------------------------------------------------
  67. #ifdef CLIENT_DLL
  68. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CParticleSystemQuery, IParticleSystemQuery, PARTICLE_SYSTEM_QUERY_INTERFACE_VERSION, s_ParticleSystemQuery );
  69. #endif
  70. static CThreadFastMutex s_LightMutex;
  71. // This mutex exists because EntityToWorldTransform was not threadsafe, and could potentially have been called from multiple
  72. // particle update threads. It has now been fixed to be threadsafe, so this mutex can safely just be a no-op (meaingful perf win for this).
  73. // static CThreadFastMutex s_BoneMutex;
  74. static CThreadNullMutex s_BoneMutex;
  75. //-----------------------------------------------------------------------------
  76. // Inherited from IParticleSystemQuery
  77. //-----------------------------------------------------------------------------
  78. void CParticleSystemQuery::GetLightingAtPoint( const Vector& vecOrigin, Color &cTint )
  79. {
  80. #ifdef GAME_DLL
  81. // FIXME: Go through to the engine from the server to get these values
  82. cTint.SetColor( 255, 255, 255, 255 );
  83. #else
  84. if ( engine->IsInGame() )
  85. {
  86. s_LightMutex.Lock();
  87. // Compute our lighting at our position
  88. Vector totalColor = engine->GetLightForPoint( vecOrigin, true );
  89. s_LightMutex.Unlock();
  90. // Get our lighting information
  91. cTint.SetColor( totalColor.x*255, totalColor.y*255, totalColor.z*255, 0 );
  92. }
  93. else
  94. {
  95. // FIXME: Go through to the engine from the server to get these values
  96. cTint.SetColor( 255, 255, 255, 255 );
  97. }
  98. #endif
  99. }
  100. void CParticleSystemQuery::SetUpLightingEnvironment( const Vector& pos )
  101. {
  102. #ifndef GAME_DLL
  103. if ( !engine->IsInGame() )
  104. return;
  105. s_LightMutex.Lock();
  106. modelrender->SetupLighting( pos );
  107. s_LightMutex.Unlock();
  108. #endif
  109. }
  110. void CParticleSystemQuery::TraceLine( const Vector& vecAbsStart,
  111. const Vector& vecAbsEnd, unsigned int mask,
  112. const IHandleEntity *ignore,
  113. int collisionGroup, CBaseTrace *ptr )
  114. {
  115. bool bDoTrace = false;
  116. #ifndef GAME_DLL
  117. bDoTrace = engine->IsInGame();
  118. #endif
  119. if ( bDoTrace )
  120. {
  121. trace_t tempTrace;
  122. UTIL_TraceLine( vecAbsStart, vecAbsEnd, mask, ignore, collisionGroup, &tempTrace );
  123. memcpy( ptr, &tempTrace, sizeof ( CBaseTrace ) );
  124. }
  125. else
  126. {
  127. ptr->startsolid = 0;
  128. ptr->fraction = 1.0;
  129. }
  130. }
  131. bool CParticleSystemQuery::MovePointInsideControllingObject(
  132. CParticleCollection *pParticles, void *pObject, Vector *pPnt )
  133. {
  134. #ifdef GAME_DLL
  135. return true;
  136. #else
  137. if (! pObject )
  138. return true; // accept the input point unmodified
  139. Ray_t ray;
  140. trace_t tr;
  141. ray.Init( *pPnt, *pPnt );
  142. enginetrace->ClipRayToEntity( ray, MASK_ALL, (CBaseEntity *) pObject, &tr );
  143. return ( tr.startsolid );
  144. #endif
  145. }
  146. static float GetSurfaceCoord( float flRand, float flMinX, float flMaxX )
  147. {
  148. return Lerp( flRand, flMinX, flMaxX );
  149. }
  150. void CParticleSystemQuery::GetRandomPointsOnControllingObjectHitBox(
  151. CParticleCollection *pParticles,
  152. int nControlPointNumber,
  153. int nNumPtsOut,
  154. float flBBoxScale,
  155. int nNumTrysToGetAPointInsideTheModel,
  156. Vector *pPntsOut,
  157. Vector vecDirectionalBias,
  158. Vector *pHitBoxRelativeCoordOut,
  159. int *pHitBoxIndexOut
  160. )
  161. {
  162. bool bSucesss = false;
  163. #ifndef GAME_DLL
  164. EHANDLE *phMoveParent = reinterpret_cast<EHANDLE *> ( pParticles->m_ControlPoints[nControlPointNumber].m_pObject );
  165. CBaseEntity *pMoveParent = NULL;
  166. if ( phMoveParent )
  167. {
  168. pMoveParent = *( phMoveParent );
  169. }
  170. if ( pMoveParent )
  171. {
  172. float flRandMax = flBBoxScale;
  173. float flRandMin = 1.0 - flBBoxScale;
  174. Vector vecBasePos;
  175. pParticles->GetControlPointAtTime( nControlPointNumber, pParticles->m_flCurTime, &vecBasePos );
  176. s_BoneMutex.Lock();
  177. C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating();
  178. if ( pAnimating )
  179. {
  180. matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
  181. if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) )
  182. {
  183. studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() );
  184. if ( pStudioHdr )
  185. {
  186. mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() );
  187. if ( set )
  188. {
  189. bSucesss = true;
  190. Vector vecWorldPosition(0, 0, 0);
  191. float u = 0, v = 0, w = 0;
  192. int nHitbox = 0;
  193. int nNumIters = nNumTrysToGetAPointInsideTheModel;
  194. if (! vecDirectionalBias.IsZero( 0.0001 ) )
  195. nNumIters = MAX( nNumIters, 5 );
  196. for( int i=0 ; i < nNumPtsOut; i++)
  197. {
  198. int nTryCnt = nNumIters;
  199. float flBestPointGoodness = -1.0e20;
  200. do
  201. {
  202. int nTryHitbox = pParticles->RandomInt( 0, set->numhitboxes - 1 );
  203. mstudiobbox_t *pBox = set->pHitbox(nTryHitbox);
  204. float flTryU = pParticles->RandomFloat( flRandMin, flRandMax );
  205. float flTryV = pParticles->RandomFloat( flRandMin, flRandMax );
  206. float flTryW = pParticles->RandomFloat( flRandMin, flRandMax );
  207. Vector vecLocalPosition;
  208. vecLocalPosition.x = GetSurfaceCoord( flTryU, pBox->bbmin.x * pAnimating->GetModelScale(), pBox->bbmax.x * pAnimating->GetModelScale() );
  209. vecLocalPosition.y = GetSurfaceCoord( flTryV, pBox->bbmin.y * pAnimating->GetModelScale(), pBox->bbmax.y * pAnimating->GetModelScale() );
  210. vecLocalPosition.z = GetSurfaceCoord( flTryW, pBox->bbmin.z * pAnimating->GetModelScale(), pBox->bbmax.z * pAnimating->GetModelScale() );
  211. Vector vecTryWorldPosition;
  212. VectorTransform( vecLocalPosition, *hitboxbones[pBox->bone], vecTryWorldPosition );
  213. float flPointGoodness = pParticles->RandomFloat( 0, 72 )
  214. + DotProduct( vecTryWorldPosition - vecBasePos,
  215. vecDirectionalBias );
  216. if ( nNumTrysToGetAPointInsideTheModel )
  217. {
  218. // do a point in solid test
  219. Ray_t ray;
  220. trace_t tr;
  221. ray.Init( vecTryWorldPosition, vecTryWorldPosition );
  222. enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr );
  223. if ( tr.startsolid )
  224. flPointGoodness += 1000.; // got a point inside!
  225. }
  226. if ( flPointGoodness > flBestPointGoodness )
  227. {
  228. u = flTryU;
  229. v = flTryV;
  230. w = flTryW;
  231. vecWorldPosition = vecTryWorldPosition;
  232. nHitbox = nTryHitbox;
  233. flBestPointGoodness = flPointGoodness;
  234. }
  235. } while ( nTryCnt-- );
  236. *( pPntsOut++ ) = vecWorldPosition;
  237. if ( pHitBoxRelativeCoordOut )
  238. ( pHitBoxRelativeCoordOut++ )->Init( u, v, w );
  239. if ( pHitBoxIndexOut )
  240. *( pHitBoxIndexOut++ ) = nHitbox;
  241. }
  242. }
  243. }
  244. }
  245. }
  246. if ( pMoveParent->IsBrushModel() )
  247. {
  248. Vector vecMin;
  249. Vector vecMax;
  250. matrix3x4_t matOrientation;
  251. Vector VecOrigin;
  252. pMoveParent->GetRenderBounds( vecMin, vecMax );
  253. VecOrigin = pMoveParent->GetRenderOrigin();
  254. matOrientation = pMoveParent->EntityToWorldTransform();
  255. Vector vecWorldPosition(0, 0, 0);
  256. float u = 0, v = 0, w = 0;
  257. int nHitbox = 0;
  258. int nNumIters = nNumTrysToGetAPointInsideTheModel;
  259. if (! vecDirectionalBias.IsZero( 0.0001 ) )
  260. nNumIters = MAX( nNumIters, 5 );
  261. for( int i=0 ; i < nNumPtsOut; i++)
  262. {
  263. int nTryCnt = nNumIters;
  264. float flBestPointGoodness = -1.0e20;
  265. do
  266. {
  267. float flTryU = pParticles->RandomFloat( flRandMin, flRandMax );
  268. float flTryV = pParticles->RandomFloat( flRandMin, flRandMax );
  269. float flTryW = pParticles->RandomFloat( flRandMin, flRandMax );
  270. Vector vecLocalPosition;
  271. vecLocalPosition.x = GetSurfaceCoord( flTryU, vecMin.x, vecMax.x );
  272. vecLocalPosition.y = GetSurfaceCoord( flTryV, vecMin.y, vecMax.y );
  273. vecLocalPosition.z = GetSurfaceCoord( flTryW, vecMin.z, vecMax.z );
  274. Vector vecTryWorldPosition;
  275. VectorTransform( vecLocalPosition, matOrientation, vecTryWorldPosition );
  276. float flPointGoodness = pParticles->RandomFloat( 0, 72 )
  277. + DotProduct( vecTryWorldPosition - vecBasePos,
  278. vecDirectionalBias );
  279. if ( nNumTrysToGetAPointInsideTheModel )
  280. {
  281. // do a point in solid test
  282. Ray_t ray;
  283. trace_t tr;
  284. ray.Init( vecTryWorldPosition, vecTryWorldPosition );
  285. enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr );
  286. if ( tr.startsolid )
  287. flPointGoodness += 1000.; // got a point inside!
  288. }
  289. if ( flPointGoodness > flBestPointGoodness )
  290. {
  291. u = flTryU;
  292. v = flTryV;
  293. w = flTryW;
  294. vecWorldPosition = vecTryWorldPosition;
  295. nHitbox = 0;
  296. flBestPointGoodness = flPointGoodness;
  297. }
  298. } while ( nTryCnt-- );
  299. *( pPntsOut++ ) = vecWorldPosition;
  300. if ( pHitBoxRelativeCoordOut )
  301. ( pHitBoxRelativeCoordOut++ )->Init( u, v, w );
  302. if ( pHitBoxIndexOut )
  303. *( pHitBoxIndexOut++ ) = nHitbox;
  304. }
  305. }
  306. s_BoneMutex.Unlock();
  307. }
  308. #endif
  309. if (! bSucesss )
  310. {
  311. // don't have a model or am in editor or something - fill return with control point
  312. for( int i=0 ; i < nNumPtsOut; i++)
  313. {
  314. pPntsOut[i] = pParticles->m_ControlPoints[nControlPointNumber].m_Position; // fallback if anything goes wrong
  315. if ( pHitBoxIndexOut )
  316. pHitBoxIndexOut[i] = 0;
  317. if ( pHitBoxRelativeCoordOut )
  318. pHitBoxRelativeCoordOut[i].Init();
  319. }
  320. }
  321. }
  322. int CParticleSystemQuery::GetControllingObjectHitBoxInfo(
  323. CParticleCollection *pParticles,
  324. int nControlPointNumber,
  325. int nBufSize, // # of output slots available
  326. ModelHitBoxInfo_t *pHitBoxOutputBuffer )
  327. {
  328. int nRet = 0;
  329. #ifndef GAME_DLL
  330. s_BoneMutex.Lock();
  331. EHANDLE *phMoveParent = reinterpret_cast<EHANDLE *> ( pParticles->m_ControlPoints[nControlPointNumber].m_pObject );
  332. CBaseEntity *pMoveParent = NULL;
  333. if ( phMoveParent )
  334. {
  335. pMoveParent = *( phMoveParent );
  336. }
  337. if ( pMoveParent )
  338. {
  339. C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating();
  340. if ( pAnimating )
  341. {
  342. matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
  343. if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) )
  344. {
  345. studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() );
  346. if ( pStudioHdr )
  347. {
  348. mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() );
  349. if ( set )
  350. {
  351. nRet = MIN( nBufSize, set->numhitboxes );
  352. for( int i=0 ; i < nRet; i++ )
  353. {
  354. mstudiobbox_t *pBox = set->pHitbox( i );
  355. pHitBoxOutputBuffer[i].m_vecBoxMins.x = pBox->bbmin.x;
  356. pHitBoxOutputBuffer[i].m_vecBoxMins.y = pBox->bbmin.y;
  357. pHitBoxOutputBuffer[i].m_vecBoxMins.z = pBox->bbmin.z;
  358. pHitBoxOutputBuffer[i].m_vecBoxMaxes.x = pBox->bbmax.x;
  359. pHitBoxOutputBuffer[i].m_vecBoxMaxes.y = pBox->bbmax.y;
  360. pHitBoxOutputBuffer[i].m_vecBoxMaxes.z = pBox->bbmax.z;
  361. pHitBoxOutputBuffer[i].m_Transform = *hitboxbones[pBox->bone];
  362. }
  363. }
  364. }
  365. }
  366. }
  367. if ( pMoveParent->IsBrushModel() )
  368. {
  369. Vector vecMin;
  370. Vector vecMax;
  371. matrix3x4_t matOrientation;
  372. pMoveParent->GetRenderBounds( vecMin, vecMax );
  373. matOrientation = pMoveParent->EntityToWorldTransform();
  374. pHitBoxOutputBuffer[0].m_vecBoxMins = vecMin;
  375. pHitBoxOutputBuffer[0].m_vecBoxMaxes = vecMax;
  376. pHitBoxOutputBuffer[0].m_Transform = matOrientation;
  377. nRet = 1;
  378. }
  379. }
  380. s_BoneMutex.Unlock();
  381. #endif
  382. return nRet;
  383. }
  384. bool CParticleSystemQuery::IsPointInControllingObjectHitBox(
  385. CParticleCollection *pParticles,
  386. int nControlPointNumber, Vector vecPos, bool bBBoxOnly )
  387. {
  388. bool bSuccess = false;
  389. #ifndef GAME_DLL
  390. EHANDLE *phMoveParent = reinterpret_cast<EHANDLE *> ( pParticles->m_ControlPoints[nControlPointNumber].m_pObject );
  391. CBaseEntity *pMoveParent = NULL;
  392. if ( phMoveParent )
  393. {
  394. pMoveParent = *( phMoveParent );
  395. }
  396. if ( pMoveParent )
  397. {
  398. s_BoneMutex.Lock();
  399. C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating();
  400. bool bInBBox = false;
  401. Vector vecBBoxMin;
  402. Vector vecBBoxMax;
  403. Vector vecOrigin;
  404. vecBBoxMin = pMoveParent->CollisionProp()->OBBMins();
  405. vecBBoxMax = pMoveParent->CollisionProp()->OBBMaxs();
  406. matrix3x4_t matOrientation;
  407. matOrientation = pMoveParent->EntityToWorldTransform();
  408. Vector vecLocalPos;
  409. VectorITransform( vecPos, matOrientation, vecLocalPos );
  410. if ( IsPointInBox( vecLocalPos, vecBBoxMin, vecBBoxMax ) )
  411. bInBBox = true;
  412. if ( bInBBox && bBBoxOnly )
  413. bSuccess = true;
  414. else if ( pAnimating && bInBBox )
  415. {
  416. matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
  417. if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) )
  418. {
  419. studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() );
  420. if ( pStudioHdr )
  421. {
  422. mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() );
  423. if ( set )
  424. {
  425. // do a point in solid test
  426. Ray_t ray;
  427. trace_t tr;
  428. ray.Init( vecPos, vecPos );
  429. enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr );
  430. if ( tr.startsolid )
  431. bSuccess = true;
  432. }
  433. }
  434. }
  435. }
  436. else if ( pMoveParent->IsBrushModel() && bInBBox )
  437. {
  438. // do a point in solid test
  439. Ray_t ray;
  440. trace_t tr;
  441. ray.Init( vecPos, vecPos );
  442. enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr );
  443. if ( tr.startsolid )
  444. bSuccess = true;
  445. }
  446. s_BoneMutex.Unlock();
  447. }
  448. #endif
  449. return bSuccess;
  450. }
  451. struct CollisionGroupNameRecord_t
  452. {
  453. const char *m_pszGroupName;
  454. int m_nGroupID;
  455. };
  456. static CollisionGroupNameRecord_t s_NameMap[]={
  457. { "NONE", COLLISION_GROUP_NONE },
  458. { "DEBRIS", COLLISION_GROUP_DEBRIS },
  459. { "INTERACTIVE", COLLISION_GROUP_INTERACTIVE },
  460. { "NPC", COLLISION_GROUP_NPC },
  461. { "ACTOR", COLLISION_GROUP_NPC_ACTOR },
  462. { "PASSABLE", COLLISION_GROUP_PASSABLE_DOOR },
  463. #if defined( TF_CLIENT_DLL )
  464. { "ROCKETS", TFCOLLISION_GROUP_ROCKETS },
  465. #endif
  466. };
  467. int CParticleSystemQuery::GetCollisionGroupFromName( const char *pszCollisionGroupName )
  468. {
  469. for(int i = 0; i < ARRAYSIZE( s_NameMap ); i++ )
  470. {
  471. if ( ! stricmp( s_NameMap[i].m_pszGroupName, pszCollisionGroupName ) )
  472. return s_NameMap[i].m_nGroupID;
  473. }
  474. return COLLISION_GROUP_NONE;
  475. }
  476. Vector CParticleSystemQuery::GetLocalPlayerPos( void )
  477. {
  478. #ifdef CLIENT_DLL
  479. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  480. if ( !pPlayer )
  481. return vec3_origin;
  482. return pPlayer->WorldSpaceCenter();
  483. #else
  484. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  485. if ( !pPlayer )
  486. return vec3_origin;
  487. return pPlayer->WorldSpaceCenter();
  488. #endif
  489. }
  490. void CParticleSystemQuery::GetLocalPlayerEyeVectors( Vector *pForward, Vector *pRight, Vector *pUp )
  491. {
  492. #ifdef CLIENT_DLL
  493. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  494. if ( !pPlayer )
  495. {
  496. *pForward = vec3_origin;
  497. *pRight = vec3_origin;
  498. *pUp = vec3_origin;
  499. return;
  500. }
  501. pPlayer->EyeVectors( pForward, pRight, pUp );
  502. #else
  503. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  504. if ( !pPlayer )
  505. {
  506. *pForward = vec3_origin;
  507. *pRight = vec3_origin;
  508. *pUp = vec3_origin;
  509. return;
  510. }
  511. pPlayer->EyeVectors( pForward, pRight, pUp );
  512. #endif
  513. }
  514. float CParticleSystemQuery::GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale )
  515. {
  516. #ifdef CLIENT_DLL
  517. pixelvis_queryparams_t params;
  518. params.Init( vecOrigin, flScale, 1.0 );
  519. float flVisibility = PixelVisibility_FractionVisible( params, pQueryHandle );
  520. flVisibility = MAX( 0.0f, flVisibility );
  521. return flVisibility;
  522. #else
  523. return 0.0f;
  524. #endif
  525. }