#ifndef __PARTICLE_H__ #define __PARTICLE_H__ #include "common.h" /* max particles per emitter */ #define MAX_PARTICLES 256 typedef struct { GLint aliveTime; /* time the particle's been alive */ GLint lifetime; /* how long this particle should stay alive (in ms) */ GLfloat alpha; Vector speed; Vector pos; Vector iGrav; } Particle; typedef struct { GLboolean active; /* emitter active? */ GLint lifetime; /* base lifetime of particles from this emitter */ GLint lifeEdge; /* the random adjustment to each particle's lifetime (between lifeEdge and lifeEdge*2) */ GLfloat alphaFade; /* a floating point value between 0.0 and 1.0 that tells how much transparency should be added to the particle each frame */ Vector pos; /* base position of particles from this emitter */ Vector speed; /* base speed (direction) of particles from this emitter */ Vector pGrav; /* the base x, y, and z gravity acting on particles from this emitter*/ Vector rSpeed; /* the random speed adjustment applied to particles */ Vector rGrav; /* the random gravity adjustment applied to particles */ Vector rPos; /* the random position adjustment applied to particles */ Particle particles[MAX_PARTICLES]; /* the actual particles */ GLfloat texCoords[4][2]; // the texture coordinates for all quad or triangle particles GLuint texHandle; /* the texture handle for all particles from this emitter*/ GLuint pType; /* GL_POINTS, GL_TRIANGLES, or GL_QUADS.*/ GLfloat color[3]; /* color of particles in this emitter */ GLfloat size[2]; /* the size in X and Y of all particles from this emitter */ } PEmitter; void DrawParticle(Particle *particle, PEmitter *emitter) { /* set up the texturing if we're not doing points */ if ( emitter->pType != GL_POINTS ) { glBindTexture(GL_TEXTURE_2D, emitter->texHandle); glEnable(GL_TEXTURE_2D); } glBegin(emitter->pType); if ( emitter->pType == GL_TRIANGLES ) { glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[0][0], emitter->texCoords[0][1]); glVertex3f(particle->pos.x - ( emitter->size[0] / 2), particle->pos.y - ( emitter->size[1] / 2), particle->pos.z); glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[1][0], emitter->texCoords[1][1]); glVertex3f(particle->pos.x + ( emitter->size[0] / 2), particle->pos.y - ( emitter->size[1] / 2), particle->pos.z); glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[2][0], emitter->texCoords[2][1]); glVertex3f(particle->pos.x, particle->pos.y + ( emitter->size[1] / 2), particle->pos.z); } else if ( emitter->pType == GL_QUADS ) { glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[0][0], emitter->texCoords[0][1]); glVertex3f(particle->pos.x - ( emitter->size[0] / 2), particle->pos.y + ( emitter->size[1] / 2), particle->pos.z); glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[1][0], emitter->texCoords[1][1]); glVertex3f(particle->pos.x + ( emitter->size[0] / 2), particle->pos.y - ( emitter->size[1] / 2), particle->pos.z); glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[2][0], emitter->texCoords[2][1]); glVertex3f(particle->pos.x + ( emitter->size[0] / 2), particle->pos.y - ( emitter->size[1] / 2), particle->pos.z); glColor3f(emitter->color[0], emitter->color[1], emitter->color[2]); glTexCoord2f(emitter->texCoords[3][0], emitter->texCoords[3][1]); glVertex3f(particle->pos.x + ( emitter->size[0] / 2), particle->pos.y + ( emitter->size[1] / 2), particle->pos.z); } else { glColor4f(emitter->color[0], emitter->color[1], emitter->color[2], particle->alpha); glVertex3f(particle->pos.x, particle->pos.y, particle->pos.z); } glEnd(); glDisable(GL_TEXTURE_2D); } GLuint DrawEmitter(PEmitter *emitter) { /* this function assumes the viewport and etc has already been setup * (e.g., call this from within your main GL drawing function) */ int i = 0; Particle *currParticle; if ( !emitter ) { return 1; } if ( emitter->active != GL_TRUE ) { return 0; // emitter not active, but this isn't an error } for ( i = 0 ; i < MAX_PARTICLES ; i++ ) { currParticle = &emitter->particles[i]; /* draw the particle given its type */ DrawParticle(currParticle, emitter); } return 0; } void ResetParticle(Particle *particle, PEmitter *emitter) { particle->aliveTime = 0; /* get a new lifetime for the particle based on the base from the emitter + random adj */ particle->lifetime = emitter->lifetime + (rand() % (emitter->lifeEdge*2)) - emitter->lifeEdge; particle->alpha = 1.0F; /* reset the particle's x, y, z position from the emitter base */ vectorCopy(&particle->pos, &emitter->pos); if ( emitter->rPos.x != 0.0f) particle->pos.x += frand( emitter->rPos.x); if ( emitter->rPos.y != 0.0f ) particle->pos.y += frand( emitter->rPos.y ); if ( emitter->rPos.z != 0.0f ) particle->pos.z += frand( emitter->rPos.z ); /* reset the particle's gravity from the base gravity of the emitter + rand adjust */ vectorCopy(&particle->iGrav, &emitter->pGrav); if ( emitter->rGrav.x != 0.0f ) particle->iGrav.x += frand( emitter->rGrav.x ); if ( emitter->rGrav.y != 0.0f ) particle->iGrav.y += frand( emitter->rGrav.y ); if ( emitter->rGrav.z != 0.0f ) particle->iGrav.z += frand( emitter->rGrav.z ); /* reset the particle's speed from the base gravity of the emitter + rand adjust */ vectorCopy(&particle->speed, &emitter->speed); if ( emitter->rSpeed.x != 0.0f ) particle->speed.x += frand( emitter->rSpeed.x); if ( emitter->rSpeed.y != 0.0f ) particle->speed.y += frand( emitter->rSpeed.y); if ( emitter->rSpeed.z != 0.0f ) particle->speed.z += frand( emitter->rSpeed.z); } void UpdateEmitter(PEmitter *emitter, GLuint timePassed) { /* here's where we do the emitter's logic. It's assumed * we're being called once per frame, so we don't do any * time calculations, other than updating the lifetimes * ... timePassed is the number of ms passed since the last call */ int i = 0; Particle *currParticle; for ( i = 0 ; i < MAX_PARTICLES ; i++ ) { currParticle = &emitter->particles[i]; currParticle->aliveTime += timePassed; if ( currParticle->aliveTime >= currParticle->lifetime ) { /* this particle has died; set it to inactive and reset its lifetime/pos/dir */ ResetParticle(currParticle,emitter); continue; } /* apply gravity and such to the particle */ currParticle->speed.x += currParticle->iGrav.x; currParticle->speed.y += currParticle->iGrav.y; currParticle->speed.z += currParticle->iGrav.z; currParticle->pos.x += (currParticle->speed.x); currParticle->pos.y += (currParticle->speed.y); currParticle->pos.z += (currParticle->speed.z); /* the alpha is a constant drop over the lifetime of the particle */ currParticle->alpha -= emitter->alphaFade; } } GLuint PopulateEmitter(PEmitter *emitter, GLboolean active, GLfloat color[], GLfloat alphaFade, GLint lifetime, Vector *pGrav, Vector *pos, Vector *speed, Vector *rgrav, Vector *rpos, Vector *rspeed, GLint lifeEdge, GLuint pType, GLuint texHandle) { int i = 0; if ( !emitter ) { return 1; } emitter->active = active; emitter->color[0] = color[0]; emitter->color[1] = color[1]; emitter->color[2] = color[2]; emitter->lifetime = lifetime; emitter->lifeEdge = lifeEdge; emitter->alphaFade = alphaFade; vectorCopy(&emitter->pGrav, pGrav); vectorCopy(&emitter->pos, pos); vectorCopy(&emitter->speed, speed); vectorCopy(&emitter->rSpeed, rspeed); vectorCopy(&emitter->rPos, rpos); vectorCopy(&emitter->rGrav, rgrav); emitter->pType = pType; emitter->texHandle = texHandle; for ( i = 0; i < MAX_PARTICLES; i++ ) { ResetParticle(&emitter->particles[i],emitter); } return 0; } #endif // __PARTICLE_H__