forked from 0ad/0ad
596 lines
18 KiB
C++
596 lines
18 KiB
C++
/* Copyright (C) 2011 Wildfire Games.
|
|
* This file is part of 0 A.D.
|
|
*
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "ParticleEmitterType.h"
|
|
|
|
#include "graphics/Color.h"
|
|
#include "graphics/ParticleEmitter.h"
|
|
#include "graphics/ParticleManager.h"
|
|
#include "graphics/TextureManager.h"
|
|
#include "lib/rand.h"
|
|
#include "maths/MathUtil.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/Filesystem.h"
|
|
#include "ps/XML/Xeromyces.h"
|
|
#include "renderer/Renderer.h"
|
|
|
|
#include <boost/random/uniform_real.hpp>
|
|
|
|
|
|
/**
|
|
* Interface for particle state variables, which get evaluated for each newly
|
|
* constructed particle.
|
|
*/
|
|
class IParticleVar
|
|
{
|
|
public:
|
|
IParticleVar() : m_LastValue(0) { }
|
|
virtual ~IParticleVar() {}
|
|
|
|
/// Computes and returns a new value.
|
|
float Evaluate(CParticleEmitter& emitter)
|
|
{
|
|
m_LastValue = Compute(*emitter.m_Type, emitter);
|
|
return m_LastValue;
|
|
}
|
|
|
|
/**
|
|
* Returns the last value that Evaluate returned.
|
|
* This is used for variables that depend on other variables (which is kind
|
|
* of fragile since it's very order-dependent), so they don't get re-randomised
|
|
* and don't have a danger of infinite recursion.
|
|
*/
|
|
float LastValue() { return m_LastValue; }
|
|
|
|
/**
|
|
* Returns the minimum value that Evaluate might ever return,
|
|
* for computing bounds.
|
|
*/
|
|
virtual float Min(CParticleEmitterType& type) = 0;
|
|
|
|
/**
|
|
* Returns the maximum value that Evaluate might ever return,
|
|
* for computing bounds.
|
|
*/
|
|
virtual float Max(CParticleEmitterType& type) = 0;
|
|
|
|
protected:
|
|
virtual float Compute(CParticleEmitterType& type, CParticleEmitter& emitter) = 0;
|
|
|
|
private:
|
|
float m_LastValue;
|
|
};
|
|
|
|
/**
|
|
* Particle variable that returns a constant value.
|
|
*/
|
|
class CParticleVarConstant : public IParticleVar
|
|
{
|
|
public:
|
|
CParticleVarConstant(float val) :
|
|
m_Value(val)
|
|
{
|
|
}
|
|
|
|
virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& UNUSED(emitter))
|
|
{
|
|
return m_Value;
|
|
}
|
|
|
|
virtual float Min(CParticleEmitterType& UNUSED(type))
|
|
{
|
|
return m_Value;
|
|
}
|
|
|
|
virtual float Max(CParticleEmitterType& UNUSED(type))
|
|
{
|
|
return m_Value;
|
|
}
|
|
|
|
private:
|
|
float m_Value;
|
|
};
|
|
|
|
/**
|
|
* Particle variable that returns a uniformly-distributed random value.
|
|
*/
|
|
class CParticleVarUniform : public IParticleVar
|
|
{
|
|
public:
|
|
CParticleVarUniform(float min, float max) :
|
|
m_Min(min), m_Max(max)
|
|
{
|
|
}
|
|
|
|
virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter))
|
|
{
|
|
return boost::uniform_real<>(m_Min, m_Max)(type.m_Manager.m_RNG);
|
|
}
|
|
|
|
virtual float Min(CParticleEmitterType& UNUSED(type))
|
|
{
|
|
return m_Min;
|
|
}
|
|
|
|
virtual float Max(CParticleEmitterType& UNUSED(type))
|
|
{
|
|
return m_Max;
|
|
}
|
|
|
|
private:
|
|
float m_Min;
|
|
float m_Max;
|
|
};
|
|
|
|
/**
|
|
* Particle variable that returns the same value as some other variable
|
|
* (assuming that variable was evaluated before this one).
|
|
*/
|
|
class CParticleVarCopy : public IParticleVar
|
|
{
|
|
public:
|
|
CParticleVarCopy(int from) :
|
|
m_From(from)
|
|
{
|
|
}
|
|
|
|
virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter))
|
|
{
|
|
return type.m_Variables[m_From]->LastValue();
|
|
}
|
|
|
|
virtual float Min(CParticleEmitterType& type)
|
|
{
|
|
return type.m_Variables[m_From]->Min(type);
|
|
}
|
|
|
|
virtual float Max(CParticleEmitterType& type)
|
|
{
|
|
return type.m_Variables[m_From]->Max(type);
|
|
}
|
|
|
|
private:
|
|
int m_From;
|
|
};
|
|
|
|
/**
|
|
* A terrible ad-hoc attempt at handling some particular variable calculation,
|
|
* which really needs to be cleaned up and generalised.
|
|
*/
|
|
class CParticleVarExpr : public IParticleVar
|
|
{
|
|
public:
|
|
CParticleVarExpr(const CStr& from, float mul, float max) :
|
|
m_From(from), m_Mul(mul), m_Max(max)
|
|
{
|
|
}
|
|
|
|
virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& emitter)
|
|
{
|
|
return std::min(m_Max, emitter.m_EntityVariables[m_From] * m_Mul);
|
|
}
|
|
|
|
virtual float Min(CParticleEmitterType& UNUSED(type))
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
virtual float Max(CParticleEmitterType& UNUSED(type))
|
|
{
|
|
return m_Max;
|
|
}
|
|
|
|
private:
|
|
CStr m_From;
|
|
float m_Mul;
|
|
float m_Max;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Interface for particle effectors, which get evaluated every frame to
|
|
* update particles.
|
|
*/
|
|
class IParticleEffector
|
|
{
|
|
public:
|
|
IParticleEffector() { }
|
|
virtual ~IParticleEffector() {}
|
|
|
|
/// Updates all particles.
|
|
virtual void Evaluate(std::vector<SParticle>& particles, float dt) = 0;
|
|
|
|
/// Returns maximum acceleration caused by this effector.
|
|
virtual CVector3D Max() = 0;
|
|
|
|
};
|
|
|
|
/**
|
|
* Particle effector that applies a constant acceleration.
|
|
*/
|
|
class CParticleEffectorForce : public IParticleEffector
|
|
{
|
|
public:
|
|
CParticleEffectorForce(float x, float y, float z) :
|
|
m_Accel(x, y, z)
|
|
{
|
|
}
|
|
|
|
virtual void Evaluate(std::vector<SParticle>& particles, float dt)
|
|
{
|
|
CVector3D dv = m_Accel * dt;
|
|
|
|
for (size_t i = 0; i < particles.size(); ++i)
|
|
particles[i].velocity += dv;
|
|
}
|
|
|
|
virtual CVector3D Max()
|
|
{
|
|
return m_Accel;
|
|
}
|
|
|
|
private:
|
|
CVector3D m_Accel;
|
|
};
|
|
|
|
|
|
|
|
|
|
CParticleEmitterType::CParticleEmitterType(const VfsPath& path, CParticleManager& manager) :
|
|
m_Manager(manager)
|
|
{
|
|
LoadXML(path);
|
|
// TODO: handle load failure
|
|
|
|
// Upper bound on number of particles depends on maximum rate and lifetime
|
|
m_MaxLifetime = m_Variables[VAR_LIFETIME]->Max(*this);
|
|
m_MaxParticles = ceil(m_Variables[VAR_EMISSIONRATE]->Max(*this) * m_MaxLifetime);
|
|
|
|
|
|
// Compute the worst-case bounds of all possible particles,
|
|
// based on the min/max values of positions and velocities and accelerations
|
|
// and sizes. (This isn't a guaranteed bound but it should be sufficient for
|
|
// culling.)
|
|
|
|
// Assuming constant acceleration,
|
|
// p = p0 + v0*t + 1/2 a*t^2
|
|
// => dp/dt = v0 + a*t
|
|
// = 0 at t = -v0/a
|
|
// max(p) is at t=0, or t=tmax, or t=-v0/a if that's between 0 and tmax
|
|
// => max(p) = max(p0, p0 + v0*tmax + 1/2 a*tmax, p0 - 1/2 v0^2/a)
|
|
|
|
// Compute combined acceleration (assume constant)
|
|
CVector3D accel;
|
|
for (size_t i = 0; i < m_Effectors.size(); ++i)
|
|
accel += m_Effectors[i]->Max();
|
|
|
|
CVector3D vmin(m_Variables[VAR_VELOCITY_X]->Min(*this), m_Variables[VAR_VELOCITY_Y]->Min(*this), m_Variables[VAR_VELOCITY_Z]->Min(*this));
|
|
CVector3D vmax(m_Variables[VAR_VELOCITY_X]->Max(*this), m_Variables[VAR_VELOCITY_Y]->Max(*this), m_Variables[VAR_VELOCITY_Z]->Max(*this));
|
|
|
|
// Start by assuming p0 = 0; compute each XYZ component individually
|
|
m_MaxBounds.SetEmpty();
|
|
// Lower/upper bounds at t=0, t=tmax
|
|
m_MaxBounds[0].X = std::min(0.f, vmin.X*m_MaxLifetime + 0.5f*accel.X*m_MaxLifetime*m_MaxLifetime);
|
|
m_MaxBounds[0].Y = std::min(0.f, vmin.Y*m_MaxLifetime + 0.5f*accel.Y*m_MaxLifetime*m_MaxLifetime);
|
|
m_MaxBounds[0].Z = std::min(0.f, vmin.Z*m_MaxLifetime + 0.5f*accel.Z*m_MaxLifetime*m_MaxLifetime);
|
|
m_MaxBounds[1].X = std::max(0.f, vmax.X*m_MaxLifetime + 0.5f*accel.X*m_MaxLifetime*m_MaxLifetime);
|
|
m_MaxBounds[1].Y = std::max(0.f, vmax.Y*m_MaxLifetime + 0.5f*accel.Y*m_MaxLifetime*m_MaxLifetime);
|
|
m_MaxBounds[1].Z = std::max(0.f, vmax.Z*m_MaxLifetime + 0.5f*accel.Z*m_MaxLifetime*m_MaxLifetime);
|
|
// Extend bounds to include position at t where dp/dt=0, if 0 < t < tmax
|
|
if (accel.X && 0 < -vmin.X/accel.X && -vmin.X/accel.X < m_MaxLifetime)
|
|
m_MaxBounds[0].X = std::min(m_MaxBounds[0].X, -0.5f*vmin.X*vmin.X / accel.X);
|
|
if (accel.Y && 0 < -vmin.Y/accel.Y && -vmin.Y/accel.Y < m_MaxLifetime)
|
|
m_MaxBounds[0].Y = std::min(m_MaxBounds[0].Y, -0.5f*vmin.Y*vmin.Y / accel.Y);
|
|
if (accel.Z && 0 < -vmin.Z/accel.Z && -vmin.Z/accel.Z < m_MaxLifetime)
|
|
m_MaxBounds[0].Z = std::min(m_MaxBounds[0].Z, -0.5f*vmin.Z*vmin.Z / accel.Z);
|
|
if (accel.X && 0 < -vmax.X/accel.X && -vmax.X/accel.X < m_MaxLifetime)
|
|
m_MaxBounds[1].X = std::max(m_MaxBounds[1].X, -0.5f*vmax.X*vmax.X / accel.X);
|
|
if (accel.Y && 0 < -vmax.Y/accel.Y && -vmax.Y/accel.Y < m_MaxLifetime)
|
|
m_MaxBounds[1].Y = std::max(m_MaxBounds[1].Y, -0.5f*vmax.Y*vmax.Y / accel.Y);
|
|
if (accel.Z && 0 < -vmax.Z/accel.Z && -vmax.Z/accel.Z < m_MaxLifetime)
|
|
m_MaxBounds[1].Z = std::max(m_MaxBounds[1].Z, -0.5f*vmax.Z*vmax.Z / accel.Z);
|
|
|
|
// Offset by the initial positions
|
|
m_MaxBounds[0] += CVector3D(m_Variables[VAR_POSITION_X]->Min(*this), m_Variables[VAR_POSITION_Y]->Min(*this), m_Variables[VAR_POSITION_Z]->Min(*this));
|
|
m_MaxBounds[1] += CVector3D(m_Variables[VAR_POSITION_X]->Max(*this), m_Variables[VAR_POSITION_Y]->Max(*this), m_Variables[VAR_POSITION_Z]->Max(*this));
|
|
}
|
|
|
|
int CParticleEmitterType::GetVariableID(const std::string& name)
|
|
{
|
|
if (name == "emissionrate") return VAR_EMISSIONRATE;
|
|
if (name == "lifetime") return VAR_LIFETIME;
|
|
if (name == "position.x") return VAR_POSITION_X;
|
|
if (name == "position.y") return VAR_POSITION_Y;
|
|
if (name == "position.z") return VAR_POSITION_Z;
|
|
if (name == "angle") return VAR_ANGLE;
|
|
if (name == "velocity.x") return VAR_VELOCITY_X;
|
|
if (name == "velocity.y") return VAR_VELOCITY_Y;
|
|
if (name == "velocity.z") return VAR_VELOCITY_Z;
|
|
if (name == "velocity.angle") return VAR_VELOCITY_ANGLE;
|
|
if (name == "size") return VAR_SIZE;
|
|
if (name == "color.r") return VAR_COLOR_R;
|
|
if (name == "color.g") return VAR_COLOR_G;
|
|
if (name == "color.b") return VAR_COLOR_B;
|
|
LOGWARNING(L"Particle sets unknown variable '%hs'", name.c_str());
|
|
return -1;
|
|
}
|
|
|
|
bool CParticleEmitterType::LoadXML(const VfsPath& path)
|
|
{
|
|
// Initialise with sane defaults
|
|
m_Variables.clear();
|
|
m_Variables.resize(VAR__MAX);
|
|
m_Variables[VAR_EMISSIONRATE] = IParticleVarPtr(new CParticleVarConstant(10.f));
|
|
m_Variables[VAR_LIFETIME] = IParticleVarPtr(new CParticleVarConstant(3.f));
|
|
m_Variables[VAR_POSITION_X] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_POSITION_Y] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_POSITION_Z] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_VELOCITY_X] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_VELOCITY_Y] = IParticleVarPtr(new CParticleVarConstant(1.f));
|
|
m_Variables[VAR_VELOCITY_Z] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_VELOCITY_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f));
|
|
m_Variables[VAR_SIZE] = IParticleVarPtr(new CParticleVarConstant(1.f));
|
|
m_Variables[VAR_COLOR_R] = IParticleVarPtr(new CParticleVarConstant(1.f));
|
|
m_Variables[VAR_COLOR_G] = IParticleVarPtr(new CParticleVarConstant(1.f));
|
|
m_Variables[VAR_COLOR_B] = IParticleVarPtr(new CParticleVarConstant(1.f));
|
|
m_BlendEquation = GL_FUNC_ADD;
|
|
m_BlendFuncSrc = GL_SRC_ALPHA;
|
|
m_BlendFuncDst = GL_ONE_MINUS_SRC_ALPHA;
|
|
m_StartFull = false;
|
|
m_Texture = g_Renderer.GetTextureManager().GetErrorTexture();
|
|
|
|
CXeromyces XeroFile;
|
|
PSRETURN ret = XeroFile.Load(g_VFS, path);
|
|
if (ret != PSRETURN_OK)
|
|
return false;
|
|
|
|
// TODO: should do some RNG schema validation
|
|
|
|
// Define all the elements and attributes used in the XML file
|
|
#define EL(x) int el_##x = XeroFile.GetElementID(#x)
|
|
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
|
|
EL(texture);
|
|
EL(blend);
|
|
EL(start_full);
|
|
EL(constant);
|
|
EL(uniform);
|
|
EL(copy);
|
|
EL(expr);
|
|
EL(force);
|
|
AT(mode);
|
|
AT(name);
|
|
AT(value);
|
|
AT(min);
|
|
AT(max);
|
|
AT(mul);
|
|
AT(from);
|
|
AT(x);
|
|
AT(y);
|
|
AT(z);
|
|
#undef AT
|
|
#undef EL
|
|
|
|
XMBElement Root = XeroFile.GetRoot();
|
|
|
|
XERO_ITER_EL(Root, Child)
|
|
{
|
|
if (Child.GetNodeName() == el_texture)
|
|
{
|
|
CTextureProperties textureProps(Child.GetText().FromUTF8());
|
|
textureProps.SetWrap(GL_CLAMP_TO_EDGE);
|
|
m_Texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
|
|
}
|
|
else if (Child.GetNodeName() == el_blend)
|
|
{
|
|
CStr mode = Child.GetAttributes().GetNamedItem(at_mode);
|
|
if (mode == "add")
|
|
{
|
|
m_BlendEquation = GL_FUNC_ADD;
|
|
m_BlendFuncSrc = GL_SRC_ALPHA;
|
|
m_BlendFuncDst = GL_ONE;
|
|
}
|
|
else if (mode == "subtract")
|
|
{
|
|
m_BlendEquation = GL_FUNC_REVERSE_SUBTRACT;
|
|
m_BlendFuncSrc = GL_SRC_ALPHA;
|
|
m_BlendFuncDst = GL_ONE;
|
|
}
|
|
else if (mode == "over")
|
|
{
|
|
m_BlendEquation = GL_FUNC_ADD;
|
|
m_BlendFuncSrc = GL_SRC_ALPHA;
|
|
m_BlendFuncDst = GL_ONE_MINUS_SRC_ALPHA;
|
|
}
|
|
else if (mode == "multiply")
|
|
{
|
|
m_BlendEquation = GL_FUNC_ADD;
|
|
m_BlendFuncSrc = GL_ZERO;
|
|
m_BlendFuncDst = GL_ONE_MINUS_SRC_COLOR;
|
|
}
|
|
}
|
|
else if (Child.GetNodeName() == el_start_full)
|
|
{
|
|
m_StartFull = true;
|
|
}
|
|
else if (Child.GetNodeName() == el_constant)
|
|
{
|
|
int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
|
|
if (id != -1)
|
|
{
|
|
m_Variables[id] = IParticleVarPtr(new CParticleVarConstant(
|
|
Child.GetAttributes().GetNamedItem(at_value).ToFloat()
|
|
));
|
|
}
|
|
}
|
|
else if (Child.GetNodeName() == el_uniform)
|
|
{
|
|
int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
|
|
if (id != -1)
|
|
{
|
|
float min = Child.GetAttributes().GetNamedItem(at_min).ToFloat();
|
|
float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat();
|
|
// To avoid hangs in the RNG, only use it if [min, max) is non-empty
|
|
if (min < max)
|
|
m_Variables[id] = IParticleVarPtr(new CParticleVarUniform(min, max));
|
|
else
|
|
m_Variables[id] = IParticleVarPtr(new CParticleVarConstant(min));
|
|
}
|
|
}
|
|
else if (Child.GetNodeName() == el_copy)
|
|
{
|
|
int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
|
|
int from = GetVariableID(Child.GetAttributes().GetNamedItem(at_from));
|
|
if (id != -1 && from != -1)
|
|
m_Variables[id] = IParticleVarPtr(new CParticleVarCopy(from));
|
|
}
|
|
else if (Child.GetNodeName() == el_expr)
|
|
{
|
|
int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
|
|
CStr from = Child.GetAttributes().GetNamedItem(at_from);
|
|
float mul = Child.GetAttributes().GetNamedItem(at_mul).ToFloat();
|
|
float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat();
|
|
if (id != -1)
|
|
m_Variables[id] = IParticleVarPtr(new CParticleVarExpr(from, mul, max));
|
|
}
|
|
else if (Child.GetNodeName() == el_force)
|
|
{
|
|
float x = Child.GetAttributes().GetNamedItem(at_x).ToFloat();
|
|
float y = Child.GetAttributes().GetNamedItem(at_y).ToFloat();
|
|
float z = Child.GetAttributes().GetNamedItem(at_z).ToFloat();
|
|
m_Effectors.push_back(IParticleEffectorPtr(new CParticleEffectorForce(x, y, z)));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CParticleEmitterType::UpdateEmitter(CParticleEmitter& emitter, float dt)
|
|
{
|
|
// If dt is very large, we should do the update in multiple small
|
|
// steps to prevent all the particles getting clumped together at
|
|
// low framerates
|
|
|
|
const float maxStepLength = 0.2f;
|
|
|
|
// Avoid wasting time by computing periods longer than the lifetime
|
|
// period of the particles
|
|
dt = std::min(dt, m_MaxLifetime);
|
|
|
|
while (dt > maxStepLength)
|
|
{
|
|
UpdateEmitterStep(emitter, maxStepLength);
|
|
dt -= maxStepLength;
|
|
}
|
|
|
|
UpdateEmitterStep(emitter, dt);
|
|
}
|
|
|
|
void CParticleEmitterType::UpdateEmitterStep(CParticleEmitter& emitter, float dt)
|
|
{
|
|
ENSURE(emitter.m_Type.get() == this);
|
|
|
|
if (emitter.m_Active)
|
|
{
|
|
float emissionRate = m_Variables[VAR_EMISSIONRATE]->Evaluate(emitter);
|
|
|
|
// Find how many new particles to spawn, and accumulate any rounding errors
|
|
// (to maintain a constant emission rate even if dt is very small)
|
|
int newParticles = floor(emitter.m_EmissionRoundingError + dt*emissionRate);
|
|
emitter.m_EmissionRoundingError += dt*emissionRate - newParticles;
|
|
|
|
// If dt was very large, there's no point spawning new particles that
|
|
// we'll immediately overwrite, so clamp it
|
|
newParticles = std::min(newParticles, (int)m_MaxParticles);
|
|
|
|
for (int i = 0; i < newParticles; ++i)
|
|
{
|
|
// Compute new particle state based on variables
|
|
SParticle particle;
|
|
|
|
particle.pos.X = m_Variables[VAR_POSITION_X]->Evaluate(emitter);
|
|
particle.pos.Y = m_Variables[VAR_POSITION_Y]->Evaluate(emitter);
|
|
particle.pos.Z = m_Variables[VAR_POSITION_Z]->Evaluate(emitter);
|
|
particle.pos += emitter.m_Pos;
|
|
|
|
particle.velocity.X = m_Variables[VAR_VELOCITY_X]->Evaluate(emitter);
|
|
particle.velocity.Y = m_Variables[VAR_VELOCITY_Y]->Evaluate(emitter);
|
|
particle.velocity.Z = m_Variables[VAR_VELOCITY_Z]->Evaluate(emitter);
|
|
|
|
particle.angle = m_Variables[VAR_ANGLE]->Evaluate(emitter);
|
|
particle.angleSpeed = m_Variables[VAR_VELOCITY_ANGLE]->Evaluate(emitter);
|
|
|
|
particle.size = m_Variables[VAR_SIZE]->Evaluate(emitter);
|
|
|
|
RGBColor color;
|
|
color.X = m_Variables[VAR_COLOR_R]->Evaluate(emitter);
|
|
color.Y = m_Variables[VAR_COLOR_G]->Evaluate(emitter);
|
|
color.Z = m_Variables[VAR_COLOR_B]->Evaluate(emitter);
|
|
particle.color = ConvertRGBColorTo4ub(color);
|
|
|
|
particle.age = 0.f;
|
|
particle.maxAge = m_Variables[VAR_LIFETIME]->Evaluate(emitter);
|
|
|
|
emitter.AddParticle(particle);
|
|
}
|
|
}
|
|
|
|
// Update particle states
|
|
for (size_t i = 0; i < emitter.m_Particles.size(); ++i)
|
|
{
|
|
SParticle& p = emitter.m_Particles[i];
|
|
|
|
// Don't bother updating particles already at the end of their life
|
|
if (p.age > p.maxAge)
|
|
continue;
|
|
|
|
p.pos += p.velocity * dt;
|
|
p.angle += p.angleSpeed * dt;
|
|
p.age += dt;
|
|
|
|
// Make alpha fade in/out nicely
|
|
// TODO: this should probably be done as a variable or something,
|
|
// instead of hardcoding
|
|
float ageFrac = p.age / p.maxAge;
|
|
float a = std::min(1.f-ageFrac, 5.f*ageFrac);
|
|
p.color.A = clamp((int)(a*255.f), 0, 255);
|
|
}
|
|
|
|
for (size_t i = 0; i < m_Effectors.size(); ++i)
|
|
{
|
|
m_Effectors[i]->Evaluate(emitter.m_Particles, dt);
|
|
}
|
|
}
|
|
|
|
CBoundingBoxAligned CParticleEmitterType::CalculateBounds(CVector3D emitterPos, CBoundingBoxAligned emittedBounds)
|
|
{
|
|
CBoundingBoxAligned bounds = m_MaxBounds;
|
|
bounds[0] += emitterPos;
|
|
bounds[1] += emitterPos;
|
|
|
|
bounds += emittedBounds;
|
|
|
|
// The current bounds is for the particles' centers, so expand by
|
|
// sqrt(2) * max_size/2 to ensure any rotated billboards fit in
|
|
bounds.Expand(m_Variables[VAR_SIZE]->Max(*this)/2.f * sqrt(2.f));
|
|
|
|
return bounds;
|
|
}
|