ManaPlus
particle.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 
23 #include "particle/particle.h"
24 
25 #include "actormanager.h"
26 #include "logger.h"
27 
28 #include "being/actorsprite.h"
29 
33 
35 
36 #include "resources/dye/dye.h"
37 
38 #include "resources/image/image.h"
39 
42 
43 #include "utils/delete2.h"
44 #include "utils/dtor.h"
45 #include "utils/foreach.h"
46 #include "utils/likely.h"
47 #include "utils/mathutils.h"
48 #include "utils/mrand.h"
49 
50 #include "debug.h"
51 
52 static const float SIN45 = 0.707106781F;
53 static const double PI = M_PI;
54 static const float PI2 = 2 * M_PI;
55 
56 class Graphics;
57 
59  Actor(),
60  mAlpha(1.0F),
61  mLifetimeLeft(-1),
62  mLifetimePast(0),
63  mFadeOut(0),
64  mFadeIn(0),
65  mVelocity(),
66  mAlive(AliveStatus::ALIVE),
67  mType(ParticleType::Normal),
68  mAnimation(nullptr),
69  mImage(nullptr),
70  mActor(BeingId_zero),
71  mChildEmitters(),
72  mChildParticles(),
73  mChildMoveParticles(),
74  mDeathEffect(),
75  mGravity(0.0F),
76  mBounce(0.0F),
77  mAcceleration(0.0F),
78  mInvDieDistance(-1.0F),
79  mMomentum(1.0F),
80  mTarget(nullptr),
81  mRandomness(0),
82  mDeathEffectConditions(0x00),
83  mAutoDelete(true),
84  mAllowSizeAdjust(false),
85  mFollow(false)
86 {
88 }
89 
91 {
92  if (mActor != BeingId_zero &&
93  (actorManager != nullptr))
94  {
95  ActorSprite *const actor = actorManager->findActor(mActor);
96  if (actor != nullptr)
97  actor->controlParticleDeleted(this);
98  }
99  // Delete child emitters and child particles
100  clear();
102  if (mImage != nullptr)
103  {
104  if (mType == ParticleType::Image)
105  {
106  const std::string &restrict name = mImage->mIdPath;
110  {
111  int &cnt = (*it).second;
112  if (cnt > 0)
113  cnt --;
114  }
115  mImage->decRef();
116  }
117  mImage = nullptr;
118  }
119 
121 }
122 
124  const int offsetX A_UNUSED,
125  const int offsetY A_UNUSED) const restrict2
126 {
127 }
128 
130 {
131  // calculate particle movement
132  if (A_LIKELY(mMomentum != 1.0F))
133  mVelocity *= mMomentum;
134 
135  if ((mTarget != nullptr) && mAcceleration != 0.0F)
136  {
137  Vector dist = mPos - mTarget->mPos;
138  dist.x *= SIN45;
139  float invHypotenuse;
140 
142  {
144  invHypotenuse = fastInvSqrt(
145  dist.x * dist.x + dist.y * dist.y + dist.z * dist.z);
146  break;
148  if (dist.x == 0.0f)
149  {
150  invHypotenuse = 0;
151  break;
152  }
153 
154  invHypotenuse = 2.0F / (static_cast<float>(fabs(dist.x))
155  + static_cast<float>(fabs(dist.y))
156  + static_cast<float>(fabs(dist.z)));
157  break;
159  default:
160  invHypotenuse = 1.0F / static_cast<float>(sqrt(
161  dist.x * dist.x + dist.y * dist.y + dist.z * dist.z));
162  break;
163  }
164 
165  if (invHypotenuse != 0.0f)
166  {
167  if (mInvDieDistance > 0.0F && invHypotenuse > mInvDieDistance)
169  const float accFactor = invHypotenuse * mAcceleration;
170  mVelocity -= dist * accFactor;
171  }
172  }
173 
174  if (A_LIKELY(mRandomness >= 10)) // reduce useless calculations
175  {
176  const int rand2 = mRandomness * 2;
177  mVelocity.x += static_cast<float>(mrand() % rand2 - mRandomness)
178  / 1000.0F;
179  mVelocity.y += static_cast<float>(mrand() % rand2 - mRandomness)
180  / 1000.0F;
181  mVelocity.z += static_cast<float>(mrand() % rand2 - mRandomness)
182  / 1000.0F;
183  }
184 
185  mVelocity.z -= mGravity;
186 
187  // Update position
188  mPos.x += mVelocity.x;
189  mPos.y += mVelocity.y * SIN45;
190  mPos.z += mVelocity.z * SIN45;
191 
192  // Update other stuff
193  if (A_LIKELY(mLifetimeLeft > 0))
194  mLifetimeLeft--;
195 
196  mLifetimePast++;
197 
198  if (mPos.z < 0.0F)
199  {
200  if (mBounce > 0.0F)
201  {
202  mPos.z *= -mBounce;
203  mVelocity *= mBounce;
204  mVelocity.z = -mVelocity.z;
205  }
206  else
207  {
209  }
210  }
212  {
214  }
215 
216  // Update child emitters
217  if ((ParticleEngine::emitterSkip != 0) &&
219  {
221  {
222  STD_VECTOR<Particle*> newParticles;
223  (*e)->createParticles(mLifetimePast, newParticles);
224  FOR_EACH (STD_VECTOR<Particle*>::const_iterator,
225  it,
226  newParticles)
227  {
228  Particle *const p = *it;
229  p->moveBy(mPos);
230  mChildParticles.push_back(p);
231  if (p->mFollow)
232  mChildMoveParticles.push_back(p);
233  }
234  }
235  }
236 
237  // create death effect when the particle died
240  {
242  > 0x00 && !mDeathEffect.empty())
243  {
244  Particle *restrict const deathEffect = particleEngine->addEffect(
245  mDeathEffect, 0, 0);
246  if (deathEffect != nullptr)
247  deathEffect->moveBy(mPos);
248  }
250  }
251 }
252 
254 {
256  {
257  if (A_UNLIKELY(mLifetimeLeft == 0))
258  {
260  if (mChildParticles.empty())
261  {
262  if (mAutoDelete)
263  return false;
264  return true;
265  }
266  }
267  else
268  {
269  if (mAnimation != nullptr)
270  {
272  {
273  // particle engine is updated every 10ms
274  mAnimation->update(10);
275  }
276  else // ParticleType::Rotational
277  {
278  // TODO: cache velocities to avoid spamming atan2()
279  const int size = mAnimation->getLength();
280  if (size == 0)
281  return false;
282 
283  float rad = static_cast<float>(atan2(mVelocity.x,
284  mVelocity.y));
285  if (rad < 0)
286  rad = PI2 + rad;
287 
288  const float range = static_cast<float>(PI / size);
289 
290  // Determines which frame the particle should play
291  if (A_UNLIKELY(rad < range || rad > PI2 - range))
292  {
293  mAnimation->setFrame(0);
294  }
295  else
296  {
297  const float range2 = 2 * range;
298  // +++ need move condition outside of for
299  for (int c = 1; c < size; c++)
300  {
301  const float cRange = static_cast<float>(c) *
302  range2;
303  if (cRange - range < rad &&
304  rad < cRange + range)
305  {
306  mAnimation->setFrame(c);
307  break;
308  }
309  }
310  }
311  }
313  }
314  const Vector oldPos = mPos;
315 
316  updateSelf();
317 
318  const Vector change = mPos - oldPos;
319  if (mChildParticles.empty())
320  {
321  if (mAlive != AliveStatus::ALIVE &&
322  mAutoDelete)
323  {
324  return false;
325  }
326  return true;
327  }
328  for (ParticleIterator p = mChildMoveParticles.begin(),
329  fp2 = mChildMoveParticles.end(); p != fp2; )
330  {
331  // move particle with its parent if desired
332  (*p)->moveBy(change);
333  ++p;
334  }
335  }
336 
337  // Update child particles
338  for (ParticleIterator p = mChildParticles.begin(),
339  fp2 = mChildParticles.end(); p != fp2; )
340  {
341  Particle *restrict const particle = *p;
342  // update particle
343  if (A_LIKELY(particle->update()))
344  {
345  ++p;
346  }
347  else
348  {
349  mChildMoveParticles.remove(*p);
350  delete particle;
351  p = mChildParticles.erase(p);
352  }
353  }
355  mChildParticles.empty() &&
356  mAutoDelete))
357  {
358  return false;
359  }
360  }
361  else
362  {
363  if (mChildParticles.empty())
364  {
365  if (mAutoDelete)
366  return false;
367  return true;
368  }
369  // Update child particles
370  for (ParticleIterator p = mChildParticles.begin(),
371  fp2 = mChildParticles.end(); p != fp2; )
372  {
373  Particle *restrict const particle = *p;
374  // update particle
375  if (A_LIKELY(particle->update()))
376  {
377  ++p;
378  }
379  else
380  {
381  mChildMoveParticles.remove(*p);
382  delete particle;
383  p = mChildParticles.erase(p);
384  }
385  }
386  if (A_UNLIKELY(mChildParticles.empty() &&
387  mAutoDelete))
388  {
389  return false;
390  }
391  }
392 
393  return true;
394 }
395 
397 {
398  mPos += change;
400  {
401  (*p)->moveBy(change);
402  }
403 }
404 
405 void Particle::moveTo(const float x, const float y) restrict2
406 {
407  moveTo(Vector(x, y, mPos.z));
408 }
409 
410 Particle *Particle::addEffect(const std::string &restrict particleEffectFile,
411  const int pixelX, const int pixelY,
412  const int rotation) restrict2
413 {
414  Particle *newParticle = nullptr;
415 
416  const size_t pos = particleEffectFile.find('|');
417  const std::string dyePalettes = (pos != std::string::npos)
418  ? particleEffectFile.substr(pos + 1) : "";
419  XML::Document *doc = Loader::getXml(particleEffectFile.substr(0, pos),
422  if (doc == nullptr)
423  return nullptr;
424  XmlNodeConstPtrConst rootNode = doc->rootNode();
425 
426  if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "effect"))
427  {
428  logger->log("Error loading particle: %s", particleEffectFile.c_str());
429  doc->decRef();
430  return nullptr;
431  }
432 
433  // Parse particles
434  for_each_xml_child_node(effectChildNode, rootNode)
435  {
436  // We're only interested in particles
437  if (!xmlNameEqual(effectChildNode, "particle"))
438  continue;
439 
440  // Determine the exact particle type
441  XmlNodePtr node;
442 
443  // Animation
444  if ((node = XML::findFirstChildByName(effectChildNode, "animation")) !=
445  nullptr)
446  {
447  newParticle = new AnimationParticle(node, dyePalettes);
448  newParticle->setMap(mMap);
449  }
450  // Rotational
451  else if ((node = XML::findFirstChildByName(
452  effectChildNode, "rotation")) != nullptr)
453  {
454  newParticle = new RotationalParticle(node, dyePalettes);
455  newParticle->setMap(mMap);
456  }
457  // Image
458  else if ((node = XML::findFirstChildByName(effectChildNode,
459  "image")) != nullptr)
460  {
461  std::string imageSrc;
462  if (XmlHaveChildContent(node))
463  imageSrc = XmlChildContent(node);
464  if (!imageSrc.empty() && !dyePalettes.empty())
465  Dye::instantiate(imageSrc, dyePalettes);
466  Image *const img = Loader::getImage(imageSrc);
467 
468  newParticle = new ImageParticle(img);
469  newParticle->setMap(mMap);
470  }
471  // Other
472  else
473  {
474  newParticle = new Particle;
475  newParticle->setMap(mMap);
476  }
477 
478  // Read and set the basic properties of the particle
479  const float offsetX = XML::getFloatProperty(
480  effectChildNode, "position-x", 0);
481  const float offsetY = XML::getFloatProperty(
482  effectChildNode, "position-y", 0);
483  const float offsetZ = XML::getFloatProperty(
484  effectChildNode, "position-z", 0);
485  const Vector position(mPos.x + static_cast<float>(pixelX) + offsetX,
486  mPos.y + static_cast<float>(pixelY) + offsetY,
487  mPos.z + offsetZ);
488  newParticle->moveTo(position);
489 
490  const int lifetime = XML::getProperty(effectChildNode, "lifetime", -1);
491  newParticle->setLifetime(lifetime);
492  const bool resizeable = "false" != XML::getProperty(effectChildNode,
493  "size-adjustable", "false");
494 
495  newParticle->setAllowSizeAdjust(resizeable);
496 
497  // Look for additional emitters for this particle
498  for_each_xml_child_node(emitterNode, effectChildNode)
499  {
500  if (xmlNameEqual(emitterNode, "emitter"))
501  {
502  ParticleEmitter *restrict const newEmitter =
503  new ParticleEmitter(
504  emitterNode,
505  newParticle,
506  mMap,
507  rotation,
508  dyePalettes);
509  newParticle->addEmitter(newEmitter);
510  }
511  else if (xmlNameEqual(emitterNode, "deatheffect"))
512  {
513  std::string deathEffect;
514  if ((node != nullptr) && XmlHaveChildContent(node))
515  deathEffect = XmlChildContent(emitterNode);
516 
517  char deathEffectConditions = 0x00;
518  if (XML::getBoolProperty(emitterNode, "on-floor", true))
519  {
520  deathEffectConditions += CAST_S8(
522  }
523  if (XML::getBoolProperty(emitterNode, "on-sky", true))
524  {
525  deathEffectConditions += CAST_S8(
527  }
528  if (XML::getBoolProperty(emitterNode, "on-other", false))
529  {
530  deathEffectConditions += CAST_S8(
532  }
533  if (XML::getBoolProperty(emitterNode, "on-impact", true))
534  {
535  deathEffectConditions += CAST_S8(
537  }
538  if (XML::getBoolProperty(emitterNode, "on-timeout", true))
539  {
540  deathEffectConditions += CAST_S8(
542  }
543  newParticle->setDeathEffect(
544  deathEffect, deathEffectConditions);
545  }
546  }
547 
548  mChildParticles.push_back(newParticle);
549  }
550 
551  doc->decRef();
552  return newParticle;
553 }
554 
555 void Particle::adjustEmitterSize(const int w, const int h) restrict2
556 {
557  if (mAllowSizeAdjust)
558  {
560  (*e)->adjustSize(w, h);
561  }
562 }
563 
565 {
567  {
568  Particle *restrict const particle = *p;
569  if (particle == nullptr)
570  continue;
571  particle->prepareToDie();
572  if (particle->isAlive() &&
573  particle->mLifetimeLeft == -1 &&
574  particle->mAutoDelete)
575  {
576  particle->kill();
577  }
578  }
579 }
580 
582 {
584  mChildEmitters.clear();
585 
587  mChildParticles.clear();
588 
589  mChildMoveParticles.clear();
590 }
#define CAST_U32
Definition: cast.h:30
void draw(Graphics *const graphics, const int offsetX, const int offsetY) const A_CONST
Definition: particle.cpp:123
static void instantiate(std::string &target, const std::string &palettes)
Definition: dye.cpp:96
#define FOR_EACH(type, iter, array)
Definition: foreach.h:24
void controlParticleDeleted(const Particle *const particle)
const bool SkipError_false
Definition: skiperror.h:29
float fastInvSqrt(float x)
Definition: mathutils.h:209
float getFloatProperty(const xmlNodePtr node, const char *const name, float def)
Definition: libxml.cpp:208
static const double PI
Definition: particle.cpp:53
virtual void setDeathEffect(const std::string &effectFile, const signed char conditions)
Definition: particle.h:239
Vector mVelocity
Definition: particle.h:265
Particle()
Definition: particle.cpp:58
virtual void decRef()
Definition: resource.cpp:49
Definition: vector.h:38
void clear()
Definition: particle.cpp:581
void setAllowSizeAdjust(const bool adjust)
Definition: particle.h:202
int mRandomness
Definition: particle.h:313
void prepareToDie()
Definition: particle.cpp:564
Vector mPos
Definition: actor.h:139
ParticleEngine * particleEngine
StringIntMap::iterator StringIntMapIter
Definition: stringmap.h:28
float mGravity
Definition: particle.h:294
int getProperty(const xmlNodePtr node, const char *const name, int def)
Definition: libxml.cpp:171
void updateSelf()
Definition: particle.cpp:129
std::string mDeathEffect
Definition: particle.h:290
Particle * addEffect(const std::string &particleEffectFile, const int pixelX, const int pixelY, const int rotation=0)
static int emitterSkip
float mInvDieDistance
Definition: particle.h:304
#define delete2(var)
Definition: delete2.h:24
#define A_UNLIKELY(x)
Definition: likely.h:29
int mLifetimeLeft
Definition: particle.h:253
ParticleTypeT mType
Definition: particle.h:270
Logger * logger
Definition: logger.cpp:95
static int particleCount
BeingId mActor
Definition: particle.h:278
const bool UseVirtFs_true
Definition: usevirtfs.h:29
void delete_all(Container &c)
Definition: dtor.h:55
Particles::iterator ParticleIterator
void moveTo(const Vector &pos)
Definition: particle.h:106
bool mAutoDelete
Definition: particle.h:320
xmlNodePtr findFirstChildByName(const xmlNode *const parent, const char *const name)
Definition: libxml.cpp:286
Image * getImage(const std::string &idPath)
Definition: imageloader.cpp:85
static const float PARTICLE_SKY
static const float PI2
Definition: particle.cpp:54
float mMomentum
Definition: particle.h:307
bool update(const int timePassed)
bool getBoolProperty(const xmlNodePtr node, const char *const name, const bool def)
Definition: libxml.cpp:266
static ParticlePhysicsT fastPhysics
bool mAlpha
Definition: graphics.h:537
float mAcceleration
Definition: particle.h:300
Image * mImage
Definition: particle.h:276
SimpleAnimation * mAnimation
Definition: particle.h:273
#define nullptr
Definition: localconsts.h:44
bool update()
Definition: particle.cpp:253
virtual ~Particle()
Definition: particle.cpp:90
#define CAST_S8
Definition: cast.h:25
virtual void setMap(Map *const map)
Definition: actor.cpp:47
static StringIntMap imageParticleCountByName
Definition: imageparticle.h:58
ActorSprite * findActor(const BeingId id) const
bool mAllowSizeAdjust
Definition: particle.h:323
#define A_LIKELY(x)
Definition: likely.h:28
XML::Document * getXml(const std::string &idPath, const UseVirtFs useResman, const SkipError skipError)
Definition: xmlloader.cpp:55
xmlNodePtr rootNode()
Definition: libxml.cpp:166
float x
Definition: vector.h:208
float mBounce
Definition: particle.h:297
#define A_UNUSED
Definition: localconsts.h:171
int getLength() const
#define for_each_xml_child_node(var, parent)
Definition: libxml.h:160
Emitters::const_iterator EmitterConstIterator
int mLifetimePast
Definition: particle.h:256
Image * getCurrentImage() const
void addEmitter(ParticleEmitter *const emitter)
Definition: particle.h:100
int mrand()
Definition: mrand.cpp:40
Particle * addEffect(const std::string &particleEffectFile, const int pixelX, const int pixelY, const int rotation=0)
Definition: particle.cpp:410
float y
Definition: vector.h:208
Definition: actor.h:40
Definition: image.h:61
Particles mChildMoveParticles
Definition: particle.h:287
void setFrame(int frame)
Emitters mChildEmitters
Definition: particle.h:282
#define restrict
Definition: localconsts.h:176
void setLifetime(const int lifetime)
Definition: particle.h:122
void moveBy(const Vector &change)
Definition: particle.cpp:396
const BeingId BeingId_zero
Definition: beingid.h:29
static const float SIN45
Definition: particle.cpp:52
void log(const char *const log_text,...)
Definition: logger.cpp:243
std::string mIdPath
Definition: resource.h:83
Particles mChildParticles
Definition: particle.h:285
void decRef()
Definition: image.cpp:521
Particle * mTarget
Definition: particle.h:310
Particles::const_iterator ParticleConstIterator
#define restrict2
Definition: localconsts.h:177
void adjustEmitterSize(const int w, const int h)
Definition: particle.cpp:555
signed char mDeathEffectConditions
Definition: particle.h:317
ActorManager * actorManager
bool mFollow
Definition: particle.h:326
float z
Definition: vector.h:208
AliveStatusT mAlive
Definition: particle.h:268
Map * mMap
Definition: actor.h:138