//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: A panel that display particle systems // //=============================================================================// #include "cbase.h" #include #include #include #include #include "vgui/IVGui.h" #include "tf_particlepanel.h" #include "matsys_controls/matsyscontrols.h" #include "VGuiMatSurface/IMatSystemSurface.h" #include "tier2/renderutils.h" #include "renderparm.h" using namespace vgui; CTFParticlePanel::ParticleEffect_t::ParticleEffect_t() : m_bLoop( true ) , m_pParticleSystem( NULL ) , m_flLastTime( FLT_MAX ) , m_ParticleSystemName( NULL ) , m_bStartActivated( true ) , m_flScale( 1.f ) , m_flEndTime( FLT_MAX ) , m_nXPos( 0 ) , m_nYPos( 0 ) , m_Angles( 0.f, 0.f, 0.f ) , m_pParent( NULL ) , m_bForceStopped( false ) , m_bAutoDelete( false ) , m_bStarted( false ) {} DECLARE_BUILD_FACTORY( CTFParticlePanel ); //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CTFParticlePanel::CTFParticlePanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName ) { m_Camera.m_flZNear = 3.0f; m_Camera.m_flZFar = 16384.0f * 1.73205080757f; m_Camera.m_flFOV = 30.0f; m_Camera.m_origin = Vector(0,0,0); m_Camera.m_angles = QAngle(0,0,0); m_pLightmapTexture.Init( "//platform/materials/debug/defaultlightmap", "editor" ); m_DefaultEnvCubemap.Init( "editor/cubemap", "editor", true ); } CTFParticlePanel::~CTFParticlePanel() { m_pLightmapTexture.Shutdown(); m_DefaultEnvCubemap.Shutdown(); m_vecParticleEffects.PurgeAndDeleteElements(); } void CTFParticlePanel::ApplySettings( KeyValues *inResourceData ) { BaseClass::ApplySettings( inResourceData ); KeyValues *pKVParticleEffects = inResourceData->FindKey( "ParticleEffects" ); if ( pKVParticleEffects ) { FOR_EACH_SUBKEY( pKVParticleEffects, pKVEffect ) { m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t(); ParticleEffect_t* pEffect = m_vecParticleEffects.Tail(); // get the position int alignScreenWide = GetWide(), alignScreenTall = GetTall(); // screen dimensions used for pinning in splitscreen int x, y; GetPos(x, y); const char *xstr = pKVEffect->GetString( "particle_xpos", NULL ); const char *ystr = pKVEffect->GetString( "particle_ypos", NULL ); if (xstr) { bool bRightAlign = false; bool bCenterAlign = false; // look for alignment flags if (xstr[0] == 'r' || xstr[0] == 'R') { bRightAlign = true; xstr++; } else if (xstr[0] == 'c' || xstr[0] == 'C') { bCenterAlign = true; xstr++; } // get the value x = atoi(xstr); // scale the x up to our screen co-ords if ( IsProportional() ) { x = scheme()->GetProportionalScaledValueEx(GetScheme(), x); } // now correct the alignment if ( bRightAlign ) { x = alignScreenWide - x; } else if ( bCenterAlign ) { x = (alignScreenWide / 2) + x; } } if (ystr) { bool bBottomAlign = false; bool bCenterAlign = false; // look for alignment flags if (ystr[0] == 'r' || ystr[0] == 'R') { bBottomAlign = true; ystr++; } else if (ystr[0] == 'c' || ystr[0] == 'C') { bCenterAlign = true; ystr++; } y = atoi(ystr); if (IsProportional()) { // scale the y up to our screen co-ords y = scheme()->GetProportionalScaledValueEx(GetScheme(), y); } // now correct the alignment if ( bBottomAlign ) { y = alignScreenTall - y; } else if ( bCenterAlign ) { y = (alignScreenTall / 2) + y; } } pEffect->m_nXPos = x; pEffect->m_nYPos = y; pEffect->m_flScale = pKVEffect->GetFloat( "particle_scale", 1.f ); // Scale the scale factor the same way we do the XY position coordinates if( IsProportional() ) { int wide, tall; surface()->GetScreenSize( wide, tall ); int proH, proW; surface()->GetProportionalBase( proW, proH ); double scale = (double)tall / (double)proH; pEffect->m_flScale *= scale; } pEffect->m_pParent = this; pEffect->m_bLoop = pKVEffect->GetBool( "loop", true ); pEffect->m_bStartActivated = pKVEffect->GetBool( "start_activated", true ); pEffect->SetParticleSystem( pKVEffect->GetString( "particleName" ) ); // Read angles for the particle system { float x1,y1,z1; const char* pszAngles = pKVEffect->GetString( "angles" ); if( *pszAngles ) { if( pEffect->m_pParticleSystem && sscanf( pszAngles, "%f %f %f", &x1, &y1, &z1 ) == 3 ) { pEffect->m_Angles = QAngle( x1, y1, z1 ); Quaternion q; AngleQuaternion( pEffect->m_Angles , q ); pEffect->m_pParticleSystem->SetControlPointOrientation( 0, q ); } } } pEffect->SetControlPointValue( 0, Vector(0,0,0) ); // Read all control point values const char* pszControlPoint = NULL; int nControlPointNumber = 0; do { pszControlPoint = pKVEffect->GetString( VarArgs("control_point%d", nControlPointNumber), "" ); if ( *pszControlPoint ) { float x2,y2,z2; if (sscanf(pszControlPoint, "%f %f %f", &x2, &y2, &z2 ) == 3) { pEffect->SetControlPointValue( nControlPointNumber, Vector( x2, y2, z2 ) ); } } ++nControlPointNumber; } while( *pszControlPoint ); } } } //----------------------------------------------------------------------------- // Scheme //----------------------------------------------------------------------------- void CTFParticlePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); SetMouseInputEnabled( false ); SetKeyBoardInputEnabled( false ); } void CTFParticlePanel::OnCommand( const char *command ) { if ( !Q_strnicmp( command, "start", ARRAYSIZE("start") - 1 ) ) { // Check if they specified a particular one to turn on const char* pszNum = command + ARRAYSIZE( "start" ) - 1; int nIndex = m_vecParticleEffects.InvalidIndex(); if( pszNum && pszNum[0] ) { nIndex = atoi(pszNum); Assert( nIndex >= 0 && nIndex < m_vecParticleEffects.Count() ); } FOR_EACH_VEC( m_vecParticleEffects, i ) { if ( nIndex != m_vecParticleEffects.InvalidIndex() && i != nIndex ) continue; ParticleEffect_t* pEffect = m_vecParticleEffects[ i ]; if( !pEffect->m_pParticleSystem ) { pEffect->SetParticleSystem( pEffect->m_ParticleSystemName ); } if( !pEffect->m_pParticleSystem ) continue; pEffect->StartupParticleCollection(); pEffect->m_pParticleSystem->StartEmission(); pEffect->m_bForceStopped = false; } } else if ( !Q_strnicmp( command, "stop", ARRAYSIZE("stop") - 1 ) ) { // Check if they specified a specific one to turn off const char* pszNum = command + ARRAYSIZE( "start" ) - 1; if( pszNum && pszNum[0] ) { int iIndex = atoi(pszNum); Assert( iIndex >= 0 && iIndex < m_vecParticleEffects.Count() ); ParticleEffect_t* pEffect = m_vecParticleEffects[iIndex]; if( pEffect->m_pParticleSystem ) { pEffect->m_pParticleSystem->StopEmission(); pEffect->m_bForceStopped = true; } } else { // Turn them ALL off FOR_EACH_VEC( m_vecParticleEffects, i ) { ParticleEffect_t* pEffect = m_vecParticleEffects[ i ]; if( pEffect->m_pParticleSystem ) { pEffect->m_pParticleSystem->StopEmission(); pEffect->m_bForceStopped = true; } } } } } void CTFParticlePanel::FireParticleEffect( const char *pszName, int xPos, int yPos, float flScale, bool bLoop, float flEndTime ) { m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t(); ParticleEffect_t* pEffect = m_vecParticleEffects.Tail(); int iParentAbsX, iParentAbsY; vgui::ipanel()->GetAbsPos( GetParent()->GetVPanel(), iParentAbsX, iParentAbsY ); pEffect->m_pParent = this; pEffect->m_nXPos = xPos - iParentAbsX; pEffect->m_nYPos = yPos - iParentAbsY; pEffect->m_flScale = flScale; pEffect->m_bLoop = bLoop; pEffect->m_bAutoDelete = true; // This will get automatically deleted once it stops pEffect->m_bStartActivated = true; pEffect->m_flEndTime = gpGlobals->curtime + flEndTime; if( IsProportional() ) { int wide, tall; surface()->GetScreenSize( wide, tall ); int proH, proW; surface()->GetProportionalBase( proW, proH ); double scale = (double)tall / (double)proH; pEffect->m_flScale *= scale; } for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i ) { pEffect->SetControlPointValue( i, Vector( 0, 0, 10.0f * i ) ); } pEffect->SetParticleSystem( pszName ); } static bool IsValidHierarchy( CParticleCollection *pCollection ) { if ( !pCollection->IsValid() ) return false; for( CParticleCollection *pChild = pCollection->m_Children.m_pHead; pChild; pChild = pChild->m_pNext ) { if ( !IsValidHierarchy( pChild ) ) return false; } return true; } //----------------------------------------------------------------------------- // Simulate the particle system //----------------------------------------------------------------------------- void CTFParticlePanel::OnTick() { BaseClass::OnTick(); float flTime = engine->Time(); bool bAnyActive = false; // Update all particles FOR_EACH_VEC_BACK( m_vecParticleEffects, i ) { bAnyActive |= m_vecParticleEffects[i]->Update( flTime ); // If this effect is done and should auto-delete, then now is when we delete if( m_vecParticleEffects[i]->m_pParticleSystem == NULL && m_vecParticleEffects[i]->m_bAutoDelete ) { delete m_vecParticleEffects[i]; m_vecParticleEffects.FastRemove( i ); } } if ( !bAnyActive ) { vgui::ivgui()->RemoveTickSignal( GetVPanel() ); } } void CTFParticlePanel::Paint() { // This needs calling to reset various counters. g_pParticleSystemMgr->SetLastSimulationTime( gpGlobals->curtime ); // No particles? Do nothing. if( m_vecParticleEffects.Count() == 0 ) return; int screenW, screenH; vgui::surface()->GetScreenSize( screenW, screenH ); vgui::MatSystemSurface()->Begin3DPaint( 0, 0, screenW, screenH, false ); VMatrix view, projection; ComputeViewMatrix( &view, m_Camera ); ComputeProjectionMatrix( &projection, m_Camera, screenW, screenH ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); pRenderContext->CullMode( MATERIAL_CULLMODE_CCW ); pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false ); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->LoadIdentity( ); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->LoadMatrix( view ); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->LoadMatrix( projection ); int iXOffset, iYOffset; vgui::ipanel()->GetAbsPos( GetVPanel(), iXOffset, iYOffset ); if ( iXOffset > 0 ) iXOffset = 0; if ( iYOffset > 0 ) iYOffset = 0; float flXScale = 1.f; if ( GetWide() > screenW ) flXScale = (float)screenW / GetWide(); float flYScale = 1.f; if ( GetTall() > screenH ) flYScale = (float)screenH / GetTall(); FOR_EACH_VEC( m_vecParticleEffects, i ) { m_vecParticleEffects[i]->Paint( pRenderContext, iXOffset, iYOffset, flXScale, flYScale, screenW, screenH ); } pRenderContext->CullMode( MATERIAL_CULLMODE_CW ); vgui::MatSystemSurface()->End3DPaint(); } bool CTFParticlePanel::ParticleEffect_t::Update( float flTime ) { if ( !m_pParticleSystem || !m_bStarted ) return false; if ( m_flLastTime == FLT_MAX ) { m_flLastTime = flTime; } float flDt = flTime - m_flLastTime; m_flLastTime = flTime; Quaternion q; AngleQuaternion( m_Angles, q ); for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i ) { if ( !m_pParticleSystem->ReadsControlPoint( i ) ) continue; m_pParticleSystem->SetControlPoint( i, m_pControlPointValue[i] ); m_pParticleSystem->SetControlPointOrientation( i, q ); m_pParticleSystem->SetControlPointParent( i, i ); } // Restart the particle system if it's finished bool bIsInvalid = !IsValidHierarchy( m_pParticleSystem ); if ( !bIsInvalid ) { m_pParticleSystem->Simulate( flDt, false ); } // Past our end time? bool bEnd = gpGlobals->curtime >= m_flEndTime; if ( m_pParticleSystem->IsFinished() || bIsInvalid || bEnd ) { delete m_pParticleSystem; m_pParticleSystem = NULL; // Loop if we're supposed to if ( m_bLoop && m_ParticleSystemName.Length() && !m_bForceStopped ) { m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( m_ParticleSystemName ); } if ( bIsInvalid && m_pParent ) { m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) ); } m_flLastTime = FLT_MAX; } return m_pParticleSystem != NULL; } //----------------------------------------------------------------------------- // Startup, shutdown particle collection //----------------------------------------------------------------------------- void CTFParticlePanel::ParticleEffect_t::StartupParticleCollection() { if ( m_pParticleSystem && m_pParent ) { vgui::ivgui()->AddTickSignal( m_pParent->GetVPanel(), 0 ); } m_flLastTime = FLT_MAX; m_bStarted = true; } void CTFParticlePanel::ParticleEffect_t::ShutdownParticleCollection() { if ( m_pParticleSystem && m_pParent ) { delete m_pParticleSystem; m_pParticleSystem = NULL; } m_bStarted = false; } //----------------------------------------------------------------------------- // Set the particle system to draw //----------------------------------------------------------------------------- void CTFParticlePanel::ParticleEffect_t::SetParticleSystem( const char* pszParticleSystemName ) { ShutdownParticleCollection(); if( !g_pParticleSystemMgr->IsParticleSystemDefined( pszParticleSystemName ) ) { AssertMsg1( false, "%s is not a valid particle system name", pszParticleSystemName ); return; } m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( pszParticleSystemName ); m_ParticleSystemName = pszParticleSystemName; m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) ); if( m_bStartActivated ) { StartupParticleCollection(); } } void CTFParticlePanel::ParticleEffect_t::Paint( CMatRenderContextPtr& pRenderContext, int iXOffset, int iYOffset, float flXScale, float flYScale, int screenW, int screenH ) { if ( !m_pParticleSystem || !m_bStarted ) return; pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->Ortho( 0, 0, screenW, screenH, -9999, 9999 ); pRenderContext->Translate( flXScale * ( m_nXPos + iXOffset ), screenH - flYScale * ( m_nYPos + iYOffset ), 0.f ); pRenderContext->Scale( m_flScale, m_flScale, m_flScale ); // Render Particles pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity( ); m_pParticleSystem->Render( pRenderContext ); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PopMatrix(); }