|
|
//========= Copyright � 1996-2005, 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[ RandomInt( 0, m_nEmitters-1 )]; int iOffset = RandomInt( 0, NUM_FIREGRID_OFFSETS - 1 ); 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 = RandomInt( 0, NUM_FIRE_RAMPS - 1 ); 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(); } }
|