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