/* 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 "particle_engine.h" #include "field.h" #include "model.h" #include "load_param.h" #include "factory.h" #include "material.h" #include "state.h" #include "shell_command.h" #include "tinyxml.h" CREATE_FACTORY(ParticleSystem, CL_PARTICLE_SYSTEM); SHELL_COMMAND(texture, ParticleSystem, setMaterialTexture) ->defaultValues(1, "maps/evil-flower.png"); using namespace std; /** * 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); } /** * 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, "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)"); LoadParam(root, "type", this, ParticleSystem, setType) .describe("sets the type of the Particles, (dot, spark, sprite or model)"); 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); } /** * @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 MUST 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("maps/radialTransparency.png"); // material->setTransparency(.5); break; } } // setting properties /** * 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; } void ParticleSystem::setMaterialTexture(const char* textureFile) { if (this->material != NULL) this->material->setDiffuseMap(textureFile); } /** * Sets the lifespan of newly created particles */ void ParticleSystem::setLifeSpan(float lifeSpan, float randomLifeSpan) { this->lifeSpan = lifeSpan; this->randomLifeSpan = randomLifeSpan; } /** * 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 */ ///////////////////////////// /** * 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); } /** * 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); } /** * 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); } /** * 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; } } } /** * 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; } } /** * 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(); glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); //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::getCamera(); //!< @todo MUST be different Vector cameraPos = camera->getAbsCoor(); Vector cameraTargetPos = State::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(); } /** * 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"); } /** * 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); } }