1
/*
2
 *  The Mana World
3
 *  Copyright (C) 2006  The Mana World Development Team
4
 *
5
 *  This file is part of The Mana World.
6
 *
7
 *  This program is free software; you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
9
 *  the Free Software Foundation; either version 2 of the License, or
10
 *  any later version.
11
 *
12
 *  This program is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU General Public License
18
 *  along with this program; if not, write to the Free Software
19
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
 */
21
22
#include <algorithm>
23
#include <cmath>
24
25
#include <guichan/color.hpp>
26
27
#include "animationparticle.h"
28
#include "configuration.h"
29
#include "imageparticle.h"
30
#include "log.h"
31
#include "map.h"
32
#include "particle.h"
33
#include "particleemitter.h"
34
#include "rotationalparticle.h"
35
#include "textparticle.h"
36
37
#include "resources/resourcemanager.h"
38
39
#include "utils/dtor.h"
40
#include "utils/mathutils.h"
41
#include "utils/xml.h"
42
43
#define SIN45 0.707106781f
44
45
class Graphics;
46
class Image;
47
48
int Particle::particleCount = 0;
49
int Particle::maxCount = 0;
50
int Particle::fastPhysics = 0;
51
int Particle::emitterSkip = 1;
52
const float Particle::PARTICLE_SKY = 800.0f;
53
54
Particle::Particle(Map *map):
55
    mAlive(true),
56
    mLifetimeLeft(-1),
57
    mLifetimePast(0),
58
    mFadeOut(0),
59
    mFadeIn(0),
60
    mAlpha(1.0f),
61
    mAutoDelete(true),
62
    mMap(map),
63
    mGravity(0.0f),
64
    mRandomness(0),
65
    mBounce(0.0f),
66
    mFollow(false),
67
    mTarget(NULL),
68
    mAcceleration(0.0f),
69
    mInvDieDistance(-1.0f),
70
    mMomentum(1.0f)
71
{
72
    Particle::particleCount++;
73
    if (mMap)
74
        setSpriteIterator(mMap->addSprite(this));
75
}
76
77
Particle::~Particle()
78
{
79
    // Remove from map sprite list
80
    if (mMap)
81
        mMap->removeSprite(mSpriteIterator);
82
    // Delete child emitters and child particles
83
    clear();
84
    Particle::particleCount--;
85
}
86
87
void Particle::setupEngine()
88
{
89
    Particle::maxCount = (int)config.getValue("particleMaxCount", 3000);
90
    Particle::fastPhysics = (int)config.getValue("particleFastPhysics", 0);
91
    Particle::emitterSkip = (int)config.getValue("particleEmitterSkip", 1) + 1;
92
    disableAutoDelete();
93
    logger->log("Particle engine set up");
94
}
95
96
void Particle::draw(Graphics *, int, int) const
97
{
98
}
99
100
bool Particle::update()
101
{
102
    if (!mMap)
103
        return false;
104
105
    if (mLifetimeLeft == 0)
106
        mAlive = false;
107
108
    Vector oldPos = mPos;
109
110
    if (mAlive)
111
    {
112
        //calculate particle movement
113
        if (mMomentum != 1.0f)
114
        {
115
            mVelocity *= mMomentum;
116
        }
117
118
        if (mTarget && mAcceleration != 0.0f)
119
        {
120
            Vector dist = mPos - mTarget->getPosition();
121
            dist.x *= SIN45;
122
            float invHypotenuse;
123
124
            switch (Particle::fastPhysics)
125
            {
126
                case 1:
127
                    invHypotenuse = fastInvSqrt(
128
                        dist.x * dist.x + dist.y * dist.y + dist.z * dist.z);
129
                    break;
130
                case 2:
131
                    invHypotenuse = 2.0f /
132
                        fabs(dist.x) + fabs(dist.y) + fabs(dist.z);
133
                    break;
134
                default:
135
                    invHypotenuse = 1.0f / sqrt(
136
                        dist.x * dist.x + dist.y * dist.y + dist.z * dist.z);
137
                    break;
138
            }
139
140
            if (invHypotenuse)
141
            {
142
                if (mInvDieDistance > 0.0f && invHypotenuse > mInvDieDistance)
143
                {
144
                    mAlive = false;
145
                }
146
                float accFactor = invHypotenuse * mAcceleration;
147
                mVelocity -= dist * accFactor;
148
            }
149
        }
150
151
        if (mRandomness > 0)
152
        {
153
            mVelocity.x += (rand()%mRandomness - rand()%mRandomness) / 1000.0f;
154
            mVelocity.y += (rand()%mRandomness - rand()%mRandomness) / 1000.0f;
155
            mVelocity.z += (rand()%mRandomness - rand()%mRandomness) / 1000.0f;
156
        }
157
158
        mVelocity.z -= mGravity;
159
160
        // Update position
161
        mPos.x += mVelocity.x;
162
        mPos.y += mVelocity.y * SIN45;
163
        mPos.z += mVelocity.z * SIN45;
164
165
        // Update other stuff
166
        if (mLifetimeLeft > 0)
167
        {
168
            mLifetimeLeft--;
169
        }
170
        mLifetimePast++;
171
172
        if (mPos.z > PARTICLE_SKY || mPos.z < 0.0f)
173
        {
174
            if (mBounce > 0.0f)
175
            {
176
                mPos.z *= -mBounce;
177
                mVelocity *= mBounce;
178
                mVelocity.z = -mVelocity.z;
179
            }
180
            else
181
            {
182
                mAlive = false;
183
            }
184
        }
185
186
        // Update child emitters
187
        if ((mLifetimePast-1)%Particle::emitterSkip == 0)
188
        {
189
            for (EmitterIterator e = mChildEmitters.begin();
190
                 e != mChildEmitters.end(); e++)
191
            {
192
                Particles newParticles = (*e)->createParticles(mLifetimePast);
193
                for (ParticleIterator p = newParticles.begin();
194
                     p != newParticles.end(); p++)
195
                {
196
                    (*p)->moveBy(mPos);
197
                    mChildParticles.push_back (*p);
198
                }
199
            }
200
        }
201
    }
202
203
    Vector change = mPos - oldPos;
204
205
    // Update child particles
206
207
    for (ParticleIterator p = mChildParticles.begin();
208
         p != mChildParticles.end();)
209
    {
210
        //move particle with its parent if desired
211
        if ((*p)->doesFollow())
212
        {
213
            (*p)->moveBy(change);
214
        }
215
        //update particle
216
        if ((*p)->update())
217
        {
218
            p++;
219
        }
220
        else
221
        {
222
            delete (*p);
223
            p = mChildParticles.erase(p);
224
        }
225
    }
226
    if (!mAlive && mChildParticles.empty() && mAutoDelete)
227
    {
228
        return false;
229
    }
230
231
    return true;
232
}
233
234
void Particle::moveBy(const Vector &change)
235
{
236
    mPos += change;
237
    for (ParticleIterator p = mChildParticles.begin();
238
         p != mChildParticles.end(); p++)
239
    {
240
        if ((*p)->doesFollow())
241
        {
242
            (*p)->moveBy(change);
243
        }
244
    }
245
}
246
247
void Particle::moveTo(float x, float y)
248
{
249
    moveTo(Vector(x, y, mPos.z));
250
}
251
252
Particle *Particle::addEffect(const std::string &particleEffectFile,
253
                              int pixelX, int pixelY, int rotation)
254
{
255
    Particle *newParticle = NULL;
256
257
    XML::Document doc(particleEffectFile);
258
    xmlNodePtr rootNode = doc.rootNode();
259
260
    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "effect"))
261
    {
262
        logger->log("Error loading particle: %s", particleEffectFile.c_str());
263
        return NULL;
264
    }
265
266
    ResourceManager *resman = ResourceManager::getInstance();
267
268
    // Parse particles
269
    for_each_xml_child_node(effectChildNode, rootNode)
270
    {
271
        // We're only interested in particles
272
        if (!xmlStrEqual(effectChildNode->name, BAD_CAST "particle"))
273
            continue;
274
275
        // Determine the exact particle type
276
        xmlNodePtr node;
277
278
        // Animation
279
        if ((node = XML::findFirstChildByName(effectChildNode, "animation")))
280
        {
281
            newParticle = new AnimationParticle(mMap, node);
282
        }
283
        // Rotational
284
        else if ((node = XML::findFirstChildByName(effectChildNode, "rotation")))
285
        {
286
            newParticle = new RotationalParticle(mMap, node);
287
        }
288
        // Image
289
        else if ((node = XML::findFirstChildByName(effectChildNode, "image")))
290
        {
291
            Image *img= resman->getImage((const char*)
292
                    node->xmlChildrenNode->content);
293
294
            newParticle = new ImageParticle(mMap, img);
295
        }
296
        // Other
297
        else
298
        {
299
            newParticle = new Particle(mMap);
300
        }
301
302
        // Read and set the basic properties of the particle
303
        float offsetX = XML::getFloatProperty(effectChildNode, "position-x", 0);
304
        float offsetY = XML::getFloatProperty(effectChildNode, "position-y", 0);
305
        float offsetZ = XML::getFloatProperty(effectChildNode, "position-z", 0);
306
        Vector position (mPos.x + (float)pixelX + offsetX,
307
                         mPos.y + (float)pixelY + offsetY,
308
                         mPos.z + offsetZ);
309
        newParticle->moveTo(position);
310
311
        int lifetime = XML::getProperty(effectChildNode, "lifetime", -1);
312
        newParticle->setLifetime(lifetime);
313
314
        // Look for additional emitters for this particle
315
        for_each_xml_child_node(emitterNode, effectChildNode)
316
        {
317
            if (!xmlStrEqual(emitterNode->name, BAD_CAST "emitter"))
318
                continue;
319
320
            ParticleEmitter *newEmitter;
321
            newEmitter = new ParticleEmitter(emitterNode, newParticle, mMap,
322
                                             rotation);
323
            newParticle->addEmitter(newEmitter);
324
        }
325
326
        mChildParticles.push_back(newParticle);
327
    }
328
329
    return newParticle;
330
}
331
332
Particle *Particle::addTextSplashEffect(const std::string &text, int x, int y,
333
                                        const gcn::Color *color,
334
                                        gcn::Font *font, bool outline)
335
{
336
    Particle *newParticle = new TextParticle(mMap, text, color, font, outline);
337
    newParticle->moveTo(x, y);
338
    newParticle->setVelocity(((rand() % 100) - 50) / 200.0f,    // X
339
                             ((rand() % 100) - 50) / 200.0f,    // Y
340
                             ((rand() % 100) / 200.0f) + 4.0f); // Z
341
    newParticle->setGravity(0.1f);
342
    newParticle->setBounce(0.5f);
343
    newParticle->setLifetime(200);
344
    newParticle->setFadeOut(100);
345
346
    mChildParticles.push_back(newParticle);
347
348
    return newParticle;
349
}
350
351
Particle *Particle::addTextRiseFadeOutEffect(const std::string &text,
352
                                             int x, int y,
353
                                             const gcn::Color *color,
354
                                             gcn::Font *font, bool outline)
355
{
356
    Particle *newParticle = new TextParticle(mMap, text, color, font, outline);
357
    newParticle->moveTo(x, y);
358
    newParticle->setVelocity(0.0f, 0.0f, 0.5f);
359
    newParticle->setGravity(0.0015f);
360
    newParticle->setLifetime(300);
361
    newParticle->setFadeOut(50);
362
    newParticle->setFadeIn(200);
363
364
    mChildParticles.push_back(newParticle);
365
366
    return newParticle;
367
}
368
369
void Particle::setMap(Map *map)
370
{
371
    mMap = map;
372
    if (mMap)
373
        setSpriteIterator(mMap->addSprite(this));
374
}
375
376
void Particle::clear()
377
{
378
    delete_all(mChildEmitters);
379
    mChildEmitters.clear();
380
381
    delete_all(mChildParticles);
382
    mChildParticles.clear();
383
}