//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "particle_prototype.h" #include "particle_util.h" #include "baseparticleentity.h" #include "engine/IEngineTrace.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // ------------------------------------------------------------------------- // // Defines. // ------------------------------------------------------------------------- // #define MAX_FIRE_EMITTERS 128 #define FIRE_PARTICLE_LIFETIME 2 Vector g_FireSpreadDirection(0,0,1); class FireRamp { public: FireRamp(const Vector &s, const Vector &e) { m_Start=s; m_End=e; } Vector m_Start; Vector m_End; }; FireRamp g_FireRamps[] = { FireRamp(Vector(1,0,0), Vector(1,1,0)), FireRamp(Vector(0.5,0.5,0), Vector(0,0,0)) }; #define NUM_FIRE_RAMPS (sizeof(g_FireRamps) / sizeof(g_FireRamps[0])) #define NUM_FIREGRID_OFFSETS 8 Vector g_Offsets[NUM_FIREGRID_OFFSETS] = { Vector(-1,-1,-1), Vector( 1,-1,-1), Vector(-1, 1,-1), Vector( 1, 1,-1), Vector(-1,-1, 1), Vector( 1,-1, 1), Vector(-1, 1, 1), Vector( 1, 1, 1), }; // If you follow g_Offset[index], you can follow g_Offsets[GetOppositeOffset(index)] to get back. inline int GetOppositeOffset(int offset) { return NUM_FIREGRID_OFFSETS - offset - 1; } // ------------------------------------------------------------------------- // // Classes. // ------------------------------------------------------------------------- // class C_ParticleFire : public C_BaseParticleEntity, public IPrototypeAppEffect { public: DECLARE_CLASS( C_ParticleFire, C_BaseParticleEntity ); DECLARE_CLIENTCLASS(); C_ParticleFire(); ~C_ParticleFire(); class FireEmitter { public: Vector m_Pos; TimedEvent m_SpawnEvent; float m_Lifetime; // How long it's been emitting. unsigned char m_DirectionsTested; // 1 bit for each of g_Offsets. }; class FireParticle : public Particle { public: Vector m_StartPos; // The particle moves from m_StartPos to (m_StartPos+m_Direction) over its lifetime. Vector m_Direction; float m_Lifetime; float m_SpinAngle; unsigned char m_iRamp; // Which fire ramp are we using? }; // C_BaseEntity. public: virtual void OnDataChanged( DataUpdateType_t updateType ); // IPrototypeAppEffect. public: virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); // IParticleEffect. public: virtual void Update(float fTimeDelta); virtual void RenderParticles( CParticleRenderIterator *pIterator ); virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); public: CParticleMgr *m_pParticleMgr; PMaterialHandle m_MaterialHandle; // Controls where the initial fire goes. Vector m_vOrigin; Vector m_vDirection; TimedEvent m_EmitterSpawn; FireEmitter m_Emitters[MAX_FIRE_EMITTERS]; int m_nEmitters; private: C_ParticleFire( const C_ParticleFire & ); }; // ------------------------------------------------------------------------- // // Tables. // ------------------------------------------------------------------------- // // Expose to the particle app. EXPOSE_PROTOTYPE_EFFECT(ParticleFire, C_ParticleFire); // Datatable.. IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_ParticleFire, DT_ParticleFire, CParticleFire) RecvPropVector(RECVINFO(m_vOrigin)), RecvPropVector(RECVINFO(m_vDirection)), END_RECV_TABLE() // ------------------------------------------------------------------------- // // C_FireSmoke implementation. // ------------------------------------------------------------------------- // C_ParticleFire::C_ParticleFire() { m_pParticleMgr = NULL; m_MaterialHandle = INVALID_MATERIAL_HANDLE; } C_ParticleFire::~C_ParticleFire() { if(m_pParticleMgr) m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); } void C_ParticleFire::OnDataChanged(DataUpdateType_t updateType) { C_BaseEntity::OnDataChanged(updateType); if(updateType == DATA_UPDATE_CREATED) { Start(ParticleMgr(), NULL); } } void C_ParticleFire::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) { m_pParticleMgr = pParticleMgr; m_pParticleMgr->AddEffect( &m_ParticleEffect, this ); m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial("particle/particle_fire"); // Start m_nEmitters = 0; m_EmitterSpawn.Init(15); } static float fireSpreadDist = 15; static float size = 20; void C_ParticleFire::Update(float fTimeDelta) { if(!m_pParticleMgr) { assert(false); return; } // Add new emitters. if(m_nEmitters < MAX_FIRE_EMITTERS) { float tempDelta = fTimeDelta; while(m_EmitterSpawn.NextEvent(tempDelta)) { FireEmitter *pEmitter = NULL; if(m_nEmitters == 0) { // Make the first emitter. trace_t trace; UTIL_TraceLine(m_vOrigin, m_vOrigin+m_vDirection*1000, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1) { pEmitter = &m_Emitters[m_nEmitters]; pEmitter->m_Pos = trace.endpos + trace.plane.normal * (size - 1); pEmitter->m_DirectionsTested = 0; } } else { static int nTries = 50; for(int iTry=0; iTry < nTries; iTry++) { FireEmitter *pSourceEmitter = &m_Emitters[rand() % m_nEmitters]; int iOffset = rand() % NUM_FIREGRID_OFFSETS; if(pSourceEmitter->m_DirectionsTested & (1 << iOffset)) continue; // Test the corners of the new cube. If some points are solid and some are not, then // we can put fire here. Vector basePos = pSourceEmitter->m_Pos + g_Offsets[iOffset] * fireSpreadDist; int nSolidCorners = 0; for(int iCorner=0; iCorner < NUM_FIREGRID_OFFSETS; iCorner++) { Vector vCorner = basePos + g_Offsets[iCorner]*fireSpreadDist; if ( enginetrace->GetPointContents(vCorner) & CONTENTS_SOLID ) ++nSolidCorners; } // Don't test this square again. pSourceEmitter->m_DirectionsTested |= 1 << iOffset; if(nSolidCorners != 0 && nSolidCorners != NUM_FIREGRID_OFFSETS) { pEmitter = &m_Emitters[m_nEmitters]; pEmitter->m_Pos = basePos; pEmitter->m_DirectionsTested = 1 << GetOppositeOffset(iOffset); } } } if(pEmitter) { pEmitter->m_Lifetime = 0; pEmitter->m_SpawnEvent.Init(1); ++m_nEmitters; } } } // Spawn particles out of the emitters. for(int i=0; i < m_nEmitters; i++) { FireEmitter *pEmitter = &m_Emitters[i]; float tempDelta = fTimeDelta; while(pEmitter->m_SpawnEvent.NextEvent(tempDelta)) { FireParticle *pParticle = (FireParticle*)m_ParticleEffect.AddParticle(sizeof(FireParticle), m_MaterialHandle); if(pParticle) { static float particleSpeed = 15; pParticle->m_StartPos = pEmitter->m_Pos; pParticle->m_Direction = g_FireSpreadDirection * particleSpeed + RandomVector(0, particleSpeed*0.5); pParticle->m_iRamp = rand() % NUM_FIRE_RAMPS; pParticle->m_Lifetime = 0; } } } } void C_ParticleFire::RenderParticles( CParticleRenderIterator *pIterator ) { const FireParticle *pParticle = (const FireParticle*)pIterator->GetFirst(); while ( pParticle ) { float smooth01 = 1 - (cos(pParticle->m_Lifetime * 3.14159 / FIRE_PARTICLE_LIFETIME) * 0.5 + 0.5); float smooth00 = 1 - (cos(pParticle->m_Lifetime * 3.14159 * 2 / FIRE_PARTICLE_LIFETIME) * 0.5 + 0.5); FireRamp *pRamp = &g_FireRamps[pParticle->m_iRamp]; Vector curColor = pRamp->m_Start + (pRamp->m_End - pRamp->m_Start) * smooth01; // Render. Vector tPos; TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos); float sortKey = (int)tPos.z; RenderParticle_ColorSize( pIterator->GetParticleDraw(), tPos, curColor, smooth00, size); pParticle = (const FireParticle*)pIterator->GetNext( sortKey ); } } void C_ParticleFire::SimulateParticles( CParticleSimulateIterator *pIterator ) { FireParticle *pParticle = (FireParticle*)pIterator->GetFirst(); while ( pParticle ) { // Should this particle die? pParticle->m_Lifetime += pIterator->GetTimeDelta(); if(pParticle->m_Lifetime > FIRE_PARTICLE_LIFETIME) { pIterator->RemoveParticle( pParticle ); } else { float smooth01 = 1 - (cos(pParticle->m_Lifetime * 3.14159 / FIRE_PARTICLE_LIFETIME) * 0.5 + 0.5); pParticle->m_Pos = pParticle->m_StartPos + pParticle->m_Direction * smooth01; } pParticle = (FireParticle*)pIterator->GetNext(); } }