3
* Copyright (C) 2006-2009 The Mana World Development Team
4
* Copyright (C) 2009-2010 The Mana Developers
5
* Copyright (C) 2011-2013 The ManaPlus Developers
7
* This file is part of The ManaPlus Client.
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
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.
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/>.
23
#include "particle/particleemitter.h"
27
#include "particle/animationparticle.h"
28
#include "particle/rotationalparticle.h"
30
#include "resources/dye.h"
31
#include "resources/image.h"
32
#include "resources/imageset.h"
33
#include "resources/resourcemanager.h"
39
static const float SIN45 = 0.707106781f;
40
static const float DEG_RAD_FACTOR = 0.017453293f;
42
typedef std::vector<ImageSet*>::const_iterator ImageSetVectorCIter;
43
typedef std::list<ParticleEmitter>::const_iterator ParticleEmitterListCIter;
45
ParticleEmitter::ParticleEmitter(const XmlNodePtr emitterNode,
46
Particle *const target,
47
Map *const map, const int rotation,
48
const std::string& dyePalettes) :
49
mParticleTarget(target),
51
mParticleImage(nullptr),
53
mDeathEffectConditions(0),
54
mParticleFollow(false)
56
// Initializing default values
57
mParticlePosX.set(0.0f);
58
mParticlePosY.set(0.0f);
59
mParticlePosZ.set(0.0f);
60
mParticleAngleHorizontal.set(0.0f);
61
mParticleAngleVertical.set(0.0f);
62
mParticlePower.set(0.0f);
63
mParticleGravity.set(0.0f);
64
mParticleRandomness.set(0);
65
mParticleBounce.set(0.0f);
66
mParticleAcceleration.set(0.0f);
67
mParticleDieDistance.set(-1.0f);
68
mParticleMomentum.set(1.0f);
69
mParticleLifetime.set(-1);
70
mParticleFadeOut.set(0);
71
mParticleFadeIn.set(0);
74
mParticleAlpha.set(1.0f);
76
for_each_xml_child_node(propertyNode, emitterNode)
78
if (xmlNameEqual(propertyNode, "property"))
80
const std::string name = XML::getProperty(
81
propertyNode, "name", "");
83
if (name == "position-x")
85
mParticlePosX = readParticleEmitterProp(propertyNode, 0.0f);
87
else if (name == "position-y")
89
mParticlePosY = readParticleEmitterProp(propertyNode, 0.0f);
90
mParticlePosY.minVal *= SIN45;
91
mParticlePosY.maxVal *= SIN45;
92
mParticlePosY.changeAmplitude *= SIN45;
94
else if (name == "position-z")
96
mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0f);
97
mParticlePosZ.minVal *= SIN45;
98
mParticlePosZ.maxVal *= SIN45;
99
mParticlePosZ.changeAmplitude *= SIN45;
101
else if (name == "image")
103
std::string image = XML::getProperty(
104
propertyNode, "value", "");
105
// Don't leak when multiple images are defined
106
if (!image.empty() && !mParticleImage)
108
if (!dyePalettes.empty())
109
Dye::instantiate(image, dyePalettes);
111
ResourceManager *const resman
112
= ResourceManager::getInstance();
113
mParticleImage = resman->getImage(image);
116
else if (name == "subimage")
118
std::string image = XML::getProperty(
119
propertyNode, "value", "");
120
// Don't leak when multiple images are defined
121
if (!image.empty() && !mParticleImage)
123
if (!dyePalettes.empty())
124
Dye::instantiate(image, dyePalettes);
126
ResourceManager *const resman
127
= ResourceManager::getInstance();
128
Image *img = resman->getImage(image);
131
mParticleImage = resman->getSubImage(img,
132
XML::getProperty(propertyNode, "x", 0),
133
XML::getProperty(propertyNode, "y", 0),
134
XML::getProperty(propertyNode, "width", 0),
135
XML::getProperty(propertyNode, "height", 0));
140
mParticleImage = nullptr;
144
else if (name == "horizontal-angle")
146
mParticleAngleHorizontal =
147
readParticleEmitterProp(propertyNode, 0.0f);
148
mParticleAngleHorizontal.minVal
149
+= static_cast<float>(rotation);
150
mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR;
151
mParticleAngleHorizontal.maxVal
152
+= static_cast<float>(rotation);
153
mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR;
154
mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR;
156
else if (name == "vertical-angle")
158
mParticleAngleVertical =
159
readParticleEmitterProp(propertyNode, 0.0f);
160
mParticleAngleVertical.minVal *= DEG_RAD_FACTOR;
161
mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR;
162
mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR;
164
else if (name == "power")
166
mParticlePower = readParticleEmitterProp(propertyNode, 0.0f);
168
else if (name == "gravity")
170
mParticleGravity = readParticleEmitterProp(propertyNode, 0.0f);
172
else if (name == "randomnes"
173
|| name == "randomness") // legacy bug
175
mParticleRandomness = readParticleEmitterProp(propertyNode, 0);
177
else if (name == "bounce")
179
mParticleBounce = readParticleEmitterProp(propertyNode, 0.0f);
181
else if (name == "lifetime")
183
mParticleLifetime = readParticleEmitterProp(propertyNode, 0);
184
mParticleLifetime.minVal += 1;
186
else if (name == "output")
188
mOutput = readParticleEmitterProp(propertyNode, 0);
191
else if (name == "output-pause")
193
mOutputPause = readParticleEmitterProp(propertyNode, 0);
194
mOutputPauseLeft = mOutputPause.value(0);
196
else if (name == "acceleration")
198
mParticleAcceleration = readParticleEmitterProp(
201
else if (name == "die-distance")
203
mParticleDieDistance = readParticleEmitterProp(
206
else if (name == "momentum")
208
mParticleMomentum = readParticleEmitterProp(
211
else if (name == "fade-out")
213
mParticleFadeOut = readParticleEmitterProp(propertyNode, 0);
215
else if (name == "fade-in")
217
mParticleFadeIn = readParticleEmitterProp(propertyNode, 0);
219
else if (name == "alpha")
221
mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0f);
223
else if (name == "follow-parent")
225
mParticleFollow = true;
229
logger->log("Particle Engine: Warning, "
230
"unknown emitter property \"%s\"",
234
else if (xmlNameEqual(propertyNode, "emitter"))
236
ParticleEmitter newEmitter(propertyNode, mParticleTarget, map,
237
rotation, dyePalettes);
238
mParticleChildEmitters.push_back(newEmitter);
240
else if (xmlNameEqual(propertyNode, "rotation"))
242
ImageSet *const imageset = getImageSet(propertyNode);
245
logger->log1("Error: no valid imageset");
248
mTempSets.push_back(imageset);
250
// Get animation frames
251
for_each_xml_child_node(frameNode, propertyNode)
253
const int delay = XML::getIntProperty(
254
frameNode, "delay", 0, 0, 100000);
255
const int offsetX = XML::getProperty(frameNode, "offsetX", 0)
256
- imageset->getWidth() / 2 + 16;
257
const int offsetY = XML::getProperty(frameNode, "offsetY", 0)
258
- imageset->getHeight() + 32;
259
const int rand = XML::getIntProperty(
260
frameNode, "rand", 100, 0, 100);
262
if (xmlNameEqual(frameNode, "frame"))
264
const int index = XML::getProperty(frameNode, "index", -1);
268
logger->log1("No valid value for 'index'");
272
Image *const img = imageset->get(index);
276
logger->log("No image at index %d", index);
280
mParticleRotation.addFrame(img, delay,
281
offsetX, offsetY, rand);
283
else if (xmlNameEqual(frameNode, "sequence"))
285
int start = XML::getProperty(frameNode, "start", -1);
286
const int end = XML::getProperty(frameNode, "end", -1);
288
if (start < 0 || end < 0)
290
logger->log1("No valid value for 'start' or 'end'");
296
Image *const img = imageset->get(start);
299
logger->log("No image at index %d", start);
303
mParticleRotation.addFrame(img, delay,
304
offsetX, offsetY, rand);
308
else if (xmlNameEqual(frameNode, "end"))
310
mParticleRotation.addTerminator(rand);
314
else if (xmlNameEqual(propertyNode, "animation"))
316
ImageSet *const imageset = getImageSet(propertyNode);
319
logger->log1("Error: no valid imageset");
322
mTempSets.push_back(imageset);
324
// Get animation frames
325
for_each_xml_child_node(frameNode, propertyNode)
327
const int delay = XML::getIntProperty(
328
frameNode, "delay", 0, 0, 100000);
329
const int offsetX = XML::getProperty(frameNode, "offsetX", 0)
330
- imageset->getWidth() / 2 + 16;
331
const int offsetY = XML::getProperty(frameNode, "offsetY", 0)
332
- imageset->getHeight() + 32;
333
const int rand = XML::getIntProperty(
334
frameNode, "rand", 100, 0, 100);
336
if (xmlNameEqual(frameNode, "frame"))
338
const int index = XML::getProperty(frameNode, "index", -1);
341
logger->log1("No valid value for 'index'");
345
Image *const img = imageset->get(index);
349
logger->log("No image at index %d", index);
353
mParticleAnimation.addFrame(img, delay,
354
offsetX, offsetY, rand);
356
else if (xmlNameEqual(frameNode, "sequence"))
358
int start = XML::getProperty(frameNode, "start", -1);
359
const int end = XML::getProperty(frameNode, "end", -1);
361
if (start < 0 || end < 0)
363
logger->log1("No valid value for 'start' or 'end'");
369
Image *const img = imageset->get(start);
373
logger->log("No image at index %d", start);
377
mParticleAnimation.addFrame(img, delay,
378
offsetX, offsetY, rand);
382
else if (xmlNameEqual(frameNode, "end"))
384
mParticleAnimation.addTerminator(rand);
388
else if (xmlNameEqual(propertyNode, "deatheffect"))
390
mDeathEffect = reinterpret_cast<const char*>(
391
propertyNode->xmlChildrenNode->content);
392
mDeathEffectConditions = 0x00;
393
if (XML::getBoolProperty(propertyNode, "on-floor", true))
395
mDeathEffectConditions += static_cast<signed char>(
396
Particle::DEAD_FLOOR);
398
if (XML::getBoolProperty(propertyNode, "on-sky", true))
400
mDeathEffectConditions += static_cast<signed char>(
403
if (XML::getBoolProperty(propertyNode, "on-other", false))
405
mDeathEffectConditions += static_cast<signed char>(
406
Particle::DEAD_OTHER);
408
if (XML::getBoolProperty(propertyNode, "on-impact", true))
410
mDeathEffectConditions += static_cast<signed char>(
411
Particle::DEAD_IMPACT);
413
if (XML::getBoolProperty(propertyNode, "on-timeout", true))
415
mDeathEffectConditions += static_cast<signed char>(
416
Particle::DEAD_TIMEOUT);
422
ParticleEmitter::ParticleEmitter(const ParticleEmitter &o)
427
ImageSet *ParticleEmitter::getImageSet(XmlNodePtr node)
429
ResourceManager *const resman = ResourceManager::getInstance();
430
ImageSet *imageset = nullptr;
431
const int subX = XML::getProperty(node, "subX", -1);
434
Image *const img = resman->getImage(XML::getProperty(
435
node, "imageset", ""));
439
Image *const img2 = resman->getSubImage(img, subX,
440
XML::getProperty(node, "subY", 0),
441
XML::getProperty(node, "subWidth", 0),
442
XML::getProperty(node, "subHeight", 0));
449
imageset = resman->getSubImageSet(img2,
450
XML::getProperty(node, "width", 0),
451
XML::getProperty(node, "height", 0));
457
imageset = resman->getImageSet(
458
XML::getProperty(node, "imageset", ""),
459
XML::getProperty(node, "width", 0),
460
XML::getProperty(node, "height", 0));
465
ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o)
467
mParticlePosX = o.mParticlePosX;
468
mParticlePosY = o.mParticlePosY;
469
mParticlePosZ = o.mParticlePosZ;
470
mParticleAngleHorizontal = o.mParticleAngleHorizontal;
471
mParticleAngleVertical = o.mParticleAngleVertical;
472
mParticlePower = o.mParticlePower;
473
mParticleGravity = o.mParticleGravity;
474
mParticleRandomness = o.mParticleRandomness;
475
mParticleBounce = o.mParticleBounce;
476
mParticleFollow = o.mParticleFollow;
477
mParticleTarget = o.mParticleTarget;
478
mParticleAcceleration = o.mParticleAcceleration;
479
mParticleDieDistance = o.mParticleDieDistance;
480
mParticleMomentum = o.mParticleMomentum;
481
mParticleLifetime = o.mParticleLifetime;
482
mParticleFadeOut = o.mParticleFadeOut;
483
mParticleFadeIn = o.mParticleFadeIn;
484
mParticleAlpha = o.mParticleAlpha;
487
mOutputPause = o.mOutputPause;
488
mParticleImage = o.mParticleImage;
489
mParticleAnimation = o.mParticleAnimation;
490
mParticleRotation = o.mParticleRotation;
491
mParticleChildEmitters = o.mParticleChildEmitters;
492
mDeathEffectConditions = o.mDeathEffectConditions;
493
mDeathEffect = o.mDeathEffect;
494
mTempSets = o.mTempSets;
496
FOR_EACH (ImageSetVectorCIter, i, mTempSets)
502
mOutputPauseLeft = 0;
505
mParticleImage->incRef();
510
ParticleEmitter::~ParticleEmitter()
512
FOR_EACH (ImageSetVectorCIter, i, mTempSets)
521
mParticleImage->decRef();
522
mParticleImage = nullptr;
526
template <typename T> ParticleEmitterProp<T>
527
ParticleEmitter::readParticleEmitterProp(XmlNodePtr propertyNode, T def)
529
ParticleEmitterProp<T> retval;
531
def = static_cast<T>(XML::getFloatProperty(propertyNode, "value",
532
static_cast<double>(def)));
533
retval.set(static_cast<T>(XML::getFloatProperty(propertyNode, "min",
534
static_cast<double>(def))), static_cast<T>(XML::getFloatProperty(
535
propertyNode, "max", static_cast<double>(def))));
537
const std::string change = XML::getProperty(
538
propertyNode, "change-func", "none");
539
T amplitude = static_cast<T>(XML::getFloatProperty(propertyNode,
540
"change-amplitude", 0.0));
542
const int period = XML::getProperty(propertyNode, "change-period", 0);
543
const int phase = XML::getProperty(propertyNode, "change-phase", 0);
544
if (change == "saw" || change == "sawtooth")
545
retval.setFunction(FUNC_SAW, amplitude, period, phase);
546
else if (change == "sine" || change == "sinewave")
547
retval.setFunction(FUNC_SINE, amplitude, period, phase);
548
else if (change == "triangle")
549
retval.setFunction(FUNC_TRIANGLE, amplitude, period, phase);
550
else if (change == "square")
551
retval.setFunction(FUNC_SQUARE, amplitude, period, phase);
556
std::list<Particle *> ParticleEmitter::createParticles(const int tick)
558
std::list<Particle *> newParticles;
560
if (mOutputPauseLeft > 0)
565
mOutputPauseLeft = mOutputPause.value(tick);
567
for (int i = mOutput.value(tick); i > 0; i--)
569
// Limit maximum particles
570
if (Particle::particleCount > Particle::maxCount)
573
Particle *newParticle;
576
const std::string name = mParticleImage->getIdPath();
577
if (ImageParticle::imageParticleCountByName.find(name) ==
578
ImageParticle::imageParticleCountByName.end())
580
ImageParticle::imageParticleCountByName[name] = 0;
583
if (ImageParticle::imageParticleCountByName[name] > 200)
586
newParticle = new ImageParticle(mMap, mParticleImage);
588
else if (!mParticleRotation.mFrames.empty())
590
Animation *const newAnimation = new Animation(mParticleRotation);
591
newParticle = new RotationalParticle(mMap, newAnimation);
593
else if (!mParticleAnimation.mFrames.empty())
595
Animation *const newAnimation = new Animation(mParticleAnimation);
596
newParticle = new AnimationParticle(mMap, newAnimation);
600
newParticle = new Particle(mMap);
603
const Vector position(mParticlePosX.value(tick),
604
mParticlePosY.value(tick),
605
mParticlePosZ.value(tick));
606
newParticle->moveTo(position);
608
const float angleH = mParticleAngleHorizontal.value(tick);
609
const float cosAngleH = cos(angleH);
610
const float sinAngleH = sin(angleH);
611
const float angleV = mParticleAngleVertical.value(tick);
612
const float cosAngleV = cos(angleV);
613
const float sinAngleV = sin(angleV);
614
const float power = mParticlePower.value(tick);
615
newParticle->setVelocity(cosAngleH * cosAngleV * power,
616
sinAngleH * cosAngleV * power,
619
newParticle->setRandomness(mParticleRandomness.value(tick));
620
newParticle->setGravity(mParticleGravity.value(tick));
621
newParticle->setBounce(mParticleBounce.value(tick));
622
newParticle->setFollow(mParticleFollow);
624
newParticle->setDestination(mParticleTarget,
625
mParticleAcceleration.value(tick),
626
mParticleMomentum.value(tick));
628
newParticle->setDieDistance(mParticleDieDistance.value(tick));
630
newParticle->setLifetime(mParticleLifetime.value(tick));
631
newParticle->setFadeOut(mParticleFadeOut.value(tick));
632
newParticle->setFadeIn(mParticleFadeIn.value(tick));
633
newParticle->setAlpha(mParticleAlpha.value(tick));
635
FOR_EACH (ParticleEmitterListCIter, it, mParticleChildEmitters)
636
newParticle->addEmitter(new ParticleEmitter(*it));
638
if (!mDeathEffect.empty())
639
newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions);
641
newParticles.push_back(newParticle);
647
void ParticleEmitter::adjustSize(const int w, const int h)
649
if (w == 0 || h == 0)
650
return; // new dimensions are illegal
652
// calculate the old rectangle
653
const int oldArea = static_cast<int>(
654
mParticlePosX.maxVal - mParticlePosX.minVal) * static_cast<int>(
655
mParticlePosX.maxVal - mParticlePosY.minVal);
658
// when the effect has no dimension it is
659
// not designed to be resizeable
663
// set the new dimensions
664
mParticlePosX.set(0, static_cast<float>(w));
665
mParticlePosY.set(0, static_cast<float>(h));
666
const int newArea = w * h;
667
// adjust the output so that the particle density stays the same
668
const float outputFactor = static_cast<float>(newArea)
669
/ static_cast<float>(oldArea);
670
mOutput.minVal = static_cast<int>(static_cast<float>(
671
mOutput.minVal) * outputFactor);
672
mOutput.maxVal = static_cast<int>(static_cast<float>(
673
mOutput.maxVal) * outputFactor);