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 ¶m_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