//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "particlemgr.h" #include "particles_new.h" #include "iclientmode.h" #include "engine/ivdebugoverlay.h" #include "particle_property.h" #include "toolframework/itoolframework.h" #include "toolframework_client.h" #include "tier1/KeyValues.h" #include "model_types.h" #include "vprof.h" #include "input.h" extern ConVar cl_particleeffect_aabb_buffer; //extern ConVar cl_particle_show_bbox; //extern ConVar cl_particle_show_bbox_cost; extern bool g_cl_particle_show_bbox; extern int g_cl_particle_show_bbox_cost; //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CNewParticleEffect::CNewParticleEffect( CBaseEntity *pOwner, CParticleSystemDefinition *pEffect ) { m_hOwner = pOwner; Init( pEffect ); Construct(); } CNewParticleEffect::CNewParticleEffect( CBaseEntity *pOwner, const char* pEffectName ) { m_hOwner = pOwner; Init( pEffectName ); Construct(); } void CNewParticleEffect::Construct() { m_vSortOrigin.Init(); m_bDontRemove = false; m_bRemove = false; m_bDrawn = false; m_bNeedsBBoxUpdate = false; m_bIsFirstFrame = true; m_bAutoUpdateBBox = false; m_bAllocated = true; m_bSimulate = true; m_bShouldPerformCullCheck = false; m_nToolParticleEffectId = TOOLPARTICLESYSTEMID_INVALID; m_RefCount = 0; ParticleMgr()->AddEffect( this ); m_LastMax = Vector( -1.0e6, -1.0e6, -1.0e6 ); m_LastMin = Vector( 1.0e6, 1.0e6, 1.0e6 ); m_MinBounds = Vector( 1.0e6, 1.0e6, 1.0e6 ); m_MaxBounds = Vector( -1.0e6, -1.0e6, -1.0e6 ); m_pDebugName = NULL; m_bViewModelEffect = m_pDef ? m_pDef->IsViewModelEffect() : false; if ( IsValid() && clienttools->IsInRecordingMode() ) { int nId = AllocateToolParticleEffectId(); static ParticleSystemCreatedState_t state; state.m_nParticleSystemId = nId; state.m_flTime = gpGlobals->curtime; state.m_pName = GetName(); state.m_nOwner = m_hOwner.Get() ? m_hOwner->entindex() : -1; KeyValues *msg = new KeyValues( "ParticleSystem_Create" ); msg->SetPtr( "state", &state ); ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); } } CNewParticleEffect::~CNewParticleEffect(void) { if ( m_nToolParticleEffectId != TOOLPARTICLESYSTEMID_INVALID && clienttools->IsInRecordingMode() ) { static ParticleSystemDestroyedState_t state; state.m_nParticleSystemId = gpGlobals->curtime; state.m_flTime = gpGlobals->curtime; KeyValues *msg = new KeyValues( "ParticleSystem_Destroy" ); msg->SetPtr( "state", &state ); ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); m_nToolParticleEffectId = TOOLPARTICLESYSTEMID_INVALID; } m_bAllocated = false; if ( m_hOwner ) { // NOTE: This can provoke another NotifyRemove call which is why flags is set to 0 m_hOwner->ParticleProp()->OnParticleSystemDeleted( this ); } } //----------------------------------------------------------------------------- // Refcounting //----------------------------------------------------------------------------- void CNewParticleEffect::AddRef() { ++m_RefCount; } void CNewParticleEffect::Release() { Assert( m_RefCount > 0 ); --m_RefCount; // If all the particles are already gone, delete ourselves now. // If there are still particles, wait for the last NotifyDestroyParticle. if ( m_RefCount == 0 ) { if ( m_bAllocated ) { if ( IsFinished() ) { SetRemoveFlag(); } } } } void CNewParticleEffect::NotifyRemove() { if ( m_bAllocated ) { delete this; } } int CNewParticleEffect::IsReleased() { return m_RefCount == 0; } //----------------------------------------------------------------------------- // Refraction and soft particle support //----------------------------------------------------------------------------- bool CNewParticleEffect::UsesPowerOfTwoFrameBufferTexture( void ) { // NOTE: Need to do this because CParticleCollection's version is non-virtual return CParticleCollection::UsesPowerOfTwoFrameBufferTexture( true ); } bool CNewParticleEffect::UsesFullFrameBufferTexture( void ) { // NOTE: Need to do this because CParticleCollection's version is non-virtual return CParticleCollection::UsesFullFrameBufferTexture( true ); } bool CNewParticleEffect::IsTwoPass( void ) { // NOTE: Need to do this because CParticleCollection's version is non-virtual return CParticleCollection::IsTwoPass(); } //----------------------------------------------------------------------------- // Overrides for recording //----------------------------------------------------------------------------- void CNewParticleEffect::StopEmission( bool bInfiniteOnly, bool bRemoveAllParticles, bool bWakeOnStop ) { if ( m_nToolParticleEffectId != TOOLPARTICLESYSTEMID_INVALID && clienttools->IsInRecordingMode() ) { KeyValues *msg = new KeyValues( "ParticleSystem_StopEmission" ); static ParticleSystemStopEmissionState_t state; state.m_nParticleSystemId = GetToolParticleEffectId(); state.m_flTime = gpGlobals->curtime; state.m_bInfiniteOnly = bInfiniteOnly; msg->SetPtr( "state", &state ); ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); } CParticleCollection::StopEmission( bInfiniteOnly, bRemoveAllParticles, bWakeOnStop ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNewParticleEffect::SetDormant( bool bDormant ) { CParticleCollection::SetDormant( bDormant ); } void CNewParticleEffect::SetControlPointEntity( int nWhichPoint, CBaseEntity *pEntity ) { if ( m_nToolParticleEffectId != TOOLPARTICLESYSTEMID_INVALID && clienttools->IsInRecordingMode() ) { static ParticleSystemSetControlPointObjectState_t state; state.m_nParticleSystemId = GetToolParticleEffectId(); state.m_flTime = gpGlobals->curtime; state.m_nControlPoint = nWhichPoint; state.m_nObject = pEntity ? pEntity->entindex() : -1; KeyValues *msg = new KeyValues( "ParticleSystem_SetControlPointObject" ); msg->SetPtr( "state", &state ); ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); } if ( pEntity ) { CParticleCollection::SetControlPointObject( nWhichPoint, &m_hControlPointOwners[ nWhichPoint ] ); m_hControlPointOwners[ nWhichPoint ] = pEntity; } else CParticleCollection::SetControlPointObject( nWhichPoint, NULL ); } void CNewParticleEffect::SetControlPoint( int nWhichPoint, const Vector &v ) { if ( m_nToolParticleEffectId != TOOLPARTICLESYSTEMID_INVALID && clienttools->IsInRecordingMode() ) { static ParticleSystemSetControlPointPositionState_t state; state.m_nParticleSystemId = GetToolParticleEffectId(); state.m_flTime = gpGlobals->curtime; state.m_nControlPoint = nWhichPoint; state.m_vecPosition = v; KeyValues *msg = new KeyValues( "ParticleSystem_SetControlPointPosition" ); msg->SetPtr( "state", &state ); ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); } CParticleCollection::SetControlPoint( nWhichPoint, v ); } void CNewParticleEffect::RecordControlPointOrientation( int nWhichPoint ) { if ( m_nToolParticleEffectId != TOOLPARTICLESYSTEMID_INVALID && clienttools->IsInRecordingMode() ) { // FIXME: Make a more direct way of getting QAngle angles; VectorAngles( m_ControlPoints[nWhichPoint].m_ForwardVector, m_ControlPoints[nWhichPoint].m_UpVector, angles ); static ParticleSystemSetControlPointOrientationState_t state; state.m_nParticleSystemId = GetToolParticleEffectId(); state.m_flTime = gpGlobals->curtime; state.m_nControlPoint = nWhichPoint; AngleQuaternion( angles, state.m_qOrientation ); KeyValues *msg = new KeyValues( "ParticleSystem_SetControlPointOrientation" ); msg->SetPtr( "state", &state ); ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg ); } } void CNewParticleEffect::SetControlPointOrientation( int nWhichPoint, const Vector &forward, const Vector &right, const Vector &up ) { CParticleCollection::SetControlPointOrientation( nWhichPoint, forward, right, up ); RecordControlPointOrientation( nWhichPoint ); } void CNewParticleEffect::SetControlPointOrientation( int nWhichPoint, const Quaternion &q ) { CParticleCollection::SetControlPointOrientation( nWhichPoint, q ); RecordControlPointOrientation( nWhichPoint ); } void CNewParticleEffect::SetControlPointForwardVector( int nWhichPoint, const Vector &v ) { CParticleCollection::SetControlPointForwardVector( nWhichPoint, v ); RecordControlPointOrientation( nWhichPoint ); } void CNewParticleEffect::SetControlPointUpVector( int nWhichPoint, const Vector &v ) { CParticleCollection::SetControlPointUpVector( nWhichPoint, v ); RecordControlPointOrientation( nWhichPoint ); } void CNewParticleEffect::SetControlPointRightVector( int nWhichPoint, const Vector &v ) { CParticleCollection::SetControlPointRightVector( nWhichPoint, v ); RecordControlPointOrientation( nWhichPoint ); } //----------------------------------------------------------------------------- // Called when the particle effect is about to update //----------------------------------------------------------------------------- void CNewParticleEffect::Update( float flTimeDelta ) { if ( m_hOwner ) { m_hOwner->ParticleProp()->OnParticleSystemUpdated( this, flTimeDelta ); } } //----------------------------------------------------------------------------- // Bounding box //----------------------------------------------------------------------------- CNewParticleEffect* CNewParticleEffect::ReplaceWith( const char *pParticleSystemName ) { StopEmission( false, true, true ); if ( !pParticleSystemName || !pParticleSystemName[0] ) return NULL; CSmartPtr< CNewParticleEffect > pNewEffect = CNewParticleEffect::Create( GetOwner(), pParticleSystemName, pParticleSystemName ); if ( !pNewEffect->IsValid() ) return pNewEffect.GetObject(); // Copy over the control point data for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i ) { if ( !ReadsControlPoint( i ) ) continue; Vector vecForward, vecRight, vecUp; pNewEffect->SetControlPoint( i, GetControlPointAtCurrentTime( i ) ); GetControlPointOrientationAtCurrentTime( i, &vecForward, &vecRight, &vecUp ); pNewEffect->SetControlPointOrientation( i, vecForward, vecRight, vecUp ); pNewEffect->SetControlPointParent( i, GetControlPointParent( i ) ); } if ( !m_hOwner ) return pNewEffect.GetObject(); m_hOwner->ParticleProp()->ReplaceParticleEffect( this, pNewEffect.GetObject() ); return pNewEffect.GetObject(); } //----------------------------------------------------------------------------- // Bounding box //----------------------------------------------------------------------------- void CNewParticleEffect::SetParticleCullRadius( float radius ) { } bool CNewParticleEffect::RecalculateBoundingBox() { BloatBoundsUsingControlPoint(); if ( m_MaxBounds.x < m_MinBounds.x ) { m_MaxBounds = m_MinBounds = GetSortOrigin(); return false; } return true; } void CNewParticleEffect::GetRenderBounds( Vector& mins, Vector& maxs ) { VectorSubtract( m_MinBounds, GetRenderOrigin(), mins ); VectorSubtract( m_MaxBounds, GetRenderOrigin(), maxs ); } void CNewParticleEffect::DetectChanges() { // if we have no render handle, return if ( m_hRenderHandle == INVALID_CLIENT_RENDER_HANDLE ) return; float flBuffer = cl_particleeffect_aabb_buffer.GetFloat(); float flExtraBuffer = flBuffer * 1.3f; // if nothing changed, return if ( m_MinBounds.x < m_LastMin.x || m_MinBounds.y < m_LastMin.y || m_MinBounds.z < m_LastMin.z || m_MinBounds.x > (m_LastMin.x + flExtraBuffer) || m_MinBounds.y > (m_LastMin.y + flExtraBuffer) || m_MinBounds.z > (m_LastMin.z + flExtraBuffer) || m_MaxBounds.x > m_LastMax.x || m_MaxBounds.y > m_LastMax.y || m_MaxBounds.z > m_LastMax.z || m_MaxBounds.x < (m_LastMax.x - flExtraBuffer) || m_MaxBounds.y < (m_LastMax.y - flExtraBuffer) || m_MaxBounds.z < (m_LastMax.z - flExtraBuffer) ) { // call leafsystem to updated this guy ClientLeafSystem()->RenderableChanged( m_hRenderHandle ); // remember last parameters // Add some padding in here so we don't reinsert it into the leaf system if it just changes a tiny amount. m_LastMin = m_MinBounds - Vector( flBuffer, flBuffer, flBuffer ); m_LastMax = m_MaxBounds + Vector( flBuffer, flBuffer, flBuffer ); } } extern ConVar r_DrawParticles; void CNewParticleEffect::DebugDrawBbox ( bool bCulled ) { int nParticlesShowBboxCost = g_cl_particle_show_bbox_cost; bool bShowCheapSystems = false; if ( nParticlesShowBboxCost < 0 ) { nParticlesShowBboxCost = -nParticlesShowBboxCost; bShowCheapSystems = true; } Vector center = GetRenderOrigin(); Vector mins = m_MinBounds - center; Vector maxs = m_MaxBounds - center; int r, g, b; bool bDraw = true; if ( bCulled ) { r = 64; g = 64; b = 64; } else if ( nParticlesShowBboxCost > 0 ) { float fAmount = (float)m_nActiveParticles / (float)nParticlesShowBboxCost; if ( fAmount < 0.5f ) { if ( bShowCheapSystems ) { r = 0; g = 255; b = 0; } else { // Prevent the screen getting spammed with low-count particles which aren't that expensive. bDraw = false; r = 0; g = 0; b = 0; } } else if ( fAmount < 1.0f ) { // green 0.5-1.0 blue int nBlend = (int)( 512.0f * ( fAmount - 0.5f ) ); nBlend = MIN ( 255, MAX ( 0, nBlend ) ); r = 0; g = 255 - nBlend; b = nBlend; } else if ( fAmount < 2.0f ) { // blue 1.0-2.0 red int nBlend = (int)( 256.0f * ( fAmount - 1.0f ) ); nBlend = MIN ( 255, MAX ( 0, nBlend ) ); r = nBlend; g = 0; b = 255 - nBlend; } else { r = 255; g = 0; b = 0; } } else { if ( GetAutoUpdateBBox() ) { // red is bad, the bbox update is costly r = 255; g = 0; b = 0; } else { // green, this effect presents less cpu load r = 0; g = 255; b = 0; } } if ( bDraw ) { if ( debugoverlay ) { debugoverlay->AddBoxOverlay( center, mins, maxs, QAngle( 0, 0, 0 ), r, g, b, 16, 0 ); debugoverlay->AddTextOverlayRGB( center, 0, 0, r, g, b, 64, "%s:(%d)", GetEffectName(), m_nActiveParticles ); } } } //----------------------------------------------------------------------------- // Rendering //----------------------------------------------------------------------------- int CNewParticleEffect::DrawModel( int flags ) { VPROF_BUDGET( "CNewParticleEffect::DrawModel", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); if ( r_DrawParticles.GetBool() == false ) return 0; if ( !g_pClientMode->ShouldDrawParticles() || !ParticleMgr()->ShouldRenderParticleSystems() ) return 0; if ( ( flags & ( STUDIO_SHADOWDEPTHTEXTURE | STUDIO_SSAODEPTHTEXTURE ) ) != 0 ) { return 0; } // do distance cull check here. We do it here instead of in particles so we can easily only do // it for root objects, not bothering to cull children individually CMatRenderContextPtr pRenderContext( materials ); Vector vecCamera; pRenderContext->GetWorldSpaceCameraPosition( &vecCamera ); if ( CalcSqrDistanceToAABB( m_MinBounds, m_MaxBounds, vecCamera ) > ( m_pDef->m_flMaxDrawDistance * m_pDef->m_flMaxDrawDistance ) ) { if ( !IsRetail() && ( g_cl_particle_show_bbox || ( g_cl_particle_show_bbox_cost != 0 ) ) ) { DebugDrawBbox ( true ); } // Still need to make sure we set this or they won't follow their attachemnt points. m_flNextSleepTime = Max ( m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep )); return 0; } if ( flags & STUDIO_TRANSPARENCY ) { int viewentity = render->GetViewEntity(); C_BaseEntity *pCameraObject = cl_entitylist->GetEnt( viewentity ); // apply logic that lets you skip rendering a system if the camera is attached to its entity if ( pCameraObject && ( m_pDef->m_nSkipRenderControlPoint != -1 ) && ( m_pDef->m_nSkipRenderControlPoint <= m_nHighestCP ) ) { C_BaseEntity *pEntity = (EHANDLE)GetControlPointEntity( m_pDef->m_nSkipRenderControlPoint ); if ( pEntity ) { // If we're in thirdperson, we still see it if ( !input->CAM_IsThirdPerson() ) { if ( pEntity == pCameraObject ) return 0; C_BaseEntity *pRootMove = pEntity->GetRootMoveParent(); if ( pRootMove == pCameraObject ) return 0; // If we're spectating in-eyes of the camera object, we don't see it C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( pPlayer == pCameraObject ) { C_BaseEntity *pObTarget = pPlayer->GetObserverTarget(); if ( pPlayer->GetObserverMode() == OBS_MODE_IN_EYE && (pObTarget == pEntity || pRootMove == pObTarget ) ) return 0; } } } } pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); Render( pRenderContext, IsTwoPass(), pCameraObject ); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); } else { g_pParticleSystemMgr->AddToRenderCache( this ); } if ( !IsRetail() ) { CParticleMgr *pMgr = ParticleMgr(); if ( pMgr->m_bStatsRunning ) { pMgr->StatsNewParticleEffectDrawn ( this ); } if ( g_cl_particle_show_bbox || ( g_cl_particle_show_bbox_cost != 0 ) ) { DebugDrawBbox ( false ); } } return 1; } static void DumpParticleStats_f( void ) { g_pParticleSystemMgr->DumpProfileInformation(); } static ConCommand cl_dump_particle_stats( "cl_dump_particle_stats", DumpParticleStats_f, "dump particle profiling info to particle_profile.csv") ;