GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/particle/particleemitter.cpp Lines: 1 304 0.3 %
Date: 2017-11-29 Branches: 0 366 0.0 %

Line Branch Exec Source
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/particleemitter.h"
24
25
#include "logger.h"
26
27
#include "const/resources/map/map.h"
28
29
#include "particle/animationparticle.h"
30
#include "particle/rotationalparticle.h"
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
40
#include "resources/loaders/imageloader.h"
41
#include "resources/loaders/imagesetloader.h"
42
#include "resources/loaders/subimageloader.h"
43
#include "resources/loaders/subimagesetloader.h"
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);
76
    mParticleAngleHorizontal.set(0.0F);
77
    mParticleAngleVertical.set(0.0F);
78
    mParticlePower.set(0.0F);
79
    mParticleGravity.set(0.0F);
80
    mParticleRandomness.set(0);
81
    mParticleBounce.set(0.0F);
82
    mParticleAcceleration.set(0.0F);
83
    mParticleDieDistance.set(-1.0F);
84
    mParticleMomentum.set(1.0F);
85
    mParticleLifetime.set(-1);
86
    mParticleFadeOut.set(0);
87
    mParticleFadeIn.set(0);
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);
108
                mParticlePosY.minVal *= SIN45;
109
                mParticlePosY.maxVal *= SIN45;
110
                mParticlePosY.changeAmplitude *= SIN45;
111
            }
112
            else if (name == "position-z")
113
            {
114
                mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0F);
115
                mParticlePosZ.minVal *= SIN45;
116
                mParticlePosZ.maxVal *= SIN45;
117
                mParticlePosZ.changeAmplitude *= SIN45;
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);
128
                    mParticleImage = Loader::getImage(image);
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
                    {
143
                        mParticleImage = Loader::getSubImage(img,
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
            {
158
                mParticleAngleHorizontal =
159
                    readParticleEmitterProp(propertyNode, 0.0F);
160
                mParticleAngleHorizontal.minVal
161
                    += static_cast<float>(rotation);
162
                mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR;
163
                mParticleAngleHorizontal.maxVal
164
                    += static_cast<float>(rotation);
165
                mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR;
166
                mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR;
167
            }
168
            else if (name == "vertical-angle")
169
            {
170
                mParticleAngleVertical =
171
                    readParticleEmitterProp(propertyNode, 0.0F);
172
                mParticleAngleVertical.minVal *= DEG_RAD_FACTOR;
173
                mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR;
174
                mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR;
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);
196
                mParticleLifetime.minVal += 1;
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);
206
                mOutputPauseLeft = mOutputPause.value(0);
207
            }
208
            else if (name == "acceleration")
209
            {
210
                mParticleAcceleration = readParticleEmitterProp(
211
                    propertyNode, 0.0F);
212
            }
213
            else if (name == "die-distance")
214
            {
215
                mParticleDieDistance = readParticleEmitterProp(
216
                    propertyNode, 0.0F);
217
            }
218
            else if (name == "momentum")
219
            {
220
                mParticleMomentum = readParticleEmitterProp(
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")) !=
267
                0 ? mParticleRotation : mParticleAnimation;
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
            {
341
                mDeathEffectConditions += CAST_S8(
342
                    AliveStatus::DEAD_FLOOR);
343
            }
344
            if (XML::getBoolProperty(propertyNode, "on-sky", true))
345
            {
346
                mDeathEffectConditions += CAST_S8(
347
                    AliveStatus::DEAD_SKY);
348
            }
349
            if (XML::getBoolProperty(propertyNode, "on-other", false))
350
            {
351
                mDeathEffectConditions += CAST_S8(
352
                    AliveStatus::DEAD_OTHER);
353
            }
354
            if (XML::getBoolProperty(propertyNode, "on-impact", true))
355
            {
356
                mDeathEffectConditions += CAST_S8(
357
                    AliveStatus::DEAD_IMPACT);
358
            }
359
            if (XML::getBoolProperty(propertyNode, "on-timeout", true))
360
            {
361
                mDeathEffectConditions += CAST_S8(
362
                    AliveStatus::DEAD_TIMEOUT);
363
            }
364
        }
365
    }
366
}
367
368
ParticleEmitter::ParticleEmitter(const ParticleEmitter &o)
369
{
370
    *this = o;
371
}
372
373
ImageSet *ParticleEmitter::getImageSet(XmlNodePtrConst node)
374
{
375
    ImageSet *imageset = nullptr;
376
    const int subX = XML::getProperty(node, "subX", -1);
377
    if (subX != -1)
378
    {
379
        Image *const img = Loader::getImage(XML::getProperty(
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
410
ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o)
411
{
412
    mParticlePosX = o.mParticlePosX;
413
    mParticlePosY = o.mParticlePosY;
414
    mParticlePosZ = o.mParticlePosZ;
415
    mParticleAngleHorizontal = o.mParticleAngleHorizontal;
416
    mParticleAngleVertical = o.mParticleAngleVertical;
417
    mParticlePower = o.mParticlePower;
418
    mParticleGravity = o.mParticleGravity;
419
    mParticleRandomness = o.mParticleRandomness;
420
    mParticleBounce = o.mParticleBounce;
421
    mParticleFollow = o.mParticleFollow;
422
    mParticleTarget = o.mParticleTarget;
423
    mParticleAcceleration = o.mParticleAcceleration;
424
    mParticleDieDistance = o.mParticleDieDistance;
425
    mParticleMomentum = o.mParticleMomentum;
426
    mParticleLifetime = o.mParticleLifetime;
427
    mParticleFadeOut = o.mParticleFadeOut;
428
    mParticleFadeIn = o.mParticleFadeIn;
429
    mParticleAlpha = o.mParticleAlpha;
430
    mMap = o.mMap;
431
    mOutput = o.mOutput;
432
    mOutputPause = o.mOutputPause;
433
    mParticleImage = o.mParticleImage;
434
    mParticleAnimation = o.mParticleAnimation;
435
    mParticleRotation = o.mParticleRotation;
436
    mParticleChildEmitters = o.mParticleChildEmitters;
437
    mDeathEffectConditions = o.mDeathEffectConditions;
438
    mDeathEffect = o.mDeathEffect;
439
    mTempSets = o.mTempSets;
440
441
    FOR_EACH (ImageSetVectorCIter, i, mTempSets)
442
    {
443
        if (*i != nullptr)
444
            (*i)->incRef();
445
    }
446
447
    mOutputPauseLeft = 0;
448
449
    if (mParticleImage != nullptr)
450
        mParticleImage->incRef();
451
452
    return *this;
453
}
454
455
ParticleEmitter::~ParticleEmitter()
456
{
457
    FOR_EACH (ImageSetVectorCIter, i, mTempSets)
458
    {
459
        if (*i != nullptr)
460
            (*i)->decRef();
461
    }
462
    mTempSets.clear();
463
464
    if (mParticleImage != nullptr)
465
    {
466
        mParticleImage->decRef();
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
    {
491
        retval.setFunction(ParticleChangeFunc::FUNC_SAW,
492
            amplitude, period, phase);
493
    }
494
    else if (change == "sine" || change == "sinewave")
495
    {
496
        retval.setFunction(ParticleChangeFunc::FUNC_SINE,
497
            amplitude, period, phase);
498
    }
499
    else if (change == "triangle")
500
    {
501
        retval.setFunction(ParticleChangeFunc::FUNC_TRIANGLE,
502
            amplitude, period, phase);
503
    }
504
    else if (change == "square")
505
    {
506
        retval.setFunction(ParticleChangeFunc::FUNC_SQUARE,
507
            amplitude, period, phase);
508
    }
509
510
    return retval;
511
}
512
513
void ParticleEmitter::createParticles(const int tick,
514
                                      STD_VECTOR<Particle*> &newParticles)
515
{
516
    if (mOutputPauseLeft > 0)
517
    {
518
        mOutputPauseLeft --;
519
        return;
520
    }
521
    mOutputPauseLeft = mOutputPause.value(tick);
522
523
    for (int i = mOutput.value(tick); i > 0; i--)
524
    {
525
        // Limit maximum particles
526
        if (ParticleEngine::particleCount > ParticleEngine::maxCount)
527
            break;
528
529
        Particle *newParticle = nullptr;
530
        if (mParticleImage != nullptr)
531
        {
532
            const std::string &name = mParticleImage->mIdPath;
533
            if (ImageParticle::imageParticleCountByName.find(name) ==
534
                ImageParticle::imageParticleCountByName.end())
535
            {
536
                ImageParticle::imageParticleCountByName[name] = 0;
537
            }
538
539
            if (ImageParticle::imageParticleCountByName[name] > 200)
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,
585
            mParticleAcceleration.value(tick),
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
595
        FOR_EACH (ParticleEmitterListCIter, it,  mParticleChildEmitters)
596
            newParticle->addEmitter(new ParticleEmitter(*it));
597
598
        if (!mDeathEffect.empty())
599
            newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions);
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(
612
        mParticlePosX.maxVal - mParticlePosX.minVal) * CAST_S32(
613
        mParticlePosX.maxVal - mParticlePosY.minVal);
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
4
}