/* * The Mana World * Copyright 2006 The Mana World Development Team * * This file is part of The Mana World. * * The Mana World 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. * * The Mana World 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 The Mana World; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id$ */ #include "particle.h" #include #include "animationparticle.h" #include "configuration.h" #include "imageparticle.h" #include "log.h" #include "map.h" #include "particleemitter.h" #include "textparticle.h" #include "resources/resourcemanager.h" #include "utils/dtor.h" #include "utils/fastsqrt.h" #include "utils/xml.h" 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), mPosX(0.0f), mPosY(0.0f), mPosZ(0.0f), mLifetimeLeft(-1), mLifetimePast(0), mFadeOut(0), mFadeIn(0), mAutoDelete(true), mMap(map), mVectorX(0.0f), mVectorY(0.0f), mVectorZ(0.0f), mGravity(0.0f), mRandomnes(0), mBounce(0.0f), mTarget(NULL), mAcceleration(0.0f), mInvDieDistance(-1.0f), mMomentum(1.0f) { Particle::particleCount++; if (mMap) setSpriteIterator(mMap->addSprite(this)); } void Particle::setupEngine() { Particle::maxCount = (int)config.getValue("particleMaxCount", 3000); Particle::fastPhysics = (int)config.getValue("particleFastPhysics", 0); Particle::emitterSkip = (int)config.getValue("particleEmitterSkip", 0) + 1; disableAutoDelete(); logger->log("Particle engine set up"); } bool Particle::update() { if (!mMap) return false; if (mLifetimeLeft == 0) { mAlive = false; } if (mAlive) { // Update child emitters if (mLifetimePast%Particle::emitterSkip == 0) { for ( EmitterIterator e = mChildEmitters.begin(); e != mChildEmitters.end(); e++ ) { Particles newParticles = (*e)->createParticles(); for ( ParticleIterator p = newParticles.begin(); p != newParticles.end(); p++ ) { (*p)->moveBy(mPosX, mPosY, mPosZ); mChildParticles.push_back (*p); } } } if (mMomentum != 1.0f) { mVectorX *= mMomentum; mVectorY *= mMomentum; mVectorZ *= mMomentum; } if (mTarget && mAcceleration != 0.0f) { float distX = mPosX - mTarget->getPosX(); float distY = mPosY - mTarget->getPosY(); float distZ = mPosZ - mTarget->getPosZ(); float invHypotenuse; switch(Particle::fastPhysics) { case 1: invHypotenuse = fastInvSqrt( distX * distX + distY * distY + distZ * distZ); break; case 2: invHypotenuse = 2.0f / fabs(distX) + fabs(distY) + fabs(distZ); break; default: invHypotenuse = 1.0f / sqrt( distX * distX + distY * distY + distZ * distZ); break; } if (invHypotenuse) { if (mInvDieDistance > 0.0f && invHypotenuse > mInvDieDistance) { mAlive = false; } float accFactor = invHypotenuse * mAcceleration; mVectorX -= distX * accFactor; mVectorY -= distY * accFactor; mVectorZ -= distZ * accFactor; } } if (mRandomnes > 0) { mVectorX += (rand()%mRandomnes - rand()%mRandomnes) / 1000.0f; mVectorY += (rand()%mRandomnes - rand()%mRandomnes) / 1000.0f; mVectorZ += (rand()%mRandomnes - rand()%mRandomnes) / 1000.0f; } mVectorZ -= mGravity; // Update position mPosX += mVectorX; mPosY += mVectorY; mPosZ += mVectorZ; // Update other stuff if (mLifetimeLeft > 0) { mLifetimeLeft--; } mLifetimePast++; if (mPosZ > PARTICLE_SKY || mPosZ < 0.0f) { if (mBounce > 0.0f) { mPosZ *= -mBounce; mVectorX *= mBounce; mVectorY *= mBounce; mVectorZ *= -mBounce; } else { mAlive = false; } } } // Update child particles for ( ParticleIterator p = mChildParticles.begin(); p != mChildParticles.end(); ) { if ((*p)->update()) { p++; } else { delete (*p); p = mChildParticles.erase(p); } } if (!mAlive && mChildParticles.empty() && mAutoDelete) { return false; } return true; } void Particle::draw(Graphics *graphics, int offsetX, int offsetY) const { } Particle* Particle::addEffect(std::string particleEffectFile, int pixelX, int pixelY) { Particle *newParticle = NULL; // XML parser initialisation stuff int size; ResourceManager *resman = ResourceManager::getInstance(); char *data = (char*) resman->loadFile(particleEffectFile.c_str(), size); if (!data) { logger->log("Warning: Particle engine could not find %s !", particleEffectFile.c_str()); return NULL; } xmlDocPtr doc = xmlParseMemory(data, size); free(data); if (!doc) { logger->log("Warning: Particle engine found syntax error in %s!", particleEffectFile.c_str()); return NULL; } xmlNodePtr rootNode = xmlDocGetRootElement(doc); if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "effect")) { logger->log("Warning: %s is not a valid particle effect definition file!", particleEffectFile.c_str()); return NULL; } // 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 int offsetX = XML::getProperty(effectChildNode, "position-x", 0); int offsetY = XML::getProperty(effectChildNode, "position-y", 0); int offsetZ = XML::getProperty(effectChildNode, "position-z", 0); int particleX = (int)mPosX + pixelX + offsetX; int particleY = (int)mPosY + pixelY + offsetY; int particleZ = (int)mPosZ + offsetZ; int lifetime = XML::getProperty(effectChildNode, "lifetime", -1); newParticle->setPosition(particleX, particleY, particleZ); 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); newParticle->addEmitter(newEmitter); } mChildParticles.push_back(newParticle); } return newParticle; } Particle* Particle::addTextSplashEffect(std::string text, int colorR, int colorG, int colorB, gcn::Font *font, int x, int y) { Particle *newParticle = new TextParticle(mMap, text, colorR, colorG, colorB, font); newParticle->setPosition(x, y, 0); newParticle->setVector ( ((rand()%100) - 50) / 200.0f, // X vector ((rand()%100) - 50) / 200.0f, // Y vector ((rand()%100) / 200.0f) + 4.0f // Z vector ); newParticle->setGravity(0.1f); newParticle->setBounce(0.5f); newParticle->setLifetime(200); newParticle->setFadeOut(100); mChildParticles.push_back(newParticle); return newParticle; } void Particle::setMap(Map *map) { mMap = map; if (mMap) setSpriteIterator(mMap->addSprite(this)); // TODO: Create map emitters based on emitter data in map data } Particle::~Particle() { // Remove from map sprite list if (mMap) mMap->removeSprite(mSpriteIterator); // Delete child emitters and child particles clear(); Particle::particleCount--; } void Particle::clear() { std::for_each(mChildEmitters.begin(), mChildEmitters.end(), make_dtor(mChildEmitters)); mChildEmitters.clear(); std::for_each(mChildParticles.begin(), mChildParticles.end(), make_dtor(mChildParticles)); mChildParticles.clear(); }