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_effect.cpp
13 *** \author  Raj Sharma, roos@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Source file for particle effects
16 *** **************************************************************************/
17 
18 #include "engine/video/particle_effect.h"
19 
20 #include "engine/video/particle_system.h"
21 #include "engine/video/video.h"
22 
23 #include "script/script_read.h"
24 #include "engine/system.h"
25 
26 #include "utils/utils_files.h"
27 
28 using namespace vt_script;
29 using namespace vt_video;
30 
31 namespace vt_mode_manager
32 {
33 
_LoadEffectDef(const std::string & particle_file)34 bool ParticleEffect::_LoadEffectDef(const std::string &particle_file)
35 {
36     _effect_def.Clear();
37     _loaded = false;
38 
39     // Make sure the corresponding tables are empty
40     ScriptManager->DropGlobalTable("systems");
41     ScriptManager->DropGlobalTable("map_effect_collision");
42 
43     vt_script::ReadScriptDescriptor particle_script;
44     if(!particle_script.OpenFile(particle_file)) {
45         PRINT_WARNING << "No script file: '"
46                       << particle_file << "' The corresponding particle effect won't work."
47                       << std::endl;
48         return false;
49     }
50 
51     // Read the particle image rectangle when existing
52     if (particle_script.OpenTable("map_effect_collision")) {
53         _effect_def.effect_collision_width = particle_script.ReadFloat("effect_collision_width");
54         _effect_def.effect_collision_height = particle_script.ReadFloat("effect_collision_height");
55         _effect_def.effect_width = particle_script.ReadFloat("effect_width");
56         _effect_def.effect_height = particle_script.ReadFloat("effect_height");
57         particle_script.CloseTable(); // map_effect_collision
58     }
59 
60     if(!particle_script.DoesTableExist("systems")) {
61         PRINT_WARNING << "Could not find the 'systems' array in particle effect "
62                       << particle_file << std::endl;
63         particle_script.CloseFile();
64         _effect_def.Clear();
65         _loaded = false;
66         return false;
67     }
68 
69     particle_script.OpenTable("systems");
70     uint32_t system_size = particle_script.GetTableSize();
71     if(system_size < 1) {
72         PRINT_WARNING << "No valid particle systems defined in the particle effect "
73                       << particle_file << std::endl;
74         particle_script.CloseTable();
75         particle_script.CloseFile();
76         _effect_def.Clear();
77         _loaded = false;
78         return false;
79     }
80 
81     for(uint32_t sys = 0; sys < system_size; ++sys) {
82         // open the table for the sys'th system
83         if(!particle_script.DoesTableExist(sys)) {
84             PRINT_WARNING << "Could not find system #" << sys
85                           << " in particle effect " << particle_file << std::endl;
86             particle_script.CloseAllTables();
87             particle_script.CloseFile();
88             _effect_def.Clear();
89             _loaded = false;
90             return false;
91         }
92         particle_script.OpenTable(sys);
93 
94         // open up the emitter table
95         if(!particle_script.DoesTableExist("emitter")) {
96             PRINT_WARNING << "Could not find 'emitter' the array in system #"
97                           << sys << " in particle effect " << particle_file << std::endl;
98             particle_script.CloseAllTables();
99             particle_script.CloseFile();
100             _effect_def.Clear();
101             _loaded = false;
102             return false;
103         }
104         particle_script.OpenTable("emitter");
105 
106         ParticleSystemDef sys_def;
107 
108         sys_def.emitter._pos.x = particle_script.ReadFloat("x");
109         sys_def.emitter._pos.y = particle_script.ReadFloat("y");
110         sys_def.emitter._pos2.x = particle_script.ReadFloat("x2");
111         sys_def.emitter._pos2.y = particle_script.ReadFloat("y2");
112         sys_def.emitter._center.x = particle_script.ReadFloat("center_x");
113         sys_def.emitter._center.y = particle_script.ReadFloat("center_y");
114         sys_def.emitter._variation.x = particle_script.ReadFloat("x_variation");
115         sys_def.emitter._variation.y = particle_script.ReadFloat("y_variation");
116         sys_def.emitter._radius = particle_script.ReadFloat("radius");
117 
118         std::string shape_string = particle_script.ReadString("shape");
119         if(!strcasecmp(shape_string.c_str(), "point"))
120             sys_def.emitter._shape = EMITTER_SHAPE_POINT;
121         else if(!strcasecmp(shape_string.c_str(), "line"))
122             sys_def.emitter._shape = EMITTER_SHAPE_LINE;
123         else if(!strcasecmp(shape_string.c_str(), "ellipse outline"))
124             sys_def.emitter._shape = EMITTER_SHAPE_ELLIPSE;
125         else if(!strcasecmp(shape_string.c_str(), "circle outline"))
126             sys_def.emitter._shape = EMITTER_SHAPE_CIRCLE;
127         else if(!strcasecmp(shape_string.c_str(), "circle"))
128             sys_def.emitter._shape = EMITTER_SHAPE_FILLED_CIRCLE;
129         else if(!strcasecmp(shape_string.c_str(), "rectangle"))
130             sys_def.emitter._shape = EMITTER_SHAPE_FILLED_RECTANGLE;
131 
132         sys_def.emitter._omnidirectional = particle_script.ReadBool("omnidirectional");
133         sys_def.emitter._orientation = particle_script.ReadFloat("orientation");
134         sys_def.emitter._angle_variation = particle_script.ReadFloat("angle_variation");
135         sys_def.emitter._initial_speed = particle_script.ReadFloat("initial_speed");
136         sys_def.emitter._initial_speed_variation = particle_script.ReadFloat("initial_speed_variation");
137         sys_def.emitter._emission_rate = particle_script.ReadFloat("emission_rate");
138         sys_def.emitter._start_time = particle_script.ReadFloat("start_time");
139 
140         std::string emitter_mode_string = particle_script.ReadString("emitter_mode");
141         if(!strcasecmp(emitter_mode_string.c_str(), "looping"))
142             sys_def.emitter._emitter_mode = EMITTER_MODE_LOOPING;
143         else if(!strcasecmp(emitter_mode_string.c_str(), "one shot"))
144             sys_def.emitter._emitter_mode = EMITTER_MODE_ONE_SHOT;
145         else if(!strcasecmp(emitter_mode_string.c_str(), "burst"))
146             sys_def.emitter._emitter_mode = EMITTER_MODE_BURST;
147         else //.. if(!strcasecmp(emitter_mode_string.c_str(), "always"))
148             sys_def.emitter._emitter_mode = EMITTER_MODE_ALWAYS;
149 
150         std::string spin_string = particle_script.ReadString("spin");
151         if(!strcasecmp(spin_string.c_str(), "random"))
152             sys_def.emitter._spin = EMITTER_SPIN_RANDOM;
153         else if(!strcasecmp(spin_string.c_str(), "counterclockwise"))
154             sys_def.emitter._spin = EMITTER_SPIN_COUNTERCLOCKWISE;
155         else //..if(!strcasecmp(spin_string.c_str(), "clockwise"))
156             sys_def.emitter._spin = EMITTER_SPIN_CLOCKWISE;
157 
158         // Close the emitter table
159         particle_script.CloseTable();
160 
161         // open up the keyframes table
162         if(!particle_script.DoesTableExist("keyframes")) {
163             PRINT_WARNING << "Could not find the 'keyframes' array in system #"
164                           << sys << " in particle effect " << particle_file << std::endl;
165             particle_script.CloseAllTables();
166             particle_script.CloseFile();
167             _effect_def.Clear();
168             _loaded = false;
169             return false;
170         }
171         particle_script.OpenTable("keyframes");
172 
173         uint32_t num_keyframes = particle_script.GetTableSize();
174         sys_def.keyframes.resize(num_keyframes);
175 
176         for(uint32_t kf = 0; kf < num_keyframes; ++kf) {
177             // get the kf'th keyframe table
178             // unamed tables starts at offset 1 in lua.
179             particle_script.OpenTable(kf + 1);
180 
181             sys_def.keyframes[kf].size.x = particle_script.ReadFloat("size_x");
182             sys_def.keyframes[kf].size.y = particle_script.ReadFloat("size_y");
183             sys_def.keyframes[kf].color = _ReadColor(particle_script, "color");
184             sys_def.keyframes[kf].rotation_speed = particle_script.ReadFloat("rotation_speed");
185             sys_def.keyframes[kf].size_variation.x = particle_script.ReadFloat("size_variation_x");
186             sys_def.keyframes[kf].size_variation.y = particle_script.ReadFloat("size_variation_y");
187             sys_def.keyframes[kf].color_variation = _ReadColor(particle_script, "color_variation");
188             sys_def.keyframes[kf].rotation_speed_variation = particle_script.ReadFloat("rotation_speed_variation");
189             sys_def.keyframes[kf].time = particle_script.ReadFloat("time");
190 
191             // pop the current keyframe
192             particle_script.CloseTable();
193         }
194 
195         // pop the keyframes table
196         particle_script.CloseTable();
197 
198         // open up the animation_frames table
199         particle_script.ReadStringVector("animation_frames", sys_def.animation_frame_filenames);
200 
201         if(sys_def.animation_frame_filenames.size() < 1) {
202             PRINT_WARNING << "No animation filenames found while opening particle effect "
203                           << particle_file << std::endl;
204             particle_script.CloseAllTables();
205             particle_script.CloseFile();
206             _effect_def.Clear();
207             _loaded = false;
208             return false;
209         }
210 
211         particle_script.ReadIntVector("animation_frame_times", sys_def.animation_frame_times);
212 
213         // Test each file availability
214         std::vector<std::string>::const_iterator it, it_end;
215         for(it = sys_def.animation_frame_filenames.begin(),
216                 it_end = sys_def.animation_frame_filenames.end(); it != it_end; ++it) {
217             if(!vt_utils::DoesFileExist(*it)) {
218                 PRINT_WARNING << "Could not find file: "
219                               << *it << " in system #" << sys << " in particle effect "
220                               << particle_file << std::endl;
221                 particle_script.CloseAllTables();
222                 particle_script.CloseFile();
223                 _effect_def.Clear();
224                 _loaded = false;
225                 return false;
226             }
227         }
228 
229         if(sys_def.animation_frame_times.size() < 1) {
230             PRINT_WARNING << "No animation frame times found while opening particle effect "
231                           << particle_file << std::endl;
232             particle_script.CloseAllTables();
233             particle_script.CloseFile();
234             _effect_def.Clear();
235             _loaded = false;
236             return false;
237         }
238 
239         sys_def.enabled = particle_script.ReadBool("enabled");
240         sys_def.blend_mode = particle_script.ReadInt("blend_mode");
241         sys_def.system_lifetime = particle_script.ReadFloat("system_lifetime");
242 
243         sys_def.particle_lifetime = particle_script.ReadFloat("particle_lifetime");
244         sys_def.particle_lifetime_variation = particle_script.ReadFloat("particle_lifetime_variation");
245         sys_def.max_particles = particle_script.ReadInt("max_particles");
246 
247         sys_def.damping = particle_script.ReadFloat("damping");
248         sys_def.damping_variation = particle_script.ReadFloat("damping_variation");
249 
250         sys_def.acceleration.x = particle_script.ReadFloat("acceleration_x");
251         sys_def.acceleration.y = particle_script.ReadFloat("acceleration_y");
252         sys_def.acceleration_variation.x = particle_script.ReadFloat("acceleration_variation_x");
253         sys_def.acceleration_variation.y = particle_script.ReadFloat("acceleration_variation_y");
254 
255         sys_def.wind_velocity.x = particle_script.ReadFloat("wind_velocity_x");
256         sys_def.wind_velocity.y = particle_script.ReadFloat("wind_velocity_y");
257         sys_def.wind_velocity_variation.x = particle_script.ReadFloat("wind_velocity_variation_x");
258         sys_def.wind_velocity_variation.y = particle_script.ReadFloat("wind_velocity_variation_y");
259 
260         if (particle_script.OpenTable("wave_motion")) {
261 
262             sys_def.wave_motion_used = true;
263             sys_def.wave_length = particle_script.ReadFloat("wave_length");
264             sys_def.wave_length_variation = particle_script.ReadFloat("wave_length_variation");
265             sys_def.wave_amplitude = particle_script.ReadFloat("wave_amplitude");
266             sys_def.wave_amplitude_variation = particle_script.ReadFloat("wave_amplitude_variation");
267 
268             particle_script.CloseTable();
269         }
270         else {
271             sys_def.wave_motion_used = false;
272         }
273 
274         sys_def.tangential_acceleration = particle_script.ReadFloat("tangential_acceleration");
275         sys_def.tangential_acceleration_variation = particle_script.ReadFloat("tangential_acceleration_variation");
276 
277         sys_def.radial_acceleration = particle_script.ReadFloat("radial_acceleration");
278         sys_def.radial_acceleration_variation = particle_script.ReadFloat("radial_acceleration_variation");
279 
280         sys_def.user_defined_attractor = particle_script.ReadBool("user_defined_attractor");
281         sys_def.attractor_falloff = particle_script.ReadFloat("attractor_falloff");
282 
283         if (particle_script.OpenTable("rotation")) {
284             sys_def.rotation_used = true;
285 
286             if (particle_script.OpenTable("rotate_to_velocity")) {
287 
288                 sys_def.rotate_to_velocity = true;
289 
290                 if (particle_script.DoesFloatExist("speed_scale")) {
291                     sys_def.speed_scale_used = true;
292 
293                     sys_def.speed_scale = particle_script.ReadFloat("speed_scale");
294                     sys_def.min_speed_scale = particle_script.ReadFloat("min_speed_scale");
295                     sys_def.max_speed_scale = particle_script.ReadFloat("max_speed_scale");
296 
297                 }
298                 else {
299                     sys_def.speed_scale_used = false;
300                 }
301 
302                 particle_script.CloseTable(); // rotate_to_velocity
303             }
304             else {
305                 sys_def.rotate_to_velocity = false;
306             }
307 
308             particle_script.CloseTable(); // rotation
309         }
310         else {
311             sys_def.rotation_used = false;
312         }
313 
314         sys_def.smooth_animation = particle_script.ReadBool("smooth_animation");
315         sys_def.modify_stencil = particle_script.ReadBool("modify_stencil");
316 
317         std::string stencil_op_string = particle_script.ReadString("stencil_op");
318 
319         if(!strcasecmp(stencil_op_string.c_str(), "incr"))
320             sys_def.stencil_op = VIDEO_STENCIL_OP_INCREASE;
321         else if(!strcasecmp(stencil_op_string.c_str(), "decr"))
322             sys_def.stencil_op = VIDEO_STENCIL_OP_DECREASE;
323         else if(!strcasecmp(stencil_op_string.c_str(), "zero"))
324             sys_def.stencil_op = VIDEO_STENCIL_OP_ZERO;
325         else //..if(!strcasecmp(stencil_op_string.c_str(), "one"))
326             sys_def.stencil_op = VIDEO_STENCIL_OP_ONE;
327 
328         sys_def.use_stencil = particle_script.ReadBool("use_stencil");
329         sys_def.random_initial_angle = particle_script.ReadBool("random_initial_angle");
330 
331         // pop the system table
332         particle_script.CloseTable();
333 
334         _effect_def._systems.push_back(sys_def);
335     }
336 
337     _loaded = true;
338     return true;
339 }
340 
341 // A helper function reading a lua subtable of 4 float values.
_ReadColor(vt_script::ReadScriptDescriptor & particle_script,const std::string & param_name)342 Color ParticleEffect::_ReadColor(vt_script::ReadScriptDescriptor &particle_script,
343                                  const std::string &param_name)
344 {
345     std::vector<float> float_vec;
346     particle_script.ReadFloatVector(param_name, float_vec);
347     if(float_vec.size() < 4) {
348         PRINT_WARNING << "Invalid color read in parameter: " << param_name
349                       << " for file: " << particle_script.GetFilename() << std::endl;
350         return Color();
351     }
352     Color new_color(float_vec[0], float_vec[1], float_vec[2], float_vec[3]);
353 
354     return new_color;
355 }
356 
_CreateEffect()357 bool ParticleEffect::_CreateEffect()
358 {
359     // The effect isn't loaded, so we can't create the effect.
360     if(!IsLoaded())
361         return false;
362 
363     // Initialize systems
364     _systems.clear();
365     std::vector<ParticleSystemDef>::iterator it = _effect_def._systems.begin();
366     for(; it != _effect_def._systems.end(); ++it) {
367         if((*it).enabled) {
368             ParticleSystem sys(&(*it));
369             if(!sys.IsAlive()) {
370                 // If a system could not be created then we bail out
371                 _systems.clear();
372 
373                 IF_PRINT_WARNING(VIDEO_DEBUG)
374                         << "sys->Create() returned false while trying to create effect!" << std::endl;
375                 return false;
376 
377             }
378             _systems.push_back(sys);
379         }
380     }
381 
382     _alive = true;
383     _age = 0.0f;
384     return true;
385 }
386 
LoadEffect(const std::string & filename)387 bool ParticleEffect::LoadEffect(const std::string &filename)
388 {
389     if(!_LoadEffectDef(filename)) {
390         PRINT_WARNING << "Failed to load particle definition file: "
391                       << filename << std::endl;
392         return false;
393     }
394 
395     if(!_CreateEffect()) {
396         PRINT_WARNING << "Failed to create particle effect from file: "
397                       << filename << std::endl;
398         return false;
399     }
400 
401     return true;
402 }
403 
Draw()404 void ParticleEffect::Draw()
405 {
406     // Move to the effect's location.
407     VideoManager->Move(_pos.x, _pos.y);
408 
409     std::vector<ParticleSystem>::iterator iSystem = _systems.begin();
410     while (iSystem != _systems.end()) {
411         (*iSystem).Draw();
412         ++iSystem;
413     }
414 
415     VideoManager->DisableStencilTest();
416 }
417 
Update()418 void ParticleEffect::Update()
419 {
420     Update(static_cast<float>(vt_system::SystemManager->GetUpdateTime()) / 1000.0f);
421 }
422 
Update(float frame_time)423 void ParticleEffect::Update(float frame_time)
424 {
425     _age += frame_time;
426     _num_particles = 0;
427 
428     if(!_alive)
429         return;
430 
431     vt_mode_manager::EffectParameters effect_parameters;
432     effect_parameters.orientation = _orientation;
433 
434     // note we subtract the effect position to put the attractor point in effect
435     // space instead of screen space
436     effect_parameters.attractor.x = _attractor.x - _pos.x;
437     effect_parameters.attractor.y = _attractor.y - _pos.y;
438 
439     std::vector<ParticleSystem>::iterator iSystem = _systems.begin();
440 
441     while(iSystem != _systems.end()) {
442         if(!(*iSystem).IsAlive()) {
443             iSystem = _systems.erase(iSystem);
444 
445             if(_systems.empty())
446                 _alive = false;
447         } else {
448             (*iSystem).Update(frame_time, effect_parameters);
449 
450             _num_particles += (*iSystem).GetNumParticles();
451             ++iSystem;
452         }
453     }
454 }
455 
456 
_Destroy()457 void ParticleEffect::_Destroy()
458 {
459     _alive = false;
460     _pos.x = 0.0f;
461     _pos.y = 0.0f;
462     _attractor.x = 0.0f;
463     _attractor.y = 0.0f;
464     _age = 0.0f;
465     _orientation = 0.0f;
466 
467     _systems.clear();
468 
469     _loaded = false;
470 }
471 
472 
Move(float x,float y)473 void ParticleEffect::Move(float x, float y)
474 {
475     _pos.x = x;
476     _pos.y = y;
477 }
478 
MoveRelative(float dx,float dy)479 void ParticleEffect::MoveRelative(float dx, float dy)
480 {
481     _pos.x += dx;
482     _pos.y += dy;
483 }
484 
SetAttractorPoint(float x,float y)485 void ParticleEffect::SetAttractorPoint(float x, float y)
486 {
487     _attractor.x = x;
488     _attractor.y = y;
489 }
490 
Stop(bool kill_immediate)491 void ParticleEffect::Stop(bool kill_immediate)
492 {
493     if(kill_immediate) {
494         _alive = false;
495         _systems.clear();
496     } else {
497         // if we're not killing immediately, then calling Stop() just means to stop emitting NEW
498         // particles, so go through each system and turn off its emitter
499         std::vector<ParticleSystem>::iterator iSystem = _systems.begin();
500 
501         while(iSystem != _systems.end()) {
502             (*iSystem).Stop();
503             ++iSystem;
504         }
505     }
506 }
507 
Start()508 bool ParticleEffect::Start()
509 {
510     if(IsAlive())
511         return true;
512 
513     return _CreateEffect();
514 }
515 
GetPosition() const516 const vt_common::Position2D& ParticleEffect::GetPosition() const
517 {
518     return _pos;
519 }
520 
521 } // namespace vt_mode_manager
522