/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx 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, or (at your option) any later version. ### File Specific: main-programmer: Benjamin Grauer co-programmer: ... */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_GRAPHICS #include "particle_system.h" #include "particle_emitter.h" #include "physics/fields/field.h" #include "model.h" #include "util/loading/load_param_xml.h" #include "util/loading/factory.h" #include "material.h" #include "state.h" #include "shell_command.h" #include ObjectListDefinition(ParticleSystem); /** * standard constructor * @param maxCount the Count of particles in the System * @param type The Type of the ParticleSystem */ ParticleSystem::ParticleSystem (unsigned int maxCount) { this->registerObject(this, ParticleSystem::_objectList); this->setMaxCount(PARTICLE_DEFAULT_MAX_COUNT); this->count = 0; this->particles = NULL; this->deadList = NULL; this->conserve = 1.0; this->lifeSpan = 1.0; this->randomLifeSpan = 0.0; this->toList(OM_ENVIRON); this->maxCount = maxCount; } /** * @brief standard deconstructor */ ParticleSystem::~ParticleSystem() { // deleting all the living Particles while (this->particles) { Particle* tmpDelPart = this->particles; this->particles = this->particles->next; delete tmpDelPart; } // deleting all the dead particles while (this->deadList) { Particle* tmpDelPart = this->deadList; this->deadList = this->deadList->next; delete tmpDelPart; } while(!this->emitters.empty()) { this->removeEmitter(this->emitters.front()); } } /** * @brief loads Parameters from a TiXmlElement * @param root the XML-element to load from. */ void ParticleSystem::loadParams(const TiXmlElement* root) { WorldEntity::loadParams(root); PhysicsInterface::loadParams(root); LoadParam(root, "max-count", this, ParticleSystem, setMaxCount) .describe("the maximal count of Particles, that can be emitted into this system"); LoadParam(root, "life-span", this, ParticleSystem, setLifeSpan) .describe("sets the life-span of the Particles."); LoadParam(root, "conserve", this, ParticleSystem, setConserve) .describe("sets the Conserve factor of the Particles (1.0: they keep all their energy, 0.0:they keep no energy)"); LoadParamXML(root, "emitters", this, ParticleSystem, loadEmitters); LOAD_PARAM_START_CYCLE(root, element); { element->ToText(); // PER-PARTICLE-ATTRIBUTES: LoadParam_CYCLE(element, "radius", this, ParticleSystem, setRadius) .describe("The Radius of each particle over time (TimeIndex [0-1], radius at TimeIndex, randomRadius at TimeIndex)"); LoadParam_CYCLE(element, "mass", this, ParticleSystem, setMass) .describe("The Mass of each particle over time (TimeIndex: [0-1], mass at TimeIndex, randomMass at TimeIndex)"); LoadParam_CYCLE(element, "color", this, ParticleSystem, setColor) .describe("The Color of each particle over time (TimeIndex: [0-1], red: [0-1], green: [0-1], blue: [0-1], alpha: [0-1])"); } LOAD_PARAM_END_CYCLE(element); LoadParam(root, "precache", this, ParticleSystem, precache) .describe("Precaches the ParticleSystem for %1 seconds, %2 times per Second") .defaultValues(1.0, 25.0); } /** * @brief loads the Emitters from An XML-Root * @param root the XML-Element to load all emitters from */ void ParticleSystem::loadEmitters(const TiXmlElement* root) { LOAD_PARAM_START_CYCLE(root, element); { BaseObject* emitter = Factory::fabricate(element); if (emitter != NULL) { if (emitter->isA(ParticleEmitter::staticClassID())) this->addEmitter(dynamic_cast(emitter)); else { PRINTF(2)("Tried to load an Element of type '%s' that should be a ParticleEmitter onto '%s::%s'.\n", emitter->getClassCName(), this->getClassCName(), this->getCName()); delete emitter; } } else { PRINTF(2)("Could not Generate Emitter for system %s::%s (wrong type in XML-format)\n", this->getClassCName(), getCName()); } } LOAD_PARAM_END_CYCLE(element); } /** * @param maxCount the maximum count of particles that can be emitted */ void ParticleSystem::setMaxCount(unsigned int maxCount) { this->maxCount = maxCount; PRINTF(4)("MAXCOUNT of %s::%s is %d\n", this->getClassCName(), this->getCName(), maxCount); } // setting properties /** * @brief Sets the lifespan of newly created particles * @param lifeSpan the LifeSpan of each particle in the System * @param randomLifeSpan the Deviation from lifeSpan (random Value). */ void ParticleSystem::setLifeSpan(float lifeSpan, float randomLifeSpan) { this->lifeSpan = lifeSpan; this->randomLifeSpan = randomLifeSpan; PRINTF(4)("LifeTime of %s::%s is %f\n", this->getClassCName(), this->getCName(), lifeSpan); } /** * @brief sets the conserve Factor of newly created particles * @param conserve sets the conserve factor of each particle. * Conserve is the ammount of energy a particle takes from the last Frame into the next. * A Value of 1 means, that all energy is conserved, a Value of 0 means infinit friction. */ void ParticleSystem::setConserve(float conserve) { if (conserve > 1.0) this->conserve = 1.0; else if (conserve < 0.0) this->conserve = 0.0; else this->conserve = conserve; PRINTF(4)("Conserve of %s::%s is %f\n", this->getClassCName(), this->getCName(), conserve); } ///////////////////////////// /* Per-Particle Attributes */ ///////////////////////////// /** * @brief sets a key in the radius-animation on a per-particle basis * @param lifeCycleTime the time (partilceLifeTime/particleAge) [0-1] * @param radius the radius at this position * @param randRadius the randRadius at this position */ void ParticleSystem::setRadius(float lifeCycleTime, float radius, float randRadius) { this->radiusAnim.changeValue(lifeCycleTime, radius); this->randRadiusAnim.changeValue(lifeCycleTime, randRadius); PRINTF(4)("Radius of %s::%s at timeSlice %f is %f with a Random of %f\n", this->getClassCName(), this->getCName(), lifeCycleTime, radius, randRadius); } /** * @brief sets a key in the mass-animation on a per-particle basis * @param lifeCycleTime the time (partilceLifeTime/particleAge) [0-1] * @param mass the mass at this position * @param randMass the randomMass at this position */ void ParticleSystem::setMass(float lifeCycleTime, float mass, float randMass) { this->massAnim.changeValue(lifeCycleTime, mass); this->randMassAnim.changeValue(lifeCycleTime, randMass); } /** * @brief sets a key in the color-animation on a per-particle basis * @param lifeCycleTime: the time (partilceLifeTime/particleAge) [0-1] * @param red: red * @param green: green * @param blue: blue * @param alpha: alpha */ void ParticleSystem::setColor(float lifeCycleTime, float red, float green, float blue, float alpha) { this->colorAnim[0].changeValue(lifeCycleTime, red); this->colorAnim[1].changeValue(lifeCycleTime, green); this->colorAnim[2].changeValue(lifeCycleTime, blue); this->colorAnim[3].changeValue(lifeCycleTime, alpha); PRINTF(4)("Color of %s::%s on timeslice %f is r:%f g:%f b:%f a:%f\n", this->getClassCName(), this->getCName(), lifeCycleTime, red, green, blue, alpha); } /** * @brief sets a key in the color-animation on a per-particle basis * @param lifeCycleTime: the time (partilceLifeTime/particleAge) [0-1] * @param color the Color. */ void ParticleSystem::setColor(float lifeCycleTime, const Color& color) { this->setColor(lifeCycleTime, color.r(), color.g(), color.b(), color.a()); } /** * @brief adds an Emitter to this System. * @param emitter the Emitter to add. */ void ParticleSystem::addEmitter(ParticleEmitter* emitter) { assert (emitter != NULL); if (emitter->getSystem() != NULL) emitter->getSystem()->removeEmitter(emitter); emitter->system = this; this->emitters.push_back(emitter); } /** * @brief removes a ParticleEmitter from this System * @param emitter the Emitter to remove */ void ParticleSystem::removeEmitter(ParticleEmitter* emitter) { assert (emitter != NULL); emitter->system = NULL; this->emitters.remove(emitter); /* std::list::iterator it = std::find(this->emitters.begin(), this->emitters.end(), emitter); if (it != this->emitters.end()) this->emitters.erase(it);*/ } /** * @brief does a Precaching, meaning, that the ParticleSystem(and its emitters) will be ticked force * @param seconds: seconds * @param ticksPerSeconds times per Second. */ void ParticleSystem::precache(unsigned int seconds, unsigned int ticksPerSecond) { std::list::iterator emitter; for (emitter = this->emitters.begin(); emitter != this->emitters.end(); emitter++) (*emitter)->updateNode(.1), (*emitter)->updateNode(.1); PRINTF(4)("Precaching %s::%s %d seconds %d timesPerSecond\n", this->getClassCName(), this->getCName(), seconds, ticksPerSecond); for (unsigned int i = 0; i < seconds*ticksPerSecond; i++) this->tick(1.0/(float)ticksPerSecond); } /** * @brief ticks the system. * @param dt the time to tick all the Particles of the System this is used to get all the particles some motion */ void ParticleSystem::tick(float dt) { Particle* tickPart = particles; // the particle to Tick Particle* prevPart = NULL; while (likely(tickPart != NULL)) { // applying force to the System. if (likely (tickPart->mass > 0.0)) tickPart->velocity += tickPart->extForce / tickPart->mass * dt; tickPart->radius = radiusAnim.getValue(tickPart->lifeCycle) + randRadiusAnim.getValue(tickPart->lifeCycle) * tickPart->radiusRand; tickPart->mass = massAnim.getValue(tickPart->lifeCycle) + randMassAnim.getValue(tickPart->lifeCycle) * tickPart->massRand; tickPart->extForce = Vector(0,0,0); // applying Color this->colorAnim[0].getValue(tickPart->color[0], tickPart->lifeCycle); this->colorAnim[1].getValue(tickPart->color[1], tickPart->lifeCycle); this->colorAnim[2].getValue(tickPart->color[2], tickPart->lifeCycle); this->colorAnim[3].getValue(tickPart->color[3], tickPart->lifeCycle); // rendering new position. tickPart->position += tickPart->velocity * dt; tickPart->orientation *= tickPart->momentum *dt; // many more to come if (this->conserve < 1.0) { tickPart->velocity *= this->conserve; tickPart->momentum *= this->conserve; } // find out if we have to delete tickPart if (unlikely((tickPart->lifeCycle += dt/tickPart->lifeTime) >= 1.0)) { // remove the particle from the list if (likely(prevPart != NULL)) { prevPart->next = tickPart->next; tickPart->next = this->deadList; this->deadList = tickPart; tickPart = prevPart->next; } else { prevPart = NULL; this->particles = tickPart->next; tickPart->next = this->deadList; this->deadList = tickPart; tickPart = this->particles; } --this->count; } else { prevPart = tickPart; tickPart = tickPart->next; } } std::list::iterator emitter; for (emitter = this->emitters.begin(); emitter != this->emitters.end(); emitter++) (*emitter)->tick(dt); } /** * applies some force to a Particle. * @param field the Field to apply. */ void ParticleSystem::applyField(const Field* field) { Particle* tickPart = particles; while (tickPart) { tickPart->extForce += field->calcForce(tickPart->position); tickPart = tickPart->next; } } /** * @returns the count of Faces of this ParticleSystem */ unsigned int ParticleSystem::getFaceCount() const { return this->count; } /** * @brief adds a new Particle to the System * @param position the initial position, where the particle gets emitted. * @param velocity the initial velocity of the particle. * @param orientation the initial orientation of the Paritcle. * @param momentum the initial momentum of the Particle (the speed of its rotation). * @param data some more data given by the emitter */ void ParticleSystem::addParticle(const Vector& position, const Vector& velocity, const Quaternion& orientation, const Quaternion& momentum, unsigned int data) { if (this->count <= this->maxCount) { // if it is the first Particle if (unlikely(particles == NULL)) { if (likely(deadList != NULL)) { this->particles = this->deadList; deadList = deadList->next; } else { PRINTF(5)("Generating new Particle\n"); this->particles = new Particle; } this->particles->next = NULL; } // filling the List from the beginning else { Particle* tmpPart; if (likely(deadList != NULL)) { tmpPart = this->deadList; deadList = deadList->next; } else { PRINTF(5)("Generating new Particle\n"); tmpPart = new Particle; } tmpPart->next = this->particles; this->particles = tmpPart; } particles->lifeTime = this->lifeSpan + (float)(rand()/RAND_MAX)* this->randomLifeSpan; particles->lifeCycle = 0.0; particles->position = position; particles->velocity = velocity; particles->orientation = orientation; particles->momentum = momentum; // particle->rotation = ; //! @todo rotation is once again something to be done. particles->massRand = 2*(float)rand()/RAND_MAX -1; particles->radiusRand = 2* (float)rand()/RAND_MAX -1; particles->mass = this->massAnim.getValue(0.0) + this->randMassAnim.getValue(0.0)*particles->massRand; particles->radius = this->radiusAnim.getValue(0.0) + this->randRadiusAnim.getValue(0.0)*particles->radiusRand; ++this->count; } else PRINTF(4)("maximum count of particles reached not adding any more\n"); } /** * outputs some nice debug information */ void ParticleSystem::debug() const { PRINT(0)(" ParticleCount: %d emitters: %d, maximumCount: %d :: filled %d%%\n", this->count, this->emitters.size(), this->maxCount, ((this->maxCount!=0)?100*this->count/this->maxCount:0)); PRINT(0)(" Coloring sceme: r:"), this->colorAnim[0].debug(); PRINT(0)(" Coloring sceme: g:"), this->colorAnim[1].debug(); PRINT(0)(" Coloring sceme: b:"), this->colorAnim[2].debug(); PRINT(0)(" Coloring sceme: a:"), this->colorAnim[3].debug(); if (likely(this->deadList != NULL)) { PRINT(0)(" - ParticleDeadList is used: "); int i = 1; Particle* tmpPart = this->deadList; while ((tmpPart = tmpPart->next) != NULL) { ++i; } PRINT(0)("count: %d\n", i); } }