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/>.
25
#include "animationparticle.h"
26
#include "configuration.h"
27
#include "resources/dye.h"
30
#include "particleemitter.h"
31
#include "rotationalparticle.h"
32
#include "textparticle.h"
34
#include "resources/resourcemanager.h"
36
#include "gui/sdlfont.h"
38
#include "utils/dtor.h"
39
#include "utils/mathutils.h"
41
#include <guichan/color.hpp>
48
static const float SIN45 = 0.707106781f;
53
int Particle::particleCount = 0;
54
int Particle::maxCount = 0;
55
int Particle::fastPhysics = 0;
56
int Particle::emitterSkip = 1;
57
bool Particle::enabled = true;
58
const float Particle::PARTICLE_SKY = 800.0f;
60
Particle::Particle(Map *const map) :
75
mInvDieDistance(-1.0f),
79
mDeathEffectConditions(0x00),
81
mAllowSizeAdjust(false),
85
Particle::particleCount++;
90
// Delete child emitters and child particles
92
Particle::particleCount--;
95
void Particle::setupEngine()
97
Particle::maxCount = config.getIntValue("particleMaxCount");
98
Particle::fastPhysics = config.getIntValue("particleFastPhysics");
99
Particle::emitterSkip = config.getIntValue("particleEmitterSkip") + 1;
100
if (!Particle::emitterSkip)
101
Particle::emitterSkip = 1;
102
Particle::enabled = config.getBoolValue("particleeffects");
104
logger->log1("Particle engine set up");
107
bool Particle::draw(Graphics *const, const int, const int) const
112
bool Particle::update()
117
if (mLifetimeLeft == 0 && mAlive == ALIVE)
118
mAlive = DEAD_TIMEOUT;
120
const Vector oldPos = mPos;
124
// calculate particle movement
125
if (mMomentum != 1.0f)
126
mVelocity *= mMomentum;
128
if (mTarget && mAcceleration != 0.0f)
130
Vector dist = mPos - mTarget->mPos;
134
switch (Particle::fastPhysics)
137
invHypotenuse = fastInvSqrt(
138
dist.x * dist.x + dist.y * dist.y + dist.z * dist.z);
147
invHypotenuse = 2.0f / (static_cast<float>(fabs(dist.x))
148
+ static_cast<float>(fabs(dist.y))
149
+ static_cast<float>(fabs(dist.z)));
152
invHypotenuse = 1.0f / static_cast<float>(sqrt(
153
dist.x * dist.x + dist.y * dist.y + dist.z * dist.z));
159
if (mInvDieDistance > 0.0f && invHypotenuse > mInvDieDistance)
160
mAlive = DEAD_IMPACT;
161
const float accFactor = invHypotenuse * mAcceleration;
162
mVelocity -= dist * accFactor;
168
mVelocity.x += static_cast<float>((rand() % mRandomness - rand()
169
% mRandomness)) / 1000.0f;
170
mVelocity.y += static_cast<float>((rand() % mRandomness - rand()
171
% mRandomness)) / 1000.0f;
172
mVelocity.z += static_cast<float>((rand() % mRandomness - rand()
173
% mRandomness)) / 1000.0f;
176
mVelocity.z -= mGravity;
179
mPos.x += mVelocity.x;
180
mPos.y += mVelocity.y * SIN45;
181
mPos.z += mVelocity.z * SIN45;
183
// Update other stuff
184
if (mLifetimeLeft > 0)
194
mVelocity *= mBounce;
195
mVelocity.z = -mVelocity.z;
202
else if (mPos.z > PARTICLE_SKY)
207
// Update child emitters
208
if (Particle::emitterSkip && (mLifetimePast - 1)
209
% Particle::emitterSkip == 0)
211
FOR_EACH (EmitterConstIterator, e, mChildEmitters)
213
Particles newParticles = (*e)->createParticles(mLifetimePast);
214
FOR_EACH (ParticleConstIterator, it, newParticles)
216
Particle *const p = *it;
218
mChildParticles.push_back(p);
224
// create death effect when the particle died
225
if (mAlive != ALIVE && mAlive != DEAD_LONG_AGO)
227
if ((mAlive & mDeathEffectConditions) > 0x00 && !mDeathEffect.empty())
229
Particle *const deathEffect = particleEngine->addEffect(
232
deathEffect->moveBy(mPos);
234
mAlive = DEAD_LONG_AGO;
237
const Vector change = mPos - oldPos;
239
// Update child particles
241
for (ParticleIterator p = mChildParticles.begin(),
242
p2 = mChildParticles.end(); p != p2; )
244
Particle *const particle = *p;
245
// move particle with its parent if desired
246
if (particle->mFollow)
247
particle->moveBy(change);
250
if (particle->update())
257
p = mChildParticles.erase(p);
260
if (mAlive != ALIVE && mChildParticles.empty() && mAutoDelete)
266
void Particle::moveBy(const Vector &change)
269
FOR_EACH (ParticleConstIterator, p, mChildParticles)
271
Particle *const particle = *p;
272
if (particle->mFollow)
273
particle->moveBy(change);
277
void Particle::moveTo(const float x, const float y)
279
moveTo(Vector(x, y, mPos.z));
282
Particle *Particle::createChild()
284
Particle *const newParticle = new Particle(mMap);
285
mChildParticles.push_back(newParticle);
289
Particle *Particle::addEffect(const std::string &particleEffectFile,
290
const int pixelX, const int pixelY,
293
Particle *newParticle = nullptr;
295
const size_t pos = particleEffectFile.find('|');
296
const std::string dyePalettes = (pos != std::string::npos)
297
? particleEffectFile.substr(pos + 1) : "";
298
XML::Document doc(particleEffectFile.substr(0, pos));
299
const XmlNodePtr rootNode = doc.rootNode();
301
if (!rootNode || !xmlNameEqual(rootNode, "effect"))
303
logger->log("Error loading particle: %s", particleEffectFile.c_str());
307
ResourceManager *const resman = ResourceManager::getInstance();
310
for_each_xml_child_node(effectChildNode, rootNode)
312
// We're only interested in particles
313
if (!xmlNameEqual(effectChildNode, "particle"))
316
// Determine the exact particle type
320
if ((node = XML::findFirstChildByName(effectChildNode, "animation")))
322
newParticle = new AnimationParticle(mMap, node, dyePalettes);
325
else if ((node = XML::findFirstChildByName(
326
effectChildNode, "rotation")))
328
newParticle = new RotationalParticle(mMap, node, dyePalettes);
331
else if ((node = XML::findFirstChildByName(effectChildNode, "image")))
333
std::string imageSrc = reinterpret_cast<const char*>(
334
node->xmlChildrenNode->content);
335
if (!imageSrc.empty() && !dyePalettes.empty())
336
Dye::instantiate(imageSrc, dyePalettes);
337
Image *const img = resman->getImage(imageSrc);
339
newParticle = new ImageParticle(mMap, img);
344
newParticle = new Particle(mMap);
347
// Read and set the basic properties of the particle
348
const float offsetX = static_cast<float>(XML::getFloatProperty(
349
effectChildNode, "position-x", 0));
350
const float offsetY = static_cast<float>(XML::getFloatProperty(
351
effectChildNode, "position-y", 0));
352
const float offsetZ = static_cast<float>(XML::getFloatProperty(
353
effectChildNode, "position-z", 0));
354
const Vector position(mPos.x + static_cast<float>(pixelX) + offsetX,
355
mPos.y + static_cast<float>(pixelY) + offsetY,
357
newParticle->moveTo(position);
359
const int lifetime = XML::getProperty(effectChildNode, "lifetime", -1);
360
newParticle->setLifetime(lifetime);
361
const bool resizeable = "false" != XML::getProperty(effectChildNode,
362
"size-adjustable", "false");
364
newParticle->setAllowSizeAdjust(resizeable);
366
// Look for additional emitters for this particle
367
for_each_xml_child_node(emitterNode, effectChildNode)
369
if (xmlNameEqual(emitterNode, "emitter"))
371
ParticleEmitter *const newEmitter = new ParticleEmitter(
372
emitterNode, newParticle, mMap, rotation, dyePalettes);
373
newParticle->addEmitter(newEmitter);
375
else if (xmlNameEqual(emitterNode, "deatheffect"))
377
const std::string deathEffect = reinterpret_cast<const char*>(
378
emitterNode->xmlChildrenNode->content);
380
char deathEffectConditions = 0x00;
381
if (XML::getBoolProperty(emitterNode, "on-floor", true))
383
deathEffectConditions += static_cast<signed char>(
384
Particle::DEAD_FLOOR);
386
if (XML::getBoolProperty(emitterNode, "on-sky", true))
388
deathEffectConditions += static_cast<signed char>(
391
if (XML::getBoolProperty(emitterNode, "on-other", false))
393
deathEffectConditions += static_cast<signed char>(
394
Particle::DEAD_OTHER);
396
if (XML::getBoolProperty(emitterNode, "on-impact", true))
398
deathEffectConditions += static_cast<signed char>(
399
Particle::DEAD_IMPACT);
401
if (XML::getBoolProperty(emitterNode, "on-timeout", true))
403
deathEffectConditions += static_cast<signed char>(
404
Particle::DEAD_TIMEOUT);
406
newParticle->setDeathEffect(
407
deathEffect, deathEffectConditions);
411
mChildParticles.push_back(newParticle);
417
Particle *Particle::addTextSplashEffect(const std::string &text,
418
const int x, const int y,
419
const gcn::Color *const color,
420
gcn::Font *const font,
423
Particle *const newParticle = new TextParticle(
424
mMap, text, color, font, outline);
425
newParticle->moveTo(static_cast<float>(x), static_cast<float>(y));
426
newParticle->setVelocity(
427
static_cast<float>((rand() % 100) - 50) / 200.0f, // X
428
static_cast<float>((rand() % 100) - 50) / 200.0f, // Y
429
(static_cast<float>((rand() % 100)) / 200.0f) + 4.0f); // Z
431
newParticle->setGravity(0.1f);
432
newParticle->setBounce(0.5f);
433
newParticle->setLifetime(200);
434
newParticle->setFadeOut(100);
436
mChildParticles.push_back(newParticle);
441
Particle *Particle::addTextRiseFadeOutEffect(const std::string &text,
442
const int x, const int y,
443
const gcn::Color *const color,
444
gcn::Font *const font,
447
Particle *const newParticle = new TextParticle(
448
mMap, text, color, font, outline);
449
newParticle->moveTo(static_cast<float>(x), static_cast<float>(y));
450
newParticle->setVelocity(0.0f, 0.0f, 0.5f);
451
newParticle->setGravity(0.0015f);
452
newParticle->setLifetime(300);
453
newParticle->setFadeOut(100);
454
newParticle->setFadeIn(0);
456
mChildParticles.push_back(newParticle);
461
void Particle::adjustEmitterSize(const int w, const int h)
463
if (mAllowSizeAdjust)
465
FOR_EACH (EmitterConstIterator, e, mChildEmitters)
466
(*e)->adjustSize(w, h);
470
void Particle::clear()
472
delete_all(mChildEmitters);
473
mChildEmitters.clear();
475
delete_all(mChildParticles);
476
mChildParticles.clear();