02061d74c2
(as received from Jonas Echterhoff)
485 lines
17 KiB
C++
485 lines
17 KiB
C++
//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;
|
|
}
|
|
}
|
|
|