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 <cmath>
23
24
#include "animationparticle.h"
25
#include "imageparticle.h"
26
#include "log.h"
27
#include "particle.h"
28
#include "particleemitter.h"
29
#include "rotationalparticle.h"
30
31
#include "resources/image.h"
32
#include "resources/imageset.h"
33
#include "resources/resourcemanager.h"
34
35
#define SIN45 0.707106781f
36
#define DEG_RAD_FACTOR 0.017453293f
37
38
ParticleEmitter::ParticleEmitter(xmlNodePtr emitterNode, Particle *target, Map *map, int rotation):
39
    mOutputPauseLeft(0),
40
    mParticleImage(0)
41
{
42
    mMap = map;
43
    mParticleTarget = target;
44
45
    // Initializing default values
46
    mParticlePosX.set(0.0f);
47
    mParticlePosY.set(0.0f);
48
    mParticlePosZ.set(0.0f);
49
    mParticleAngleHorizontal.set(0.0f);
50
    mParticleAngleVertical.set(0.0f);
51
    mParticlePower.set(0.0f);
52
    mParticleGravity.set(0.0f);
53
    mParticleRandomness.set(0);
54
    mParticleBounce.set(0.0f);
55
    mParticleFollow = false;
56
    mParticleAcceleration.set(0.0f);
57
    mParticleDieDistance.set(-1.0f);
58
    mParticleMomentum.set(1.0f);
59
    mParticleLifetime.set(-1);
60
    mParticleFadeOut.set(0);
61
    mParticleFadeIn.set(0);
62
    mOutput.set(1);
63
    mOutputPause.set(0);
64
    mParticleAlpha.set(1.0f);
65
66
    for_each_xml_child_node(propertyNode, emitterNode)
67
    {
68
        if (xmlStrEqual(propertyNode->name, BAD_CAST "property"))
69
        {
70
            std::string name = XML::getProperty(propertyNode, "name", "");
71
72
            if (name == "position-x")
73
            {
74
                mParticlePosX = readParticleEmitterProp(propertyNode, 0.0f);
75
            }
76
            else if (name == "position-y")
77
            {
78
79
                mParticlePosY = readParticleEmitterProp(propertyNode, 0.0f);
80
                mParticlePosY.minVal *= SIN45;
81
                mParticlePosY.maxVal *= SIN45;
82
                mParticlePosY.changeAmplitude *= SIN45;
83
             }
84
            else if (name == "position-z")
85
            {
86
                mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0f);
87
                mParticlePosZ.minVal *= SIN45;
88
                mParticlePosZ.maxVal *= SIN45;
89
                mParticlePosZ.changeAmplitude *= SIN45;
90
            }
91
            else if (name == "image")
92
            {
93
                std::string image = XML::getProperty(propertyNode, "value", "");
94
                // Don't leak when multiple images are defined
95
                if (!image.empty() && !mParticleImage)
96
                {
97
                    ResourceManager *resman = ResourceManager::getInstance();
98
                    mParticleImage = resman->getImage(image);
99
                }
100
            }
101
            else if (name == "horizontal-angle")
102
            {
103
                mParticleAngleHorizontal = readParticleEmitterProp(propertyNode, 0.0f);
104
                mParticleAngleHorizontal.minVal += rotation;
105
                mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR;
106
                mParticleAngleHorizontal.maxVal += rotation;
107
                mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR;
108
                mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR;
109
            }
110
            else if (name == "vertical-angle")
111
            {
112
                mParticleAngleVertical = readParticleEmitterProp(propertyNode, 0.0f);
113
                mParticleAngleVertical.minVal *= DEG_RAD_FACTOR;
114
                mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR;
115
                mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR;
116
            }
117
            else if (name == "power")
118
            {
119
                mParticlePower = readParticleEmitterProp(propertyNode, 0.0f);
120
            }
121
            else if (name == "gravity")
122
            {
123
                mParticleGravity = readParticleEmitterProp(propertyNode, 0.0f);
124
            }
125
            else if (name == "randomnes" || name == "randomness") // legacy bug
126
            {
127
                mParticleRandomness = readParticleEmitterProp(propertyNode, 0);
128
            }
129
            else if (name == "bounce")
130
            {
131
                mParticleBounce = readParticleEmitterProp(propertyNode, 0.0f);
132
            }
133
            else if (name == "lifetime")
134
            {
135
                mParticleLifetime = readParticleEmitterProp(propertyNode, 0);
136
                mParticleLifetime.minVal += 1;
137
            }
138
            else if (name == "output")
139
            {
140
                mOutput = readParticleEmitterProp(propertyNode, 0);
141
                mOutput.maxVal +=1;
142
            }
143
            else if (name == "output-pause")
144
            {
145
                mOutputPause = readParticleEmitterProp(propertyNode, 0);
146
                mOutputPauseLeft = mOutputPause.value(0);
147
            }
148
            else if (name == "acceleration")
149
            {
150
                mParticleAcceleration = readParticleEmitterProp(propertyNode, 0.0f);
151
            }
152
            else if (name == "die-distance")
153
            {
154
                mParticleDieDistance = readParticleEmitterProp(propertyNode, 0.0f);
155
            }
156
            else if (name == "momentum")
157
            {
158
                mParticleMomentum = readParticleEmitterProp(propertyNode, 1.0f);
159
            }
160
            else if (name == "fade-out")
161
            {
162
                mParticleFadeOut = readParticleEmitterProp(propertyNode, 0);
163
            }
164
            else if (name == "fade-in")
165
            {
166
                mParticleFadeIn = readParticleEmitterProp(propertyNode, 0);
167
            }
168
            else if (name == "alpha")
169
            {
170
                mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0f);
171
            }
172
            else if (name == "follow-parent")
173
            {
174
                mParticleFollow = true;
175
            }
176
            else
177
            {
178
                logger->log("Particle Engine: Warning, unknown emitter property \"%s\"",
179
                            name.c_str()
180
                           );
181
            }
182
        }
183
        else if (xmlStrEqual(propertyNode->name, BAD_CAST "emitter"))
184
        {
185
            ParticleEmitter newEmitter(propertyNode, mParticleTarget, map);
186
            mParticleChildEmitters.push_back(newEmitter);
187
        }
188
        else if (xmlStrEqual(propertyNode->name, BAD_CAST "rotation"))
189
        {
190
            ImageSet *imageset = ResourceManager::getInstance()->getImageSet(
191
                XML::getProperty(propertyNode, "imageset", ""),
192
                XML::getProperty(propertyNode, "width", 0),
193
                XML::getProperty(propertyNode, "height", 0)
194
            );
195
196
            // Get animation frames
197
            for_each_xml_child_node(frameNode, propertyNode)
198
            {
199
                int delay = XML::getProperty(frameNode, "delay", 0);
200
                int offsetX = XML::getProperty(frameNode, "offsetX", 0);
201
                int offsetY = XML::getProperty(frameNode, "offsetY", 0);
202
                offsetY -= imageset->getHeight() - 32;
203
                offsetX -= imageset->getWidth() / 2 - 16;
204
205
                if (xmlStrEqual(frameNode->name, BAD_CAST "frame"))
206
                {
207
                    int index = XML::getProperty(frameNode, "index", -1);
208
209
                    if (index < 0)
210
                    {
211
                        logger->log("No valid value for 'index'");
212
                        continue;
213
                    }
214
215
                    Image *img = imageset->get(index);
216
217
                    if (!img)
218
                    {
219
                        logger->log("No image at index %d", index);
220
                        continue;
221
                    }
222
223
                    mParticleRotation.addFrame(img, delay, offsetX, offsetY);
224
                }
225
                else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence"))
226
                {
227
                    int start = XML::getProperty(frameNode, "start", -1);
228
                    int end = XML::getProperty(frameNode, "end", -1);
229
230
                    if (start < 0 || end < 0)
231
                    {
232
                        logger->log("No valid value for 'start' or 'end'");
233
                        continue;
234
                    }
235
236
                    while (end >= start)
237
                    {
238
                        Image *img = imageset->get(start);
239
240
                        if (!img)
241
                        {
242
                            logger->log("No image at index %d", start);
243
                            continue;
244
                        }
245
246
                        mParticleRotation.addFrame(img, delay, offsetX, offsetY);
247
                        start++;
248
                    }
249
                }
250
                else if (xmlStrEqual(frameNode->name, BAD_CAST "end"))
251
                {
252
                    mParticleRotation.addTerminator();
253
                }
254
            } // for frameNode
255
        }
256
        else if (xmlStrEqual(propertyNode->name, BAD_CAST "animation"))
257
        {
258
            ImageSet *imageset = ResourceManager::getInstance()->getImageSet(
259
                XML::getProperty(propertyNode, "imageset", ""),
260
                XML::getProperty(propertyNode, "width", 0),
261
                XML::getProperty(propertyNode, "height", 0)
262
            );
263
264
            // Get animation frames
265
            for_each_xml_child_node(frameNode, propertyNode)
266
            {
267
                int delay = XML::getProperty(frameNode, "delay", 0);
268
                int offsetX = XML::getProperty(frameNode, "offsetX", 0);
269
                int offsetY = XML::getProperty(frameNode, "offsetY", 0);
270
                offsetY -= imageset->getHeight() - 32;
271
                offsetX -= imageset->getWidth() / 2 - 16;
272
273
                if (xmlStrEqual(frameNode->name, BAD_CAST "frame"))
274
                {
275
                    int index = XML::getProperty(frameNode, "index", -1);
276
277
                    if (index < 0)
278
                    {
279
                        logger->log("No valid value for 'index'");
280
                        continue;
281
                    }
282
283
                    Image *img = imageset->get(index);
284
285
                    if (!img)
286
                    {
287
                        logger->log("No image at index %d", index);
288
                        continue;
289
                    }
290
291
                    mParticleAnimation.addFrame(img, delay, offsetX, offsetY);
292
                }
293
                else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence"))
294
                {
295
                    int start = XML::getProperty(frameNode, "start", -1);
296
                    int end = XML::getProperty(frameNode, "end", -1);
297
298
                    if (start < 0 || end < 0)
299
                    {
300
                        logger->log("No valid value for 'start' or 'end'");
301
                        continue;
302
                    }
303
304
                    while (end >= start)
305
                    {
306
                        Image *img = imageset->get(start);
307
308
                        if (!img)
309
                        {
310
                            logger->log("No image at index %d", start);
311
                            continue;
312
                        }
313
314
                        mParticleAnimation.addFrame(img, delay, offsetX, offsetY);
315
                        start++;
316
                    }
317
                }
318
                else if (xmlStrEqual(frameNode->name, BAD_CAST "end"))
319
                {
320
                    mParticleAnimation.addTerminator();
321
                }
322
            } // for frameNode
323
        }
324
    }
325
}
326
327
ParticleEmitter::ParticleEmitter(const ParticleEmitter &o)
328
{
329
    *this = o;
330
}
331
332
ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o)
333
{
334
    mParticlePosX = o.mParticlePosX;
335
    mParticlePosY = o.mParticlePosY;
336
    mParticlePosZ = o.mParticlePosZ;
337
    mParticleAngleHorizontal = o.mParticleAngleHorizontal;
338
    mParticleAngleVertical = o.mParticleAngleVertical;
339
    mParticlePower = o.mParticlePower;
340
    mParticleGravity = o.mParticleGravity;
341
    mParticleRandomness = o.mParticleRandomness;
342
    mParticleBounce = o.mParticleBounce;
343
    mParticleFollow = o.mParticleFollow;
344
    mParticleTarget = o.mParticleTarget;
345
    mParticleAcceleration = o.mParticleAcceleration;
346
    mParticleDieDistance = o.mParticleDieDistance;
347
    mParticleMomentum = o.mParticleMomentum;
348
    mParticleLifetime = o.mParticleLifetime;
349
    mParticleFadeOut = o.mParticleFadeOut;
350
    mParticleFadeIn = o.mParticleFadeIn;
351
    mParticleAlpha = o.mParticleAlpha;
352
    mMap = o.mMap;
353
    mOutput = o.mOutput;
354
    mOutputPause = o.mOutputPause;
355
    mParticleImage = o.mParticleImage;
356
    mParticleAnimation = o.mParticleAnimation;
357
    mParticleRotation = o.mParticleRotation;
358
    mParticleChildEmitters = o.mParticleChildEmitters;
359
360
    mOutputPauseLeft = 0;
361
362
    if (mParticleImage) mParticleImage->incRef();
363
364
    return *this;
365
}
366
367
368
ParticleEmitter::~ParticleEmitter()
369
{
370
    if (mParticleImage) mParticleImage->decRef();
371
}
372
373
374
template <typename T> ParticleEmitterProp<T>
375
ParticleEmitter::readParticleEmitterProp(xmlNodePtr propertyNode, T def)
376
{
377
    ParticleEmitterProp<T> retval;
378
379
    def = (T) XML::getFloatProperty(propertyNode, "value", (double) def);
380
    retval.set((T) XML::getFloatProperty(propertyNode, "min", (double) def),
381
               (T) XML::getFloatProperty(propertyNode, "max", (double) def));
382
383
     std::string change = XML::getProperty(propertyNode, "change-func", "none");
384
     T amplitude = (T) XML::getFloatProperty(propertyNode, "change-amplitude", 0.0);
385
     int period = XML::getProperty(propertyNode, "change-period", 0);
386
     int phase = XML::getProperty(propertyNode, "change-phase", 0);
387
     if (change == "saw" || change == "sawtooth") {
388
         retval.setFunction(FUNC_SAW, amplitude, period, phase);
389
     } else if (change == "sine" || change == "sinewave") {
390
         retval.setFunction(FUNC_SINE, amplitude, period, phase);
391
     } else if (change == "triangle") {
392
         retval.setFunction(FUNC_TRIANGLE, amplitude, period, phase);
393
     } else if (change == "square"){
394
         retval.setFunction(FUNC_SQUARE, amplitude, period, phase);
395
     }
396
397
    return retval;
398
}
399
400
401
std::list<Particle *> ParticleEmitter::createParticles(int tick)
402
{
403
    std::list<Particle *> newParticles;
404
405
    if (mOutputPauseLeft > 0)
406
    {
407
        mOutputPauseLeft--;
408
        return newParticles;
409
    }
410
    mOutputPauseLeft = mOutputPause.value(tick);
411
412
    for (int i = mOutput.value(tick); i > 0; i--)
413
    {
414
        // Limit maximum particles
415
        if (Particle::particleCount > Particle::maxCount) break;
416
417
        Particle *newParticle;
418
        if (mParticleImage)
419
        {
420
            newParticle = new ImageParticle(mMap, mParticleImage);
421
        }
422
        else if (mParticleRotation.getLength() > 0)
423
        {
424
            Animation *newAnimation = new Animation(mParticleRotation);
425
            newParticle = new RotationalParticle(mMap, newAnimation);
426
        }
427
        else if (mParticleAnimation.getLength() > 0)
428
        {
429
            Animation *newAnimation = new Animation(mParticleAnimation);
430
            newParticle = new AnimationParticle(mMap, newAnimation);
431
        }
432
        else
433
        {
434
            newParticle = new Particle(mMap);
435
        }
436
437
        Vector position(mParticlePosX.value(tick),
438
                        mParticlePosY.value(tick),
439
                        mParticlePosZ.value(tick));
440
        newParticle->moveTo(position);
441
442
        float angleH = mParticleAngleHorizontal.value(tick);
443
        float angleV = mParticleAngleVertical.value(tick);
444
        float power = mParticlePower.value(tick);
445
        newParticle->setVelocity(
446
                cos(angleH) * cos(angleV) * power,
447
                sin(angleH) * cos(angleV) * power,
448
                sin(angleV) * power);
449
450
        newParticle->setRandomness(mParticleRandomness.value(tick));
451
        newParticle->setGravity(mParticleGravity.value(tick));
452
        newParticle->setBounce(mParticleBounce.value(tick));
453
        newParticle->setFollow(mParticleFollow);
454
455
        newParticle->setDestination(mParticleTarget,
456
                                    mParticleAcceleration.value(tick),
457
                                    mParticleMomentum.value(tick)
458
                                   );
459
        newParticle->setDieDistance(mParticleDieDistance.value(tick));
460
461
        newParticle->setLifetime(mParticleLifetime.value(tick));
462
        newParticle->setFadeOut(mParticleFadeOut.value(tick));
463
        newParticle->setFadeIn(mParticleFadeIn.value(tick));
464
        newParticle->setAlpha(mParticleAlpha.value(tick));
465
466
        for (std::list<ParticleEmitter>::iterator i = mParticleChildEmitters.begin();
467
             i != mParticleChildEmitters.end();
468
             i++)
469
        {
470
            newParticle->addEmitter(new ParticleEmitter(*i));
471
        }
472
473
        newParticles.push_back(newParticle);
474
    }
475
476
    return newParticles;
477
}