GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/particle/particle.cpp Lines: 1 242 0.4 %
Date: 2017-11-29 Branches: 0 273 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/particle.h"
24
25
#include "actormanager.h"
26
#include "logger.h"
27
28
#include "being/actorsprite.h"
29
30
#include "particle/animationparticle.h"
31
#include "particle/particleemitter.h"
32
#include "particle/rotationalparticle.h"
33
34
#include "resources/animation/simpleanimation.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/xmlloader.h"
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
58
Particle::Particle() :
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
{
87
    ParticleEngine::particleCount++;
88
}
89
90
Particle::~Particle()
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();
101
    delete2(mAnimation);
102
    if (mImage != nullptr)
103
    {
104
        if (mType == ParticleType::Image)
105
        {
106
            const std::string &restrict name = mImage->mIdPath;
107
            StringIntMapIter it
108
                = ImageParticle::imageParticleCountByName.find(name);
109
            if (it != ImageParticle::imageParticleCountByName.end())
110
            {
111
                int &cnt = (*it).second;
112
                if (cnt > 0)
113
                    cnt --;
114
            }
115
            mImage->decRef();
116
        }
117
        mImage = nullptr;
118
    }
119
120
    ParticleEngine::particleCount--;
121
}
122
123
void Particle::draw(Graphics *restrict const graphics A_UNUSED,
124
                    const int offsetX A_UNUSED,
125
                    const int offsetY A_UNUSED) const restrict2
126
{
127
}
128
129
void Particle::updateSelf() restrict2
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
141
        switch (ParticleEngine::fastPhysics)
142
        {
143
            case ParticlePhysics::Normal:
144
                invHypotenuse = fastInvSqrt(
145
                    dist.x * dist.x + dist.y * dist.y + dist.z * dist.z);
146
                break;
147
            case ParticlePhysics::Fast:
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;
158
            case ParticlePhysics::Best:
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)
168
                mAlive = AliveStatus::DEAD_IMPACT;
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
        {
208
            mAlive = AliveStatus::DEAD_FLOOR;
209
        }
210
    }
211
    else if (mPos.z > ParticleEngine::PARTICLE_SKY)
212
    {
213
        mAlive = AliveStatus::DEAD_SKY;
214
    }
215
216
    // Update child emitters
217
    if ((ParticleEngine::emitterSkip != 0) &&
218
        (mLifetimePast - 1) % ParticleEngine::emitterSkip == 0)
219
    {
220
        FOR_EACH (EmitterConstIterator, e, mChildEmitters)
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
238
    if (A_UNLIKELY(mAlive != AliveStatus::ALIVE &&
239
        mAlive != AliveStatus::DEAD_LONG_AGO))
240
    {
241
        if ((CAST_U32(mAlive) & mDeathEffectConditions)
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
        }
249
        mAlive = AliveStatus::DEAD_LONG_AGO;
250
    }
251
}
252
253
bool Particle::update() restrict2
254
{
255
    if (A_LIKELY(mAlive == AliveStatus::ALIVE))
256
    {
257
        if (A_UNLIKELY(mLifetimeLeft == 0))
258
        {
259
            mAlive = AliveStatus::DEAD_TIMEOUT;
260
            if (mChildParticles.empty())
261
            {
262
                if (mAutoDelete)
263
                    return false;
264
                return true;
265
            }
266
        }
267
        else
268
        {
269
            if (mAnimation != nullptr)
270
            {
271
                if (mType == ParticleType::Animation)
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
                }
312
                mImage = mAnimation->getCurrentImage();
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
        }
354
        if (A_UNLIKELY(mAlive != AliveStatus::ALIVE &&
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
396
void Particle::moveBy(const Vector &restrict change) restrict2
397
{
398
    mPos += change;
399
    FOR_EACH (ParticleConstIterator, p, mChildMoveParticles)
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),
420
        UseVirtFs_true,
421
        SkipError_false);
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(
521
                        AliveStatus::DEAD_FLOOR);
522
                }
523
                if (XML::getBoolProperty(emitterNode, "on-sky", true))
524
                {
525
                    deathEffectConditions += CAST_S8(
526
                        AliveStatus::DEAD_SKY);
527
                }
528
                if (XML::getBoolProperty(emitterNode, "on-other", false))
529
                {
530
                    deathEffectConditions += CAST_S8(
531
                        AliveStatus::DEAD_OTHER);
532
                }
533
                if (XML::getBoolProperty(emitterNode, "on-impact", true))
534
                {
535
                    deathEffectConditions += CAST_S8(
536
                        AliveStatus::DEAD_IMPACT);
537
                }
538
                if (XML::getBoolProperty(emitterNode, "on-timeout", true))
539
                {
540
                    deathEffectConditions += CAST_S8(
541
                        AliveStatus::DEAD_TIMEOUT);
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
    {
559
        FOR_EACH (EmitterConstIterator, e, mChildEmitters)
560
            (*e)->adjustSize(w, h);
561
    }
562
}
563
564
void Particle::prepareToDie() restrict2
565
{
566
    FOR_EACH (ParticleIterator, p, mChildParticles)
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
581
void Particle::clear() restrict2
582
{
583
    delete_all(mChildEmitters);
584
    mChildEmitters.clear();
585
586
    delete_all(mChildParticles);
587
    mChildParticles.clear();
588
589
    mChildMoveParticles.clear();
590
4
}