//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Implements a particle system steam jet. // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "particle_prototype.h" #include "baseparticleentity.h" #include "particles_simple.h" #include "filesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef HL2_EPISODIC #define SMOKESTACK_MAX_MATERIALS 8 #else #define SMOKESTACK_MAX_MATERIALS 1 #endif //================================================== // C_SmokeStack //================================================== class C_SmokeStack : public C_BaseParticleEntity, public IPrototypeAppEffect { public: DECLARE_CLIENTCLASS(); DECLARE_CLASS( C_SmokeStack, C_BaseParticleEntity ); C_SmokeStack(); ~C_SmokeStack(); class SmokeStackParticle : public Particle { public: Vector m_Velocity; Vector m_vAccel; float m_Lifetime; float m_flAngle; float m_flRollDelta; float m_flSortPos; }; //C_BaseEntity public: virtual void OnDataChanged( DataUpdateType_t updateType ); virtual void ClientThink(); //IPrototypeAppEffect public: virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); virtual bool GetPropEditInfo(RecvTable **ppTable, void **ppObj); //IParticleEffect public: virtual void Update(float fTimeDelta); virtual void RenderParticles( CParticleRenderIterator *pIterator ); virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); virtual void StartRender( VMatrix &effectMatrix ); private: void QueueLightParametersInRenderer(); //Stuff from the datatable public: CParticleSphereRenderer m_Renderer; float m_SpreadSpeed; float m_Speed; float m_StartSize; float m_EndSize; float m_Rate; float m_JetLength; // Length of the jet. Lifetime is derived from this. int m_bEmit; // Emit particles? float m_flBaseSpread; class CLightInfo { public: Vector m_vPos; Vector m_vColor; float m_flIntensity; }; // Note: there are two ways the directional light can be specified. The default is to use // DirLightColor and a default dirlight source (from above or below). // In this case, m_DirLight.m_vPos and m_DirLight.m_flIntensity are ignored. // // The other is to attach a directional env_particlelight to us. // In this case, m_DirLightSource is ignored and all the m_DirLight parameters are used. CParticleLightInfo m_AmbientLight; CParticleLightInfo m_DirLight; Vector m_vBaseColor; Vector m_vWind; float m_flTwist; int m_iMaterialModel; private: C_SmokeStack( const C_SmokeStack & ); float m_TwistMat[2][2]; int m_bTwist; float m_flAlphaScale; float m_InvLifetime; // Calculated from m_JetLength / m_Speed; CParticleMgr *m_pParticleMgr; PMaterialHandle m_MaterialHandle[SMOKESTACK_MAX_MATERIALS]; TimedEvent m_ParticleSpawn; int m_iMaxFrames; bool m_bInView; float m_flRollSpeed; }; // ------------------------------------------------------------------------- // // Tables. // ------------------------------------------------------------------------- // // Expose to the particle app. EXPOSE_PROTOTYPE_EFFECT(SmokeStack, C_SmokeStack); IMPLEMENT_CLIENTCLASS_DT(C_SmokeStack, DT_SmokeStack, CSmokeStack) RecvPropFloat(RECVINFO(m_SpreadSpeed), 0), RecvPropFloat(RECVINFO(m_Speed), 0), RecvPropFloat(RECVINFO(m_StartSize), 0), RecvPropFloat(RECVINFO(m_EndSize), 0), RecvPropFloat(RECVINFO(m_Rate), 0), RecvPropFloat(RECVINFO(m_JetLength), 0), RecvPropInt(RECVINFO(m_bEmit), 0), RecvPropFloat(RECVINFO(m_flBaseSpread)), RecvPropFloat(RECVINFO(m_flTwist)), RecvPropFloat(RECVINFO(m_flRollSpeed )), RecvPropIntWithMinusOneFlag( RECVINFO( m_iMaterialModel ) ), RecvPropVector( RECVINFO(m_AmbientLight.m_vPos) ), RecvPropVector( RECVINFO(m_AmbientLight.m_vColor) ), RecvPropFloat( RECVINFO(m_AmbientLight.m_flIntensity) ), RecvPropVector( RECVINFO(m_DirLight.m_vPos) ), RecvPropVector( RECVINFO(m_DirLight.m_vColor) ), RecvPropFloat( RECVINFO(m_DirLight.m_flIntensity) ), RecvPropVector(RECVINFO(m_vWind)) END_RECV_TABLE() // ------------------------------------------------------------------------- // // C_SmokeStack implementation. // ------------------------------------------------------------------------- // C_SmokeStack::C_SmokeStack() { m_pParticleMgr = NULL; m_MaterialHandle[0] = INVALID_MATERIAL_HANDLE; m_iMaterialModel = -1; m_SpreadSpeed = 15; m_Speed = 30; m_StartSize = 10; m_EndSize = 15; m_Rate = 80; m_JetLength = 180; m_bEmit = true; m_flBaseSpread = 20; m_bInView = false; // Lighting is (base color) + (ambient / dist^2) + bump(directional / dist^2) // By default, we use bottom-up lighting for the directional. SetRenderColor( 0, 0, 0, 255 ); m_AmbientLight.m_vPos.Init(0,0,-100); m_AmbientLight.m_vColor.Init( 40, 40, 40 ); m_AmbientLight.m_flIntensity = 8000; m_DirLight.m_vColor.Init( 255, 128, 0 ); m_vWind.Init(); m_flTwist = 0; } C_SmokeStack::~C_SmokeStack() { if(m_pParticleMgr) m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); } //----------------------------------------------------------------------------- // Purpose: Called after a data update has occured // Input : bnewentity - //----------------------------------------------------------------------------- void C_SmokeStack::OnDataChanged(DataUpdateType_t updateType) { C_BaseEntity::OnDataChanged(updateType); if(updateType == DATA_UPDATE_CREATED) { Start(ParticleMgr(), NULL); } // Recalulate lifetime in case length or speed changed. m_InvLifetime = m_Speed / m_JetLength; } static ConVar mat_reduceparticles( "mat_reduceparticles", "0" ); //----------------------------------------------------------------------------- // Purpose: Starts the effect // Input : *pParticleMgr - // *pArgs - //----------------------------------------------------------------------------- void C_SmokeStack::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) { pParticleMgr->AddEffect( &m_ParticleEffect, this ); // Figure out the material name. char str[512] = "unset_material"; const model_t *pModel = modelinfo->GetModel( m_iMaterialModel ); if ( pModel ) { Q_strncpy( str, modelinfo->GetModelName( pModel ), sizeof( str ) ); // Get rid of the extension because the material system doesn't want it. char *pExt = Q_stristr( str, ".vmt" ); if ( pExt ) pExt[0] = 0; } m_MaterialHandle[0] = m_ParticleEffect.FindOrAddMaterial( str ); #ifdef HL2_EPISODIC int iCount = 1; char szNames[512]; int iLength = Q_strlen( str ); str[iLength-1] = '\0'; Q_snprintf( szNames, sizeof( szNames ), "%s%d.vmt", str, iCount ); while ( filesystem->FileExists( VarArgs( "materials/%s", szNames ) ) && iCount < SMOKESTACK_MAX_MATERIALS ) { char *pExt = Q_stristr( szNames, ".vmt" ); if ( pExt ) pExt[0] = 0; m_MaterialHandle[iCount] = m_ParticleEffect.FindOrAddMaterial( szNames ); iCount++; } m_iMaxFrames = iCount-1; m_ParticleSpawn.Init( mat_reduceparticles.GetBool() ? m_Rate / 4 : m_Rate ); // Obey mat_reduceparticles in episodic #else m_ParticleSpawn.Init( m_Rate ); #endif m_InvLifetime = m_Speed / m_JetLength; m_pParticleMgr = pParticleMgr; // Figure out how we need to draw. IMaterial *pMaterial = pParticleMgr->PMaterialToIMaterial( m_MaterialHandle[0] ); if( pMaterial ) { m_Renderer.Init( pParticleMgr, pMaterial ); } QueueLightParametersInRenderer(); // For the first N seconds, always simulate so it can build up the smokestack. // Afterwards, we set it to freeze when it's not being rendered. m_ParticleEffect.SetAlwaysSimulate( true ); SetNextClientThink( gpGlobals->curtime + 5 ); } void C_SmokeStack::ClientThink() { m_ParticleEffect.SetAlwaysSimulate( false ); } //----------------------------------------------------------------------------- // Purpose: // Input : **ppTable - // **ppObj - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool C_SmokeStack::GetPropEditInfo( RecvTable **ppTable, void **ppObj ) { *ppTable = &REFERENCE_RECV_TABLE(DT_SmokeStack); *ppObj = this; return true; } //----------------------------------------------------------------------------- // Purpose: // Input : fTimeDelta - //----------------------------------------------------------------------------- void C_SmokeStack::Update(float fTimeDelta) { if( !m_pParticleMgr ) { assert(false); return; } // Don't spawn particles unless we're visible. if( m_bEmit && (m_ParticleEffect.WasDrawnPrevFrame() || m_ParticleEffect.GetAlwaysSimulate()) ) { // Add new particles. Vector forward, right, up; AngleVectors(GetAbsAngles(), &forward, &right, &up); float tempDelta = fTimeDelta; while(m_ParticleSpawn.NextEvent(tempDelta)) { int iRandomFrame = random->RandomInt( 0, m_iMaxFrames ); #ifndef HL2_EPISODIC iRandomFrame = 0; #endif // Make a new particle. if(SmokeStackParticle *pParticle = (SmokeStackParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeStackParticle), m_MaterialHandle[iRandomFrame])) { float angle = FRand( 0, 2.0f*M_PI_F ); pParticle->m_Pos = GetAbsOrigin() + right * (cos( angle ) * m_flBaseSpread) + forward * (sin( angle ) * m_flBaseSpread); pParticle->m_Velocity = FRand(-m_SpreadSpeed,m_SpreadSpeed) * right + FRand(-m_SpreadSpeed,m_SpreadSpeed) * forward + m_Speed * up; pParticle->m_vAccel = m_vWind; pParticle->m_Lifetime = 0; pParticle->m_flAngle = 0.0f; #ifdef HL2_EPISODIC pParticle->m_flAngle = RandomFloat( 0, 360 ); #endif pParticle->m_flRollDelta = random->RandomFloat( -m_flRollSpeed, m_flRollSpeed ); pParticle->m_flSortPos = pParticle->m_Pos.z; } } } // Setup the twist matrix. float flTwist = (m_flTwist * (M_PI_F * 2.f) / 360.0f) * Helper_GetFrameTime(); if( ( m_bTwist = !!flTwist ) ) { m_TwistMat[0][0] = cos(flTwist); m_TwistMat[0][1] = sin(flTwist); m_TwistMat[1][0] = -sin(flTwist); m_TwistMat[1][1] = cos(flTwist); } QueueLightParametersInRenderer(); } void C_SmokeStack::StartRender( VMatrix &effectMatrix ) { m_Renderer.StartRender( effectMatrix ); } void C_SmokeStack::QueueLightParametersInRenderer() { m_Renderer.SetBaseColor( Vector( m_clrRender->r / 255.0f, m_clrRender->g / 255.0f, m_clrRender->b / 255.0f ) ); m_Renderer.SetAmbientLight( m_AmbientLight ); m_Renderer.SetDirectionalLight( m_DirLight ); m_flAlphaScale = (float)m_clrRender->a; } void C_SmokeStack::RenderParticles( CParticleRenderIterator *pIterator ) { const SmokeStackParticle *pParticle = (const SmokeStackParticle*)pIterator->GetFirst(); while ( pParticle ) { // Transform. Vector tPos; TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos ); // Figure out its alpha. Squaring it after it gets halfway through its lifetime // makes it get translucent and fade out for a longer time. //float alpha = cosf( -M_PI_F + tLifetime * M_PI_F * 2.f ) * 0.5f + 0.5f; float tLifetime = pParticle->m_Lifetime * m_InvLifetime; float alpha = TableCos( -M_PI_F + tLifetime * M_PI_F * 2.f ) * 0.5f + 0.5f; if( tLifetime > 0.5f ) alpha *= alpha; m_Renderer.RenderParticle( pIterator->GetParticleDraw(), pParticle->m_Pos, tPos, alpha * m_flAlphaScale, FLerp(m_StartSize, m_EndSize, tLifetime), DEG2RAD( pParticle->m_flAngle ) ); pParticle = (const SmokeStackParticle*)pIterator->GetNext( pParticle->m_flSortPos ); } } void C_SmokeStack::SimulateParticles( CParticleSimulateIterator *pIterator ) { bool bSortNow = true; // Change this to false if we see sorting issues. bool bQuickTest = false; bool bDrawn = m_ParticleEffect.WasDrawnPrevFrame(); if ( bDrawn == true && m_bInView == false ) { bSortNow = true; } if ( bDrawn == false && m_bInView == true ) { bQuickTest = true; } #ifndef HL2_EPISODIC bQuickTest = false; bSortNow = true; #endif if( bQuickTest == false && m_bEmit && (!m_ParticleEffect.WasDrawnPrevFrame() && !m_ParticleEffect.GetAlwaysSimulate()) ) return; SmokeStackParticle *pParticle = (SmokeStackParticle*)pIterator->GetFirst(); while ( pParticle ) { // Should this particle die? pParticle->m_Lifetime += pIterator->GetTimeDelta(); float tLifetime = pParticle->m_Lifetime * m_InvLifetime; if( tLifetime >= 1 ) { pIterator->RemoveParticle( pParticle ); } else { // Transform. if( m_bTwist ) { Vector vTwist( pParticle->m_Pos.x - GetAbsOrigin().x, pParticle->m_Pos.y - GetAbsOrigin().y, 0); pParticle->m_Pos.x = vTwist.x * m_TwistMat[0][0] + vTwist.y * m_TwistMat[0][1] + GetAbsOrigin().x; pParticle->m_Pos.y = vTwist.x * m_TwistMat[1][0] + vTwist.y * m_TwistMat[1][1] + GetAbsOrigin().y; } #ifndef HL2_EPISODIC pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta() + pParticle->m_vAccel * (0.5f * pIterator->GetTimeDelta() * pIterator->GetTimeDelta()); pParticle->m_Velocity += pParticle->m_vAccel * pIterator->GetTimeDelta(); #else pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta() + pParticle->m_vAccel * pIterator->GetTimeDelta(); #endif pParticle->m_flAngle += pParticle->m_flRollDelta * pIterator->GetTimeDelta(); if ( bSortNow == true ) { Vector tPos; TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos ); pParticle->m_flSortPos = tPos.z; } } pParticle = (SmokeStackParticle*)pIterator->GetNext(); } m_bInView = bDrawn; }