1 /* Copyright (C) 2015 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "ParticleEmitterType.h"
21 
22 #include "graphics/Color.h"
23 #include "graphics/ParticleEmitter.h"
24 #include "graphics/ParticleManager.h"
25 #include "graphics/TextureManager.h"
26 #include "lib/rand.h"
27 #include "maths/MathUtil.h"
28 #include "ps/CLogger.h"
29 #include "ps/Filesystem.h"
30 #include "ps/XML/Xeromyces.h"
31 #include "renderer/Renderer.h"
32 
33 #include <boost/random/uniform_real.hpp>
34 
35 
36 /**
37  * Interface for particle state variables, which get evaluated for each newly
38  * constructed particle.
39  */
40 class IParticleVar
41 {
42 public:
IParticleVar()43 	IParticleVar() : m_LastValue(0) { }
~IParticleVar()44 	virtual ~IParticleVar() {}
45 
46 	/// Computes and returns a new value.
Evaluate(CParticleEmitter & emitter)47 	float Evaluate(CParticleEmitter& emitter)
48 	{
49 		m_LastValue = Compute(*emitter.m_Type, emitter);
50 		return m_LastValue;
51 	}
52 
53 	/**
54 	 * Returns the last value that Evaluate returned.
55 	 * This is used for variables that depend on other variables (which is kind
56 	 * of fragile since it's very order-dependent), so they don't get re-randomised
57 	 * and don't have a danger of infinite recursion.
58 	 */
LastValue()59 	float LastValue() { return m_LastValue; }
60 
61 	/**
62 	 * Returns the minimum value that Evaluate might ever return,
63 	 * for computing bounds.
64 	 */
65 	virtual float Min(CParticleEmitterType& type) = 0;
66 
67 	/**
68 	 * Returns the maximum value that Evaluate might ever return,
69 	 * for computing bounds.
70 	 */
71 	virtual float Max(CParticleEmitterType& type) = 0;
72 
73 protected:
74 	virtual float Compute(CParticleEmitterType& type, CParticleEmitter& emitter) = 0;
75 
76 private:
77 	float m_LastValue;
78 };
79 
80 /**
81  * Particle variable that returns a constant value.
82  */
83 class CParticleVarConstant : public IParticleVar
84 {
85 public:
CParticleVarConstant(float val)86 	CParticleVarConstant(float val) :
87 		m_Value(val)
88 	{
89 	}
90 
Compute(CParticleEmitterType & UNUSED (type),CParticleEmitter & UNUSED (emitter))91 	virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& UNUSED(emitter))
92 	{
93 		return m_Value;
94 	}
95 
Min(CParticleEmitterType & UNUSED (type))96 	virtual float Min(CParticleEmitterType& UNUSED(type))
97 	{
98 		return m_Value;
99 	}
100 
Max(CParticleEmitterType & UNUSED (type))101 	virtual float Max(CParticleEmitterType& UNUSED(type))
102 	{
103 		return m_Value;
104 	}
105 
106 private:
107 	float m_Value;
108 };
109 
110 /**
111  * Particle variable that returns a uniformly-distributed random value.
112  */
113 class CParticleVarUniform : public IParticleVar
114 {
115 public:
CParticleVarUniform(float min,float max)116 	CParticleVarUniform(float min, float max) :
117 		m_Min(min), m_Max(max)
118 	{
119 	}
120 
Compute(CParticleEmitterType & type,CParticleEmitter & UNUSED (emitter))121 	virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter))
122 	{
123 		return boost::uniform_real<>(m_Min, m_Max)(type.m_Manager.m_RNG);
124 	}
125 
Min(CParticleEmitterType & UNUSED (type))126 	virtual float Min(CParticleEmitterType& UNUSED(type))
127 	{
128 		return m_Min;
129 	}
130 
Max(CParticleEmitterType & UNUSED (type))131 	virtual float Max(CParticleEmitterType& UNUSED(type))
132 	{
133 		return m_Max;
134 	}
135 
136 private:
137 	float m_Min;
138 	float m_Max;
139 };
140 
141 /**
142  * Particle variable that returns the same value as some other variable
143  * (assuming that variable was evaluated before this one).
144  */
145 class CParticleVarCopy : public IParticleVar
146 {
147 public:
CParticleVarCopy(int from)148 	CParticleVarCopy(int from) :
149 		m_From(from)
150 	{
151 	}
152 
Compute(CParticleEmitterType & type,CParticleEmitter & UNUSED (emitter))153 	virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter))
154 	{
155 		return type.m_Variables[m_From]->LastValue();
156 	}
157 
Min(CParticleEmitterType & type)158 	virtual float Min(CParticleEmitterType& type)
159 	{
160 		return type.m_Variables[m_From]->Min(type);
161 	}
162 
Max(CParticleEmitterType & type)163 	virtual float Max(CParticleEmitterType& type)
164 	{
165 		return type.m_Variables[m_From]->Max(type);
166 	}
167 
168 private:
169 	int m_From;
170 };
171 
172 /**
173  * A terrible ad-hoc attempt at handling some particular variable calculation,
174  * which really needs to be cleaned up and generalised.
175  */
176 class CParticleVarExpr : public IParticleVar
177 {
178 public:
CParticleVarExpr(const CStr & from,float mul,float max)179 	CParticleVarExpr(const CStr& from, float mul, float max) :
180 		m_From(from), m_Mul(mul), m_Max(max)
181 	{
182 	}
183 
Compute(CParticleEmitterType & UNUSED (type),CParticleEmitter & emitter)184 	virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& emitter)
185 	{
186 		return std::min(m_Max, emitter.m_EntityVariables[m_From] * m_Mul);
187 	}
188 
Min(CParticleEmitterType & UNUSED (type))189 	virtual float Min(CParticleEmitterType& UNUSED(type))
190 	{
191 		return 0.f;
192 	}
193 
Max(CParticleEmitterType & UNUSED (type))194 	virtual float Max(CParticleEmitterType& UNUSED(type))
195 	{
196 		return m_Max;
197 	}
198 
199 private:
200 	CStr m_From;
201 	float m_Mul;
202 	float m_Max;
203 };
204 
205 
206 
207 /**
208  * Interface for particle effectors, which get evaluated every frame to
209  * update particles.
210  */
211 class IParticleEffector
212 {
213 public:
IParticleEffector()214 	IParticleEffector() { }
~IParticleEffector()215 	virtual ~IParticleEffector() {}
216 
217 	/// Updates all particles.
218 	virtual void Evaluate(std::vector<SParticle>& particles, float dt) = 0;
219 
220 	/// Returns maximum acceleration caused by this effector.
221 	virtual CVector3D Max() = 0;
222 
223 };
224 
225 /**
226  * Particle effector that applies a constant acceleration.
227  */
228 class CParticleEffectorForce : public IParticleEffector
229 {
230 public:
CParticleEffectorForce(float x,float y,float z)231 	CParticleEffectorForce(float x, float y, float z) :
232 		m_Accel(x, y, z)
233 	{
234 	}
235 
Evaluate(std::vector<SParticle> & particles,float dt)236 	virtual void Evaluate(std::vector<SParticle>& particles, float dt)
237 	{
238 		CVector3D dv = m_Accel * dt;
239 
240 		for (size_t i = 0; i < particles.size(); ++i)
241 			particles[i].velocity += dv;
242 	}
243 
Max()244 	virtual CVector3D Max()
245 	{
246 		return m_Accel;
247 	}
248 
249 private:
250 	CVector3D m_Accel;
251 };
252 
253 
254 
255 
CParticleEmitterType(const VfsPath & path,CParticleManager & manager)256 CParticleEmitterType::CParticleEmitterType(const VfsPath& path, CParticleManager& manager) :
257 	m_Manager(manager)
258 {
259 	LoadXML(path);
260 	// TODO: handle load failure
261 
262 	// Upper bound on number of particles depends on maximum rate and lifetime
263 	m_MaxLifetime = m_Variables[VAR_LIFETIME]->Max(*this);
264 	m_MaxParticles = ceil(m_Variables[VAR_EMISSIONRATE]->Max(*this) * m_MaxLifetime);
265 
266 
267 	// Compute the worst-case bounds of all possible particles,
268 	// based on the min/max values of positions and velocities and accelerations
269 	// and sizes. (This isn't a guaranteed bound but it should be sufficient for
270 	// culling.)
271 
272 	// Assuming constant acceleration,
273 	//        p = p0 + v0*t + 1/2 a*t^2
274 	// => dp/dt = v0 + a*t
275 	//          = 0 at t = -v0/a
276 	// max(p) is at t=0, or t=tmax, or t=-v0/a if that's between 0 and tmax
277 	// => max(p) = max(p0, p0 + v0*tmax + 1/2 a*tmax, p0 - 1/2 v0^2/a)
278 
279 	// Compute combined acceleration (assume constant)
280 	CVector3D accel;
281 	for (size_t i = 0; i < m_Effectors.size(); ++i)
282 		accel += m_Effectors[i]->Max();
283 
284 	CVector3D vmin(m_Variables[VAR_VELOCITY_X]->Min(*this), m_Variables[VAR_VELOCITY_Y]->Min(*this), m_Variables[VAR_VELOCITY_Z]->Min(*this));
285 	CVector3D vmax(m_Variables[VAR_VELOCITY_X]->Max(*this), m_Variables[VAR_VELOCITY_Y]->Max(*this), m_Variables[VAR_VELOCITY_Z]->Max(*this));
286 
287 	// Start by assuming p0 = 0; compute each XYZ component individually
288 	m_MaxBounds.SetEmpty();
289 	// Lower/upper bounds at t=0, t=tmax
290 	m_MaxBounds[0].X = std::min(0.f, vmin.X*m_MaxLifetime + 0.5f*accel.X*m_MaxLifetime*m_MaxLifetime);
291 	m_MaxBounds[0].Y = std::min(0.f, vmin.Y*m_MaxLifetime + 0.5f*accel.Y*m_MaxLifetime*m_MaxLifetime);
292 	m_MaxBounds[0].Z = std::min(0.f, vmin.Z*m_MaxLifetime + 0.5f*accel.Z*m_MaxLifetime*m_MaxLifetime);
293 	m_MaxBounds[1].X = std::max(0.f, vmax.X*m_MaxLifetime + 0.5f*accel.X*m_MaxLifetime*m_MaxLifetime);
294 	m_MaxBounds[1].Y = std::max(0.f, vmax.Y*m_MaxLifetime + 0.5f*accel.Y*m_MaxLifetime*m_MaxLifetime);
295 	m_MaxBounds[1].Z = std::max(0.f, vmax.Z*m_MaxLifetime + 0.5f*accel.Z*m_MaxLifetime*m_MaxLifetime);
296 	// Extend bounds to include position at t where dp/dt=0, if 0 < t < tmax
297 	if (accel.X && 0 < -vmin.X/accel.X && -vmin.X/accel.X < m_MaxLifetime)
298 		m_MaxBounds[0].X = std::min(m_MaxBounds[0].X, -0.5f*vmin.X*vmin.X / accel.X);
299 	if (accel.Y && 0 < -vmin.Y/accel.Y && -vmin.Y/accel.Y < m_MaxLifetime)
300 		m_MaxBounds[0].Y = std::min(m_MaxBounds[0].Y, -0.5f*vmin.Y*vmin.Y / accel.Y);
301 	if (accel.Z && 0 < -vmin.Z/accel.Z && -vmin.Z/accel.Z < m_MaxLifetime)
302 		m_MaxBounds[0].Z = std::min(m_MaxBounds[0].Z, -0.5f*vmin.Z*vmin.Z / accel.Z);
303 	if (accel.X && 0 < -vmax.X/accel.X && -vmax.X/accel.X < m_MaxLifetime)
304 		m_MaxBounds[1].X = std::max(m_MaxBounds[1].X, -0.5f*vmax.X*vmax.X / accel.X);
305 	if (accel.Y && 0 < -vmax.Y/accel.Y && -vmax.Y/accel.Y < m_MaxLifetime)
306 		m_MaxBounds[1].Y = std::max(m_MaxBounds[1].Y, -0.5f*vmax.Y*vmax.Y / accel.Y);
307 	if (accel.Z && 0 < -vmax.Z/accel.Z && -vmax.Z/accel.Z < m_MaxLifetime)
308 		m_MaxBounds[1].Z = std::max(m_MaxBounds[1].Z, -0.5f*vmax.Z*vmax.Z / accel.Z);
309 
310 	// Offset by the initial positions
311 	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));
312 	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));
313 }
314 
GetVariableID(const std::string & name)315 int CParticleEmitterType::GetVariableID(const std::string& name)
316 {
317 	if (name == "emissionrate")		return VAR_EMISSIONRATE;
318 	if (name == "lifetime")			return VAR_LIFETIME;
319 	if (name == "position.x")		return VAR_POSITION_X;
320 	if (name == "position.y")		return VAR_POSITION_Y;
321 	if (name == "position.z")		return VAR_POSITION_Z;
322 	if (name == "angle")			return VAR_ANGLE;
323 	if (name == "velocity.x")		return VAR_VELOCITY_X;
324 	if (name == "velocity.y")		return VAR_VELOCITY_Y;
325 	if (name == "velocity.z")		return VAR_VELOCITY_Z;
326 	if (name == "velocity.angle")	return VAR_VELOCITY_ANGLE;
327 	if (name == "size")				return VAR_SIZE;
328 	if (name == "size.growthRate")	return VAR_SIZE_GROWTHRATE;
329 	if (name == "color.r")			return VAR_COLOR_R;
330 	if (name == "color.g")			return VAR_COLOR_G;
331 	if (name == "color.b")			return VAR_COLOR_B;
332 	LOGWARNING("Particle sets unknown variable '%s'", name.c_str());
333 	return -1;
334 }
335 
LoadXML(const VfsPath & path)336 bool CParticleEmitterType::LoadXML(const VfsPath& path)
337 {
338 	// Initialise with sane defaults
339 	m_Variables.clear();
340 	m_Variables.resize(VAR__MAX);
341 	m_Variables[VAR_EMISSIONRATE] = IParticleVarPtr(new CParticleVarConstant(10.f));
342 	m_Variables[VAR_LIFETIME] = IParticleVarPtr(new CParticleVarConstant(3.f));
343 	m_Variables[VAR_POSITION_X] = IParticleVarPtr(new CParticleVarConstant(0.f));
344 	m_Variables[VAR_POSITION_Y] = IParticleVarPtr(new CParticleVarConstant(0.f));
345 	m_Variables[VAR_POSITION_Z] = IParticleVarPtr(new CParticleVarConstant(0.f));
346 	m_Variables[VAR_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f));
347 	m_Variables[VAR_VELOCITY_X] = IParticleVarPtr(new CParticleVarConstant(0.f));
348 	m_Variables[VAR_VELOCITY_Y] = IParticleVarPtr(new CParticleVarConstant(1.f));
349 	m_Variables[VAR_VELOCITY_Z] = IParticleVarPtr(new CParticleVarConstant(0.f));
350 	m_Variables[VAR_VELOCITY_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f));
351 	m_Variables[VAR_SIZE] = IParticleVarPtr(new CParticleVarConstant(1.f));
352 	m_Variables[VAR_SIZE_GROWTHRATE] = IParticleVarPtr(new CParticleVarConstant(0.f));
353 	m_Variables[VAR_COLOR_R] = IParticleVarPtr(new CParticleVarConstant(1.f));
354 	m_Variables[VAR_COLOR_G] = IParticleVarPtr(new CParticleVarConstant(1.f));
355 	m_Variables[VAR_COLOR_B] = IParticleVarPtr(new CParticleVarConstant(1.f));
356 	m_BlendEquation = GL_FUNC_ADD;
357 	m_BlendFuncSrc = GL_SRC_ALPHA;
358 	m_BlendFuncDst = GL_ONE_MINUS_SRC_ALPHA;
359 	m_StartFull = false;
360 	m_UseRelativeVelocity = false;
361 	m_Texture = g_Renderer.GetTextureManager().GetErrorTexture();
362 
363 	CXeromyces XeroFile;
364 	PSRETURN ret = XeroFile.Load(g_VFS, path, "particle");
365 	if (ret != PSRETURN_OK)
366 		return false;
367 
368 	// Define all the elements and attributes used in the XML file
369 #define EL(x) int el_##x = XeroFile.GetElementID(#x)
370 #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
371 	EL(texture);
372 	EL(blend);
373 	EL(start_full);
374 	EL(use_relative_velocity);
375 	EL(constant);
376 	EL(uniform);
377 	EL(copy);
378 	EL(expr);
379 	EL(force);
380 	AT(mode);
381 	AT(name);
382 	AT(value);
383 	AT(min);
384 	AT(max);
385 	AT(mul);
386 	AT(from);
387 	AT(x);
388 	AT(y);
389 	AT(z);
390 #undef AT
391 #undef EL
392 
393 	XMBElement Root = XeroFile.GetRoot();
394 
395 	XERO_ITER_EL(Root, Child)
396 	{
397 		if (Child.GetNodeName() == el_texture)
398 		{
399 			CTextureProperties textureProps(Child.GetText().FromUTF8());
400 			textureProps.SetWrap(GL_CLAMP_TO_EDGE);
401 			m_Texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
402 		}
403 		else if (Child.GetNodeName() == el_blend)
404 		{
405 			CStr mode = Child.GetAttributes().GetNamedItem(at_mode);
406 			if (mode == "add")
407 			{
408 				m_BlendEquation = GL_FUNC_ADD;
409 				m_BlendFuncSrc = GL_SRC_ALPHA;
410 				m_BlendFuncDst = GL_ONE;
411 			}
412 			else if (mode == "subtract")
413 			{
414 				m_BlendEquation = GL_FUNC_REVERSE_SUBTRACT;
415 				m_BlendFuncSrc = GL_SRC_ALPHA;
416 				m_BlendFuncDst = GL_ONE;
417 			}
418 			else if (mode == "over")
419 			{
420 				m_BlendEquation = GL_FUNC_ADD;
421 				m_BlendFuncSrc = GL_SRC_ALPHA;
422 				m_BlendFuncDst = GL_ONE_MINUS_SRC_ALPHA;
423 			}
424 			else if (mode == "multiply")
425 			{
426 				m_BlendEquation = GL_FUNC_ADD;
427 				m_BlendFuncSrc = GL_ZERO;
428 				m_BlendFuncDst = GL_ONE_MINUS_SRC_COLOR;
429 			}
430 		}
431 		else if (Child.GetNodeName() == el_start_full)
432 		{
433 			m_StartFull = true;
434 		}
435 		else if (Child.GetNodeName() == el_use_relative_velocity)
436 		{
437 			m_UseRelativeVelocity = true;
438 		}
439 		else if (Child.GetNodeName() == el_constant)
440 		{
441 			int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
442 			if (id != -1)
443 			{
444 				m_Variables[id] = IParticleVarPtr(new CParticleVarConstant(
445 					Child.GetAttributes().GetNamedItem(at_value).ToFloat()
446 				));
447 			}
448 		}
449 		else if (Child.GetNodeName() == el_uniform)
450 		{
451 			int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
452 			if (id != -1)
453 			{
454 				float min = Child.GetAttributes().GetNamedItem(at_min).ToFloat();
455 				float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat();
456 				// To avoid hangs in the RNG, only use it if [min, max) is non-empty
457 				if (min < max)
458 					m_Variables[id] = IParticleVarPtr(new CParticleVarUniform(min, max));
459 				else
460 					m_Variables[id] = IParticleVarPtr(new CParticleVarConstant(min));
461 			}
462 		}
463 		else if (Child.GetNodeName() == el_copy)
464 		{
465 			int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
466 			int from = GetVariableID(Child.GetAttributes().GetNamedItem(at_from));
467 			if (id != -1 && from != -1)
468 				m_Variables[id] = IParticleVarPtr(new CParticleVarCopy(from));
469 		}
470 		else if (Child.GetNodeName() == el_expr)
471 		{
472 			int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
473 			CStr from = Child.GetAttributes().GetNamedItem(at_from);
474 			float mul = Child.GetAttributes().GetNamedItem(at_mul).ToFloat();
475 			float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat();
476 			if (id != -1)
477 				m_Variables[id] = IParticleVarPtr(new CParticleVarExpr(from, mul, max));
478 		}
479 		else if (Child.GetNodeName() == el_force)
480 		{
481 			float x = Child.GetAttributes().GetNamedItem(at_x).ToFloat();
482 			float y = Child.GetAttributes().GetNamedItem(at_y).ToFloat();
483 			float z = Child.GetAttributes().GetNamedItem(at_z).ToFloat();
484 			m_Effectors.push_back(IParticleEffectorPtr(new CParticleEffectorForce(x, y, z)));
485 		}
486 	}
487 
488 	return true;
489 }
490 
UpdateEmitter(CParticleEmitter & emitter,float dt)491 void CParticleEmitterType::UpdateEmitter(CParticleEmitter& emitter, float dt)
492 {
493 	// If dt is very large, we should do the update in multiple small
494 	// steps to prevent all the particles getting clumped together at
495 	// low framerates
496 
497 	const float maxStepLength = 0.2f;
498 
499 	// Avoid wasting time by computing periods longer than the lifetime
500 	// period of the particles
501 	dt = std::min(dt, m_MaxLifetime);
502 
503 	while (dt > maxStepLength)
504 	{
505 		UpdateEmitterStep(emitter, maxStepLength);
506 		dt -= maxStepLength;
507 	}
508 
509 	UpdateEmitterStep(emitter, dt);
510 }
511 
UpdateEmitterStep(CParticleEmitter & emitter,float dt)512 void CParticleEmitterType::UpdateEmitterStep(CParticleEmitter& emitter, float dt)
513 {
514 	ENSURE(emitter.m_Type.get() == this);
515 
516 	if (emitter.m_Active)
517 	{
518 		float emissionRate = m_Variables[VAR_EMISSIONRATE]->Evaluate(emitter);
519 
520 		// Find how many new particles to spawn, and accumulate any rounding errors
521 		// (to maintain a constant emission rate even if dt is very small)
522 		int newParticles = floor(emitter.m_EmissionRoundingError + dt*emissionRate);
523 		emitter.m_EmissionRoundingError += dt*emissionRate - newParticles;
524 
525 		// If dt was very large, there's no point spawning new particles that
526 		// we'll immediately overwrite, so clamp it
527 		newParticles = std::min(newParticles, (int)m_MaxParticles);
528 
529 		for (int i = 0; i < newParticles; ++i)
530 		{
531 			// Compute new particle state based on variables
532 			SParticle particle;
533 
534 			particle.pos.X = m_Variables[VAR_POSITION_X]->Evaluate(emitter);
535 			particle.pos.Y = m_Variables[VAR_POSITION_Y]->Evaluate(emitter);
536 			particle.pos.Z = m_Variables[VAR_POSITION_Z]->Evaluate(emitter);
537 			particle.pos += emitter.m_Pos;
538 
539 			if (m_UseRelativeVelocity)
540 			{
541 				float xVel = m_Variables[VAR_VELOCITY_X]->Evaluate(emitter);
542 				float yVel = m_Variables[VAR_VELOCITY_Y]->Evaluate(emitter);
543 				float zVel = m_Variables[VAR_VELOCITY_Z]->Evaluate(emitter);
544 				CVector3D EmitterAngle = emitter.GetRotation().ToMatrix().Transform(CVector3D(xVel,yVel,zVel));
545 				particle.velocity.X = EmitterAngle.X;
546 				particle.velocity.Y = EmitterAngle.Y;
547 				particle.velocity.Z = EmitterAngle.Z;
548 			} else {
549 				particle.velocity.X = m_Variables[VAR_VELOCITY_X]->Evaluate(emitter);
550 				particle.velocity.Y = m_Variables[VAR_VELOCITY_Y]->Evaluate(emitter);
551 				particle.velocity.Z = m_Variables[VAR_VELOCITY_Z]->Evaluate(emitter);
552 			}
553 			particle.angle = m_Variables[VAR_ANGLE]->Evaluate(emitter);
554 			particle.angleSpeed = m_Variables[VAR_VELOCITY_ANGLE]->Evaluate(emitter);
555 
556 			particle.size = m_Variables[VAR_SIZE]->Evaluate(emitter);
557 			particle.sizeGrowthRate = m_Variables[VAR_SIZE_GROWTHRATE]->Evaluate(emitter);
558 
559 			RGBColor color;
560 			color.X = m_Variables[VAR_COLOR_R]->Evaluate(emitter);
561 			color.Y = m_Variables[VAR_COLOR_G]->Evaluate(emitter);
562 			color.Z = m_Variables[VAR_COLOR_B]->Evaluate(emitter);
563 			particle.color = ConvertRGBColorTo4ub(color);
564 
565 			particle.age = 0.f;
566 			particle.maxAge = m_Variables[VAR_LIFETIME]->Evaluate(emitter);
567 
568 			emitter.AddParticle(particle);
569 		}
570 	}
571 
572 	// Update particle states
573 	for (size_t i = 0; i < emitter.m_Particles.size(); ++i)
574 	{
575 		SParticle& p = emitter.m_Particles[i];
576 
577 		// Don't bother updating particles already at the end of their life
578 		if (p.age > p.maxAge)
579 			continue;
580 
581 		p.pos += p.velocity * dt;
582 		p.angle += p.angleSpeed * dt;
583 		p.age += dt;
584 		p.size += p.sizeGrowthRate * dt;
585 
586 		// Make alpha fade in/out nicely
587 		// TODO: this should probably be done as a variable or something,
588 		// instead of hardcoding
589 		float ageFrac = p.age / p.maxAge;
590 		float a = std::min(1.f-ageFrac, 5.f*ageFrac);
591 		p.color.A = clamp((int)(a*255.f), 0, 255);
592 	}
593 
594 	for (size_t i = 0; i < m_Effectors.size(); ++i)
595 	{
596 		m_Effectors[i]->Evaluate(emitter.m_Particles, dt);
597 	}
598 }
599 
CalculateBounds(CVector3D emitterPos,CBoundingBoxAligned emittedBounds)600 CBoundingBoxAligned CParticleEmitterType::CalculateBounds(CVector3D emitterPos, CBoundingBoxAligned emittedBounds)
601 {
602 	CBoundingBoxAligned bounds = m_MaxBounds;
603 	bounds[0] += emitterPos;
604 	bounds[1] += emitterPos;
605 
606 	bounds += emittedBounds;
607 
608 	// The current bounds is for the particles' centers, so expand by
609 	// sqrt(2) * max_size/2 to ensure any rotated billboards fit in
610 	bounds.Expand(m_Variables[VAR_SIZE]->Max(*this)/2.f * sqrt(2.f));
611 
612 	return bounds;
613 }
614