//============================================================
// Program:		CGFParticleSystem
// Description:	Simple Particle Engine
// Author:		Benjamin Gottemoller
// Website:		http://www.particlefield.com
// Date:		who knows
// Legal:		Licensed under the gnu GPL (see gpl.txt for details)
//============================================================

#include "GameFrameParticleSystem.h"
#include "GameFrame.h"
#include "GFDebugHandler.h"

CGFParticleSystem::CGFParticleSystem()
{
	GFParent = NULL;
	Camera = NULL;
	NumTypes = 0;
	NumTypesUsed = 0;
	Types = NULL;
	NumParticles = 0;
	NumParticlesActive = 0;
	Particles = NULL;
}

CGFParticleSystem::~CGFParticleSystem()
{
	Destroy();
}

int CGFParticleSystem::Initialize(GameFrame *gfparent, int num_types, int num_particles)
{
	int i = 0;

	GFParent = gfparent;

	GFParent->D3DDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ONE);
	GFParent->D3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

	NumTypes = num_types;
	NumTypesUsed = 0;
	Types = new PARTICLE_TYPE[NumTypes];
	if(Types == NULL) return 0;

	for(i=0; i<NumTypes; i++)
	{
		memset(&(Types[i]), 0, sizeof(PARTICLE_TYPE));
	}
	NumParticles = num_particles;
	NumParticlesActive = 0;
	Particles = new PARTICLE[NumParticles];
	if(Particles == NULL) return 0;

	for(i=0; i<NumParticles; i++)
	{
		memset(&(Particles[i]), 0, sizeof(PARTICLE));
	}

	return 1;
}

void CGFParticleSystem::Destroy(void)
{
	GFParent = NULL;
	Camera = NULL;

	NumTypes = 0;
	NumTypesUsed = 0;
	SAFE_DELETE_ARRAY(Types);

	NumParticles = 0;
	NumParticlesActive = 0;
	SAFE_DELETE_ARRAY(Particles);
}

int CGFParticleSystem::AddParticleType(PARTICLE_TYPE *type)
{

	int result = -1;
	if((NumTypes <= 0) || (Types == NULL) || (GFParent == NULL) || (type == NULL)) return -1;

	if(NumTypesUsed >= NumTypes)
	{
		int i = 0;
		PARTICLE_TYPE *tmp = NULL;
		tmp = new PARTICLE_TYPE[NumTypes * 2];
		if(tmp == NULL) return -1;

		for(i=0; i<NumTypes; i++)
		{
			memcpy(&(tmp[i]), &(Types[i]), sizeof(PARTICLE_TYPE));
		}

		for(i=NumTypes; i<(NumTypes * 2); i++)
		{
			memset(&(tmp[i]), 0, sizeof(PARTICLE_TYPE));
		}

		delete[] Types;
		Types = tmp;
		tmp = NULL;
		NumTypes *= 2;
	}

	memcpy(&(Types[NumTypesUsed]), type, sizeof(PARTICLE_TYPE));
	result = NumTypesUsed;
	NumTypesUsed++;

	return result;
}

int	CGFParticleSystem::AddParticle(int identifier, int type, float x, float y, float z, float xv, float yv, float zv, float scale, 
								   float scale_delta, D3DXCOLOR color, float color_delta, float max_life)
{
	if(GFParent->IsGamePaused || GFParent->IsGameLocked) return 0;

	int index = 0;
	static int last_index = 0;

	for(index=last_index; index<NumParticles; index++)
	{
		if(Particles[index].IsActive == 0)
		{
			Particles[index].IsActive = 1;
			Particles[index].Identifier = identifier;
			Particles[index].Type = type;
			Particles[index].Pos = D3DXVECTOR3(x, y, z);
			Particles[index].LastPos = D3DXVECTOR3(x, y, z);
			Particles[index].Velocity = D3DXVECTOR3(xv, yv, zv);
			Particles[index].Scale = scale;
			Particles[index].ScaleDelta = scale_delta;
			Particles[index].Color = color;
			Particles[index].ColorDelta = color_delta;
			Particles[index].MaxLife = max_life;
			Particles[index].Age = 0;

			last_index = index;
			NumParticlesActive++;
			return 1;
		}
	}

	for(index=0; index<last_index; index++)
	{
		if(Particles[index].IsActive == 0)
		{
			Particles[index].IsActive = 1;
			Particles[index].Identifier = identifier;
			Particles[index].Type = type;
			Particles[index].Pos = D3DXVECTOR3(x, y, z);
			Particles[index].LastPos = D3DXVECTOR3(x, y, z);
			Particles[index].Velocity = D3DXVECTOR3(xv, yv, zv);
			Particles[index].Scale = scale;
			Particles[index].ScaleDelta = scale_delta;
			Particles[index].Color = color;
			Particles[index].ColorDelta = color_delta;
			Particles[index].MaxLife = max_life;
			Particles[index].Age = 0;

			last_index = index;
			NumParticlesActive++;
			return 1;
		}
	}

	PARTICLE *tmp_list = NULL;
	tmp_list = new PARTICLE[NumParticles * 2];
	if(tmp_list == NULL) return 0;
	
	memcpy(tmp_list, Particles, sizeof(PARTICLE) * NumParticles);
	SAFE_DELETE_ARRAY(Particles);
	Particles = tmp_list;
	tmp_list = NULL;
	
	for(int i=NumParticles; i<(NumParticles * 2); i++)
	{
		memset(&(Particles[i]), 0, sizeof(PARTICLE));
	}

	Particles[NumParticles].IsActive = 1;
	Particles[NumParticles].Identifier = identifier;
	Particles[NumParticles].Type = type;
	Particles[NumParticles].Pos = D3DXVECTOR3(x, y, z);
	Particles[NumParticles].LastPos = D3DXVECTOR3(x, y, z);
	Particles[NumParticles].Velocity = D3DXVECTOR3(xv, yv, zv);
	Particles[NumParticles].Scale = scale;
	Particles[NumParticles].ScaleDelta = scale_delta;
	Particles[NumParticles].Color = color;
	Particles[NumParticles].ColorDelta = color_delta;
	Particles[NumParticles].MaxLife = max_life;
	Particles[NumParticles].Age = 0;

	NumParticlesActive++;
	NumParticles *= 2;
	return 1;
}

void CGFParticleSystem::KillParticles(void)
{
	if(GFParent->IsGamePaused || GFParent->IsGameLocked) return;
	memset(Particles, 0, sizeof(PARTICLE) * NumParticles);
	NumParticlesActive = 0;
}

void CGFParticleSystem::KillParticle(int index)
{
	if(GFParent->IsGamePaused || GFParent->IsGameLocked) return;
	memset(&(Particles[index]), 0, sizeof(PARTICLE));
	NumParticlesActive--;
}

void CGFParticleSystem::Update(void)
{
	if(GFParent->IsGamePaused || GFParent->IsGameLocked) return;

	float scalar = GFParent->GetTimeDiff();

	int i = 0;

	for(i=0; i<NumParticles; i++)
	{
		if(Particles[i].IsActive)
		{
			Particles[i].LastPos = Particles[i].Pos;
			Particles[i].Pos += (Particles[i].Velocity * scalar);
			Particles[i].Scale += Particles[i].ScaleDelta * scalar;

			scalar = Particles[i].ColorDelta * GFParent->GetTimeDiff();
			Particles[i].Color.a += scalar;
			Particles[i].Color.r += scalar;
			Particles[i].Color.g += scalar;
			Particles[i].Color.b += scalar;

			scalar = GFParent->GetTimeDiff();
			
			if(Particles[i].MaxLife != -1)
			{
				Particles[i].Age += scalar;
				if(Particles[i].Age >= Particles[i].MaxLife)
				{
					Particles[i].IsActive = 0;
					NumParticlesActive--;
				}
			}

			if(Types[Particles[i].Type].single_modifier != NULL)
			{
				Types[Particles[i].Type].single_modifier(&(Types[Particles[i].Type]), GFParent, &(Particles[i]));
			}
		}
	}

	for(i=0; i<NumTypesUsed; i++)
	{
		if(Types[i].full_modifier != NULL)
		{
			Types[i].full_modifier(&(Types[i]), GFParent);
		}
	}
}

void CGFParticleSystem::Render(void)
{
	if((Particles == NULL) || (Types == NULL) || (GFParent == NULL)) return;
	if(GFParent->IsGameLocked) return;

	int i = 0;
	int vlib_index = -1;
	int tlib_index = -1;
	int last_type = -1;
	PARTICLE_TYPE *type = NULL;
	OBJNODE *tmp_node = NULL;

	D3DXVECTOR3 Up_Vector(0,1,0);
	D3DXVECTOR3 Right_Vector(1,0,0);
	D3DXVECTOR3 Look_Vector(0,0,1);

	D3DXMATRIX YawMat, PitchMat, RollMat;
	D3DXMATRIX TransMat, ScaleMat;
	D3DXMATRIX WorldMat;

	for(i=0; i<NumParticles; i++)
	{
		if(Particles[i].IsActive)
		{
			type = &(Types[Particles[i].Type]);

			if(Particles[i].Type != last_type)
			{
				last_type = Particles[i].Type;

				if((Camera != NULL) && type->IsBillboardingActive)
				{
					D3DXMatrixRotationAxis(&YawMat, &Up_Vector, Camera->Yaw + type->Yaw);
					D3DXVec3TransformCoord(&Look_Vector, &Look_Vector, &YawMat);
					D3DXVec3TransformCoord(&Right_Vector, &Right_Vector, &YawMat);

					D3DXMatrixRotationAxis(&PitchMat, &Right_Vector, Camera->Pitch + type->Pitch);
					D3DXVec3TransformCoord(&Look_Vector, &Look_Vector, &PitchMat);
					D3DXVec3TransformCoord(&Up_Vector, &Up_Vector, &PitchMat);

					D3DXMatrixRotationAxis(&RollMat, &Look_Vector, Camera->Roll + type->Roll);
					D3DXVec3TransformCoord(&Up_Vector, &Up_Vector, &RollMat);
					D3DXVec3TransformCoord(&Right_Vector, &Right_Vector, &RollMat);
				}
				else
				{
					D3DXMatrixRotationAxis(&YawMat, &Up_Vector, type->Yaw);
					D3DXVec3TransformCoord(&Look_Vector, &Look_Vector, &YawMat);
					D3DXVec3TransformCoord(&Right_Vector, &Right_Vector, &YawMat);

					D3DXMatrixRotationAxis(&PitchMat, &Right_Vector, type->Pitch);
					D3DXVec3TransformCoord(&Look_Vector, &Look_Vector, &PitchMat);
					D3DXVec3TransformCoord(&Up_Vector, &Up_Vector, &PitchMat);

					D3DXMatrixRotationAxis(&RollMat, &Look_Vector, type->Roll);
					D3DXVec3TransformCoord(&Up_Vector, &Up_Vector, &RollMat);
					D3DXVec3TransformCoord(&Right_Vector, &Right_Vector, &RollMat);
				}

				if(type->IsColorActive)
				{
					GFParent->D3DDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_TFACTOR);
				}
				else
				{
					GFParent->D3DDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_CURRENT);
				}

				if(type->IsAlphaActive)
				{
					GFParent->D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
				}
				else
				{
					GFParent->D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
				}
			}

			tmp_node = GFParent->ObjectLib[type->ObjLibIndex];
			
			if((vlib_index != tmp_node->VertexLibraryIndex) && (tmp_node->VertexLibraryIndex != -1))
			{
				vlib_index = tmp_node->VertexLibraryIndex;
				GFParent->D3DDevice->SetStreamSource(0, GFParent->VertexLib[vlib_index], GFParent->VertexLib.GetSize(vlib_index));
				GFParent->D3DDevice->SetVertexShader(GFParent->VertexLib.GetFVF(vlib_index));
			}

			if(tlib_index != tmp_node->TextureLibraryIndex)
			{
				tlib_index = tmp_node->TextureLibraryIndex;
				if(tlib_index != -1)
				{
					GFParent->D3DDevice->SetTexture(0, GFParent->TextureLib[tlib_index]);
				}
				else
				{
					GFParent->D3DDevice->SetTexture(0, NULL);
				}
			}

			if(type->IsColorActive)
			{
				GFParent->D3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, Particles[i].Color);
			}

			D3DXMatrixIdentity(&WorldMat);
			D3DXMatrixTranslation(&TransMat, Particles[i].Pos.x, Particles[i].Pos.y, Particles[i].Pos.z);
			D3DXMatrixScaling(&ScaleMat, Particles[i].Scale, Particles[i].Scale, Particles[i].Scale);
			D3DXMatrixMultiply(&WorldMat, &RollMat, &TransMat);
			D3DXMatrixMultiply(&WorldMat, &PitchMat, &WorldMat);
			D3DXMatrixMultiply(&WorldMat, &YawMat, &WorldMat);
			D3DXMatrixMultiply(&WorldMat, &ScaleMat, &WorldMat);
			GFParent->D3DDevice->SetTransform(D3DTS_WORLD, &WorldMat);

			GFParent->D3DDevice->DrawPrimitive(tmp_node->PrimitiveType, tmp_node->StartVertex, tmp_node->PrimitiveCount);
		}
	}

	GFParent->D3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
	GFParent->D3DDevice->SetTexture(0, NULL);
	GFParent->D3DDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_CURRENT);
}

/**************************Particle System Modifier Section************************/

void YawSpinModifier(PARTICLE_TYPE *type, GameFrame *gfparent)
{
	type->Yaw += type->Extra1;
	if(type->Yaw >= PI2) type->Yaw = 0;
	if(type->Yaw <= -PI2) type->Yaw = 0;
}

void PitchSpinModifier(PARTICLE_TYPE *type, GameFrame *gfparent)
{
	type->Pitch += type->Extra1;
	if(type->Pitch >= PI2) type->Pitch = 0;
	if(type->Pitch <= -PI2) type->Pitch = 0;
}

void RollSpinModifier(PARTICLE_TYPE *type, GameFrame *gfparent)
{
	type->Roll += type->Extra1;
	if(type->Roll >= PI2) type->Roll = 0;
	if(type->Roll <= -PI2) type->Roll = 0;
}
