Redline/source/particles.cpp

485 lines
17 KiB
C++
Raw Permalink Normal View History

//particles.cpp
//game's particle engine, for smoke, sparks, rain, etc..
#include <OpenGL/gl.h>
#include <math.h>
#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;i<numParticles;i++)
{
particleListEntry->particles[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(gNumScreenParticles<kMaxScreenParticles)
{
tScreenParticleListEntry *particleListEntry;
particleListEntry=(tScreenParticleListEntry*)MemoryAllocateBlock(sizeof(tScreenParticleListEntry));
particleListEntry->life=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;i<particleListEntry->numParticles;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;num<particleListEntry->def.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;i<gParticleList->numParticles;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;i<gParticleList->numParticles;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;i<particleListEntry->numParticles;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;
}
}