//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;
	}
}

