/*
** particle.c for particle in /u/a1/caput_r/rendu/
**
** Made by remi caput
** Login caput_r <caput_r@epita.fr>
**
** Started on  Fri Nov  4 21:16:15 2005 remi caput
** Last update Thu Jan  1 01:18:41 1970 Vertyos
*/

#include "particle.h"

static const struct s_particle_color	gl_particle_color[PARTICLE_COUNT_COLOR] =
{
	{ {0.6,		1,		0.6,	1},	{0,		0,		1,		0}	},
	{ {1,		0.6,	0.6,	1},	{1,		0,		0,		0}	},
	{ {0.3,		1,		0.3,	1},	{0,		1,		0,		0}	},
	{ {1,		1,		0.3,	1},	{1,		1,		0,		0}	},
	{ {1,		0.3,	1,		1},	{1,		0,		1,		0}	},
	{ {0.3,		1,		1,		1},	{0,		1,		1,		0}	},
	{ {1,		1,		0,		1},	{1,		0.6,	0,		0}	},
	{ {0.5,		0.5,	0.5,	1},	{1,		1,		1,		0}	},
	{ {1,		1,		0,		1},	{1,		0,		0,		0}	},
	{ {1,		0.6,	0.3,	1},	{1,		0.6,	0.3,	0}	}
};

static void particle_func_ball_create (struct s_particle_spark *spark, float param __attribute__ ((unused)))
{
	float   a = (rand () % 360) * 3.14159 / 180.0f;
	float   b = 5 + (rand () % 500) * 0.1f;
	float   c = 0.01f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * cos (b) * c;
	spark->speed[1] = sin (a) * cos (b) * c;
	spark->speed[2] = sin (b) * c;
}

static void particle_func_ball_spark (struct s_particle_spark *spark, float param __attribute__ ((unused)))
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float	b = (rand () % 360) * 3.14159f / 180.0f;
	float   c = (rand () % 1000) * 0.01f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * cos (b) * c * 0.001f;
	spark->speed[1] = sin (a) * cos (b) * c * 0.001f;
	spark->speed[2] = sin (b) * c * 0.001f;
}

static void	particle_func_ball_trail (struct s_particle_spark *spark, float param)
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float   b = 0.001f * param;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * b;
	spark->speed[1] = sin (a) * b;
	spark->speed[2] = 0;
}

static void	particle_func_block_destroy (struct s_particle_spark *spark, float param __attribute__ ((unused)))
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float	b = (rand () % 360) * 3.14159f / 180.0f;
	float   c = (rand () % 1000) * 0.00001f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * cos (b) * c;
	spark->speed[1] = sin (a) * cos (b) * c;
	spark->speed[2] = sin (b) * c;
}

static void	particle_func_block_hit (struct s_particle_spark *spark, float param __attribute__ ((unused)))
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float	b = (rand () % 360) * 3.14159f / 180.0f;
	float   c = 0.01f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * cos (b) * c;
	spark->speed[1] = sin (a) * cos (b) * c;
	spark->speed[2] = sin (b) * c;
}

static void	particle_func_bonus_fall (struct s_particle_spark *spark, float param __attribute__ ((unused)))
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float   b = 0.0025f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * b;
	spark->speed[1] = sin (a) * b;
	spark->speed[2] = 0;
}

static void	particle_func_bonus_take (struct s_particle_spark *spark, float param __attribute__ ((unused)))
{
	float   a = (rand () % 360) * 3.14159 / 180.0f;
	float	b = (rand () % 360) * 3.14159 / 180.0f;
	float   c = (rand () % 1000) * 0.00001f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos(a) * cos (b) * c;
	spark->speed[1] = sin (a) * cos (b) * c;
	spark->speed[2] = sin (b) * c;
}

static void	particle_func_pad_length (struct s_particle_spark *spark, float param)
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float   b = 0.0025f;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * b + param;
	spark->speed[1] = sin (a) * b;
	spark->speed[2] = 0;
}

static void	particle_func_pad_load (struct s_particle_spark *spark, float param)
{
	float   a = (rand () % 360) * 3.14159f / 180.0f;
	float   b = (rand () % 360) * 3.14159f / 180.0f;
	float   c = 0.01f * param;

	spark->speed[0] = cos (a) * cos (b) * c;
	spark->speed[1] = sin (a) * cos (b) * c;
	spark->speed[2] = sin (b) * c;
	spark->coord[0] = - 500 * spark->speed[0];
	spark->coord[1] = - 500 * spark->speed[1];
	spark->coord[2] = - 500 * spark->speed[2];
}

static void	particle_func_pad_release (struct s_particle_spark *spark, float param)
{
	float   a = (rand () % 360) * 3.14159 / 180.0f;
	float   b = (rand () % 360) * 3.14159 / 180.0f;
	float	c = (rand () % 1000) * 0.000025f * param;

	memset (spark->coord, 0, sizeof (spark->coord));
	spark->speed[0] = cos (a) * cos (b) * c;
	spark->speed[1] = sin (a) * cos (b) * c;
	spark->speed[2] = sin (b) * c;
}

static const struct s_particle_define	gl_particle_define[PARTICLE_COUNT_DEFINE] =
{
	{ &particle_func_pad_release,	200,	1000,	0.4,	0.4,	"data/particle.bmp"},
	{ &particle_func_bonus_take,	50,		1000,	0.4,	0.8,	"data/particle.bmp"},
	{ &particle_func_pad_length,	25,		750,	0.4,	0.2,	"data/particle.bmp"},
	{ &particle_func_block_hit,		25,		250,	0.4,	0.4,	"data/particle.bmp"},
	{ &particle_func_ball_trail,	25,		1000,	0.4,	0.4,	"data/particle.bmp"},
	{ &particle_func_pad_load,		100,	500,	0.0,	0.8,	"data/particle.bmp"},
	{ &particle_func_block_destroy,	100,	500,	0.4,	0.4,	"data/particle.bmp"},
	{ &particle_func_ball_create,	50,		1000,	0.4,	0.4,	"data/particle.bmp"},
	{ &particle_func_bonus_fall,	10,		500,	0.4,	0.2,	"data/particle.bmp"},
	{ &particle_func_ball_spark,	10,		250,	0.2,	0.2,	"data/particle.bmp"}
};

int									particle_create (struct s_particle* particle, struct s_coord* coord, enum e_particle_color color, enum e_particle_define define, float param)
{
	const struct s_particle_define* def = gl_particle_define + define;
	struct s_particle_item*			item;
	struct s_particle_spark*		spark;
	unsigned long					size;
	unsigned long					i;

	/* Reallocate table if too small */
	if (particle->count == particle->size)
	{
		size = particle->size + PARTICLE_REALLOC_ITEM;

		if (!(item = realloc (particle->item, size * sizeof (*particle->item))))
		{
			if (particle->item)
				free (particle->item);

			return 1;
		}

		for (particle->item = item; particle->size < size; ++particle->size)
		{
			particle->item[particle->size].count = 0;
			particle->item[particle->size].size = 0;
			particle->item[particle->size].spark = NULL;
		}
	}

	/* Create new particle set and realloc spark if needed */
	item = particle->item + particle->count++;

	if (item->size < def->count)
	{
		if (!(spark = realloc (item->spark, def->count * sizeof (*item->spark))))
		{
			if (item->spark)
				free (item->spark);

			return 1;
		}

		item->count = def->count;
		item->spark = spark;
	}

	for (i = 0; i < def->count; ++i)
		(*def->func) (item->spark + i, param);

	/* Define position */
	coord_copy_pos (&item->coord, coord);
	coord_copy_rot (&item->coord, coord);

	item->color = color;
	item->count = def->count;
	item->define = define;
	item->delay = def->delay;
	item->texture = particle->texture[define];
	item->time = gl_vertex.time;

	return 0;
}

void								particle_cycle (struct s_particle *particle)
{
	struct s_particle_item			*item;
	struct s_particle_item			swap;
	unsigned long					i;
	unsigned long					j;

	/* For each particle group */
	for (i = 0; i < particle->count; ++i)
	{
		item = particle->item + i;

		/* Particle is "alive" */
		if ((float)(gl_vertex.time - item->time) / (float)item->delay <= 1)
		{
			/* Cycle through all sparks */
			for (j = 0; j < item->count; ++j)
			{
				item->spark[j].coord[0] += item->spark[j].speed[0] * gl_vertex.speed;
				item->spark[j].coord[1] += item->spark[j].speed[1] * gl_vertex.speed;
				item->spark[j].coord[2] += item->spark[j].speed[2] * gl_vertex.speed;
			}
		}

		/* Particle must be deleted */
		else
		{
			memcpy (&swap, item, sizeof (swap));
			memcpy (item, particle->item + particle->count - 1, sizeof (swap));
			memcpy (particle->item + particle->count - 1, &swap, sizeof (swap));

			--particle->count;
			--i;
		}
	}
}

void								particle_draw (const struct s_particle *particle)
{
	GLfloat							matrix[16];
	const struct s_particle_color	*color;
	const struct s_particle_define	*define;
	struct s_particle_item			*item;
	struct s_particle_spark			*spark;
	char							set_blend;
	char							set_texture;
	unsigned long					i;
	unsigned long					j;
	float							radius;
	float							step;
	float							x;
	float							y;
	float							z;

	/* Save and set modes, then push matrix */
	if ((set_blend = !glIsEnabled (GL_BLEND)))
		glEnable (GL_BLEND);
	if ((set_texture = !glIsEnabled (GL_TEXTURE_2D)))
		glEnable (GL_TEXTURE_2D);

	glPushMatrix ();
	glBlendFunc (GL_SRC_ALPHA, GL_ONE);
	glDepthMask (GL_FALSE);
	glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	/* For each particle group */
	for (i = 0; i < particle->count; ++i)
	{
		item = particle->item + i;
		step = (float)(gl_vertex.time - item->time) / (float)item->delay;

		/* Initialize current color and radius */
		color = gl_particle_color + item->color;
		define = gl_particle_define + item->define;
		radius = define->radius_begin + (define->radius_end - define->radius_begin) * step;

		/* Translate to explosion, save and reset matrix, then do translation again */
		coord_item (&item->coord);
		glGetFloatv (GL_MODELVIEW_MATRIX, matrix);
		glLoadIdentity ();
		glTranslatef (matrix[12], matrix[13], matrix[14]);
		glColor4f (color->begin[0] + (color->end[0] - color->begin[0]) * step, color->begin[1] + (color->end[1] - color->begin[1]) * step, color->begin[2] + (color->end[2] - color->begin[2]) * step, color->begin[3] + (color->end[3] - color->begin[3]) * step);
		glBindTexture (GL_TEXTURE_2D, item->texture);
		glBegin (GL_QUADS);

		/* Draw all sparks */
		for (j = 0; j < item->count; ++j)
		{
			spark = item->spark + j;

			x = spark->coord[0] * matrix[0] + spark->coord[1] * matrix[4] + spark->coord[2] * matrix[8];
			y = spark->coord[0] * matrix[1] + spark->coord[1] * matrix[5] + spark->coord[2] * matrix[9];
			z = spark->coord[0] * matrix[2] + spark->coord[1] * matrix[6] + spark->coord[2] * matrix[10];

			glTexCoord2f (0, 0);
			glVertex3f (x - radius, y - radius, z);
			glTexCoord2f (0, 1);
			glVertex3f (x - radius, y + radius, z);
			glTexCoord2f (1, 1);
			glVertex3f (x + radius, y + radius, z);
			glTexCoord2f (1, 0);
			glVertex3f (x + radius, y - radius, z);
		}

		/* End drawing and restore matrix */
		glEnd ();
		coord_stop ();
	}

	if (set_blend)
		glDisable (GL_BLEND);
	if (set_texture)
		glDisable (GL_TEXTURE_2D);

	glDepthMask (GL_TRUE);
	glPopMatrix ();
}

int					particle_init (struct s_particle *particle, struct s_surface* surface)
{
	unsigned int	i;

	if (!(particle->texture = malloc (PARTICLE_COUNT_DEFINE * sizeof (*particle->texture))))
		return 1;

	for (i = 0; i < PARTICLE_COUNT_DEFINE; ++i)
		if (surface_load (surface, gl_particle_define[i].path, particle->texture + i))
			return 1;

	particle->count = 0;
	particle->item = NULL;
	particle->size = 0;

	return 0;
}

void				particle_stop (struct s_particle *particle)
{
	unsigned int	i;

	for (i = 0; i < particle->count; ++i)
		if (particle->item[i].spark)
			free (particle->item[i].spark);

	if (particle->item)
		free (particle->item);

	if (particle->texture)
		free (particle->texture);
}
