//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: particle system definitions // //===========================================================================// #include "cbase.h" #include "particles/particles.h" #include "baseparticleentity.h" #include "entityparticletrail_shared.h" #include "collisionutils.h" #if defined( CLIENT_DLL ) #include "c_pixel_visibility.h" #endif #ifdef TF_CLIENT_DLL #include "tf_shareddefs.h" #endif #ifdef GAME_DLL #include "ai_utils.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Interface to allow the particle system to call back into the game code //----------------------------------------------------------------------------- class CParticleSystemQuery : public CBaseAppSystem< IParticleSystemQuery > { public: // Inherited from IParticleSystemQuery virtual void GetLightingAtPoint( const Vector& vecOrigin, Color &cTint ); virtual void TraceLine( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, const IHandleEntity *ignore, int collisionGroup, CBaseTrace *ptr ); virtual bool MovePointInsideControllingObject( CParticleCollection *pParticles, void *pObject, Vector *pPnt ); virtual void GetRandomPointsOnControllingObjectHitBox( CParticleCollection *pParticles, int nControlPointNumber, int nNumPtsOut, float flBBoxScale, int nNumTrysToGetAPointInsideTheModel, Vector *pPntsOut, Vector vecDirectionalBias, Vector *pHitBoxRelativeCoordOut, int *pHitBoxIndexOut ); virtual int GetCollisionGroupFromName( const char *pszCollisionGroupName ); virtual int GetControllingObjectHitBoxInfo( CParticleCollection *pParticles, int nControlPointNumber, int nBufSize, // # of output slots available ModelHitBoxInfo_t *pHitBoxOutputBuffer ); virtual bool IsPointInControllingObjectHitBox( CParticleCollection *pParticles, int nControlPointNumber, Vector vecPos, bool bBBoxOnly ); virtual Vector GetLocalPlayerPos( void ); virtual void GetLocalPlayerEyeVectors( Vector *pForward, Vector *pRight = NULL, Vector *pUp = NULL ); virtual float GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale ); virtual void SetUpLightingEnvironment( const Vector& pos ); }; static CParticleSystemQuery s_ParticleSystemQuery; IParticleSystemQuery *g_pParticleSystemQuery = &s_ParticleSystemQuery; //----------------------------------------------------------------------------- // Exposes the interface (so tools can get at it) //----------------------------------------------------------------------------- #ifdef CLIENT_DLL EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CParticleSystemQuery, IParticleSystemQuery, PARTICLE_SYSTEM_QUERY_INTERFACE_VERSION, s_ParticleSystemQuery ); #endif static CThreadFastMutex s_LightMutex; // This mutex exists because EntityToWorldTransform was not threadsafe, and could potentially have been called from multiple // 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). // static CThreadFastMutex s_BoneMutex; static CThreadNullMutex s_BoneMutex; //----------------------------------------------------------------------------- // Inherited from IParticleSystemQuery //----------------------------------------------------------------------------- void CParticleSystemQuery::GetLightingAtPoint( const Vector& vecOrigin, Color &cTint ) { #ifdef GAME_DLL // FIXME: Go through to the engine from the server to get these values cTint.SetColor( 255, 255, 255, 255 ); #else if ( engine->IsInGame() ) { s_LightMutex.Lock(); // Compute our lighting at our position Vector totalColor = engine->GetLightForPoint( vecOrigin, true ); s_LightMutex.Unlock(); // Get our lighting information cTint.SetColor( totalColor.x*255, totalColor.y*255, totalColor.z*255, 0 ); } else { // FIXME: Go through to the engine from the server to get these values cTint.SetColor( 255, 255, 255, 255 ); } #endif } void CParticleSystemQuery::SetUpLightingEnvironment( const Vector& pos ) { #ifndef GAME_DLL if ( !engine->IsInGame() ) return; s_LightMutex.Lock(); modelrender->SetupLighting( pos ); s_LightMutex.Unlock(); #endif } void CParticleSystemQuery::TraceLine( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, const IHandleEntity *ignore, int collisionGroup, CBaseTrace *ptr ) { bool bDoTrace = false; #ifndef GAME_DLL bDoTrace = engine->IsInGame(); #endif if ( bDoTrace ) { trace_t tempTrace; UTIL_TraceLine( vecAbsStart, vecAbsEnd, mask, ignore, collisionGroup, &tempTrace ); memcpy( ptr, &tempTrace, sizeof ( CBaseTrace ) ); } else { ptr->startsolid = 0; ptr->fraction = 1.0; } } bool CParticleSystemQuery::MovePointInsideControllingObject( CParticleCollection *pParticles, void *pObject, Vector *pPnt ) { #ifdef GAME_DLL return true; #else if (! pObject ) return true; // accept the input point unmodified Ray_t ray; trace_t tr; ray.Init( *pPnt, *pPnt ); enginetrace->ClipRayToEntity( ray, MASK_ALL, (CBaseEntity *) pObject, &tr ); return ( tr.startsolid ); #endif } static float GetSurfaceCoord( float flRand, float flMinX, float flMaxX ) { return Lerp( flRand, flMinX, flMaxX ); } void CParticleSystemQuery::GetRandomPointsOnControllingObjectHitBox( CParticleCollection *pParticles, int nControlPointNumber, int nNumPtsOut, float flBBoxScale, int nNumTrysToGetAPointInsideTheModel, Vector *pPntsOut, Vector vecDirectionalBias, Vector *pHitBoxRelativeCoordOut, int *pHitBoxIndexOut ) { bool bSucesss = false; #ifndef GAME_DLL EHANDLE *phMoveParent = reinterpret_cast ( pParticles->m_ControlPoints[nControlPointNumber].m_pObject ); CBaseEntity *pMoveParent = NULL; if ( phMoveParent ) { pMoveParent = *( phMoveParent ); } if ( pMoveParent ) { float flRandMax = flBBoxScale; float flRandMin = 1.0 - flBBoxScale; Vector vecBasePos; pParticles->GetControlPointAtTime( nControlPointNumber, pParticles->m_flCurTime, &vecBasePos ); s_BoneMutex.Lock(); C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating(); if ( pAnimating ) { matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) ) { studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() ); if ( pStudioHdr ) { mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() ); if ( set ) { bSucesss = true; Vector vecWorldPosition(0, 0, 0); float u = 0, v = 0, w = 0; int nHitbox = 0; int nNumIters = nNumTrysToGetAPointInsideTheModel; if (! vecDirectionalBias.IsZero( 0.0001 ) ) nNumIters = MAX( nNumIters, 5 ); for( int i=0 ; i < nNumPtsOut; i++) { int nTryCnt = nNumIters; float flBestPointGoodness = -1.0e20; do { int nTryHitbox = pParticles->RandomInt( 0, set->numhitboxes - 1 ); mstudiobbox_t *pBox = set->pHitbox(nTryHitbox); float flTryU = pParticles->RandomFloat( flRandMin, flRandMax ); float flTryV = pParticles->RandomFloat( flRandMin, flRandMax ); float flTryW = pParticles->RandomFloat( flRandMin, flRandMax ); Vector vecLocalPosition; vecLocalPosition.x = GetSurfaceCoord( flTryU, pBox->bbmin.x * pAnimating->GetModelScale(), pBox->bbmax.x * pAnimating->GetModelScale() ); vecLocalPosition.y = GetSurfaceCoord( flTryV, pBox->bbmin.y * pAnimating->GetModelScale(), pBox->bbmax.y * pAnimating->GetModelScale() ); vecLocalPosition.z = GetSurfaceCoord( flTryW, pBox->bbmin.z * pAnimating->GetModelScale(), pBox->bbmax.z * pAnimating->GetModelScale() ); Vector vecTryWorldPosition; VectorTransform( vecLocalPosition, *hitboxbones[pBox->bone], vecTryWorldPosition ); float flPointGoodness = pParticles->RandomFloat( 0, 72 ) + DotProduct( vecTryWorldPosition - vecBasePos, vecDirectionalBias ); if ( nNumTrysToGetAPointInsideTheModel ) { // do a point in solid test Ray_t ray; trace_t tr; ray.Init( vecTryWorldPosition, vecTryWorldPosition ); enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr ); if ( tr.startsolid ) flPointGoodness += 1000.; // got a point inside! } if ( flPointGoodness > flBestPointGoodness ) { u = flTryU; v = flTryV; w = flTryW; vecWorldPosition = vecTryWorldPosition; nHitbox = nTryHitbox; flBestPointGoodness = flPointGoodness; } } while ( nTryCnt-- ); *( pPntsOut++ ) = vecWorldPosition; if ( pHitBoxRelativeCoordOut ) ( pHitBoxRelativeCoordOut++ )->Init( u, v, w ); if ( pHitBoxIndexOut ) *( pHitBoxIndexOut++ ) = nHitbox; } } } } } if ( pMoveParent->IsBrushModel() ) { Vector vecMin; Vector vecMax; matrix3x4_t matOrientation; Vector VecOrigin; pMoveParent->GetRenderBounds( vecMin, vecMax ); VecOrigin = pMoveParent->GetRenderOrigin(); matOrientation = pMoveParent->EntityToWorldTransform(); Vector vecWorldPosition(0, 0, 0); float u = 0, v = 0, w = 0; int nHitbox = 0; int nNumIters = nNumTrysToGetAPointInsideTheModel; if (! vecDirectionalBias.IsZero( 0.0001 ) ) nNumIters = MAX( nNumIters, 5 ); for( int i=0 ; i < nNumPtsOut; i++) { int nTryCnt = nNumIters; float flBestPointGoodness = -1.0e20; do { float flTryU = pParticles->RandomFloat( flRandMin, flRandMax ); float flTryV = pParticles->RandomFloat( flRandMin, flRandMax ); float flTryW = pParticles->RandomFloat( flRandMin, flRandMax ); Vector vecLocalPosition; vecLocalPosition.x = GetSurfaceCoord( flTryU, vecMin.x, vecMax.x ); vecLocalPosition.y = GetSurfaceCoord( flTryV, vecMin.y, vecMax.y ); vecLocalPosition.z = GetSurfaceCoord( flTryW, vecMin.z, vecMax.z ); Vector vecTryWorldPosition; VectorTransform( vecLocalPosition, matOrientation, vecTryWorldPosition ); float flPointGoodness = pParticles->RandomFloat( 0, 72 ) + DotProduct( vecTryWorldPosition - vecBasePos, vecDirectionalBias ); if ( nNumTrysToGetAPointInsideTheModel ) { // do a point in solid test Ray_t ray; trace_t tr; ray.Init( vecTryWorldPosition, vecTryWorldPosition ); enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr ); if ( tr.startsolid ) flPointGoodness += 1000.; // got a point inside! } if ( flPointGoodness > flBestPointGoodness ) { u = flTryU; v = flTryV; w = flTryW; vecWorldPosition = vecTryWorldPosition; nHitbox = 0; flBestPointGoodness = flPointGoodness; } } while ( nTryCnt-- ); *( pPntsOut++ ) = vecWorldPosition; if ( pHitBoxRelativeCoordOut ) ( pHitBoxRelativeCoordOut++ )->Init( u, v, w ); if ( pHitBoxIndexOut ) *( pHitBoxIndexOut++ ) = nHitbox; } } s_BoneMutex.Unlock(); } #endif if (! bSucesss ) { // don't have a model or am in editor or something - fill return with control point for( int i=0 ; i < nNumPtsOut; i++) { pPntsOut[i] = pParticles->m_ControlPoints[nControlPointNumber].m_Position; // fallback if anything goes wrong if ( pHitBoxIndexOut ) pHitBoxIndexOut[i] = 0; if ( pHitBoxRelativeCoordOut ) pHitBoxRelativeCoordOut[i].Init(); } } } int CParticleSystemQuery::GetControllingObjectHitBoxInfo( CParticleCollection *pParticles, int nControlPointNumber, int nBufSize, // # of output slots available ModelHitBoxInfo_t *pHitBoxOutputBuffer ) { int nRet = 0; #ifndef GAME_DLL s_BoneMutex.Lock(); EHANDLE *phMoveParent = reinterpret_cast ( pParticles->m_ControlPoints[nControlPointNumber].m_pObject ); CBaseEntity *pMoveParent = NULL; if ( phMoveParent ) { pMoveParent = *( phMoveParent ); } if ( pMoveParent ) { C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating(); if ( pAnimating ) { matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) ) { studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() ); if ( pStudioHdr ) { mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() ); if ( set ) { nRet = MIN( nBufSize, set->numhitboxes ); for( int i=0 ; i < nRet; i++ ) { mstudiobbox_t *pBox = set->pHitbox( i ); pHitBoxOutputBuffer[i].m_vecBoxMins.x = pBox->bbmin.x; pHitBoxOutputBuffer[i].m_vecBoxMins.y = pBox->bbmin.y; pHitBoxOutputBuffer[i].m_vecBoxMins.z = pBox->bbmin.z; pHitBoxOutputBuffer[i].m_vecBoxMaxes.x = pBox->bbmax.x; pHitBoxOutputBuffer[i].m_vecBoxMaxes.y = pBox->bbmax.y; pHitBoxOutputBuffer[i].m_vecBoxMaxes.z = pBox->bbmax.z; pHitBoxOutputBuffer[i].m_Transform = *hitboxbones[pBox->bone]; } } } } } if ( pMoveParent->IsBrushModel() ) { Vector vecMin; Vector vecMax; matrix3x4_t matOrientation; pMoveParent->GetRenderBounds( vecMin, vecMax ); matOrientation = pMoveParent->EntityToWorldTransform(); pHitBoxOutputBuffer[0].m_vecBoxMins = vecMin; pHitBoxOutputBuffer[0].m_vecBoxMaxes = vecMax; pHitBoxOutputBuffer[0].m_Transform = matOrientation; nRet = 1; } } s_BoneMutex.Unlock(); #endif return nRet; } bool CParticleSystemQuery::IsPointInControllingObjectHitBox( CParticleCollection *pParticles, int nControlPointNumber, Vector vecPos, bool bBBoxOnly ) { bool bSuccess = false; #ifndef GAME_DLL EHANDLE *phMoveParent = reinterpret_cast ( pParticles->m_ControlPoints[nControlPointNumber].m_pObject ); CBaseEntity *pMoveParent = NULL; if ( phMoveParent ) { pMoveParent = *( phMoveParent ); } if ( pMoveParent ) { s_BoneMutex.Lock(); C_BaseAnimating *pAnimating = pMoveParent->GetBaseAnimating(); bool bInBBox = false; Vector vecBBoxMin; Vector vecBBoxMax; Vector vecOrigin; vecBBoxMin = pMoveParent->CollisionProp()->OBBMins(); vecBBoxMax = pMoveParent->CollisionProp()->OBBMaxs(); matrix3x4_t matOrientation; matOrientation = pMoveParent->EntityToWorldTransform(); Vector vecLocalPos; VectorITransform( vecPos, matOrientation, vecLocalPos ); if ( IsPointInBox( vecLocalPos, vecBBoxMin, vecBBoxMax ) ) bInBBox = true; if ( bInBBox && bBBoxOnly ) bSuccess = true; else if ( pAnimating && bInBBox ) { matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) ) { studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() ); if ( pStudioHdr ) { mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() ); if ( set ) { // do a point in solid test Ray_t ray; trace_t tr; ray.Init( vecPos, vecPos ); enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr ); if ( tr.startsolid ) bSuccess = true; } } } } else if ( pMoveParent->IsBrushModel() && bInBBox ) { // do a point in solid test Ray_t ray; trace_t tr; ray.Init( vecPos, vecPos ); enginetrace->ClipRayToEntity( ray, MASK_ALL, pMoveParent, &tr ); if ( tr.startsolid ) bSuccess = true; } s_BoneMutex.Unlock(); } #endif return bSuccess; } struct CollisionGroupNameRecord_t { const char *m_pszGroupName; int m_nGroupID; }; static CollisionGroupNameRecord_t s_NameMap[]={ { "NONE", COLLISION_GROUP_NONE }, { "DEBRIS", COLLISION_GROUP_DEBRIS }, { "INTERACTIVE", COLLISION_GROUP_INTERACTIVE }, { "NPC", COLLISION_GROUP_NPC }, { "ACTOR", COLLISION_GROUP_NPC_ACTOR }, { "PASSABLE", COLLISION_GROUP_PASSABLE_DOOR }, #if defined( TF_CLIENT_DLL ) { "ROCKETS", TFCOLLISION_GROUP_ROCKETS }, #endif }; int CParticleSystemQuery::GetCollisionGroupFromName( const char *pszCollisionGroupName ) { for(int i = 0; i < ARRAYSIZE( s_NameMap ); i++ ) { if ( ! stricmp( s_NameMap[i].m_pszGroupName, pszCollisionGroupName ) ) return s_NameMap[i].m_nGroupID; } return COLLISION_GROUP_NONE; } Vector CParticleSystemQuery::GetLocalPlayerPos( void ) { #ifdef CLIENT_DLL C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( !pPlayer ) return vec3_origin; return pPlayer->WorldSpaceCenter(); #else CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( !pPlayer ) return vec3_origin; return pPlayer->WorldSpaceCenter(); #endif } void CParticleSystemQuery::GetLocalPlayerEyeVectors( Vector *pForward, Vector *pRight, Vector *pUp ) { #ifdef CLIENT_DLL C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( !pPlayer ) { *pForward = vec3_origin; *pRight = vec3_origin; *pUp = vec3_origin; return; } pPlayer->EyeVectors( pForward, pRight, pUp ); #else CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( !pPlayer ) { *pForward = vec3_origin; *pRight = vec3_origin; *pUp = vec3_origin; return; } pPlayer->EyeVectors( pForward, pRight, pUp ); #endif } float CParticleSystemQuery::GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale ) { #ifdef CLIENT_DLL pixelvis_queryparams_t params; params.Init( vecOrigin, flScale, 1.0 ); float flVisibility = PixelVisibility_FractionVisible( params, pQueryHandle ); flVisibility = MAX( 0.0f, flVisibility ); return flVisibility; #else return 0.0f; #endif }