/* 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_PARTICLE #include "particle_system.h" #include "particle_emitter.h" #include "particle_engine.h" #include "field.h" #include "compiler.h" #include "material.h" #include "state.h" #include "objModel.h" #include "tinyxml.h" using namespace std; /** \brief standard constructor \param maxCount the Count of particles in the System \param type The Type of the ParticleSystem */ ParticleSystem::ParticleSystem (unsigned int maxCount, PARTICLE_TYPE type) : PhysicsInterface(this) { this->init(); this->maxCount = maxCount; this->setType(type, 1); } /** \brief creates a Particle System out of a XML-element \param root: the XML-element to load from */ ParticleSystem::ParticleSystem(const TiXmlElement* root) : PhysicsInterface(this) { this->init(); this->loadParams(root); } /** \brief standard deconstructor */ ParticleSystem::~ParticleSystem() { // delete what has to be deleted here ParticleEngine::getInstance()->removeSystem(this); // 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; } if (this->material) delete this->material; } /** \brief initializes the ParticleSystem with default values */ void ParticleSystem::init(void) { this->setClassID(CL_PARTICLE_SYSTEM, "ParticleSystem"); this->material = NULL; this->model = NULL; this->maxCount = PARTICLE_DEFAULT_MAX_COUNT; this->count = 0; this->particles = NULL; this->deadList = NULL; this->setConserve(1); this->setLifeSpan(1); this->glID = NULL; this->setType(PARTICLE_DEFAULT_TYPE, 1); ParticleEngine::getInstance()->addSystem(this); } /** * loads Parameters from a TiXmlElement * @param root the XML-element to load from. */ void ParticleSystem::loadParams(const TiXmlElement* root) { static_cast(this)->loadParams(root); static_cast(this)->loadParams(root); //LoadParam(root, "type", this, &ParticleSystem::setType); LoadParam(root, "life-span", this, &ParticleSystem::setLifeSpan); LoadParam(root, "conserve", this, &ParticleSystem::setConserve); // void setLifeSpan(float lifeSpan, float randomLifeSpan = 0.0); // void setConserve(float conserve); // // /* Per-Particle-Attributes */ // void setRadius(float lifeCycleTime, float radius, float randRadius = 0.0); // void setMass(float lifeCycleTime, float mass, float randMass = 0.0); // void setColor(float lifeCycleTime, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); } /** \param particleType the type of particles in this System \param count how many particles (in PARTICLE_MULTI-mode) \todo this will be different */ void ParticleSystem::setType(PARTICLE_TYPE particleType, int count) { this->particleType = particleType; this->dialectCount = count; // if (glID != NULL) // delete glID; // glID = new GLuint[count]; // for (int i = 0; i< count; i++) // glID[i] = 0; // glID[0] = glGenLists(count); if (this->material) delete this->material; this->material = NULL; switch (this->particleType) { case PARTICLE_SPRITE: this->material = new Material("transperencyMap"); this->material->setDiffuseMap("pictures/radialTransparency.png"); // material->setTransparency(.5); break; case PARTICLE_MODEL: if (!this->model) { PRINTF(2)("Model not loaded yet, please do this through ParticleSystem::loadModel()"); this->setType(PARTICLE_SPRITE); } break; } } // setting properties /** \brief sets the material to an external material \param material: the material to set this material to. !! important if the extern material gets deleted it MUST be unregistered here or segfault !! */ void ParticleSystem::setMaterial(Material* material) { this->material = material; } /** * sets a Model to the Particles * @param modelName the Name of the Model to load */ void ParticleSystem::setModel(const char* modelName) { if (this->model) delete this->model; if (modelName) this->model = new OBJModel(modelName); this->setType(PARTICLE_MODEL); } /** \brief Sets the lifespan of newly created particles */ void ParticleSystem::setLifeSpan(float lifeSpan, float randomLifeSpan) { this->lifeSpan = lifeSpan; this->randomLifeSpan = randomLifeSpan; } /** \brief sets the conserve Factor of newly created particles */ 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; } ///////////////////////////// /* 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.changeEntry(lifeCycleTime, radius); this->randRadiusAnim.changeEntry(lifeCycleTime, 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.changeEntry(lifeCycleTime, mass); this->randMassAnim.changeEntry(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, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { this->colorAnim[0].changeEntry(lifeCycleTime, red); this->colorAnim[1].changeEntry(lifeCycleTime, green); this->colorAnim[2].changeEntry(lifeCycleTime, blue); this->colorAnim[3].changeEntry(lifeCycleTime, alpha); } /** \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; // rendering new position. tickPart->position = tickPart->position + tickPart->velocity * 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 tickPart->color[0] = this->colorAnim[0].getValue(tickPart->lifeCycle); tickPart->color[1] = this->colorAnim[1].getValue(tickPart->lifeCycle); tickPart->color[2] = this->colorAnim[2].getValue(tickPart->lifeCycle); tickPart->color[3] = this->colorAnim[3].getValue(tickPart->lifeCycle); // many more to come if (this->conserve < 1.0) tickPart->velocity = tickPart->velocity * 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; } } } /** \brief applies some force to a Particle. \param field the Field to apply. */ void ParticleSystem::applyField(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(void) const { switch (this->particleType) { case PARTICLE_SPRITE: return this->count; break; case PARTICLE_MODEL: if (this->model) return this->count * this->model->getFaceCount(); break; } } /** \brief draws all the Particles of this System The Cases in this Function all do the same: Drawing all the particles with the appropriate Type. This is just the fastest Way to do this, but will most likely be changed in the future. */ void ParticleSystem::draw(void) const { glPushAttrib(GL_ENABLE_BIT); Particle* drawPart = particles; switch (this->particleType) { default: case PARTICLE_SPRITE: glDisable(GL_LIGHTING); glMatrixMode(GL_MODELVIEW); glDepthMask(GL_FALSE); material->select(); //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_MODULATE); while (likely(drawPart != NULL)) { glColor4fv(drawPart->color); //! \todo implement a faster code for the look-at Camera algorithm. const PNode* camera = State::getInstance()->getCamera(); //!< \todo MUST be different Vector cameraPos = camera->getAbsCoor(); Vector cameraTargetPos = State::getInstance()->getCameraTarget()->getAbsCoor(); Vector view = cameraTargetPos - cameraPos; Vector up = Vector(0, 1, 0); up = camera->getAbsDir().apply(up); Vector h = up.cross(view); Vector v = h.cross(view); h.normalize(); v.normalize(); v *= .5 * drawPart->radius; h *= .5 * drawPart->radius; glBegin(GL_TRIANGLE_STRIP); glTexCoord2i(1, 1); glVertex3f(drawPart->position.x - h.x - v.x, drawPart->position.y - h.y - v.y, drawPart->position.z - h.z - v.z); glTexCoord2i(0, 1); glVertex3f(drawPart->position.x - h.x + v.x, drawPart->position.y - h.y + v.y, drawPart->position.z - h.z + v.z); glTexCoord2i(1, 0); glVertex3f(drawPart->position.x + h.x - v.x, drawPart->position.y + h.y - v.y, drawPart->position.z + h.z - v.z); glTexCoord2i(0, 0); glVertex3f(drawPart->position.x + h.x + v.x, drawPart->position.y + h.y + v.y, drawPart->position.z + h.z + v.z); glEnd(); drawPart = drawPart->next; } glDepthMask(GL_TRUE); break; case PARTICLE_SPARK: glDisable(GL_LIGHTING); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); while (likely(drawPart != NULL)) { glColor4fv(drawPart->color); glVertex3f(drawPart->position.x, drawPart->position.y, drawPart->position.z); glVertex3f(drawPart->position.x - drawPart->velocity.x, drawPart->position.y - drawPart->velocity.y, drawPart->position.z - drawPart->velocity.z); drawPart = drawPart->next; } glEnd(); break; case PARTICLE_MODEL: if (likely(this->model != NULL)) while (likely(drawPart != NULL)) { glPushMatrix(); glMatrixMode(GL_MODELVIEW); glTranslatef(drawPart->position.x, drawPart->position.y, drawPart->position.z); glScalef(drawPart->radius, drawPart->radius, drawPart->radius); this->model->draw(); glPopMatrix(); drawPart = drawPart->next; } break; case PARTICLE_DOT: glDisable(GL_LIGHTING); glBegin(GL_POINTS); while (likely(drawPart != NULL)) { glColor4fv(drawPart->color); glLineWidth(drawPart->radius); glVertex3f(drawPart->position.x, drawPart->position.y, drawPart->position.z); drawPart = drawPart->next; } glEnd(); break; } glPopAttrib(); } /** \brief adds a new Particle to the System \param position the position where the particle gets emitted. \param velocity the Starting velocity of the particle. \param data some more data given by the emitter */ void ParticleSystem::addParticle(const Vector& position, const Vector& velocity, 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; // 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(5)("maximum count of particles reached not adding any more\n"); } /** \brief outputs some nice debug information */ void ParticleSystem::debug(void) { PRINT(0)(" ParticleSystem %s\n", this->getName()); PRINT(0)(" ParticleCount: %d, maximumCount: %d :: filled %d%%\n", this->count, this->maxCount, 100*this->count/this->maxCount); if (deadList) { PRINT(0)(" - ParticleDeadList is used: "); int i = 1; Particle* tmpPart = this->deadList; while (tmpPart = tmpPart->next) ++i; PRINT(0)("count: %d\n", i); } }