1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2011 by The Allacrost Project
3 //            Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 //                         All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See http://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 /** ***************************************************************************
12 *** \file    particle_system.h
13 *** \author  Raj Sharma, roos@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Header file for particle system
16 ***
17 *** This file contains two classes: ParticleSystemDef, and ParticleSystem.
18 ***
19 *** ParticleSystemDef is a "definition" class, meaning that it holds information
20 *** about a particle system, like its lifetime, the emitter, and other properties.
21 ***
22 *** ParticleSystem is an "instance" class, meaning that it holds information about
23 *** a particle system which is currently being drawn on screen.
24 ***
25 *** This way, if you have 100 explosions for example, the properties of the
26 *** effect are stored only once, and the only thing that gets repeated 100 times
27 *** is instance-specific stuff like positions of vertices, etc.
28 *** **************************************************************************/
29 
30 #ifndef __PARTICLE_SYSTEM_HEADER__
31 #define __PARTICLE_SYSTEM_HEADER__
32 
33 #include "particle.h"
34 #include "particle_emitter.h"
35 
36 #include "engine/video/image.h"
37 
38 namespace vt_mode_manager
39 {
40 
41 //! \brief Specifies the stencil operation to use and describes how the stencil buffer is modified
42 enum VIDEO_STENCIL_OP {
43     VIDEO_STENCIL_OP_INVALID = -1,
44 
45     //! Set the stencil value to one
46     VIDEO_STENCIL_OP_ZERO = 0,
47 
48     //! Set the stencil value to zero
49     VIDEO_STENCIL_OP_ONE = 1,
50 
51     //! Increase the stencil value
52     VIDEO_STENCIL_OP_INCREASE = 2,
53 
54     //! Decrease the stencil value
55     VIDEO_STENCIL_OP_DECREASE = 3,
56 
57     VIDEO_STENCIL_OP_TOTAL = 4
58 };
59 
60 /*!***************************************************************************
61  *  \brief when we change a property of an effect, it affects all of the
62  *         systems contained within that effect. So, this structure contains
63  *         any relevant parameters that particle systems need to know about.
64  *****************************************************************************/
65 
66 class EffectParameters
67 {
68 public:
EffectParameters()69     EffectParameters():
70         orientation(0.0f),
71         attractor(0.0f, 0.0f)
72     {}
73 
74     //! orientation of the effect, called with ParticleEffect::SetOrientation()
75     float orientation;
76 
77     //! attraction point, particles gravitate towards this
78     vt_common::Position2D attractor;
79 };
80 
81 
82 class ParticleSystemDef
83 {
84 public:
ParticleSystemDef()85     ParticleSystemDef():
86         enabled(false),
87         blend_mode(0),
88         system_lifetime(0.0f),
89         particle_lifetime(0.0f),
90         particle_lifetime_variation(0.0f),
91         max_particles(0),
92         damping(0.0f),
93         damping_variation(0.0f),
94         acceleration(0.0f, 0.0f),
95         acceleration_variation(0.0f, 0.0f),
96         wind_velocity(0.0f, 0.0f),
97         wind_velocity_variation(0.0f, 0.0f),
98         wave_motion_used(false),
99         wave_length(0.0f),
100         wave_length_variation(0.0f),
101         wave_amplitude(0.0f),
102         wave_amplitude_variation(0.0f),
103         tangential_acceleration(0.0f),
104         tangential_acceleration_variation(0.0f),
105         radial_acceleration(0.0f),
106         radial_acceleration_variation(0.0f),
107         user_defined_attractor(false),
108         attractor_falloff(0.0f),
109         rotation_used(false),
110         rotate_to_velocity(false),
111         speed_scale_used(false),
112         speed_scale(0.0f),
113         min_speed_scale(0.0f),
114         max_speed_scale(0.0f),
115         smooth_animation(false),
116         modify_stencil(false),
117         stencil_op(VIDEO_STENCIL_OP_INVALID),
118         use_stencil(false),
119         random_initial_angle(false)
120     {}
121 
~ParticleSystemDef()122     ~ParticleSystemDef()
123     {}
124 
125     //! Is this system supposed to be displayed
126     bool enabled;
127 
128     //! Each system contains 1 emitter, which mainly determines where to shoot particles out from
129     //! and how fast to shoot them out
130     ParticleEmitter emitter;
131 
132     //! Array of keyframes, which specify how particle properties vary over time. This array must
133     //! contain at least 1 keyframe (in that case, the properties are all held constant)
134     std::vector<ParticleKeyframe> keyframes;
135 
136     //! How to blend the particles: VIDEO_NO_BLEND, VIDEO_BLEND, or VIDEO_BLEND_ADD
137     //! For most effects, we want VIDEO_BLEND_ADD
138     int32_t blend_mode;
139 
140     //! How many seconds the system should live for before it dies out. This is only
141     //! meaningful if our emitter mode is EMITTER_MODE_ONE_SHOT. The other modes
142     //! will simply keep playing until the effect is destroyed or stopped, except for
143     //! EMITTER_MODE_BURST, which spits out a bunch of particles at the beginning and
144     //! then dies as soon as all of those particles die
145     float system_lifetime;
146 
147     //! How long each particle should live for before it dies
148     float particle_lifetime;
149 
150     //! Random variation added to particle lifetime
151     float particle_lifetime_variation;
152 
153     //! Maximum number of particles this system can have at one time
154     int32_t max_particles;
155 
156     //! A number below 1.0 (but generally pretty close to 1.0). A damp of .99 means that
157     //! each second, particle velocity drops by 1%
158     float damping;
159 
160     //! Random variation added to damping
161     float damping_variation;
162 
163     //! constant acceleration, good example is gravity. Note that down is in the positive y
164     //! direction since we are using screen coordinates
165     vt_common::Position2D acceleration;
166 
167     vt_common::Position2D acceleration_variation;
168 
169     //! wind velocity, more generally any velocity which is added to each particle's velocity
170     vt_common::Position2D wind_velocity;
171 
172     //! wind velocity variation
173     vt_common::Position2D wind_velocity_variation;
174 
175     //! true if we should use wave motion for this system
176     bool wave_motion_used;
177 
178     //! wavelength. For example a wavelength of 5 means that it takes 5 seconds to go from
179     //! one point on the sinusoidal curve to the next
180     float wave_length;
181 
182     //! Random variation added to wave length
183     float wave_length_variation;
184 
185     //! wave amplitude- the distance from the peak to the bottom of the sinusoidal curve
186     float wave_amplitude;
187 
188     //! Random variation added to wave amplitude
189     float wave_amplitude_variation;
190 
191     //! tangential acceleration, how particle accelerates tangential to vector from particle
192     //! to center of emitter region. Positive is clockwise.
193     float tangential_acceleration;
194 
195     //! tangential acceleration variation
196     float tangential_acceleration_variation;
197 
198     //! radial acceleration. Positive means particles accelerate away from the emitter, negative
199     //! means they accelerate back towards it
200     float radial_acceleration;
201 
202     //! radial acceleration variation
203     float radial_acceleration_variation;
204 
205     //! if true, we use a user defined attractor instead of the emitter position for radial
206     //! acceleration. The user defined attractor is set using ParticleEffect::SetAttractorPoint()
207     bool user_defined_attractor;
208 
209     //! how quickly the "pull" of an attractor falls off as a particle gets further away from it.
210     //! For example if falloff is 10^-3, and a particle is 500 pixels away from the attractor,
211     //! then the radial acceleration is lessened by (500 * 10^-3) = .5, or 50%
212     float attractor_falloff;
213 
214     //! True if ANY of the keyframes for the particles contain non-zero rotations
215     //! This is used by the Draw() function so it knows whether it needs to factor in
216     //! rotations when determining the particle vertices (since this adds a lot of extra
217     //! computation)
218     bool rotation_used;
219 
220     //! True if you want particles to rotate to face the same direction they are going.
221     //! So for example a particle going straight up will not be rotated, but a particle
222     //! that is going 45 degrees northeast will be rotated 45 degrees clockwise
223     bool rotate_to_velocity;
224 
225     //! true if speed scaling is used. Note that speed scaling can only be used if
226     //! rotate to velocity is also used.
227     bool speed_scale_used;
228 
229     //! this number is multiplied by a particle's speed to come up with a scale of how much
230     //! to stretch a particle in its direction of motion
231     float speed_scale;
232 
233     //! if you use speed_scale, this can cause particles to become extremely tiny at low speeds
234     //! this variable allows you to set a minimum bound on the scaling due to speed
235     float min_speed_scale;
236 
237     //! if you use speed_scale, this can cause particles to become extremely large at high speeds
238     //! this variable allows you to set a maximum bound on the scaling due to speed
239     float max_speed_scale;
240 
241     //! True if alpha blending should be used to create smooth transitions between animation
242     //! frames
243     bool smooth_animation;
244 
245     //! True if this system should not actually write to the screen, but instead modify the
246     //! stencil buffer. Every time a pixel passes the alpha test, the stencil buffer will be
247     //! modified, according to _stencil_op
248     bool modify_stencil;
249 
250     //! if modify stencil is true, then the operation to use when the alpha test passes
251     //! can either be VIDEO_STENCIL_OP_INCREASE, VIDEO_STENCIL_OP_ONE, VIDEO_STENCIL_OP_ZERO, or
252     //! VIDEO_STENCIL_OP_DECREASE. The stencil test we use is "equal to 1"
253     VIDEO_STENCIL_OP stencil_op;
254 
255     //! if this is true, then we only draw in areas where the stencil buffer contains a 1.
256     //! Note that _use_stencil and _modify_stencil cannot both be 1 at the same time
257     bool use_stencil;
258 
259     //! true if particles' initial angle should be randomized. If false, then all particles
260     //! have an angle of zero when they spawn
261     bool random_initial_angle;
262 
263     //! Array telling how long each animation should last for
264     std::vector<int32_t> animation_frame_times;
265 
266     //! Array of filenames for each frame of animation
267     std::vector<std::string> animation_frame_filenames;
268 
269 }; // class ParticleSystemDef
270 
271 
272 
273 class ParticleSystem
274 {
275 public:
276     /*!
277      * \brief Constructor
278      */
ParticleSystem(ParticleSystemDef * sys_def)279     explicit ParticleSystem(ParticleSystemDef* sys_def) {
280         _Destroy();
281         _Create(sys_def);
282     }
283 
~ParticleSystem()284     ~ParticleSystem() {
285         _Destroy();
286     }
287 
288     //! \brief draws the system
289     void Draw();
290 
291     /*!
292      * \brief updates the system
293      * \param frame_time the current frame time
294      * \param params the effect parameters to use for this update (orientation and attractor point)
295      */
296     void Update(float frame_time, const EffectParameters &params);
297 
298     /*!
299      * \brief returns true if system is still alive
300      * \return true if alive, false if dead
301      */
IsAlive()302     bool IsAlive() const {
303         return _alive && _system_def->enabled;
304     }
305 
306     /*!
307      * \brief returns true if system has been stopped by a call to Stop()
308      * \return true if stopped, false if still going
309      */
IsStopped()310     bool IsStopped() const {
311         return _stopped;
312     }
313 
314     /*!
315      *  \brief stops the system, i.e. makes it stop emitting new particles
316      */
Stop()317     void Stop() {
318         _stopped = true;
319     }
320 
321     /*!
322      *  \brief returns how many particles are alive in this system
323      * \return the number of particles in this system
324      */
GetNumParticles()325     int32_t GetNumParticles() const {
326         return _num_particles;
327     }
328 
329     /*!
330      *  \brief returns the number of seconds since this system was created
331      * \return the age of the system
332      */
GetAge()333     float GetAge() const {
334         return _age;
335     }
336 
337 private:
338     /*!
339      *  \brief initializes this particle system as an instance of the
340      *         type of particle system specified by the ParticleSystemDef
341      * \param sys_def particle definition to base the system off of
342      * \return success/failure
343      */
344     bool _Create(ParticleSystemDef *sys_def);
345 
346     /*!
347      *  \brief destroys the system
348      */
349     void _Destroy();
350 
351     /*!
352      *  \brief helper function to update properties of particles
353      * \param t the current frame time
354      * \param params the effect parameters to use for this update (orientation and attractor point)
355      */
356     void _UpdateParticles(float t, const EffectParameters &params);
357 
358     /*!
359      *  \brief helper function to kill off any particles that have died
360      *
361      *  \param num_particles this is an optimization that lets us avoid
362      *         killing particles. Killing particles is expensive, because
363      *         it leaves holes in our array, so we have to shuffle data
364      *         around to fill those holes. So instead, say we have 10
365      *         particles to kill this frame, and we also want to emit 8
366      *         particles. Then, instead of killing 10 particles, we only
367      *         kill 2, and for the other 8, we respawn them immediately.
368      * \param params the effect parameters to use for this update (orientation and attractor point)
369      */
370     void _KillParticles(int32_t &num_particles, const EffectParameters &params);
371 
372     /*!
373      *  \brief helper function that emits whatever particles still need to be
374      *         emitted after calling _KillParticles
375      * \param num_particles the number of particles that need to be emitted
376      * \param params the effect parameters to use for this update (orientation and attractor point)
377      */
378     void _EmitParticles(int32_t num_particles, const EffectParameters &params);
379 
380     /*!
381      *  \brief helper function to move a particle from element src to element dest
382      *         in the array. This is required any time we kill a particle, because
383      *         killing particles leaves a hole in the array
384      * \param src where to move the particle from
385      * \param dest where to move the particle to
386      */
387     void _MoveParticle(int32_t src, int32_t dest);
388 
389     /*!
390      *  \brief creates a new particle at element i in the particle array
391      * \param i index of the particle to respawn
392      * \param params the effect parameters to use for this update (orientation and attractor point)
393      */
394     void _RespawnParticle(int32_t i, const EffectParameters &params);
395 
396     //! The system definition, contains information like the emitter properties, lifetime of
397     //! particles, particle keyframes, etc. Basically everything which isn't instance-specific
398     //! Note that this pointer shouldn't be deleted by the particle system, since it's handled by
399     //! the corresponding ParticleEffectDef instance.
400     ParticleSystemDef *_system_def;
401 
402     //! Animation for each particle. If it's non-animated, it just has 1 frame
403     vt_video::AnimatedImage _animation;
404 
405     //! Number of active particles in this system. (The size of the vectors may be larger, since
406     //! we might set a particle quota for the system which is higher than what's actually there.)
407     int32_t _num_particles;
408 
409     //! The array of particle vertices. Note that this array contains FOUR vertices per particle.
410     //! This is used for rendering the particles with OpenGL
411     std::vector<ParticleVertex> _particle_vertices;
412     std::vector<vt_video::Color> _particle_colors;
413     std::vector<ParticleTexCoord> _particle_texcoords;
414 
415     //! This array holds everything except positions and colors. The reason we keep positions and
416     //! colors separate is so that they can be efficiently fed to OpenGL for rendering.
417     std::vector<Particle> _particles;
418 
419     //! if stopped is true, no new particles should be emitted
420     bool _stopped;
421 
422     //! alive gets set to false when the number of active particles drops to zero
423     bool _alive;
424 
425     //! age of the system, since it was created
426     float _age;
427 
428     //! last time the system was updated (based on the system's age)
429     float _last_update_time;
430 
431 }; // class ParticleSystem
432 
433 }  // namespace vt_mode_manager
434 
435 #endif
436