/* * Aethyra * Copyright (C) 2006 The Mana World Development Team * * This file is part of Aethyra based on original code * from The Mana World. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "animationparticle.h" #include "configuration.h" #include "imageparticle.h" #include "log.h" #include "map.h" #include "particle.h" #include "particleemitter.h" #include "textparticle.h" #include "resources/resourcemanager.h" #include "utils/dtor.h" #include "utils/fastsqrt.h" #include "utils/xml.h" #define SIN45 0.707106781f class Graphics; class Image; int Particle::particleCount = 0; int Particle::maxCount = 0; int Particle::fastPhysics = 0; int Particle::emitterSkip = 1; const float Particle::PARTICLE_SKY = 800.0f; Particle::Particle(Map *map): mAlive(true), mLifetimeLeft(-1), mLifetimePast(0), mFadeOut(0), mFadeIn(0), mAlpha(1.0f), mAutoDelete(true), mMap(map), mGravity(0.0f), mRandomness(0), mBounce(0.0f), mFollow(false), mTarget(NULL), mAcceleration(0.0f), mInvDieDistance(-1.0f), mMomentum(1.0f) { Particle::particleCount++; if (mMap) setSpriteIterator(mMap->addSprite(this)); } Particle::~Particle() { // Remove from map sprite list if (mMap) mMap->removeSprite(mSpriteIterator); // Delete child emitters and child particles clear(); Particle::particleCount--; } void Particle::setupEngine() { Particle::maxCount = (int)config.getValue("particleMaxCount", 3000); Particle::fastPhysics = (int)config.getValue("particleFastPhysics", 0); Particle::emitterSkip = (int)config.getValue("particleEmitterSkip", 1) + 1; disableAutoDelete(); logger->log("Particle engine set up"); } void Particle::draw(Graphics *, int, int) const { } bool Particle::update() { if (!mMap) return false; if (mLifetimeLeft == 0) mAlive = false; Vector oldPos = mPos; if (mAlive) { //calculate particle movement if (mMomentum != 1.0f) { mVelocity *= mMomentum; } if (mTarget && mAcceleration != 0.0f) { Vector dist = mPos - mTarget->getPosition(); dist.x *= SIN45; float invHypotenuse; switch (Particle::fastPhysics) { case 1: invHypotenuse = fastInvSqrt( dist.x * dist.x + dist.y * dist.y + dist.z * dist.z); break; case 2: invHypotenuse = 2.0f / fabs(dist.x) + fabs(dist.y) + fabs(dist.z); break; default: invHypotenuse = 1.0f / sqrt( dist.x * dist.x + dist.y * dist.y + dist.z * dist.z); break; } if (invHypotenuse) { if (mInvDieDistance > 0.0f && invHypotenuse > mInvDieDistance) { mAlive = false; } float accFactor = invHypotenuse * mAcceleration; mVelocity -= dist * accFactor; } } if (mRandomness > 0) { mVelocity.x += (rand()%mRandomness - rand()%mRandomness) / 1000.0f; mVelocity.y += (rand()%mRandomness - rand()%mRandomness) / 1000.0f; mVelocity.z += (rand()%mRandomness - rand()%mRandomness) / 1000.0f; } mVelocity.z -= mGravity; // Update position mPos.x += mVelocity.x; mPos.y += mVelocity.y * SIN45; mPos.z += mVelocity.z * SIN45; // Update other stuff if (mLifetimeLeft > 0) { mLifetimeLeft--; } mLifetimePast++; if (mPos.z > PARTICLE_SKY || mPos.z < 0.0f) { if (mBounce > 0.0f) { mPos.z *= -mBounce; mVelocity *= mBounce; mVelocity.z = -mVelocity.z; } else { mAlive = false; } } // Update child emitters if ((mLifetimePast-1)%Particle::emitterSkip == 0) { for (EmitterIterator e = mChildEmitters.begin(); e != mChildEmitters.end(); e++) { Particles newParticles = (*e)->createParticles(mLifetimePast); for (ParticleIterator p = newParticles.begin(); p != newParticles.end(); p++) { (*p)->moveBy(mPos); mChildParticles.push_back (*p); } } } } Vector change = mPos - oldPos; // Update child particles for (ParticleIterator p = mChildParticles.begin(); p != mChildParticles.end();) { //move particle with its parent if desired if ((*p)->doesFollow()) { (*p)->moveBy(change); } //update particle if ((*p)->update()) { p++; } else { delete (*p); p = mChildParticles.erase(p); } } if (!mAlive && mChildParticles.empty() && mAutoDelete) { return false; } return true; } void Particle::moveBy(const Vector &change) { mPos += change; for (ParticleIterator p = mChildParticles.begin(); p != mChildParticles.end(); p++) { if ((*p)->doesFollow()) { (*p)->moveBy(change); } } } void Particle::moveTo(float x, float y) { moveTo(Vector(x, y, mPos.z)); } Particle* Particle::addEffect(const std::string &particleEffectFile, int pixelX, int pixelY, int rotation) { Particle *newParticle = NULL; XML::Document doc(particleEffectFile); xmlNodePtr rootNode = doc.rootNode(); if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "effect")) { logger->log("Error loading particle: %s", particleEffectFile.c_str()); return NULL; } ResourceManager *resman = ResourceManager::getInstance(); // Parse particles for_each_xml_child_node(effectChildNode, rootNode) { // We're only interested in particles if (!xmlStrEqual(effectChildNode->name, BAD_CAST "particle")) continue; // Determine the exact particle type xmlNodePtr node; // Animation if ((node = XML::findFirstChildByName(effectChildNode, "animation"))) { newParticle = new AnimationParticle(mMap, node); } // Image else if ((node = XML::findFirstChildByName(effectChildNode, "image"))) { Image *img= resman->getImage((const char*) node->xmlChildrenNode->content); newParticle = new ImageParticle(mMap, img); } // Other else { newParticle = new Particle(mMap); } // Read and set the basic properties of the particle float offsetX = XML::getFloatProperty(effectChildNode, "position-x", 0); float offsetY = XML::getFloatProperty(effectChildNode, "position-y", 0); float offsetZ = XML::getFloatProperty(effectChildNode, "position-z", 0); Vector position (mPos.x + (float)pixelX + offsetX, mPos.y + (float)pixelY + offsetY, mPos.z + offsetZ); newParticle->moveTo(position); int lifetime = XML::getProperty(effectChildNode, "lifetime", -1); newParticle->setLifetime(lifetime); // Look for additional emitters for this particle for_each_xml_child_node(emitterNode, effectChildNode) { if (!xmlStrEqual(emitterNode->name, BAD_CAST "emitter")) continue; ParticleEmitter *newEmitter; newEmitter = new ParticleEmitter(emitterNode, newParticle, mMap, rotation); newParticle->addEmitter(newEmitter); } mChildParticles.push_back(newParticle); } return newParticle; } Particle *Particle::addTextSplashEffect(const std::string &text, int x, int y, const gcn::Color *color, gcn::Font *font, bool outline) { Particle *newParticle = new TextParticle(mMap, text, color, font, outline); newParticle->moveTo(x, y); newParticle->setVelocity(((rand() % 100) - 50) / 200.0f, // X ((rand() % 100) - 50) / 200.0f, // Y ((rand() % 100) / 200.0f) + 4.0f); // Z newParticle->setGravity(0.1f); newParticle->setBounce(0.5f); newParticle->setLifetime(200); newParticle->setFadeOut(100); mChildParticles.push_back(newParticle); return newParticle; } Particle *Particle::addTextRiseFadeOutEffect(const std::string &text, int x, int y, const gcn::Color *color, gcn::Font *font, bool outline) { Particle *newParticle = new TextParticle(mMap, text, color, font, outline); newParticle->moveTo(x, y); newParticle->setVelocity(0.0f, 0.0f, 0.5f); newParticle->setGravity(0.0015f); newParticle->setLifetime(300); newParticle->setFadeOut(50); newParticle->setFadeIn(200); mChildParticles.push_back(newParticle); return newParticle; } void Particle::setMap(Map *map) { mMap = map; if (mMap) setSpriteIterator(mMap->addSprite(this)); } void Particle::clear() { delete_all(mChildEmitters); mChildEmitters.clear(); delete_all(mChildParticles); mChildParticles.clear(); }