summaryrefslogtreecommitdiff
path: root/src/particleemitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/particleemitter.cpp')
-rw-r--r--src/particleemitter.cpp568
1 files changed, 568 insertions, 0 deletions
diff --git a/src/particleemitter.cpp b/src/particleemitter.cpp
new file mode 100644
index 000000000..90b29422f
--- /dev/null
+++ b/src/particleemitter.cpp
@@ -0,0 +1,568 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "animationparticle.h"
+#include "imageparticle.h"
+#include "log.h"
+#include "particle.h"
+#include "particleemitter.h"
+#include "rotationalparticle.h"
+
+#include "resources/image.h"
+#include "resources/imageset.h"
+#include "resources/resourcemanager.h"
+
+#include <cmath>
+
+#define SIN45 0.707106781f
+#define DEG_RAD_FACTOR 0.017453293f
+
+ParticleEmitter::ParticleEmitter(xmlNodePtr emitterNode, Particle *target,
+ Map *map, int rotation):
+ mOutputPauseLeft(0),
+ mParticleImage(0)
+{
+ mMap = map;
+ mParticleTarget = target;
+
+ // Initializing default values
+ mParticlePosX.set(0.0f);
+ mParticlePosY.set(0.0f);
+ mParticlePosZ.set(0.0f);
+ mParticleAngleHorizontal.set(0.0f);
+ mParticleAngleVertical.set(0.0f);
+ mParticlePower.set(0.0f);
+ mParticleGravity.set(0.0f);
+ mParticleRandomness.set(0);
+ mParticleBounce.set(0.0f);
+ mParticleFollow = false;
+ mParticleAcceleration.set(0.0f);
+ mParticleDieDistance.set(-1.0f);
+ mParticleMomentum.set(1.0f);
+ mParticleLifetime.set(-1);
+ mParticleFadeOut.set(0);
+ mParticleFadeIn.set(0);
+ mOutput.set(1);
+ mOutputPause.set(0);
+ mParticleAlpha.set(1.0f);
+
+ for_each_xml_child_node(propertyNode, emitterNode)
+ {
+ if (xmlStrEqual(propertyNode->name, BAD_CAST "property"))
+ {
+ std::string name = XML::getProperty(propertyNode, "name", "");
+
+ if (name == "position-x")
+ {
+ mParticlePosX = readParticleEmitterProp(propertyNode, 0.0f);
+ }
+ else if (name == "position-y")
+ {
+
+ mParticlePosY = readParticleEmitterProp(propertyNode, 0.0f);
+ mParticlePosY.minVal *= SIN45;
+ mParticlePosY.maxVal *= SIN45;
+ mParticlePosY.changeAmplitude *= SIN45;
+ }
+ else if (name == "position-z")
+ {
+ mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0f);
+ mParticlePosZ.minVal *= SIN45;
+ mParticlePosZ.maxVal *= SIN45;
+ mParticlePosZ.changeAmplitude *= SIN45;
+ }
+ else if (name == "image")
+ {
+ std::string image = XML::getProperty(
+ propertyNode, "value", "");
+ // Don't leak when multiple images are defined
+ if (!image.empty() && !mParticleImage)
+ {
+ ResourceManager *resman = ResourceManager::getInstance();
+ mParticleImage = resman->getImage(image);
+ }
+ }
+ else if (name == "horizontal-angle")
+ {
+ mParticleAngleHorizontal =
+ readParticleEmitterProp(propertyNode, 0.0f);
+ mParticleAngleHorizontal.minVal
+ += static_cast<float>(rotation);
+ mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR;
+ mParticleAngleHorizontal.maxVal
+ += static_cast<float>(rotation);
+ mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR;
+ mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR;
+ }
+ else if (name == "vertical-angle")
+ {
+ mParticleAngleVertical =
+ readParticleEmitterProp(propertyNode, 0.0f);
+ mParticleAngleVertical.minVal *= DEG_RAD_FACTOR;
+ mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR;
+ mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR;
+ }
+ else if (name == "power")
+ {
+ mParticlePower = readParticleEmitterProp(propertyNode, 0.0f);
+ }
+ else if (name == "gravity")
+ {
+ mParticleGravity = readParticleEmitterProp(propertyNode, 0.0f);
+ }
+ else if (name == "randomnes" || name == "randomness") // legacy bug
+ {
+ mParticleRandomness = readParticleEmitterProp(propertyNode, 0);
+ }
+ else if (name == "bounce")
+ {
+ mParticleBounce = readParticleEmitterProp(propertyNode, 0.0f);
+ }
+ else if (name == "lifetime")
+ {
+ mParticleLifetime = readParticleEmitterProp(propertyNode, 0);
+ mParticleLifetime.minVal += 1;
+ }
+ else if (name == "output")
+ {
+ mOutput = readParticleEmitterProp(propertyNode, 0);
+ mOutput.maxVal += 1;
+ }
+ else if (name == "output-pause")
+ {
+ mOutputPause = readParticleEmitterProp(propertyNode, 0);
+ mOutputPauseLeft = mOutputPause.value(0);
+ }
+ else if (name == "acceleration")
+ {
+ mParticleAcceleration = readParticleEmitterProp(
+ propertyNode, 0.0f);
+ }
+ else if (name == "die-distance")
+ {
+ mParticleDieDistance = readParticleEmitterProp(
+ propertyNode, 0.0f);
+ }
+ else if (name == "momentum")
+ {
+ mParticleMomentum = readParticleEmitterProp(
+ propertyNode, 1.0f);
+ }
+ else if (name == "fade-out")
+ {
+ mParticleFadeOut = readParticleEmitterProp(propertyNode, 0);
+ }
+ else if (name == "fade-in")
+ {
+ mParticleFadeIn = readParticleEmitterProp(propertyNode, 0);
+ }
+ else if (name == "alpha")
+ {
+ mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0f);
+ }
+ else if (name == "follow-parent")
+ {
+ mParticleFollow = true;
+ }
+ else
+ {
+ logger->log("Particle Engine: Warning, "
+ "unknown emitter property \"%s\"",
+ name.c_str());
+ }
+ }
+ else if (xmlStrEqual(propertyNode->name, BAD_CAST "emitter"))
+ {
+ ParticleEmitter newEmitter(propertyNode, mParticleTarget, map);
+ mParticleChildEmitters.push_back(newEmitter);
+ }
+ else if (xmlStrEqual(propertyNode->name, BAD_CAST "rotation"))
+ {
+ ImageSet *imageset = ResourceManager::getInstance()->getImageSet(
+ XML::getProperty(propertyNode, "imageset", ""),
+ XML::getProperty(propertyNode, "width", 0),
+ XML::getProperty(propertyNode, "height", 0)
+ );
+
+ if (!imageset)
+ {
+ logger->log1("Error: no valid imageset");
+ continue;
+ }
+
+ // Get animation frames
+ for_each_xml_child_node(frameNode, propertyNode)
+ {
+ int delay = XML::getProperty(frameNode, "delay", 0);
+ int offsetX = XML::getProperty(frameNode, "offsetX", 0);
+ int offsetY = XML::getProperty(frameNode, "offsetY", 0);
+ if (!imageset)
+ {
+ logger->log1("Error: no valid imageset");
+ continue;
+ }
+
+ offsetY -= imageset->getHeight() - 32;
+ offsetX -= imageset->getWidth() / 2 - 16;
+
+ if (xmlStrEqual(frameNode->name, BAD_CAST "frame"))
+ {
+ int index = XML::getProperty(frameNode, "index", -1);
+
+ if (index < 0)
+ {
+ logger->log1("No valid value for 'index'");
+ continue;
+ }
+
+ Image *img = imageset->get(index);
+
+ if (!img)
+ {
+ logger->log("No image at index %d", index);
+ continue;
+ }
+
+ mParticleRotation.addFrame(img, delay, offsetX, offsetY);
+ }
+ else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence"))
+ {
+ int start = XML::getProperty(frameNode, "start", -1);
+ int end = XML::getProperty(frameNode, "end", -1);
+
+ if (start < 0 || end < 0)
+ {
+ logger->log1("No valid value for 'start' or 'end'");
+ continue;
+ }
+
+ while (end >= start)
+ {
+ Image *img = imageset->get(start);
+
+ if (!img)
+ {
+ logger->log("No image at index %d", start);
+ continue;
+ }
+
+ mParticleRotation.addFrame(img, delay,
+ offsetX, offsetY);
+ start++;
+ }
+ }
+ else if (xmlStrEqual(frameNode->name, BAD_CAST "end"))
+ {
+ mParticleRotation.addTerminator();
+ }
+ } // for frameNode
+ }
+ else if (xmlStrEqual(propertyNode->name, BAD_CAST "animation"))
+ {
+ ImageSet *imageset = ResourceManager::getInstance()->getImageSet(
+ XML::getProperty(propertyNode, "imageset", ""),
+ XML::getProperty(propertyNode, "width", 0),
+ XML::getProperty(propertyNode, "height", 0)
+ );
+
+ if (!imageset)
+ {
+ logger->log1("Error: no valid imageset");
+ continue;
+ }
+
+ // Get animation frames
+ for_each_xml_child_node(frameNode, propertyNode)
+ {
+ int delay = XML::getProperty(frameNode, "delay", 0);
+ int offsetX = XML::getProperty(frameNode, "offsetX", 0);
+ int offsetY = XML::getProperty(frameNode, "offsetY", 0);
+ offsetY -= imageset->getHeight() - 32;
+ offsetX -= imageset->getWidth() / 2 - 16;
+
+ if (xmlStrEqual(frameNode->name, BAD_CAST "frame"))
+ {
+ int index = XML::getProperty(frameNode, "index", -1);
+
+ if (index < 0)
+ {
+ logger->log1("No valid value for 'index'");
+ continue;
+ }
+
+ Image *img = imageset->get(index);
+
+ if (!img)
+ {
+ logger->log("No image at index %d", index);
+ continue;
+ }
+
+ mParticleAnimation.addFrame(img, delay, offsetX, offsetY);
+ }
+ else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence"))
+ {
+ int start = XML::getProperty(frameNode, "start", -1);
+ int end = XML::getProperty(frameNode, "end", -1);
+
+ if (start < 0 || end < 0)
+ {
+ logger->log1("No valid value for 'start' or 'end'");
+ continue;
+ }
+
+ while (end >= start)
+ {
+ Image *img = imageset->get(start);
+
+ if (!img)
+ {
+ logger->log("No image at index %d", start);
+ continue;
+ }
+
+ mParticleAnimation.addFrame(img, delay,
+ offsetX, offsetY);
+ start++;
+ }
+ }
+ else if (xmlStrEqual(frameNode->name, BAD_CAST "end"))
+ {
+ mParticleAnimation.addTerminator();
+ }
+ } // for frameNode
+ }
+ else if (xmlStrEqual(propertyNode->name, BAD_CAST "deatheffect"))
+ {
+ mDeathEffect = (const char*)propertyNode->xmlChildrenNode->content;
+ mDeathEffectConditions = 0x00;
+ if (XML::getBoolProperty(propertyNode, "on-floor", true))
+ mDeathEffectConditions += Particle::DEAD_FLOOR;
+ if (XML::getBoolProperty(propertyNode, "on-sky", true))
+ mDeathEffectConditions += Particle::DEAD_SKY;
+ if (XML::getBoolProperty(propertyNode, "on-other", false))
+ mDeathEffectConditions += Particle::DEAD_OTHER;
+ if (XML::getBoolProperty(propertyNode, "on-impact", true))
+ mDeathEffectConditions += Particle::DEAD_IMPACT;
+ if (XML::getBoolProperty(propertyNode, "on-timeout", true))
+ mDeathEffectConditions += Particle::DEAD_TIMEOUT;
+ }
+ }
+}
+
+ParticleEmitter::ParticleEmitter(const ParticleEmitter &o)
+{
+ *this = o;
+}
+
+ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o)
+{
+ mParticlePosX = o.mParticlePosX;
+ mParticlePosY = o.mParticlePosY;
+ mParticlePosZ = o.mParticlePosZ;
+ mParticleAngleHorizontal = o.mParticleAngleHorizontal;
+ mParticleAngleVertical = o.mParticleAngleVertical;
+ mParticlePower = o.mParticlePower;
+ mParticleGravity = o.mParticleGravity;
+ mParticleRandomness = o.mParticleRandomness;
+ mParticleBounce = o.mParticleBounce;
+ mParticleFollow = o.mParticleFollow;
+ mParticleTarget = o.mParticleTarget;
+ mParticleAcceleration = o.mParticleAcceleration;
+ mParticleDieDistance = o.mParticleDieDistance;
+ mParticleMomentum = o.mParticleMomentum;
+ mParticleLifetime = o.mParticleLifetime;
+ mParticleFadeOut = o.mParticleFadeOut;
+ mParticleFadeIn = o.mParticleFadeIn;
+ mParticleAlpha = o.mParticleAlpha;
+ mMap = o.mMap;
+ mOutput = o.mOutput;
+ mOutputPause = o.mOutputPause;
+ mParticleImage = o.mParticleImage;
+ mParticleAnimation = o.mParticleAnimation;
+ mParticleRotation = o.mParticleRotation;
+ mParticleChildEmitters = o.mParticleChildEmitters;
+ mDeathEffectConditions = o.mDeathEffectConditions;
+
+ mOutputPauseLeft = 0;
+
+ if (mParticleImage)
+ mParticleImage->incRef();
+
+ return *this;
+}
+
+
+ParticleEmitter::~ParticleEmitter()
+{
+ if (mParticleImage)
+ mParticleImage->decRef();
+}
+
+
+template <typename T> ParticleEmitterProp<T>
+ParticleEmitter::readParticleEmitterProp(xmlNodePtr propertyNode, T def)
+{
+ ParticleEmitterProp<T> retval;
+
+ def = (T) XML::getFloatProperty(propertyNode, "value", (double) def);
+ retval.set((T) XML::getFloatProperty(propertyNode, "min", (double) def),
+ (T) XML::getFloatProperty(propertyNode, "max", (double) def));
+
+ std::string change = XML::getProperty(propertyNode, "change-func", "none");
+ T amplitude = (T) XML::getFloatProperty(propertyNode,
+ "change-amplitude", 0.0);
+ int period = XML::getProperty(propertyNode, "change-period", 0);
+ int phase = XML::getProperty(propertyNode, "change-phase", 0);
+ if (change == "saw" || change == "sawtooth")
+ retval.setFunction(FUNC_SAW, amplitude, period, phase);
+ else if (change == "sine" || change == "sinewave")
+ retval.setFunction(FUNC_SINE, amplitude, period, phase);
+ else if (change == "triangle")
+ retval.setFunction(FUNC_TRIANGLE, amplitude, period, phase);
+ else if (change == "square")
+ retval.setFunction(FUNC_SQUARE, amplitude, period, phase);
+
+ return retval;
+}
+
+
+std::list<Particle *> ParticleEmitter::createParticles(int tick)
+{
+ std::list<Particle *> newParticles;
+
+ if (mOutputPauseLeft > 0)
+ {
+ mOutputPauseLeft--;
+ return newParticles;
+ }
+ mOutputPauseLeft = mOutputPause.value(tick);
+
+ for (int i = mOutput.value(tick); i > 0; i--)
+ {
+ // Limit maximum particles
+ if (Particle::particleCount > Particle::maxCount)
+ break;
+
+ Particle *newParticle;
+ if (mParticleImage)
+ {
+ std::string name = mParticleImage->getIdPath();
+ if (ImageParticle::imageParticleCountByName.find(name) ==
+ ImageParticle::imageParticleCountByName.end())
+ {
+ ImageParticle::imageParticleCountByName[name] = 0;
+ }
+
+ if (ImageParticle::imageParticleCountByName[name] > 200)
+ break;
+
+ newParticle = new ImageParticle(mMap, mParticleImage);
+ }
+ else if (mParticleRotation.getLength() > 0)
+ {
+ Animation *newAnimation = new Animation(mParticleRotation);
+ newParticle = new RotationalParticle(mMap, newAnimation);
+ }
+ else if (mParticleAnimation.getLength() > 0)
+ {
+ Animation *newAnimation = new Animation(mParticleAnimation);
+ newParticle = new AnimationParticle(mMap, newAnimation);
+ }
+ else
+ {
+ newParticle = new Particle(mMap);
+ }
+
+ Vector position(mParticlePosX.value(tick),
+ mParticlePosY.value(tick),
+ mParticlePosZ.value(tick));
+ newParticle->moveTo(position);
+
+ float angleH = mParticleAngleHorizontal.value(tick);
+ float angleV = mParticleAngleVertical.value(tick);
+ float power = mParticlePower.value(tick);
+ newParticle->setVelocity(
+ static_cast<float>(cos(angleH) * cos(angleV) * power),
+ static_cast<float>(sin(angleH) * cos(angleV) * power),
+ static_cast<float>(sin(angleV) * power));
+
+ newParticle->setRandomness(mParticleRandomness.value(tick));
+ newParticle->setGravity(mParticleGravity.value(tick));
+ newParticle->setBounce(mParticleBounce.value(tick));
+ newParticle->setFollow(mParticleFollow);
+
+ newParticle->setDestination(mParticleTarget,
+ mParticleAcceleration.value(tick),
+ mParticleMomentum.value(tick)
+ );
+ newParticle->setDieDistance(mParticleDieDistance.value(tick));
+
+ newParticle->setLifetime(mParticleLifetime.value(tick));
+ newParticle->setFadeOut(mParticleFadeOut.value(tick));
+ newParticle->setFadeIn(mParticleFadeIn.value(tick));
+ newParticle->setAlpha(mParticleAlpha.value(tick));
+
+ for (std::list<ParticleEmitter>::iterator
+ i = mParticleChildEmitters.begin();
+ i != mParticleChildEmitters.end(); i++)
+ {
+ newParticle->addEmitter(new ParticleEmitter(*i));
+ }
+
+ if (!mDeathEffect.empty())
+ {
+ newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions);
+ }
+
+ newParticles.push_back(newParticle);
+ }
+
+ return newParticles;
+}
+
+void ParticleEmitter::adjustSize(int w, int h)
+{
+ if (w == 0 || h == 0)
+ return; // new dimensions are illegal
+
+ // calculate the old rectangle
+ int oldWidth = static_cast<int>(mParticlePosX.maxVal
+ - mParticlePosX.minVal);
+ int oldHeight = static_cast<int>(mParticlePosX.maxVal
+ - mParticlePosY.minVal);
+ int oldArea = oldWidth * oldHeight;
+ if (oldArea == 0)
+ {
+ //when the effect has no dimension it is
+ //not designed to be resizeable
+ return;
+ }
+
+ // set the new dimensions
+ mParticlePosX.set(0, static_cast<float>(w));
+ mParticlePosY.set(0, static_cast<float>(h));
+ int newArea = w * h;
+ // adjust the output so that the particle density stays the same
+ float outputFactor = (float)newArea / (float)oldArea;
+ mOutput.minVal *= static_cast<float>(outputFactor);
+ mOutput.maxVal *= static_cast<float>(outputFactor);
+}