You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
13 KiB
503 lines
13 KiB
//========= 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; |
|
} |
|
|
|
|
|
|