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