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