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