ManaPlus
particleemitter.cpp
Go to the documentation of this file.
1 /*
2  * The ManaPlus Client
3  * Copyright (C) 2006-2009 The Mana World Development Team
4  * Copyright (C) 2009-2010 The Mana Developers
5  * Copyright (C) 2011-2017 The ManaPlus Developers
6  *
7  * This file is part of The ManaPlus Client.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
24 
25 #include "logger.h"
26 
28 
31 
32 #include "utils/foreach.h"
33 
34 #include "resources/imageset.h"
35 
36 #include "resources/dye/dye.h"
37 
38 #include "resources/image/image.h"
39 
44 
45 #include "debug.h"
46 
47 static const float SIN45 = 0.707106781F;
48 static const float DEG_RAD_FACTOR = 0.017453293F;
49 
50 typedef STD_VECTOR<ImageSet*>::const_iterator ImageSetVectorCIter;
51 typedef std::list<ParticleEmitter>::const_iterator ParticleEmitterListCIter;
52 
53 ParticleEmitter::ParticleEmitter(XmlNodeConstPtrConst emitterNode,
54  Particle *const target,
55  Map *const map, const int rotation,
56  const std::string& dyePalettes) :
57  mParticleTarget(target),
58  mMap(map),
59  mOutput(),
60  mOutputPause(),
61  mParticleImage(nullptr),
62  mParticleAnimation("particle animation"),
63  mParticleRotation("particle rotation"),
64  mParticleAlpha(),
65  mDeathEffect(),
66  mParticleChildEmitters(),
67  mTempSets(),
68  mOutputPauseLeft(0),
69  mDeathEffectConditions(0),
70  mParticleFollow(false)
71 {
72  // Initializing default values
73  mParticlePosX.set(0.0F);
74  mParticlePosY.set(0.0F);
75  mParticlePosZ.set(0.0F);
78  mParticlePower.set(0.0F);
79  mParticleGravity.set(0.0F);
81  mParticleBounce.set(0.0F);
84  mParticleMomentum.set(1.0F);
88  mOutput.set(1);
89  mOutputPause.set(0);
90  mParticleAlpha.set(1.0F);
91 
92  if (emitterNode == nullptr)
93  return;
94  for_each_xml_child_node(propertyNode, emitterNode)
95  {
96  if (xmlNameEqual(propertyNode, "property"))
97  {
98  const std::string name = XML::getProperty(
99  propertyNode, "name", "");
100 
101  if (name == "position-x")
102  {
103  mParticlePosX = readParticleEmitterProp(propertyNode, 0.0F);
104  }
105  else if (name == "position-y")
106  {
107  mParticlePosY = readParticleEmitterProp(propertyNode, 0.0F);
111  }
112  else if (name == "position-z")
113  {
114  mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0F);
118  }
119  else if (name == "image")
120  {
121  std::string image = XML::getProperty(
122  propertyNode, "value", "");
123  // Don't leak when multiple images are defined
124  if (!image.empty() && (mParticleImage == nullptr))
125  {
126  if (!dyePalettes.empty())
127  Dye::instantiate(image, dyePalettes);
129  }
130  }
131  else if (name == "subimage")
132  {
133  std::string image = XML::getProperty(
134  propertyNode, "value", "");
135  // Don't leak when multiple images are defined
136  if (!image.empty() && (mParticleImage == nullptr))
137  {
138  if (!dyePalettes.empty())
139  Dye::instantiate(image, dyePalettes);
140  Image *img = Loader::getImage(image);
141  if (img != nullptr)
142  {
144  XML::getProperty(propertyNode, "x", 0),
145  XML::getProperty(propertyNode, "y", 0),
146  XML::getProperty(propertyNode, "width", 0),
147  XML::getProperty(propertyNode, "height", 0));
148  img->decRef();
149  }
150  else
151  {
152  mParticleImage = nullptr;
153  }
154  }
155  }
156  else if (name == "horizontal-angle")
157  {
159  readParticleEmitterProp(propertyNode, 0.0F);
161  += static_cast<float>(rotation);
164  += static_cast<float>(rotation);
167  }
168  else if (name == "vertical-angle")
169  {
171  readParticleEmitterProp(propertyNode, 0.0F);
175  }
176  else if (name == "power")
177  {
178  mParticlePower = readParticleEmitterProp(propertyNode, 0.0F);
179  }
180  else if (name == "gravity")
181  {
182  mParticleGravity = readParticleEmitterProp(propertyNode, 0.0F);
183  }
184  else if (name == "randomnes"
185  || name == "randomness") // legacy bug
186  {
187  mParticleRandomness = readParticleEmitterProp(propertyNode, 0);
188  }
189  else if (name == "bounce")
190  {
191  mParticleBounce = readParticleEmitterProp(propertyNode, 0.0F);
192  }
193  else if (name == "lifetime")
194  {
195  mParticleLifetime = readParticleEmitterProp(propertyNode, 0);
197  }
198  else if (name == "output")
199  {
200  mOutput = readParticleEmitterProp(propertyNode, 0);
201  mOutput.maxVal += 1;
202  }
203  else if (name == "output-pause")
204  {
205  mOutputPause = readParticleEmitterProp(propertyNode, 0);
207  }
208  else if (name == "acceleration")
209  {
211  propertyNode, 0.0F);
212  }
213  else if (name == "die-distance")
214  {
216  propertyNode, 0.0F);
217  }
218  else if (name == "momentum")
219  {
221  propertyNode, 1.0F);
222  }
223  else if (name == "fade-out")
224  {
225  mParticleFadeOut = readParticleEmitterProp(propertyNode, 0);
226  }
227  else if (name == "fade-in")
228  {
229  mParticleFadeIn = readParticleEmitterProp(propertyNode, 0);
230  }
231  else if (name == "alpha")
232  {
233  mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0F);
234  }
235  else if (name == "follow-parent")
236  {
237  const std::string value = XML::getProperty(propertyNode,
238  "value", "0");
239  if (value == "1" || value == "true")
240  mParticleFollow = true;
241  }
242  else
243  {
244  logger->log("Particle Engine: Warning, "
245  "unknown emitter property \"%s\"",
246  name.c_str());
247  }
248  }
249  else if (xmlNameEqual(propertyNode, "emitter"))
250  {
251  ParticleEmitter newEmitter(propertyNode, mParticleTarget, map,
252  rotation, dyePalettes);
253  mParticleChildEmitters.push_back(newEmitter);
254  }
255  else if (xmlNameEqual(propertyNode, "rotation")
256  || xmlNameEqual(propertyNode, "animation"))
257  {
258  ImageSet *const imageset = getImageSet(propertyNode);
259  if (imageset == nullptr)
260  {
261  logger->log1("Error: no valid imageset");
262  continue;
263  }
264  mTempSets.push_back(imageset);
265 
266  Animation &animation = (xmlNameEqual(propertyNode, "rotation")) !=
268 
269  // Get animation frames
270  for_each_xml_child_node(frameNode, propertyNode)
271  {
272  const int delay = XML::getIntProperty(
273  frameNode, "delay", 0, 0, 100000);
274  const int offsetX = XML::getProperty(frameNode, "offsetX", 0)
275  - imageset->getWidth() / 2 + mapTileSize / 2;
276  const int offsetY = XML::getProperty(frameNode, "offsetY", 0)
277  - imageset->getHeight() + mapTileSize;
278  const int rand = XML::getIntProperty(
279  frameNode, "rand", 100, 0, 100);
280 
281  if (xmlNameEqual(frameNode, "frame"))
282  {
283  const int index = XML::getProperty(frameNode, "index", -1);
284 
285  if (index < 0)
286  {
287  logger->log1("No valid value for 'index'");
288  continue;
289  }
290 
291  Image *const img = imageset->get(index);
292 
293  if (img == nullptr)
294  {
295  logger->log("No image at index %d", index);
296  continue;
297  }
298 
299  animation.addFrame(img, delay,
300  offsetX, offsetY, rand);
301  }
302  else if (xmlNameEqual(frameNode, "sequence"))
303  {
304  int start = XML::getProperty(frameNode, "start", -1);
305  const int end = XML::getProperty(frameNode, "end", -1);
306 
307  if (start < 0 || end < 0)
308  {
309  logger->log1("No valid value for 'start' or 'end'");
310  continue;
311  }
312 
313  while (end >= start)
314  {
315  Image *const img = imageset->get(start);
316  if (img == nullptr)
317  {
318  logger->log("No image at index %d", start);
319  continue;
320  }
321 
322  animation.addFrame(img, delay,
323  offsetX, offsetY, rand);
324  start ++;
325  }
326  }
327  else if (xmlNameEqual(frameNode, "end"))
328  {
329  animation.addTerminator(rand);
330  }
331  } // for frameNode
332  }
333  else if (xmlNameEqual(propertyNode, "deatheffect"))
334  {
335  if (!XmlHaveChildContent(propertyNode))
336  continue;
337  mDeathEffect = XmlChildContent(propertyNode);
338  mDeathEffectConditions = 0x00;
339  if (XML::getBoolProperty(propertyNode, "on-floor", true))
340  {
343  }
344  if (XML::getBoolProperty(propertyNode, "on-sky", true))
345  {
348  }
349  if (XML::getBoolProperty(propertyNode, "on-other", false))
350  {
353  }
354  if (XML::getBoolProperty(propertyNode, "on-impact", true))
355  {
358  }
359  if (XML::getBoolProperty(propertyNode, "on-timeout", true))
360  {
363  }
364  }
365  }
366 }
367 
369 {
370  *this = o;
371 }
372 
374 {
375  ImageSet *imageset = nullptr;
376  const int subX = XML::getProperty(node, "subX", -1);
377  if (subX != -1)
378  {
380  node, "imageset", ""));
381  if (img == nullptr)
382  return nullptr;
383 
384  Image *const img2 = Loader::getSubImage(img, subX,
385  XML::getProperty(node, "subY", 0),
386  XML::getProperty(node, "subWidth", 0),
387  XML::getProperty(node, "subHeight", 0));
388  if (img2 == nullptr)
389  {
390  img->decRef();
391  return nullptr;
392  }
393 
394  imageset = Loader::getSubImageSet(img2,
395  XML::getProperty(node, "width", 0),
396  XML::getProperty(node, "height", 0));
397  img2->decRef();
398  img->decRef();
399  }
400  else
401  {
402  imageset = Loader::getImageSet(
403  XML::getProperty(node, "imageset", ""),
404  XML::getProperty(node, "width", 0),
405  XML::getProperty(node, "height", 0));
406  }
407  return imageset;
408 }
409 
411 {
430  mMap = o.mMap;
431  mOutput = o.mOutput;
439  mTempSets = o.mTempSets;
440 
442  {
443  if (*i != nullptr)
444  (*i)->incRef();
445  }
446 
447  mOutputPauseLeft = 0;
448 
449  if (mParticleImage != nullptr)
451 
452  return *this;
453 }
454 
456 {
458  {
459  if (*i != nullptr)
460  (*i)->decRef();
461  }
462  mTempSets.clear();
463 
464  if (mParticleImage != nullptr)
465  {
467  mParticleImage = nullptr;
468  }
469 }
470 
471 template <typename T> ParticleEmitterProp<T>
472 ParticleEmitter::readParticleEmitterProp(XmlNodePtrConst propertyNode, T def)
473 {
474  ParticleEmitterProp<T> retval;
475 
476  def = static_cast<T>(XML::getDoubleProperty(propertyNode, "value",
477  static_cast<double>(def)));
478  retval.set(static_cast<T>(XML::getDoubleProperty(propertyNode, "min",
479  static_cast<double>(def))), static_cast<T>(XML::getDoubleProperty(
480  propertyNode, "max", static_cast<double>(def))));
481 
482  const std::string change = XML::getProperty(
483  propertyNode, "change-func", "none");
484  T amplitude = static_cast<T>(XML::getDoubleProperty(propertyNode,
485  "change-amplitude", 0.0));
486 
487  const int period = XML::getProperty(propertyNode, "change-period", 0);
488  const int phase = XML::getProperty(propertyNode, "change-phase", 0);
489  if (change == "saw" || change == "sawtooth")
490  {
492  amplitude, period, phase);
493  }
494  else if (change == "sine" || change == "sinewave")
495  {
497  amplitude, period, phase);
498  }
499  else if (change == "triangle")
500  {
502  amplitude, period, phase);
503  }
504  else if (change == "square")
505  {
507  amplitude, period, phase);
508  }
509 
510  return retval;
511 }
512 
514  STD_VECTOR<Particle*> &newParticles)
515 {
516  if (mOutputPauseLeft > 0)
517  {
518  mOutputPauseLeft --;
519  return;
520  }
522 
523  for (int i = mOutput.value(tick); i > 0; i--)
524  {
525  // Limit maximum particles
527  break;
528 
529  Particle *newParticle = nullptr;
530  if (mParticleImage != nullptr)
531  {
532  const std::string &name = mParticleImage->mIdPath;
535  {
537  }
538 
540  break;
541 
542  newParticle = new ImageParticle(mParticleImage);
543  newParticle->setMap(mMap);
544  }
545  else if (!mParticleRotation.mFrames.empty())
546  {
547  Animation *const newAnimation = new Animation(mParticleRotation);
548  newParticle = new RotationalParticle(newAnimation);
549  newParticle->setMap(mMap);
550  }
551  else if (!mParticleAnimation.mFrames.empty())
552  {
553  Animation *const newAnimation = new Animation(mParticleAnimation);
554  newParticle = new AnimationParticle(newAnimation);
555  newParticle->setMap(mMap);
556  }
557  else
558  {
559  newParticle = new Particle;
560  newParticle->setMap(mMap);
561  }
562 
563  const Vector position(mParticlePosX.value(tick),
564  mParticlePosY.value(tick),
565  mParticlePosZ.value(tick));
566  newParticle->moveTo(position);
567 
568  const float angleH = mParticleAngleHorizontal.value(tick);
569  const float cosAngleH = static_cast<float>(cos(angleH));
570  const float sinAngleH = static_cast<float>(sin(angleH));
571  const float angleV = mParticleAngleVertical.value(tick);
572  const float cosAngleV = static_cast<float>(cos(angleV));
573  const float sinAngleV = static_cast<float>(sin(angleV));
574  const float power = mParticlePower.value(tick);
575  newParticle->setVelocity(cosAngleH * cosAngleV * power,
576  sinAngleH * cosAngleV * power,
577  sinAngleV * power);
578 
579  newParticle->setRandomness(mParticleRandomness.value(tick));
580  newParticle->setGravity(mParticleGravity.value(tick));
581  newParticle->setBounce(mParticleBounce.value(tick));
582  newParticle->setFollow(mParticleFollow);
583 
584  newParticle->setDestination(mParticleTarget,
586  mParticleMomentum.value(tick));
587 
588  newParticle->setDieDistance(mParticleDieDistance.value(tick));
589 
590  newParticle->setLifetime(mParticleLifetime.value(tick));
591  newParticle->setFadeOut(mParticleFadeOut.value(tick));
592  newParticle->setFadeIn(mParticleFadeIn.value(tick));
593  newParticle->setAlpha(mParticleAlpha.value(tick));
594 
596  newParticle->addEmitter(new ParticleEmitter(*it));
597 
598  if (!mDeathEffect.empty())
600 
601  newParticles.push_back(newParticle);
602  }
603 }
604 
605 void ParticleEmitter::adjustSize(const int w, const int h)
606 {
607  if (w == 0 || h == 0)
608  return; // new dimensions are illegal
609 
610  // calculate the old rectangle
611  const int oldArea = CAST_S32(
614  if (oldArea == 0)
615  {
616  // when the effect has no dimension it is
617  // not designed to be resizeable
618  return;
619  }
620 
621  // set the new dimensions
622  mParticlePosX.set(0, static_cast<float>(w));
623  mParticlePosY.set(0, static_cast<float>(h));
624  const int newArea = w * h;
625  // adjust the output so that the particle density stays the same
626  const float outputFactor = static_cast<float>(newArea)
627  / static_cast<float>(oldArea);
628  mOutput.minVal = CAST_S32(static_cast<float>(
629  mOutput.minVal) * outputFactor);
630  mOutput.maxVal = CAST_S32(static_cast<float>(
631  mOutput.maxVal) * outputFactor);
632 }
void setFunction(ParticleChangeFuncT func, T amplitude, const int period, const int phase)
std::vector< ImageSet * > mTempSets
Animation mParticleAnimation
static void instantiate(std::string &target, const std::string &palettes)
Definition: dye.cpp:96
#define FOR_EACH(type, iter, array)
Definition: foreach.h:24
ParticleEmitterProp< float > mParticleAcceleration
void createParticles(const int tick, std::vector< Particle *> &newParticles)
void log1(const char *const log_text)
Definition: logger.cpp:222
signed char mDeathEffectConditions
static ParticleEmitterProp< T > readParticleEmitterProp(xmlNode *const propertyNode, T def)
ParticleEmitter & operator=(const ParticleEmitter &o)
void setRandomness(const int r)
Definition: particle.h:156
T value(int tick) const
Image * get(const size_type i) const
Definition: imageset.cpp:66
ParticleEmitterProp< int > mParticleFadeOut
ParticleEmitterProp< float > mParticlePosY
void setVelocity(const float x, const float y, const float z)
Definition: particle.h:142
virtual void setDeathEffect(const std::string &effectFile, const signed char conditions)
Definition: particle.h:239
ParticleEmitterProp< int > mParticleFadeIn
void setAlpha(const float alpha)
Definition: particle.h:236
Definition: vector.h:38
ImageSet * getImageSet(const std::string &imagePath, const int w, const int h)
int getProperty(const xmlNodePtr node, const char *const name, int def)
Definition: libxml.cpp:171
void setBounce(const float bouncieness)
Definition: particle.h:163
ParticleEmitterProp< float > mParticlePosX
Particle * mParticleTarget
ImageSet * getImageSet(xmlNode *const node)
ParticleEmitterProp< float > mParticleAlpha
ParticleEmitterProp< float > mParticleGravity
Logger * logger
Definition: logger.cpp:95
bool find(const std::string &key)
ParticleEmitterProp< float > mParticleMomentum
static int particleCount
void addTerminator(const int rand)
Definition: animation.cpp:55
int getIntProperty(const xmlNodePtr node, const char *const name, int def, const int min, const int max)
Definition: libxml.cpp:187
ParticleEmitterProp< int > mParticleRandomness
#define CAST_S32
Definition: cast.h:29
static const float DEG_RAD_FACTOR
void setFadeIn(const int fadeIn)
Definition: particle.h:136
void moveTo(const Vector &pos)
Definition: particle.h:106
ParticleEmitterProp< float > mParticleDieDistance
std::vector< ImageSet * >::const_iterator ImageSetVectorCIter
Image * getImage(const std::string &idPath)
Definition: imageloader.cpp:85
void set(const T min, const T max)
bool getBoolProperty(const xmlNodePtr node, const char *const name, const bool def)
Definition: libxml.cpp:266
ImageSet * getSubImageSet(Image *const parent, const int width, const int height)
double getDoubleProperty(const xmlNodePtr node, const char *const name, double def)
Definition: libxml.cpp:224
ParticleEmitterProp< int > mOutputPause
void setFadeOut(const int fadeOut)
Definition: particle.h:129
#define nullptr
Definition: localconsts.h:44
#define CAST_S8
Definition: cast.h:25
void setFollow(const bool follow)
Definition: particle.h:169
virtual void setMap(Map *const map)
Definition: actor.cpp:47
static StringIntMap imageParticleCountByName
Definition: imageparticle.h:58
static int maxCount
int getWidth() const
Definition: imageset.h:56
void setGravity(const float gravity)
Definition: particle.h:150
std::string mDeathEffect
ParticleEmitterProp< int > mOutput
Definition: map.h:68
Image * getSubImage(Image *const parent, const int x, const int y, const int width, const int height)
virtual void incRef()
Definition: resource.cpp:37
Frames mFrames
Definition: animation.h:98
void adjustSize(const int w, const int h)
ParticleEmitter(const xmlNode *const emitterNode, Particle *const target, Map *const map, const int rotation=0, const std::string &dyePalettes=std::string())
#define for_each_xml_child_node(var, parent)
Definition: libxml.h:160
void addEmitter(ParticleEmitter *const emitter)
Definition: particle.h:100
std::string mMap
Definition: gamerecv.cpp:45
ParticleEmitterProp< int > mParticleLifetime
void setDestination(Particle *const target, const float accel, const float moment)
Definition: particle.h:182
ParticleEmitterProp< float > mParticlePower
std::list< ParticleEmitter >::const_iterator ParticleEmitterListCIter
Definition: image.h:61
void addFrame(Image *const image, const int delay, const int offsetX, const int offsetY, const int rand)
Definition: animation.cpp:45
static const int mapTileSize
Definition: map.h:26
void setLifetime(const int lifetime)
Definition: particle.h:122
void setDieDistance(const float dist)
Definition: particle.h:193
static const float SIN45
void log(const char *const log_text,...)
Definition: logger.cpp:243
std::string mIdPath
Definition: resource.h:83
ParticleEmitterProp< float > mParticleBounce
ParticleEmitterProp< float > mParticleAngleVertical
ParticleEmitterProp< float > mParticlePosZ
std::list< ParticleEmitter > mParticleChildEmitters
void decRef()
Definition: image.cpp:521
ParticleEmitterProp< float > mParticleAngleHorizontal
int getHeight() const
Definition: imageset.h:62
Animation mParticleRotation