/* 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" // needed to find the Position of the Camera #include "world.h" using namespace std; /** \brief standard constructor \param count the Count of particles in the System \param type The Type of the ParticleSystem \todo this constructor is not jet implemented - do it */ ParticleSystem::ParticleSystem (unsigned int maxCount, PARTICLE_TYPE type) : PhysicsInterface(this) { this->setClassID(CL_PARTICLE_SYSTEM, "ParticleSystem"); this->material = NULL; this->name = NULL; this->maxCount = maxCount; this->count = 0; this->particles = NULL; this->deadList = NULL; this->setConserve(1); this->setLifeSpan(1); this->setInheritSpeed(0); this->glID = NULL; this->setType(type, 1); // this->setColor(1.0,1.0,1.0,1.0, .5,.5,.5,.5, .0,.0,.0,.0); ParticleEngine::getInstance()->addSystem(this); } /** \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 sets the Name of the Particle System \param name the Name of the System */ void ParticleSystem::setName(const char* name) { if (this->name) delete this->name; this->name = new char[strlen(name)+1]; strcpy(this->name, name); } /** \returns the Name of the ParticleSystem */ const char* ParticleSystem::getName(void) const { return this->name; } /** \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; if (this->particleType == PARTICLE_SPRITE) { this->material = new Material("transperencyMap"); this->material->setDiffuseMap("pictures/radialTransparency.png"); // material->setTransparency(.5); } } // setting properties void ParticleSystem::setMaterial(Material* material) { this->material = material; } /** \brief how much of the speed from the ParticleEmitter should flow onto the ParticleSystem \param value a Value between zero and one if you want to change the value of this variable during emission time (to make it more dynamic) you may want to use the animation class */ void ParticleSystem::setInheritSpeed(float value) { if (unlikely(value > 1.0)) this->inheritSpeed = 1; else if (unlikely(value < 0.0)) this->inheritSpeed = 0; else this->inheritSpeed = value; } /** \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.addEntry(lifeCycleTime, radius); this->randRadiusAnim.addEntry(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.addEntry(lifeCycleTime, mass); this->randMassAnim.addEntry(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 */ void ParticleSystem::setColor(float lifeCycleTime, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { this->colorAnim[0].addEntry(lifeCycleTime, red); this->colorAnim[1].addEntry(lifeCycleTime, green); this->colorAnim[2].addEntry(lifeCycleTime, blue); this->colorAnim[3].addEntry(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. \param dt The time over which the field gets applied */ void ParticleSystem::applyField(Field* field) { Particle* tickPart = particles; while (tickPart) { tickPart->extForce += field->calcForce(tickPart->position); tickPart = tickPart->next; } } /** \brief draws all the Particles of this System \param the time passed in seconds (since the last draw) 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); glDisable(GL_LIGHTING); Particle* drawPart = particles; switch (this->particleType) { default: case PARTICLE_SPRITE: glMatrixMode(GL_MODELVIEW); glDisable(GL_DEPTH_TEST); 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; } break; case PARTICLE_SPARK: 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_DOT: 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->name); 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); } }