/* 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 "tinyxml.h" CREATE_FACTORY(ParticleSystem); 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) { this->init(); this->setMaxCount(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) { 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() { this->setClassID(CL_PARTICLE_SYSTEM, "ParticleSystem"); this->material = NULL; this->setMaxCount(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, "max-count", this, &ParticleSystem::setMaxCount) .describe("the maximal count of Particles, that can be emitted into this system"); //LoadParam(root, "type", this, &ParticleSystem::setType); 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 facrot of the Particles (1.0: they keep all their energy, 0.0:they keep no energy)"); LoadParam(root, "type", this, &ParticleSystem::setType) .describe("sets the type of the Particles, (dot, spark, sprite or model)"); const TiXmlElement* element = root->FirstChildElement(); while (element != NULL) { // PER-PARTICLE-ATTRIBUTES: LoadParam(element, "radius", this, &ParticleSystem::setRadius, true) .describe("The Radius of each particle over time (TimeIndex [0-1], radius at TimeIndex, randomRadius at TimeIndex)"); LoadParam(element, "mass", this, &ParticleSystem::setMass, true) .describe("The Mass of each particle over time (TimeIndex: [0-1], mass at TimeIndex, randomMass at TimeIndex)"); LoadParam(element, "color", this, &ParticleSystem::setColor, true) .describe("The Color of each particle over time (TimeIndex: [0-1], red: [0-1], green: [0-1], blue: [0-1], alpha: [0-1])"); element = element->NextSiblingElement(); } } /** \param maxCount the maximum count of particles that can be emitted */ void ParticleSystem::setMaxCount(int maxCount) { this->maxCount = maxCount; } /** \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(const char* particleType) { if (!strcmp(particleType, "dot")) this->setType(PARTICLE_DOT); else if(!strcmp(particleType, "spark")) this->setType(PARTICLE_SPARK); else if(!strcmp(particleType, "model")) this->setType(PARTICLE_MODEL); else // if (strcmp(particleType, "SPRITE")) this->setType(PARTICLE_SPRITE); } /** \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; } } // 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; } /** \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, float red, float green, float blue, float 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->velocity * dt; tickPart->orientation += tickPart->momentum *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 *= 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; } } } /** \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() 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() 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); glDepthMask(GL_FALSE); //glEnable(GL_LINE_SMOOTH); glEnable(GL_BLEND); 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->radius, drawPart->position.y - drawPart->velocity.y * drawPart->radius, drawPart->position.z - drawPart->velocity.z * drawPart->radius); drawPart = drawPart->next; } glEnd(); break; case PARTICLE_MODEL: { GLfloat matrix[4][4]; if (likely(this->model != NULL)) while (likely(drawPart != NULL)) { glPushMatrix(); glMatrixMode(GL_MODELVIEW); /* move */ glTranslatef(drawPart->position.x, drawPart->position.y, drawPart->position.z); /* scale */ glScalef(drawPart->radius, drawPart->radius, drawPart->radius); /* rotate */ drawPart->orientation.matrix (matrix); glMultMatrixf((float*)matrix); this->model->draw(); glPopMatrix(); drawPart = drawPart->next; } else PRINTF(2)("no model loaded onto ParticleSystem-%s\n", this->getName()); } 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 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(5)("maximum count of particles reached not adding any more\n"); } /** \brief outputs some nice debug information */ void ParticleSystem::debug() const { PRINT(0)(" ParticleSystem %s, type: ", this->getName()); { if (this->particleType == PARTICLE_MODEL) PRINT(0)("model"); else if (this->particleType == PARTICLE_SPRITE) PRINT(0)("sprite"); else if (this->particleType == PARTICLE_DOT) PRINT(0)("dot"); else if (this->particleType == PARTICLE_SPARK) PRINT(0)("spark"); PRINT(0)("\n"); } 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); } }