#include "pch.h"
//////////////////////////////////////////////////////////////////////////////
//
// Art names
//
//////////////////////////////////////////////////////////////////////////////
#define AWF_EFFECT_AFTERBURNER_SMOKE "f102bmp"
#define AWF_EFFECT_FIRE "firebmp"
#define AWF_EFFECT_SMOKE "f102bmp"
#define AWF_EFFECT_DAMAGE "f102bmp"
#define AWF_EFFECT_SPARK "f17bmp"
//////////////////////////////////////////////////////////////////////////////
//
// Particle Behaviors
//
//////////////////////////////////////////////////////////////////////////////
class SmokeBehavior : public ParticleBehavior
{
private:
TRef<Image> m_pImageSmoke;
public:
//////////////////////////////////////////////////////////////////////
/* void */ SmokeBehavior (Modeler* pModeler)
{
m_pImageSmoke = pModeler->LoadImage (AWF_EFFECT_SMOKE, true);
}
//////////////////////////////////////////////////////////////////////
virtual void Render (Context* pContext, ParticleData* pParticleData, float fElapsedTime)
{
// get the surface we will render from
TRef<Surface> pSmokeSurface = m_pImageSmoke->GetSurface();
// compute the remaining duration of the effect
float fCountdownToFinish = (pParticleData->m_fDuration - fElapsedTime) / pParticleData->m_fDuration;
// the effect is grey, but is linearly fading to 0.0 in all components,
// but green is scaled back a tad to reduce a greenish hue appearing
Color color;
float fColorComponent = 0.25f * fCountdownToFinish;
color.SetRGBA (fColorComponent, fColorComponent * 0.8f, fColorComponent, fColorComponent);
// the effect is growing linearly
float fStartSize = 0.75f;
float fFinalSize = 3.0f;
float fDeltaSize = fFinalSize - fStartSize;
float fSize = pParticleData->m_fSize * (fStartSize + (fDeltaSize * (1.0f - fCountdownToFinish)));
// compute the new location of the effect
Vector vecEffectPosition = pParticleData->m_vecPosition + (pParticleData->m_vecVelocity * fElapsedTime) + (pParticleData->m_vecAcceleration * (fElapsedTime * fElapsedTime * 0.5f));
// draw the decal
pContext->DrawDecal (pSmokeSurface, color, vecEffectPosition, Vector::GetZero(), Vector::GetZero(), fSize, pParticleData->m_fAngle);
}
//////////////////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////////////////////////
class FireBehavior : public ParticleBehavior
{
private:
TRef<Image> m_pImageFire;
public:
//////////////////////////////////////////////////////////////////////
/* void */ FireBehavior (Modeler* pModeler)
{
m_pImageFire = pModeler->LoadImage (AWF_EFFECT_FIRE, true);
}
//////////////////////////////////////////////////////////////////////
virtual void Render (Context* pContext, ParticleData* pParticleData, float fElapsedTime)
{
// get the surface we will render from
TRef<Surface> pFireSurface = m_pImageFire->GetSurface();
// set parameters and compute the durations of the two phases of the effect
float fRampUpFraction = 0.1f;
float fRampUpDuration = pParticleData->m_fDuration * fRampUpFraction;
float fRampDownDuration = pParticleData->m_fDuration - fRampUpDuration;
// compute the remaining duration of the effect, and clamp it at 1.0 if
// the effect is still ramping up
float fCountdownToFinish = (pParticleData->m_fDuration - fElapsedTime) / fRampDownDuration;
if (fCountdownToFinish > 1.0f)
fCountdownToFinish = 1.0f;
// if the effect hasn't finished
Color color;
float fSize;
// Several parts of this effect countdown from 0.5 to 0.0, rather than from
// 1.0 to 0.0, so we precompute that value for efficiency
float fScaledCountdownToFinish = 0.5f * fCountdownToFinish;
// check to see which phase the effect is in
if (fElapsedTime < fRampUpDuration)
{
// The effect is ramping up, so the puff is white, and it grows from half size
// to full size during the ramp up period using an exponential growth
color.SetRGBA (1.0f, fCountdownToFinish, fCountdownToFinish, fScaledCountdownToFinish * fCountdownToFinish);
fSize = pParticleData->m_fSize * (0.5f + (0.5f * powf (fElapsedTime / fRampUpDuration, 0.666f)));
}
else
{
// the effect is ramping down, during which it changes from yellow to red, and
// shrinks to half size using an exponential decay
color.SetRGBA (1.0f, fCountdownToFinish, fCountdownToFinish * 0.25f, fScaledCountdownToFinish * fCountdownToFinish);
fSize = pParticleData->m_fSize * (0.5f + (0.5f * powf (fCountdownToFinish, 0.666f)));
}
// compute the new location of the effect
Vector vecEffectPosition = pParticleData->m_vecPosition + (pParticleData->m_vecVelocity * fElapsedTime) + (pParticleData->m_vecAcceleration * (fElapsedTime * fElapsedTime * 0.5f));
// draw the decal
pContext->DrawDecal (pFireSurface, color, vecEffectPosition, Vector::GetZero(), Vector::GetZero(), fSize, pParticleData->m_fAngle);
}
//////////////////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////////////////////////
class DamageBehavior : public ParticleBehavior
{
private:
TRef<Image> m_pImageDamage;
public:
//////////////////////////////////////////////////////////////////////
/* void */ DamageBehavior (Modeler* pModeler)
{
m_pImageDamage = pModeler->LoadImage (AWF_EFFECT_DAMAGE, true);
}
//////////////////////////////////////////////////////////////////////
virtual void Render (Context* pContext, ParticleData* pParticleData, float fElapsedTime)
{
// we use smoke behavior instead of this
}
//////////////////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////////////////////////
class AfterburnerBehavior : public ParticleBehavior
{
private:
TRef<Image> m_pImageSmoke;
public:
//////////////////////////////////////////////////////////////////////
/* void */ AfterburnerBehavior (Modeler* pModeler)
{
m_pImageSmoke = pModeler->LoadImage (AWF_EFFECT_AFTERBURNER_SMOKE, true);
}
//////////////////////////////////////////////////////////////////////
virtual void Render (Context* pContext, ParticleData* pParticleData, float fElapsedTime)
{
// get the surface we will render from
TRef<Surface> pSmokeSurface = m_pImageSmoke->GetSurface();
// set parameters and compute the durations of the two phases of the effect
float fRampUpFraction = 0.1f;
float fRampUpDuration = pParticleData->m_fDuration * fRampUpFraction;
float fRampDownDuration = pParticleData->m_fDuration - fRampUpDuration;
// compute the remaining duration of the effect, and clamp it at 1.0 if
// the effect is still ramping up
float fCountdownToFinish = (pParticleData->m_fDuration - fElapsedTime) / fRampDownDuration;
if (fCountdownToFinish > 1.0f)
fCountdownToFinish = 1.0f;
// the effect starts yellow and turns grey, but is linearly fading to 0.0 in all components,
Color color;
float fColorComponent = 0.25f * fCountdownToFinish;
if (fElapsedTime < fRampUpDuration)
color.SetRGBA (fColorComponent * 4.0f, fColorComponent * 3.0f, fColorComponent, fColorComponent);
else
// green is scaled back a tad to reduce a greenish hue appearing
color.SetRGBA (fColorComponent, fColorComponent * 0.8f, fColorComponent, fColorComponent);
// the effect is growing linearly
float fStartSize = 1.0f;
float fFinalSize = 2.0f;
float fDeltaSize = fFinalSize - fStartSize;
float fSize = pParticleData->m_fSize * (fStartSize + (fDeltaSize * (1.0f - fCountdownToFinish)));
// compute the new location of the effect
Vector vecEffectPosition = pParticleData->m_vecPosition + (pParticleData->m_vecVelocity * fElapsedTime) + (pParticleData->m_vecAcceleration * (fElapsedTime * fElapsedTime * 0.5f));
// draw the decal
pContext->DrawDecal (pSmokeSurface, color, vecEffectPosition, Vector::GetZero(), Vector::GetZero(), fSize, pParticleData->m_fAngle);
}
//////////////////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////////////////////////
class SparkBehavior : public ParticleBehavior
{
private:
TRef<Image> m_pImageSpark;
public:
//////////////////////////////////////////////////////////////////////
/* void */ SparkBehavior (Modeler* pModeler)
{
m_pImageSpark = pModeler->LoadImage (AWF_EFFECT_SPARK, true);
}
//////////////////////////////////////////////////////////////////////
virtual void Render (Context* pContext, ParticleData* pParticleData, float fElapsedTime)
{
// get the surface we will render from
TRef<Surface> pSparkSurface = m_pImageSpark->GetSurface();
// draw multiple copies of the effect to produce a blur effect
for (int i = 0; i < 1; i++)
{
// compute the remaining duration of the effect
float fCountdownToFinish = (pParticleData->m_fDuration - fElapsedTime) / pParticleData->m_fDuration;
// the effect is hot blue, fading out
Color color;
float fColorComponent = 0.5f * fCountdownToFinish * fCountdownToFinish;
color.SetRGBA (fColorComponent * 1.5f, fColorComponent * 2.0f, fCountdownToFinish, fColorComponent);
// compute the new location of the effect
Vector vecEffectPosition = pParticleData->m_vecPosition + (pParticleData->m_vecVelocity * fElapsedTime) + (pParticleData->m_vecAcceleration * (fElapsedTime * fElapsedTime * 0.5f));
// draw the decal
pContext->DrawDecal (pSparkSurface, color, vecEffectPosition, Vector::GetZero(), Vector::GetZero(), pParticleData->m_fSize, pParticleData->m_fAngle + (fCountdownToFinish * pi));
// scale the elapsed time to produce a blur effect
fElapsedTime *= 0.97f;
}
}
//////////////////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////////////////////////
//
// ParticleData
//
//////////////////////////////////////////////////////////////////////////////
/* void */ ParticleData::ParticleData (void)
{
m_pParticleBehavior = NULL;
}
//////////////////////////////////////////////////////////////////////////////
void ParticleData::InitParticle (float fTime, ParticleBehavior* pParticleBehavior)
{
m_fStartTime = fTime;
m_fAngle = random (0.0f, 2.0f * pi);
m_pParticleBehavior = pParticleBehavior;
}
//////////////////////////////////////////////////////////////////////////////
//
// ParticleGeo
//
//////////////////////////////////////////////////////////////////////////////
#define PARTICLE_BUFFER_SIZE 256
//
// the smoke quality number is used as a multiplier for how many particles
// we will be generating in toto
#define TOTAL_PARTICLE_BUFFER_SIZE PARTICLE_BUFFER_SIZE * 5
class ParticleGeoImpl : public ParticleGeo
{
private:
// storage of the particles in a rolling buffer (eliminates oldest first when overflow occurs)
ParticleData m_dataParticle[TOTAL_PARTICLE_BUFFER_SIZE];
int m_iIndexCurrentParticle;
// behaviors
SmokeBehavior m_smokeBehavior;
FireBehavior m_fireBehavior;
DamageBehavior m_damageBehavior;
AfterburnerBehavior m_afterburnerBehavior;
SparkBehavior m_sparkBehavior;
// the current time
float m_fTime;
Number* GetTime() { return Number::Cast (GetChild (0)); }
public:
//////////////////////////////////////////////////////////////////////
ParticleGeoImpl(Modeler* pModeler, Number* pTime) : ParticleGeo (pTime),
m_iIndexCurrentParticle (0),
m_smokeBehavior (pModeler),
m_fireBehavior (pModeler),
m_damageBehavior (pModeler),
m_afterburnerBehavior (pModeler),
m_sparkBehavior (pModeler),
m_fTime (pTime->GetValue ())
{
}
//////////////////////////////////////////////////////////////////////
ParticleData* GetNextParticle (ParticleBehavior* pParticleBehavior)
{
// compute how much of the buffer we are going to use
int iParticleBufferSize = PARTICLE_BUFFER_SIZE * ThingGeo::GetShowSmoke ();
assert (iParticleBufferSize <= TOTAL_PARTICLE_BUFFER_SIZE);
// increment to the next buffer entry
m_iIndexCurrentParticle = (m_iIndexCurrentParticle + 1) % iParticleBufferSize;
// initialize the new particle entry
m_dataParticle[m_iIndexCurrentParticle].InitParticle (m_fTime, pParticleBehavior);
// return a pointer to it
return &m_dataParticle[m_iIndexCurrentParticle];
}
//////////////////////////////////////////////////////////////////////
ParticleData* AddAfterburner (void)
{
return GetNextParticle (&m_afterburnerBehavior);
}
//////////////////////////////////////////////////////////////////////
ParticleData* AddDamage (void)
{
return GetNextParticle (&m_damageBehavior);
}
//////////////////////////////////////////////////////////////////////
ParticleData* AddSmoke (void)
{
return GetNextParticle (&m_smokeBehavior);
}
//////////////////////////////////////////////////////////////////////
ParticleData* AddFire (void)
{
return GetNextParticle (&m_fireBehavior);
}
//////////////////////////////////////////////////////////////////////
ParticleData* AddSpark (void)
{
return GetNextParticle (&m_sparkBehavior);
}
//////////////////////////////////////////////////////////////////////
void Evaluate (void)
{
m_fTime = GetTime ()->GetValue ();
}
//////////////////////////////////////////////////////////////////////
ZString GetFunctionName (void)
{
return "ParticleGeo";
}
//////////////////////////////////////////////////////////////////////
void Render(Context* pContext)
{
// compute how much of the buffer we are going to use
int iParticleBufferSize = PARTICLE_BUFFER_SIZE * ThingGeo::GetShowSmoke ();
// loop over all of the particles
for(int index = 0; index < iParticleBufferSize; ++index)
{
// some particles have shorter lives than others, so we check for an easy out
if (m_dataParticle[index].m_pParticleBehavior)
{
// compute the elapsed time of this particle
float fElapsedTime = m_fTime - m_dataParticle[index].m_fStartTime;
// check the elapsed time against the duration of the particle
if (fElapsedTime <= m_dataParticle[index].m_fDuration)
{
// the particle is still alive, so render it
m_dataParticle[index].m_pParticleBehavior->Render (pContext, &m_dataParticle[index], fElapsedTime);
}
else
{
// the particle is expired, so clear the behavior pointer
// so we don't do extra work next time around
m_dataParticle[index].m_pParticleBehavior = NULL;
}
}
}
}
//////////////////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////////////////////////
TRef<ParticleGeo> CreateParticleGeo (Modeler* pModeler, Number* pTime)
{
return new ParticleGeoImpl (pModeler, pTime);
}
//////////////////////////////////////////////////////////////////////////////