// ParticleEngine.cpp: implementation of the ParticleEngine class.
//
//////////////////////////////////////////////////////////////////////

#include "ParticleEngine.h"

using namespace ParticleSystem;

/**
 * definition d'implementation, toute ce qui est definit et declaré ici l'est uniquement pour l'implementation.
 * il n'ont rien a faire dans le header de la classe
 */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#ifdef WIN32
	#define NOMINMAX // pour novodex
	#include <windows.h>
#endif
#include <GL/gl.h>
#include <GL/glext.h>
#include "ParticleEngine.h"

#include "MemoryManager/DebugNew.h"


void normaliseVector(float * vect)
{
	float length = sqrt(vect[0]*vect[0] + vect[1]*vect[1] + vect[2]*vect[2]);
	vect[0] /= length;
	vect[1] /= length;
	vect[2] /= length;
}

/**
 * fonction utilisée pour l'algorithme de quick sort.
 * compare la distance entre 2 particule par proximité a la camera
 * si p1.distance > p2.distance, p1 est mise avant p2 dans la tableau
 */
int particleCompare( const void *particle1, const void *particle2 )
{
	static float d1;
	static float d2;
	d1 = ((ParticleEngine::Particle*)particle1)->distance;
	d2 = ((ParticleEngine::Particle*)particle2)->distance;
	if (d1 > d2)
	{
		return -1;
	}
	else if (d1 < d2)
	{
		return 1;
	}
	else return 0;// ce cas n'arrive que tres rarement
}

/**
 * fonction utilisée pour l'algorithme de quick sort.
 * compare la distance entre 2 particule par eloignement a la camera
 * si p1.distance > p2.distance, p1 est mise apres p2 dans la tableau
 */
int particleInvCompare( const void *particle1, const void *particle2 )
{
	static float d1;
	static float d2;
	d1 = ((ParticleEngine::Particle*)particle1)->distance;
	d2 = ((ParticleEngine::Particle*)particle2)->distance;
	if (d1 > d2)
	{
		return 1;
	}
	else if (d1 < d2)
	{
		return -1;
	}
	else return 0;// ce cas n'arrive que tres rarement
}

/**
 * fin de la definition d'implementation
 */

float ParticleEngine::cameraPos[3] = {0,0,0};
float ParticleEngine::axes[9] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f};
bool ParticleEngine::sameCamePos = false;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

ParticleEngine::ParticleEngine()
{
	physique = new ParticlePhysique();
	generator = new ParticleGenerator();
	particle = NULL;
	textureId = 0;
	particleNumber = 0;
	finish = true;
	particleAlive = 0;
	maxParticleAlive = 0;
	loop = true;
	haveBeenUpdated = true;	// mis a true car pour la premiere frame, il faut faire comme si le moteur de particules
							// etait updaté...
	glDestinationFactor = GL_SRC_ALPHA;
	glSourceFactor = GL_ONE;
	sortType = NO_SORT;
	bbox[0] = bbox[1] = bbox[2] = bbox[3] = bbox[4] = bbox[5] = 0.0f;
	drawMode = GL_QUADS;
	particleOffset = sizeof (ParticleEngine::Particle);
	speedFactor = 1.0f;
	updateElapsedTime = 0.0f;
	updateDelay = 1.0f/75.0f;
}

ParticleEngine::~ParticleEngine()
{
	delete physique;
	delete generator;
	if (particle != NULL)
	{
		delete[] particle;
	}
#ifdef _DEBUG
	//printf("nombre maximum de particules vivante en même temps : %d/%d\n",maxParticleAlive,particleNumber);
#endif
}

ParticleEngine::ParticlePhysique::ParticlePhysique()
{
	setPhysique(0.0f,-0.89f,0.0f); // la physique de base est celle de la terre
}

ParticleEngine::ParticleGenerator::ParticleGenerator()
{
	maxGeneration = 10;
	generation = 0;
	loop = false;
	pos[0] = 0.0f;pos[1] = 0.0f;pos[2] = 0.0f;
	generationDelay = 1.0f;
	previousTime = 0.0f;
}

bool ParticleEngine::Particle::notInitialised()
{
	return (maxLife == -1);
}

bool ParticleEngine::Particle::isAlive()
{
	return (life <= maxLife);
}

void ParticleEngine::update(float elapsedTime)
{
	// gestion du nombre maxumim de mises a jours par secondes.
	updateElapsedTime += elapsedTime;
	// si le delay depuis la derniere mise a jours est inferieur au delay minimum, on sort de la methode.
	if (updateElapsedTime < updateDelay)
	{
		return;
	}
	// le temps ecoulé depuis la derniere mise a jour est donc updateElapsedTime.
	// TODO : voir si ca ne pose pas de probleme avec la mise en pause...
	elapsedTime = updateElapsedTime;
	updateElapsedTime = 0.0f;

	elapsedTime *= speedFactor;
	physique->update(elapsedTime);	// on change la physique des particules
	generator->update(elapsedTime);	// on met a jour le generateur.
	if (generator->previousTime >= generator->generationDelay)
	{
		generator->generation = 0;	// on réinitialise le generateur
		generator->previousTime = 0.0f;
	}
	else
	{
		generator->previousTime += elapsedTime;
	}
	if (!finish || generator->loop)
	{
		finish = true;
		bool first= true;
		particleAlive = 0;
		void * p = particle;
		for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
		{
			((Particle*)p)->update(elapsedTime);
			if(((Particle*)p)->isAlive())
			{
				if (first)
				{
					bbox[0] = bbox[3] = ((Particle*)p)->pos[0];
					bbox[1] = bbox[4] = ((Particle*)p)->pos[1];
					bbox[2] = bbox[5] = ((Particle*)p)->pos[2];
					first = false;
				}
				bboxCheck(((Particle*)p));
				finish = false;
				particleAlive++;
			}
			else if (((Particle*)p)->notInitialised())
			{
				finish = false;
			}
		}// for
	}
	if (particleAlive > maxParticleAlive)
	{
		maxParticleAlive = particleAlive;
	}
	else if (particleAlive == 0)
	{
		// plus de particules vivantes, on met la bbox a 0
		bbox[0] = bbox[3] = bbox[1] = bbox[4] = bbox[2] = bbox[5] = 0.0f;
	}
	haveBeenUpdated = true;
}

void ParticleEngine::Particle::update(float elapsedTime)
{
	if (isAlive())
	{
		// puis on update la durée de vie de la particule
		life+= elapsedTime;
		// on ajoute le vecteur physique a la direction
		static float massByTime;
		massByTime = masse*elapsedTime;
		direction[0] += physique->physique[0]*massByTime;
		direction[1] += physique->physique[1]*massByTime;
		direction[2] += physique->physique[2]*massByTime;
		// on met a jour la position de la particule
		pos[0] += direction[0]*elapsedTime;
		pos[1] += direction[1]*elapsedTime;
		pos[2] += direction[2]*elapsedTime;
		// et on met a jour l'alpha de la particule
		static float factor;
		factor = life/maxLife;
		couleur[0] = beginCouleur[0] + factor*(endCouleur[0] - beginCouleur[0]);
		couleur[1] = beginCouleur[1] + factor*(endCouleur[1] - beginCouleur[1]);
		couleur[2] = beginCouleur[2] + factor*(endCouleur[2] - beginCouleur[2]);
		couleur[3] = beginCouleur[3] + factor*(endCouleur[3] - beginCouleur[3]);
	}
	else
	{
		// la particule est morte, on essaye de la regenerer (si le generateur a depassé
		// le nombre max de particules generée, il ne la regenerera pas a ce tour mais au 
		// tour suivant.)
		if (generator->loop || notInitialised())
		{
			generator->generate(this);
		}
	}

}

void ParticleEngine::setGenerator(ParticleEngine::ParticleGenerator *gen)
{
	assert(gen != NULL);
	if (gen == generator) return;
	delete generator;
	generator = gen;
	void * p = particle;
	for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
	{
		((Particle*)p)->generator = generator;
	}

}

void ParticleEngine::setPhysique(ParticleEngine::ParticlePhysique *phy)
{
	assert (phy != NULL);
	if (phy == physique) return;
	delete physique;
	physique = phy;
	void * p = particle;
	for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
	{
		((Particle*)p)->physique = physique;
	}
}

void ParticleEngine::createParticles(int number)
{
	assert (number > 0);

	if (particle != NULL)
	{
		delete[] particle;
	}
	particleNumber = number;
	particle = new Particle[particleNumber];
	generator->generation = 0;
	particleOffset = sizeof(Particle);
	void * p = particle;
	for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
	{
		((Particle*)p)->generator = generator;
		((Particle*)p)->physique = physique;
		((Particle*)p)->life = -1;
	}
	finish = true;
	particleAlive = 0;
	maxParticleAlive = 0;
}

void ParticleEngine::setParticles(ParticleEngine::Particle *part, int number, int offset)
{
	assert (number >= 0);
	assert (offset >= sizeof(Particle));
	if (particle != NULL)
	{
		delete[] particle;
	}
	particleNumber = number;
	particle = part;
	particleOffset = offset;
	generator->generation = 0;
	void * p = particle;
	for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
	{
		((Particle*)p)->generator = generator;
		((Particle*)p)->physique = physique;
	}
	finish = true;
	particleAlive = 0;
	maxParticleAlive = 0;
}

void ParticleEngine::draw()
{
	// si le moteur de particules est arreté, on ne le dessine pas.
	if (isFinish()) return;
	// si le moteur de particule doit etre Z-sorted, qu'il a été mis a jour et que la camera n'a pas bougée.
	if (sortType != NO_SORT)
	{
		// on calcule la distance de chaque particule a le camera
		void * pt = particle;
		float infinity = INFINITY - particleNumber;
		float increment = 1;
		if (sortType == SORTED)
		{
			infinity = -INFINITY + particleNumber;
			increment = -1;
		}
		for (int i = 0; i < particleNumber; i++, (*(int*)&pt) += particleOffset)
		{
			if (((Particle*)pt)->isAlive())
			{
				static float * p;
				p = ((Particle*)pt)->pos;
				// -optimisation [originaly proposed by seb]
				// la fonction sqrt est une fonction strictement croissante sur [0.. +inf]
				// donc sqrt(a) > sqrt(b) => a > b. on a donc pas besoin d'utiliser sqrt pour calculer la
				// distance etant donnée que le seul but de la distance est de réaliser un quickSort sur les
				// particules, on a donc juste besoin d'evaluer les distance relativement au autres particules...
				static float vect[3];
				vect[0] = (p[0]-cameraPos[0]);
				vect[1] = (p[1]-cameraPos[1]);
				vect[2] = (p[2]-cameraPos[2]);
				((Particle*)pt)->distance = (vect[0] * vect[0]) + (vect[1] * vect[1]) + (vect[2] * vect[2]);
			}
			else
			{
				((Particle*)pt)->distance = infinity;//infinity
				infinity += increment;
			}
		}
		// on trie les particule par quick sort en fonction de leurs distance à la camera
		switch (sortType)
		{
		case SORTED:
			qsort(particle,particleNumber,particleOffset,particleCompare);
			break;
		case INV_SORTED:
			qsort(particle,particleNumber,particleOffset,particleInvCompare);
			break;
		default:
			// on signal que l'utilisateur utilise un type de tri inconnu
			assert(false && strcmp("utilisation d'un type de tri inconnu","") == 0);
			break;
		}
	}

	// on bind la texture
	glBindTexture(GL_TEXTURE_2D,textureId);
	glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

	// on sauvegarde l'etat du blending
	bool blend = (glIsEnabled(GL_BLEND)==GL_TRUE);
	int srcFactor = 0;
	glGetIntegerv(GL_BLEND_SRC,&srcFactor);
	int dstFactor = 0;
	glGetIntegerv(GL_BLEND_DST,&dstFactor);
	// on active le blending
	glBlendFunc(glSourceFactor,glDestinationFactor);
	glEnable(GL_BLEND);

	// on empeche l'ecriture dans le Z-Buffer. Le Z-Buffer n'est plus qu'en lecture seule.
	glDepthMask(GL_FALSE);
	
	// on dessine des quads
	glBegin(drawMode);

	// on parcours toutes les particules
	void * p = particle;
	if (sortType != NO_SORT)
	{
		// ici on sait qu'on a particleAlive particules vivante, et que ces particules se trouve en
		// tete du tableau de particules. on peut donc les afficher en direct...
		for (int i = 0; i < particleAlive; i++, (*(int*)&p) += particleOffset)
		{
			((Particle*)p)->draw();
		}
	}
	else
	{
		for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
		{
			// si la particule est vivante on l'affiche
			if (((Particle*)p)->isAlive())
			{
				((Particle*)p)->draw();
			}
		}
	}
	glEnd();

	// on remet le blending dans son etat d'activation d'origine.
	if (!blend)
	{
		glDisable(GL_BLEND);
	}
	glBlendFunc(srcFactor,dstFactor);
	
	// on réactive l'ecriture dans le Z-Buffer
	glDepthMask(GL_TRUE);
	// on signale que le moteur n'est plus considéré comme etant updaté
	haveBeenUpdated = false;
}

void ParticleEngine::Particle::draw()
{
	static float size2 = 0.5;
	size2 = size/2.0f;
	static float vertice[4][3];

	vertice[0][0]=pos[0]+(-axes[0]+axes[3])*size2;
	vertice[0][1]=pos[1]+(-axes[1]+axes[4])*size2;
	vertice[0][2]=pos[2]+(-axes[2]+axes[5])*size2;

	vertice[1][0]=pos[0]+(-axes[0]-axes[3])*size2;
	vertice[1][1]=pos[1]+(-axes[1]-axes[4])*size2;
	vertice[1][2]=pos[2]+(-axes[2]-axes[5])*size2;

	vertice[2][0]=pos[0]+(+axes[0]+axes[3])*size2;
	vertice[2][1]=pos[1]+(+axes[1]+axes[4])*size2;
	vertice[2][2]=pos[2]+(+axes[2]+axes[5])*size2;

	vertice[3][0]=pos[0]+(+axes[0]-axes[3])*size2;
	vertice[3][1]=pos[1]+(+axes[1]-axes[4])*size2;
	vertice[3][2]=pos[2]+(+axes[2]-axes[5])*size2;

	glColor4fv(couleur);
	glTexCoord2f(0.0f,0.0f);glVertex3fv(vertice[1]); 
	glTexCoord2f(1.0f,0.0f);glVertex3fv(vertice[3]);
	glTexCoord2f(1.0f,1.0f);glVertex3fv(vertice[2]); 
	glTexCoord2f(0.0f,1.0f);glVertex3fv(vertice[0]); 

}

void ParticleEngine::start(bool loop)
{
	generator->generation = 0;
	generator->previousTime = 0.0f;
	generator->loop = loop;
	particleAlive = 0;
	finish = false;
	void * p = particle;
	float infinity = INFINITY;
	if (sortType == SORTED)
	{
		infinity = -INFINITY;
	}
	for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
	{
		// on met la vie max de la particule a -1, si apres le passage du generator elle est encore a -1, c'est qu'elle
		// n'a pas été generée... CQFD
		((Particle*)p)->maxLife = -1.0f;
		((Particle*)p)->distance = infinity;// infinity
		generator->generate(((Particle*)p));
		if(((Particle*)p)->isAlive())
		{
			particleAlive ++;
		}
	}
	maxParticleAlive = particleAlive;
	haveBeenUpdated = true;
	updateElapsedTime = 0.0f;
}

void ParticleEngine::setCameraPos(float x, float y, float z)
{
	static float matrice[16];
	// on test d'abord le z avant le y car on a plus de chance de bouger sur l'axe Z que sur l'axe Y...
	sameCamePos = cameraPos[0] == x && cameraPos[2] == z && cameraPos[1] == y;
	cameraPos[0] = x;
	cameraPos[1] = y;
	cameraPos[2] = z;
	glGetFloatv(GL_MODELVIEW_MATRIX,matrice);
	axes[0] = matrice[0];
	axes[1] = matrice[4];
	axes[2] = matrice[8];
	axes[3] = matrice[1];
	axes[4] = matrice[5];
	axes[5] = matrice[9];
	axes[6] = matrice[2];
	axes[7] = matrice[6];
	axes[8] = matrice[10];
}

void ParticleEngine::bboxCheck(const ParticleEngine::Particle * p)
{
	const float * pos = p->pos;
	float size2 = p->size/2.0f;
	// on ne peut malheureusement pas sortir en avance de la fonction car
	// un moteur de particule qui aurait toutes les particules alignée sur un coté aurait bien
	// un des point de la bbox bien calculé mais l'autre ne le serait pas...
	if (pos[0]-size2<bbox[0])
	{
		bbox[0] = pos[0]-size2;
	}
	if (pos[0]+size2>bbox[3])
	{
		bbox[3] = pos[0]+size2;
	}
	if (pos[1]-size2<bbox[1])
	{
		bbox[1] = pos[1]-size2;
	}
	if (pos[1]+size2>bbox[4])
	{
		bbox[4] = pos[1]+size2;
	}
	if (pos[2]-size2<bbox[2])
	{
		bbox[2] = pos[2]-size2;
	}
	if (pos[2]+size2>bbox[5])
	{
		bbox[5] = pos[2]+size2;
	}
}

void ParticleEngine::drawBoundingBox()
{
	glBegin(GL_LINES);
		glVertex3fv(bbox);
		glVertex3f(bbox[3],bbox[1],bbox[2]);

		glVertex3fv(bbox);
		glVertex3f(bbox[0],bbox[4],bbox[2]);

		glVertex3fv(bbox);
		glVertex3f(bbox[0],bbox[1],bbox[5]);

		glVertex3fv(&bbox[3]);
		glVertex3f(bbox[0],bbox[4],bbox[5]);

		glVertex3fv(&bbox[3]);
		glVertex3f(bbox[3],bbox[1],bbox[5]);

		glVertex3fv(&bbox[3]);
		glVertex3f(bbox[3],bbox[4],bbox[2]);

		glVertex3f(bbox[3],bbox[4],bbox[2]);
		glVertex3f(bbox[0],bbox[4],bbox[2]);

		glVertex3f(bbox[3],bbox[4],bbox[2]);
		glVertex3f(bbox[3],bbox[1],bbox[2]);

		glVertex3f(bbox[3],bbox[1],bbox[5]);
		glVertex3f(bbox[3],bbox[1],bbox[2]);

		glVertex3f(bbox[3],bbox[1],bbox[5]);
		glVertex3f(bbox[0],bbox[1],bbox[5]);

		glVertex3f(bbox[0],bbox[4],bbox[5]);
		glVertex3f(bbox[0],bbox[1],bbox[5]);

		glVertex3f(bbox[0],bbox[4],bbox[5]);
		glVertex3f(bbox[0],bbox[4],bbox[2]);
	glEnd();
}

void ParticleEngine::computeMaxParticleNumber()
{
	start(true);
	for (int i = 0; i < 100000; i++)
	{
		update(0.001f);
	}
}

void ParticleEngine::printParticleInfo()
{
	void * pt = particle;
	for (int i = 0; i < particleNumber; i++, (*(int*)&pt) += particleOffset)
	{
		ParticleEngine::Particle *p = ((Particle*)pt);
		printf("position %.2f %.2f %.2f dist %f\n",p->pos[0],p->pos[1],p->pos[2],p->distance);
	}
}

void ParticleEngine::drawPointParticle()
{
	glBegin(GL_POINTS);
		void * p = particle;
		for (int i = 0; i < particleNumber; i++, (*(int*)&p) += particleOffset)
		{
			// si la particule est vivante on l'affiche
			if (((Particle*)p)->isAlive())
			{
				glVertex3fv(((Particle*)p)->pos);
			}
		}
	glEnd();
}

void ParticleEngine::setSpeedFactor(float factor)
{
	speedFactor = factor;
	if (speedFactor < 0)
	{
		speedFactor = 0;
	}
}

float ParticleEngine::getSpeedFactor()
{
	return speedFactor;
}

