//particles.cpp //game's particle engine, for smoke, sparks, rain, etc.. #include #include #include "vectors.h" #include "gamemem.h" #include "random.h" #include "renderframe.h" #include "textures.h" #include "particles.h" #include "entities.h" #include "gameframe.h" #include "network.h" #include "log.h" #include "gameinitexit.h" #include "error.h" #include "environment.h" #include "fileio.h" #include "collision.h" #include "roads.h" //a single particle typedef struct{ tVector3 pos,velo; float life; //how much lifetime the particle has left (in seconds) }tParticle; //an entry in the particle list typedef struct{ void *next; //next list member int numParticles; //number of particles in this list entry float life; //how much lifetime the list entry has left (in seconds) tParticlesDef def; //type of particles tParticle particles[1];//the actual particle data }tParticleListEntry; //an entry in the screen particle list //screen particles are particles on the 2d screen plane (such as raindrops on the camera) //unlike the normal particle list, this only refers to a single particle typedef struct{ void *next; //next list member float x1,x2,y1,y2; //coordinates of particle rectangle int texture; //file id of texture to be used float gravity; //how fast the particle runs down on the screen float maxLife; //how much lifetime the particle had left when it had full alpha float life; //how much lifetime the particle has left } tScreenParticleListEntry; tParticleListEntry *gParticleList=nil; //the particles list tScreenParticleListEntry *gScreenParticleList=nil; //the screen particles list int gNumScreenParticles=0; //number of screen particles used float gCameraTunnel=0; #define kMaxScreenParticles 25 //delimiter for screen particles (for performance reasons) #define kSparkVeloScale 0.2 //how long velocity dependant partcles (like sparks) are per m/s of speed //adds an entry to the particle list. quantity particles will be created //(quantity is rounded based on random probilities, ie. 1.6 will be rounded //to 2 with a probility of 60% and to 1 with a probility of 40%). void ParticlesCreate(float quantity,tVector3 pos,tVector3 velo,tParticlesDef *def) { //round quantity float iQuan=floor(quantity); int numParticles=iQuan+(RandomProb(quantity-iQuan)?1:0); if(numParticles<=0) return; //allocate particleListEntry tParticleListEntry *particleListEntry; particleListEntry=(tParticleListEntry*)MemoryAllocateBlock(sizeof(tParticleListEntry)+sizeof(tParticle)*(numParticles-1)); //initialize particleListEntry particleListEntry->next=gParticleList; gParticleList=particleListEntry; particleListEntry->numParticles=numParticles; particleListEntry->def=*def; particleListEntry->life=def->maxLife; //initialize particles for(int i=0;iparticles[i].pos=pos+Vector(RandomFl(-def->maxSpread,def->maxSpread),RandomFl(-def->maxSpread,def->maxSpread),RandomFl(-def->maxSpread,def->maxSpread)); particleListEntry->particles[i].velo=Vector(RandomFl(-def->maxVelo,def->maxVelo),RandomFl(-def->maxVelo,def->maxVelo),RandomFl(-def->maxVelo,def->maxVelo))+velo; particleListEntry->particles[i].life=RandomFl(0,def->maxLife); } } //adds an entry to the screen particle list void ScreenParticleCreate(tVector3 pos,float gravity,float size,int texture,float maxLife,float life) { //are there still any screen particles free? if(gNumScreenParticleslife=life; particleListEntry->maxLife=maxLife; particleListEntry->texture=texture; particleListEntry->gravity=gravity; tVector3 center=((pos-gCameraEntity->pos)*gCameraEntity->dir)*0.5; particleListEntry->x1=center.x-size*0.5; particleListEntry->x2=center.x+size*0.5; particleListEntry->y1=center.y-size*0.5; particleListEntry->y2=center.y+size*0.5; particleListEntry->next=gScreenParticleList; gScreenParticleList=particleListEntry; gNumScreenParticles++; } } void ParticlesInitDef(tParticlesDef *def) { def->sprite=FileGetReference("null.raw"); def->veloRotation=false; def->screenTexture=-1; def->grow=false; def->brightness=1; def->r=1; def->b=1; def->g=1; def->a=1; def->xSize=1; def->ySize=1; def->maxSpread=0; def->maxVelo=0; def->maxLife=1; def->gravity=0; def->screenGravity=0; def->notInTunnel=false; def->multi=1; } //Draw the particle list void DrawParticleList() { tParticleListEntry *particleListEntry=gParticleList; //get the ground type under the camera tVector3 groundNormal; int surfaceType; GetGroundOffset(gCameraEntity->pos,&gCameraEntity->lastRoadIndex,&groundNormal,&surfaceType); //check if we are in a tunnel, there should be no rain in there. if(surfaceType) if(gSurfaceTypes->types[surfaceType].soundEcho) gCameraTunnel+=10*kFrameTime; else gCameraTunnel-=10*kFrameTime; if(gCameraTunnel>1)gCameraTunnel=1; if(gCameraTunnel<0)gCameraTunnel=0; while(particleListEntry) { TexturesSelectTex(particleListEntry->def.sprite); for(int i=0;inumParticles;i++) //does the particle have life left? if(particleListEntry->particles[i].life>0) { float bright=particleListEntry->def.brightness; float a=1; if(particleListEntry->def.notInTunnel) a=1-gCameraTunnel; //alpha blend according to life left glColor4f(bright*particleListEntry->def.r,bright*particleListEntry->def.g,bright*particleListEntry->def.b,(particleListEntry->particles[i].life/particleListEntry->def.maxLife)*particleListEntry->def.a*a); if(!particleListEntry->def.veloRotation) { //translate to position of particle SetupTranslation(particleListEntry->particles[i].pos,gCameraEntity->dir); //draw the particle float xSize=particleListEntry->def.xSize*0.5*(particleListEntry->def.grow?1-(particleListEntry->particles[i].life/particleListEntry->def.maxLife):1); float ySize=particleListEntry->def.ySize*0.5*(particleListEntry->def.grow?1-(particleListEntry->particles[i].life/particleListEntry->def.maxLife):1); glBegin(GL_TRIANGLE_STRIP); glTexCoord2d(1,1); glVertex2f(xSize,ySize); glTexCoord2d(1,0); glVertex2f(xSize,-ySize); glTexCoord2d(0,1); glVertex2f(-xSize,ySize); glTexCoord2d(0,0); glVertex2f(-xSize,-ySize); glEnd(); #ifdef __POLYCOUNT gPolyCount+=2; #endif } else //veloRotation: the particle must be rotated to point into the direction it is moving (eg.: sparks) { //set up an translate to a matrix rotated to the direction a particle is moving tMatrix3 dir; tVector3 velo=particleListEntry->particles[i].velo-gCameraEntity->velo; float fvelo=~particleListEntry->particles[i].velo; *MatrixGetZVector(dir)=velo*1/fvelo; *MatrixGetYVector(dir)=Vector(0,1,0); MatrixReAdjust(dir); fvelo*=kSparkVeloScale; SetupTranslation(particleListEntry->particles[i].pos,dir); for(int num=0;numdef.multi;num++) { glTranslatef(sin(num)*num,0,cos(num)*num); //draw the particle as two crossed quads. glBegin(GL_TRIANGLE_STRIP); glTexCoord2d(1,0); glVertex3f(particleListEntry->def.xSize*0.5,0,particleListEntry->def.ySize*0.5*fvelo); glTexCoord2d(1,1); glVertex3f(particleListEntry->def.xSize*0.5,0,-particleListEntry->def.ySize*0.5*fvelo); glTexCoord2d(0,0); glVertex3f(-particleListEntry->def.xSize*0.5,0,particleListEntry->def.ySize*0.5*fvelo); glTexCoord2d(0,1); glVertex3f(-particleListEntry->def.xSize*0.5,0,-particleListEntry->def.ySize*0.5*fvelo); glEnd(); glBegin(GL_TRIANGLE_STRIP); glTexCoord2d(1,0); glVertex3f(0,particleListEntry->def.xSize*0.5,particleListEntry->def.ySize*0.5*fvelo); glTexCoord2d(1,1); glVertex3f(0,particleListEntry->def.xSize*0.5,-particleListEntry->def.ySize*0.5*fvelo); glTexCoord2d(0,0); glVertex3f(0,-particleListEntry->def.xSize*0.5,particleListEntry->def.ySize*0.5*fvelo); glTexCoord2d(0,1); glVertex3f(0,-particleListEntry->def.xSize*0.5,-particleListEntry->def.ySize*0.5*fvelo); glEnd(); } #ifdef __POLYCOUNT gPolyCount+=4; #endif } } //jump to next list entry particleListEntry=(tParticleListEntry*)particleListEntry->next; } } //draw screen particles void DrawScreenParticleList() { tScreenParticleListEntry *screenParticleListEntry=gScreenParticleList; while(screenParticleListEntry) { TexturesSelectTex(screenParticleListEntry->texture); glColor4f(1,1,1,(screenParticleListEntry->life/screenParticleListEntry->maxLife)); glBegin(GL_TRIANGLE_STRIP); glTexCoord2d(1,1); glVertex3f(screenParticleListEntry->x1,screenParticleListEntry->y2,-1); glTexCoord2d(1,0); glVertex3f(screenParticleListEntry->x1,screenParticleListEntry->y1,-1); glTexCoord2d(0,1); glVertex3f(screenParticleListEntry->x2,screenParticleListEntry->y2,-1); glTexCoord2d(0,0); glVertex3f(screenParticleListEntry->x2,screenParticleListEntry->y1,-1); glEnd(); screenParticleListEntry=(tScreenParticleListEntry*)screenParticleListEntry->next; } } //draw particles void ParticlesDraw() { glPushAttrib(GL_DEPTH_BUFFER_BIT+GL_CURRENT_BIT+GL_LIGHTING_BIT+GL_COLOR_BUFFER_BIT+GL_POLYGON_BIT); glDepthMask(GL_FALSE); glEnable(GL_BLEND); glDisable(GL_LIGHTING); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glNormal3f(0,1,0); glPushMatrix(); DrawParticleList(); glLoadIdentity(); glDisable(GL_DEPTH_TEST); DrawScreenParticleList(); glPopMatrix(); glPopAttrib(); } #define kEnvironmentParticlesSpread 25 #define kEnvironmentParticlesLife 1.5 //create particles as defined by the environment (such as rain) void CreateEnvironmentalParticles() { //are particles enabled by the environment? if(gEnvironment->particlesEnable) { //set up environment particles tParticlesDef def; def.sprite=gEnvironment->particlesType; def.maxSpread=kEnvironmentParticlesSpread; def.maxVelo=gEnvironment->particlesVeloSpread; def.gravity=0; def.multi=5; def.screenTexture=-1; def.brightness=1.0; def.maxLife=gEnvironment->particlesLife; def.grow=false; def.xSize=gEnvironment->particlesSize.x;def.ySize=gEnvironment->particlesSize.y; def.veloRotation=gEnvironment->particlesSize.x!=gEnvironment->particlesSize.y; def.brightness=1; def.notInTunnel=true; def.r=1;def.g=1;def.b=1;def.a=10; //and add them to the particle list float quantity=gEnvironment->particlesAmount*kFrameTime; ParticlesCreate(quantity, gCameraEntity->pos+gCameraEntity->velo+*MatrixGetZVector(gCameraEntity->dir)*kEnvironmentParticlesSpread+Vector(0,gEnvironment->particlesHeight,0) ,gEnvironment->particlesVelo,&def); //go to the particle list entry just created, //and set the life of all particles to kEnvironmentParticlesLife. //we don't want raindrops to fade out before hitting the ground! if(gParticleList) for(int i=0;inumParticles;i++) gParticleList->particles[i].life=gEnvironment->particlesLife; if(gEnvironment->screenParticlesEnable) //occasionally add a new raindrop running down the screen. if(RandomProb(gEnvironment->particlesAmount*kFrameTime*0.001*(1+sqr(gCameraEntity->velo)*0.003))) { tVector3 pos=gCameraEntity->pos+*MatrixGetZVector(gCameraEntity->dir)+Vector(RandomFl(-1,1),RandomFl(-1,1),RandomFl(-1,1)); ScreenParticleCreate(pos,RandomFl(0.008,0.02),RandomFl(0.03,0.08),FileGetReference("raindrop-screen.pct"),RandomFl(0,1.5),1.5); } } /* if(gEnvironment->particlesEnable) { //set up environment particles tParticlesDef def; def.sprite=FileGetReference("snow.pct"); def.maxSpread=kEnvironmentParticlesSpread; def.maxVelo=1; def.gravity=0; def.multi=10; def.screenTexture=-1; def.brightness=1.0; def.maxLife=15; def.grow=false; def.xSize=0.2;def.ySize=0.2; def.veloRotation=false; def.brightness=1; def.notInTunnel=true; def.r=1;def.g=1;def.b=1;def.a=10; //and add them to the particle list float quantity=gEnvironment->particlesAmount*kFrameTime; ParticlesCreate(quantity, gCameraEntity->pos+gCameraEntity->velo+*MatrixGetZVector(gCameraEntity->dir)*kEnvironmentParticlesSpread+Vector(0,kEnvironmentParticlesSpread*0.2,0) ,Vector(0,-2,0),&def); //go to the particle list entry just created, //and set the life of all particles to kEnvironmentParticlesLife. //we don't want raindrops to fade out before hitting the ground! if(gParticleList) for(int i=0;inumParticles;i++) gParticleList->particles[i].life=kEnvironmentParticlesLife; }*/ } //runs through the particle list, moving all the particles as required. void ProcessParticleList() { tParticleListEntry *particleListEntry=gParticleList; tParticleListEntry **lastEntry=&gParticleList; while(particleListEntry) { //decrement particle list entry life time particleListEntry->life-=kFrameTime; //if lifetime is <=0, remove entry. if(particleListEntry->life<=0) { *lastEntry=(tParticleListEntry*)particleListEntry->next; tParticleListEntry *next=(tParticleListEntry*)particleListEntry->next; MemoryFreeBlock(particleListEntry); particleListEntry=next; } else { for(int i=0;inumParticles;i++) //if particle has life left if(particleListEntry->particles[i].life>0) { //move particle particleListEntry->particles[i].pos=particleListEntry->particles[i].pos+particleListEntry->particles[i].velo*kFrameTime; //gravitional accelerate particle particleListEntry->particles[i].velo.y-=particleListEntry->def.gravity*kFrameTime; //decrement lifetiem particleListEntry->particles[i].life-=kFrameTime; //can the particle leave traces on the screen? (such as water clouds) if(particleListEntry->def.screenTexture!=-1&&gCameraMode!=kCameraCockpit) //is particle within 1 meter of camera? if(sqr(particleListEntry->particles[i].pos-gCameraEntity->pos)<1) { //create a new screen particle ScreenParticleCreate(particleListEntry->particles[i].pos,particleListEntry->def.screenGravity,particleListEntry->def.xSize,particleListEntry->def.screenTexture,particleListEntry->life*2,particleListEntry->particles[i].life*2); //and kill the impacted particle particleListEntry->particles[i].life=0; } } //jump to next particle. lastEntry=(tParticleListEntry**)particleListEntry; particleListEntry=(tParticleListEntry*)particleListEntry->next; } } } //runs through the screen particle list, moving all the particles as required. void ProcessScreenParticleList() { tScreenParticleListEntry *screenParticleListEntry=gScreenParticleList; tScreenParticleListEntry **screenLastEntry=&gScreenParticleList; float gravFactor=1+sqr(gCameraEntity->velo)*0.015; while(screenParticleListEntry) { //decrement life screenParticleListEntry->life-=kFrameTime*0.05; //if lifetime is <=0, remove entry. if(screenParticleListEntry->life<=0) { *screenLastEntry=(tScreenParticleListEntry*)screenParticleListEntry->next; tScreenParticleListEntry *next=(tScreenParticleListEntry*)screenParticleListEntry->next; MemoryFreeBlock(screenParticleListEntry); screenParticleListEntry=next; gNumScreenParticles--; } else { //move particle down the screen screenParticleListEntry->y1-=screenParticleListEntry->gravity*kFrameTime*gravFactor; screenParticleListEntry->y2-=screenParticleListEntry->gravity*kFrameTime*gravFactor; //jump to next particle screenLastEntry=(tScreenParticleListEntry**)screenParticleListEntry; screenParticleListEntry=(tScreenParticleListEntry*)screenParticleListEntry->next; } } } //process all particle movement void ParticlesProcess() { CreateEnvironmentalParticles(); ProcessParticleList(); ProcessScreenParticleList(); } void ParticlesClearScreen() { tScreenParticleListEntry *screenParticleListEntry=gScreenParticleList; tScreenParticleListEntry **screenLastEntry=&gScreenParticleList; while(screenParticleListEntry) { *screenLastEntry=(tScreenParticleListEntry*)screenParticleListEntry->next; tScreenParticleListEntry *next=(tScreenParticleListEntry*)screenParticleListEntry->next; MemoryFreeBlock(screenParticleListEntry); screenParticleListEntry=next; gNumScreenParticles--; } } void ParticlesClear() { ParticlesClearScreen(); tParticleListEntry *particleListEntry=gParticleList; tParticleListEntry **lastEntry=&gParticleList; while(particleListEntry) { *lastEntry=(tParticleListEntry*)particleListEntry->next; tParticleListEntry *next=(tParticleListEntry*)particleListEntry->next; MemoryFreeBlock(particleListEntry); particleListEntry=next; } }