diff options
Diffstat (limited to 'src/particle/particleemitter.cpp')
-rw-r--r-- | src/particle/particleemitter.cpp | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/src/particle/particleemitter.cpp b/src/particle/particleemitter.cpp new file mode 100644 index 000000000..9e273e4a1 --- /dev/null +++ b/src/particle/particleemitter.cpp @@ -0,0 +1,674 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 "particle/particleemitter.h" + +#include "logger.h" + +#include "particle/animationparticle.h" +#include "particle/rotationalparticle.h" + +#include "resources/dye.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include <cmath> + +#include "debug.h" + +static const float SIN45 = 0.707106781f; +static const float DEG_RAD_FACTOR = 0.017453293f; + +typedef std::vector<ImageSet*>::const_iterator ImageSetVectorCIter; +typedef std::list<ParticleEmitter>::const_iterator ParticleEmitterListCIter; + +ParticleEmitter::ParticleEmitter(const XmlNodePtr emitterNode, + Particle *const target, + Map *const map, const int rotation, + const std::string& dyePalettes) : + mParticleTarget(target), + mMap(map), + mParticleImage(nullptr), + mOutputPauseLeft(0), + mDeathEffectConditions(0), + mParticleFollow(false) +{ + // 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); + 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 (xmlNameEqual(propertyNode, "property")) + { + const 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) + { + if (!dyePalettes.empty()) + Dye::instantiate(image, dyePalettes); + + ResourceManager *const resman + = ResourceManager::getInstance(); + mParticleImage = resman->getImage(image); + } + } + else if (name == "subimage") + { + std::string image = XML::getProperty( + propertyNode, "value", ""); + // Don't leak when multiple images are defined + if (!image.empty() && !mParticleImage) + { + if (!dyePalettes.empty()) + Dye::instantiate(image, dyePalettes); + + ResourceManager *const resman + = ResourceManager::getInstance(); + Image *img = resman->getImage(image); + if (img) + { + mParticleImage = resman->getSubImage(img, + XML::getProperty(propertyNode, "x", 0), + XML::getProperty(propertyNode, "y", 0), + XML::getProperty(propertyNode, "width", 0), + XML::getProperty(propertyNode, "height", 0)); + img->decRef(); + } + else + { + mParticleImage = nullptr; + } + } + } + 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 (xmlNameEqual(propertyNode, "emitter")) + { + ParticleEmitter newEmitter(propertyNode, mParticleTarget, map, + rotation, dyePalettes); + mParticleChildEmitters.push_back(newEmitter); + } + else if (xmlNameEqual(propertyNode, "rotation")) + { + ImageSet *const imageset = getImageSet(propertyNode); + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + mTempSets.push_back(imageset); + + // Get animation frames + for_each_xml_child_node(frameNode, propertyNode) + { + const int delay = XML::getIntProperty( + frameNode, "delay", 0, 0, 100000); + const int offsetX = XML::getProperty(frameNode, "offsetX", 0) + - imageset->getWidth() / 2 + 16; + const int offsetY = XML::getProperty(frameNode, "offsetY", 0) + - imageset->getHeight() + 32; + const int rand = XML::getIntProperty( + frameNode, "rand", 100, 0, 100); + + if (xmlNameEqual(frameNode, "frame")) + { + const int index = XML::getProperty(frameNode, "index", -1); + + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *const img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + mParticleRotation.addFrame(img, delay, + offsetX, offsetY, rand); + } + else if (xmlNameEqual(frameNode, "sequence")) + { + int start = XML::getProperty(frameNode, "start", -1); + const 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 *const img = imageset->get(start); + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + mParticleRotation.addFrame(img, delay, + offsetX, offsetY, rand); + start ++; + } + } + else if (xmlNameEqual(frameNode, "end")) + { + mParticleRotation.addTerminator(rand); + } + } // for frameNode + } + else if (xmlNameEqual(propertyNode, "animation")) + { + ImageSet *const imageset = getImageSet(propertyNode); + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + mTempSets.push_back(imageset); + + // Get animation frames + for_each_xml_child_node(frameNode, propertyNode) + { + const int delay = XML::getIntProperty( + frameNode, "delay", 0, 0, 100000); + const int offsetX = XML::getProperty(frameNode, "offsetX", 0) + - imageset->getWidth() / 2 + 16; + const int offsetY = XML::getProperty(frameNode, "offsetY", 0) + - imageset->getHeight() + 32; + const int rand = XML::getIntProperty( + frameNode, "rand", 100, 0, 100); + + if (xmlNameEqual(frameNode, "frame")) + { + const int index = XML::getProperty(frameNode, "index", -1); + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *const img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + mParticleAnimation.addFrame(img, delay, + offsetX, offsetY, rand); + } + else if (xmlNameEqual(frameNode, "sequence")) + { + int start = XML::getProperty(frameNode, "start", -1); + const 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 *const img = imageset->get(start); + + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + mParticleAnimation.addFrame(img, delay, + offsetX, offsetY, rand); + start++; + } + } + else if (xmlNameEqual(frameNode, "end")) + { + mParticleAnimation.addTerminator(rand); + } + } // for frameNode + } + else if (xmlNameEqual(propertyNode, "deatheffect")) + { + mDeathEffect = reinterpret_cast<const char*>( + propertyNode->xmlChildrenNode->content); + mDeathEffectConditions = 0x00; + if (XML::getBoolProperty(propertyNode, "on-floor", true)) + { + mDeathEffectConditions += static_cast<signed char>( + Particle::DEAD_FLOOR); + } + if (XML::getBoolProperty(propertyNode, "on-sky", true)) + { + mDeathEffectConditions += static_cast<signed char>( + Particle::DEAD_SKY); + } + if (XML::getBoolProperty(propertyNode, "on-other", false)) + { + mDeathEffectConditions += static_cast<signed char>( + Particle::DEAD_OTHER); + } + if (XML::getBoolProperty(propertyNode, "on-impact", true)) + { + mDeathEffectConditions += static_cast<signed char>( + Particle::DEAD_IMPACT); + } + if (XML::getBoolProperty(propertyNode, "on-timeout", true)) + { + mDeathEffectConditions += static_cast<signed char>( + Particle::DEAD_TIMEOUT); + } + } + } +} + +ParticleEmitter::ParticleEmitter(const ParticleEmitter &o) +{ + *this = o; +} + +ImageSet *ParticleEmitter::getImageSet(XmlNodePtr node) +{ + ResourceManager *const resman = ResourceManager::getInstance(); + ImageSet *imageset = nullptr; + const int subX = XML::getProperty(node, "subX", -1); + if (subX != -1) + { + Image *const img = resman->getImage(XML::getProperty( + node, "imageset", "")); + if (!img) + return nullptr; + + Image *const img2 = resman->getSubImage(img, subX, + XML::getProperty(node, "subY", 0), + XML::getProperty(node, "subWidth", 0), + XML::getProperty(node, "subHeight", 0)); + if (!img2) + { + img->decRef(); + return nullptr; + } + + imageset = resman->getSubImageSet(img2, + XML::getProperty(node, "width", 0), + XML::getProperty(node, "height", 0)); + img2->decRef(); + img->decRef(); + } + else + { + imageset = resman->getImageSet( + XML::getProperty(node, "imageset", ""), + XML::getProperty(node, "width", 0), + XML::getProperty(node, "height", 0)); + } + return imageset; +} + +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; + mDeathEffect = o.mDeathEffect; + mTempSets = o.mTempSets; + + FOR_EACH (ImageSetVectorCIter, i, mTempSets) + { + if (*i) + (*i)->incRef(); + } + + mOutputPauseLeft = 0; + + if (mParticleImage) + mParticleImage->incRef(); + + return *this; +} + +ParticleEmitter::~ParticleEmitter() +{ + FOR_EACH (ImageSetVectorCIter, i, mTempSets) + { + if (*i) + (*i)->decRef(); + } + mTempSets.clear(); + + if (mParticleImage) + { + mParticleImage->decRef(); + mParticleImage = nullptr; + } +} + +template <typename T> ParticleEmitterProp<T> +ParticleEmitter::readParticleEmitterProp(XmlNodePtr propertyNode, T def) +{ + ParticleEmitterProp<T> retval; + + def = static_cast<T>(XML::getFloatProperty(propertyNode, "value", + static_cast<double>(def))); + retval.set(static_cast<T>(XML::getFloatProperty(propertyNode, "min", + static_cast<double>(def))), static_cast<T>(XML::getFloatProperty( + propertyNode, "max", static_cast<double>(def)))); + + const std::string change = XML::getProperty( + propertyNode, "change-func", "none"); + T amplitude = static_cast<T>(XML::getFloatProperty(propertyNode, + "change-amplitude", 0.0)); + + const int period = XML::getProperty(propertyNode, "change-period", 0); + const 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(const 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) + { + const 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.mFrames.empty()) + { + Animation *const newAnimation = new Animation(mParticleRotation); + newParticle = new RotationalParticle(mMap, newAnimation); + } + else if (!mParticleAnimation.mFrames.empty()) + { + Animation *const newAnimation = new Animation(mParticleAnimation); + newParticle = new AnimationParticle(mMap, newAnimation); + } + else + { + newParticle = new Particle(mMap); + } + + const Vector position(mParticlePosX.value(tick), + mParticlePosY.value(tick), + mParticlePosZ.value(tick)); + newParticle->moveTo(position); + + const float angleH = mParticleAngleHorizontal.value(tick); + const float cosAngleH = cos(angleH); + const float sinAngleH = sin(angleH); + const float angleV = mParticleAngleVertical.value(tick); + const float cosAngleV = cos(angleV); + const float sinAngleV = sin(angleV); + const float power = mParticlePower.value(tick); + newParticle->setVelocity(cosAngleH * cosAngleV * power, + sinAngleH * cosAngleV * power, + sinAngleV * 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_EACH (ParticleEmitterListCIter, it, mParticleChildEmitters) + newParticle->addEmitter(new ParticleEmitter(*it)); + + if (!mDeathEffect.empty()) + newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions); + + newParticles.push_back(newParticle); + } + + return newParticles; +} + +void ParticleEmitter::adjustSize(const int w, const int h) +{ + if (w == 0 || h == 0) + return; // new dimensions are illegal + + // calculate the old rectangle + const int oldArea = static_cast<int>( + mParticlePosX.maxVal - mParticlePosX.minVal) * static_cast<int>( + mParticlePosX.maxVal - mParticlePosY.minVal); + 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)); + const int newArea = w * h; + // adjust the output so that the particle density stays the same + const float outputFactor = static_cast<float>(newArea) + / static_cast<float>(oldArea); + mOutput.minVal = static_cast<int>(static_cast<float>( + mOutput.minVal) * outputFactor); + mOutput.maxVal = static_cast<int>(static_cast<float>( + mOutput.maxVal) * outputFactor); +} |