/*
Copyright (C) 2002-2003, Dr Labman, A. Nourai

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>

#include "quakedef.h"
#include "pmove.h"
#include "gl_local.h"

#define DEFAULT_NUM_PARTICLES				4096
#define ABSOLUTE_MIN_PARTICLES				256
#define ABSOLUTE_MAX_PARTICLES				32768

typedef byte col_t[4];

typedef enum {
	p_spark, p_smoke, p_fire, p_bubble, p_lavasplash, p_gunblast, p_chunk, p_shockwave,
	p_inferno_flame, p_inferno_trail, p_sparkray, p_staticbubble, p_trailpart,
	p_dpsmoke, p_dpfire, p_teleflare, p_blood1, p_blood2, p_blood3,
	num_particletypes,
} part_type_t;

typedef enum {
	pm_static, pm_normal, pm_bounce, pm_die, pm_nophysics, pm_float,
} part_move_t;

typedef enum {
	ptex_none, ptex_smoke, ptex_bubble, ptex_generic, ptex_dpsmoke, ptex_lava,
	ptex_blueflare, ptex_blood1, ptex_blood2, ptex_blood3,
	num_particletextures,
} part_tex_t;

typedef enum {
	pd_spark, pd_sparkray, pd_billboard, pd_billboard_vel,
} part_draw_t;

typedef struct particle_s {
	struct particle_s *next;
	vec3_t		org, endorg;
	col_t		color;
	float		growth;		
	vec3_t		vel;
	float		rotangle;
	float		rotspeed;	
	float		size;
	float		start;		
	float		die;		
	byte		hit;		
	byte		texindex;	
	byte		bounces;	
} particle_t;

typedef struct particle_tree_s {
	particle_t	*start;
	part_type_t	id;
	part_draw_t	drawtype;
	int			SrcBlend;
	int			DstBlend;
	part_tex_t	texture;
	float		startalpha;	
	float		grav;
	float		accel;
	part_move_t	move;
	float		custom;		
} particle_type_t;


#define	MAX_PTEX_COMPONENTS		8
typedef struct particle_texture_s {
	int			texnum;
	int			components;
	float		coords[MAX_PTEX_COMPONENTS][4];		
} particle_texture_t;


static float sint[7] = {0.000000, 0.781832, 0.974928, 0.433884, -0.433884, -0.974928, -0.781832};
static float cost[7] = {1.000000, 0.623490, -0.222521, -0.900969, -0.900969, -0.222521, 0.623490};

static particle_t *particles, *free_particles;
static particle_type_t particle_types[num_particletypes];
static int particle_type_index[num_particletypes];	
static particle_texture_t particle_textures[num_particletextures];

static int r_numparticles;		
static vec3_t zerodir = {22, 22, 22};
static float particle_time;		
static vec3_t trail_stop;


qboolean qmb_initialized = false;


cvar_t gl_clipparticles = {"gl_clipparticles", "1"};
cvar_t gl_bounceparticles = {"gl_bounceparticles", "1"};


#define TruePointContents(p) PM_HullPointContents(&cl.worldmodel->hulls[0], 0, p)

#define ISUNDERWATER(x) ((x) == CONTENTS_WATER || (x) == CONTENTS_SLIME || (x) == CONTENTS_LAVA)

static qboolean TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal) {
	pmtrace_t trace;

	memset (&trace, 0, sizeof(trace));
	trace.fraction = 1;
	if (PM_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace))
		return false;
	VectorCopy (trace.endpos, impact);
	if (normal)
		VectorCopy (trace.plane.normal, normal);

	return true;
}

static byte *ColorForParticle(part_type_t type) {
	int lambda;
	static col_t color;

	switch (type) {
	case p_spark:
		color[0] = 224 + (rand() & 31); color[1] = 100 + (rand() & 31); color[2] = 0;
		break;
	case p_smoke:
		color[0] = color[1] = color[2] = 255;
		break;
	case p_fire:
		color[0] = 255; color[1] = 142; color[2] = 62;
		break;
	case p_bubble:
	case p_staticbubble:
		color[0] = color[1] = color[2] = 192 + (rand() & 63);
		break;
	case p_teleflare:
	case p_lavasplash:
		color[0] = color[1] = color[2] = 128 + (rand() & 127);
		break;
	case p_gunblast:
		color[0]= 224 + (rand() & 31); color[1] = 170 + (rand() & 31); color[2] = 0;
		break;
	case p_chunk:
		color[0] = color[1] = color[2] = (32 + (rand() & 127));
		break;
	case p_shockwave:
		color[0] = color[1] = color[2] = 64 + (rand() & 31);
		break;
	case p_inferno_flame:
	case p_inferno_trail:
		color[0] = 255; color[1] = 77; color[2] = 13;
		break;
	case p_sparkray:
		color[0] = 255; color[1] = 102; color[2] = 25;
		break;
	case p_dpsmoke:
		color[0] = color[1] = color[2] = 48 + (((rand() & 0xFF) * 48) >> 8);
		break;
	case p_dpfire:
		lambda = rand() & 0xFF;
		color[0] = 160 + ((lambda * 48) >> 8); color[1] = 16 + ((lambda * 148) >> 8); color[2] = 16 + ((lambda * 16) >> 8);
		break;
	case p_blood1:
	case p_blood2:
		color[0] = color[1] = color[2] = 180 + (rand() & 63);
		break;
	case p_blood3:
		color[0] = (100 + (rand() & 31)); color[1] = color[2] = 0;
		break;
	default:
		assert(!"ColorForParticle: unexpected type");
		break;
	}
	return color;
}


#define ADD_PARTICLE_TEXTURE(_ptex, _texnum, _texindex, _components, _s1, _t1, _s2, _t2)	\
do {																						\
	particle_textures[_ptex].texnum = _texnum;												\
	particle_textures[_ptex].components = _components;										\
	particle_textures[_ptex].coords[_texindex][0] = (_s1 + 1) / 256.0;						\
	particle_textures[_ptex].coords[_texindex][1] = (_t1 + 1) / 256.0;						\
	particle_textures[_ptex].coords[_texindex][2] = (_s2 - 1) / 256.0;						\
	particle_textures[_ptex].coords[_texindex][3] = (_t2 - 1) / 256.0;						\
} while(0);

#define ADD_PARTICLE_TYPE(_id, _drawtype, _SrcBlend, _DstBlend, _texture, _startalpha, _grav, _accel, _move, _custom)	\
do {																													\
	particle_types[count].id = (_id);																					\
	particle_types[count].drawtype = (_drawtype);																		\
	particle_types[count].SrcBlend = (_SrcBlend);																		\
	particle_types[count].DstBlend = (_DstBlend);																		\
	particle_types[count].texture = (_texture);																			\
	particle_types[count].startalpha = (_startalpha);																	\
	particle_types[count].grav = 9.8 * (_grav);																			\
	particle_types[count].accel = (_accel);																				\
	particle_types[count].move = (_move);																				\
	particle_types[count].custom = (_custom);																			\
	particle_type_index[_id] = count;																					\
	count++;																											\
} while(0);

void GL_Particles_CvarInit(void)
{
	Cvar_SetCurrentGroup(CVAR_GROUP_PARTICLES);
	Cvar_Register (&gl_clipparticles);
	Cvar_Register (&gl_bounceparticles);

	Cvar_ResetCurrentGroup();
}

void GL_Particles_TextureInit(void)
{
	int i, count = 0, particlefont;

	if (!(particlefont = GL_LoadTextureImage ("textures/particles/particlefont", "qmb:particlefont", 256, 256, TEX_ALPHA | TEX_COMPLAIN))) 
	{
		qmb_initialized = false;
		return;		
	}

	qmb_initialized = true;
	
	ADD_PARTICLE_TEXTURE(ptex_none, 0, 0, 1, 0, 0, 0, 0);	
	ADD_PARTICLE_TEXTURE(ptex_blood1, particlefont, 0, 1, 0, 0, 64, 64);
	ADD_PARTICLE_TEXTURE(ptex_blood2, particlefont, 0, 1, 64, 0, 128, 64);
	ADD_PARTICLE_TEXTURE(ptex_lava, particlefont, 0, 1, 128, 0, 192, 64);
	ADD_PARTICLE_TEXTURE(ptex_blueflare, particlefont, 0, 1, 192, 0, 256, 64);
	ADD_PARTICLE_TEXTURE(ptex_generic, particlefont, 0, 1, 0, 96, 96, 192);
	ADD_PARTICLE_TEXTURE(ptex_smoke, particlefont, 0, 1, 96, 96, 192, 192);
	ADD_PARTICLE_TEXTURE(ptex_blood3, particlefont, 0, 1, 192, 96, 256, 160);
	ADD_PARTICLE_TEXTURE(ptex_bubble, particlefont, 0, 1, 192, 160, 224, 192);
	for (i = 0; i < 8; i++)
		ADD_PARTICLE_TEXTURE(ptex_dpsmoke, particlefont, i, 8, i * 32, 64, (i + 1) * 32, 96);

	ADD_PARTICLE_TYPE(p_spark, pd_spark, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, -32, 0, pm_bounce, 1.3);
	ADD_PARTICLE_TYPE(p_sparkray, pd_sparkray, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, -0, 0, pm_nophysics, 0);
	ADD_PARTICLE_TYPE(p_gunblast, pd_spark, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, -16, 0, pm_bounce, 1.3);

	ADD_PARTICLE_TYPE(p_fire, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 204, 0, -2.95, pm_die, 0);
	ADD_PARTICLE_TYPE(p_chunk, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 255, -16, 0, pm_bounce, 1.475);
	ADD_PARTICLE_TYPE(p_shockwave, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 255, 0, -4.85, pm_nophysics, 0);
	ADD_PARTICLE_TYPE(p_inferno_flame, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 153, 0, 0, pm_static, 0);
	ADD_PARTICLE_TYPE(p_inferno_trail, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 204, 0, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_trailpart, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 230, 0, 0, pm_static, 0);
	ADD_PARTICLE_TYPE(p_smoke, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_smoke, 140, 3, 0, pm_normal, 0);
	ADD_PARTICLE_TYPE(p_dpfire, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_dpsmoke, 144, 0, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_dpsmoke, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_dpsmoke, 85, 3, 0, pm_die, 0);

	ADD_PARTICLE_TYPE(p_teleflare, pd_billboard, GL_ONE, GL_ONE, ptex_blueflare, 255, 0, 0, pm_die, 0);

	ADD_PARTICLE_TYPE(p_blood1, pd_billboard, GL_ZERO, GL_ONE_MINUS_SRC_COLOR, ptex_blood1, 255, -20, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_blood2, pd_billboard_vel, GL_ZERO, GL_ONE_MINUS_SRC_COLOR, ptex_blood2, 255, -25, 0, pm_die, 0.018);

	ADD_PARTICLE_TYPE(p_lavasplash, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_lava, 170, 0, 0, pm_nophysics, 0);
	ADD_PARTICLE_TYPE(p_blood3, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_blood3, 255, -20, 0, pm_normal, 0);
	ADD_PARTICLE_TYPE(p_bubble, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 8, 0, pm_float, 0);
	ADD_PARTICLE_TYPE(p_staticbubble, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 0, 0, pm_static, 0);
}

int QMB_InitParticles(void)
{
	int i;

	if ((i = COM_CheckParm ("-particles")) && i + 1 < com_argc)
	{
		r_numparticles = (int) (Q_atoi(com_argv[i + 1]));
		r_numparticles = bound(ABSOLUTE_MIN_PARTICLES, r_numparticles, ABSOLUTE_MAX_PARTICLES);
	}
	else
	{
		r_numparticles = DEFAULT_NUM_PARTICLES;
	}

	particles = malloc(r_numparticles * sizeof(particle_t));
	if (particles)
		return 1;

	return 0;
}

void QMB_ShutdownParticles()
{
	free(particles);
}

void QMB_ClearParticles (void) {
	int	i;

	if (!qmb_initialized)
		return;

	memset(particles, 0, r_numparticles * sizeof(particle_t));
	free_particles = &particles[0];

	for (i = 0; i + 1 < r_numparticles; i++)
		particles[i].next = &particles[i + 1];
	particles[r_numparticles - 1].next = NULL;

	for (i = 0; i < num_particletypes; i++)
		particle_types[i].start = NULL;
}

static void QMB_UpdateParticles(void) {
	int i, contents;
	float grav, bounce;
	vec3_t oldorg, stop, normal;
	particle_type_t *pt;
	particle_t *p;
	particle_t *prevp;
	particle_t *nextp;

	grav = movevars.gravity / 800.0;

	for (i = 0; i < num_particletypes; i++) {
		pt = &particle_types[i];

		prevp = ((void *)pt) + (((void*)&((particle_type_t *)0)->start) - ((void *)&((particle_t *)0)->next)) ;
		nextp = pt->start;
		while((p = nextp))
		{
			nextp = p->next;

			if (p->die <= particle_time)
			{
				prevp->next = p->next;
				p->next = free_particles;
				free_particles = p;

				continue;
			}
			else
				prevp = p;

			if (particle_time < p->start)
				continue;
			
			p->size += p->growth * cls.frametime;
			
			if (p->size <= 0) {
				p->die = 0;
				continue;
			}
			
			p->color[3] = pt->startalpha * ((p->die - particle_time) / (p->die - p->start));
			
			p->rotangle += p->rotspeed * cls.frametime;
			
			if (p->hit)
				continue;
			
			p->vel[2] += pt->grav * grav * cls.frametime;
			
			VectorScale(p->vel, 1 + pt->accel * cls.frametime, p->vel)
			
			switch (pt->move) {
			case pm_static:
				break;
			case pm_normal:
				VectorCopy(p->org, oldorg);
				VectorMA(p->org, cls.frametime, p->vel, p->org);
				if (CONTENTS_SOLID == TruePointContents (p->org)) {
					p->hit = 1;
					VectorCopy(oldorg, p->org);
					VectorClear(p->vel);
				}
				break;
			case pm_float:
				VectorMA(p->org, cls.frametime, p->vel, p->org);
				p->org[2] += p->size + 1;		
				contents = TruePointContents(p->org);
				if (!ISUNDERWATER(contents))
					p->die = 0;
				p->org[2] -= p->size + 1;
				break;
			case pm_nophysics:
				VectorMA(p->org, cls.frametime, p->vel, p->org);
				break;
			case pm_die:
				VectorMA(p->org, cls.frametime, p->vel, p->org);
				if (CONTENTS_SOLID == TruePointContents (p->org))
					p->die = 0;
				break;
			case pm_bounce:
				
				if (!gl_bounceparticles.value || p->bounces) {
					VectorMA(p->org, cls.frametime, p->vel, p->org);
					if (CONTENTS_SOLID == TruePointContents (p->org))
						p->die = 0;
				} else {
					VectorCopy(p->org, oldorg);
					VectorMA(p->org, cls.frametime, p->vel, p->org);
					if (CONTENTS_SOLID == TruePointContents (p->org)) {
						if (TraceLineN(oldorg, p->org, stop, normal)) {
							VectorCopy(stop, p->org);
							bounce = -pt->custom * DotProduct(p->vel, normal);
							VectorMA(p->vel, bounce, normal, p->vel);
							p->bounces++;
						}
					}
				}
				break;
			default:
				assert(!"QMB_UpdateParticles: unexpected pt->move");
				break;
			}
		}
	}
}


static void QWB_DrawBillboardParticle(particle_texture_t *ptex, particle_t *p, vec3_t *coord)
{
	vec3_t newcoord[4];

	newcoord[0][0] = coord[0][0]*p->size+p->org[0];
	newcoord[0][1] = coord[0][1]*p->size+p->org[1];
	newcoord[0][2] = coord[0][2]*p->size+p->org[2];

	newcoord[1][0] = coord[1][0]*p->size+p->org[0];
	newcoord[1][1] = coord[1][1]*p->size+p->org[1];
	newcoord[1][2] = coord[1][2]*p->size+p->org[2];

	newcoord[2][0] = coord[2][0]*p->size+p->org[0];
	newcoord[2][1] = coord[2][1]*p->size+p->org[1];
	newcoord[2][2] = coord[2][2]*p->size+p->org[2];

	newcoord[3][0] = coord[3][0]*p->size+p->org[0];
	newcoord[3][1] = coord[3][1]*p->size+p->org[1];
	newcoord[3][2] = coord[3][2]*p->size+p->org[2];

	glColor4ubv(p->color);
	glBegin(GL_QUADS);

	glTexCoord2f(ptex->coords[p->texindex][0], ptex->coords[p->texindex][3]);
	glVertex3fv(newcoord[0]);

	glTexCoord2f(ptex->coords[p->texindex][0], ptex->coords[p->texindex][1]);
	glVertex3fv(newcoord[1]);

	glTexCoord2f(ptex->coords[p->texindex][2], ptex->coords[p->texindex][1]);
	glVertex3fv(newcoord[2]);

	glTexCoord2f(ptex->coords[p->texindex][2], ptex->coords[p->texindex][3]);
	glVertex3fv(newcoord[3]);

	glEnd();
}

static void QWB_DrawBillboardParticleRotate(particle_texture_t *ptex, particle_t *p, vec3_t *coord, vec3_t vpn)
{
	glPushMatrix();
	glTranslatef(p->org[0], p->org[1], p->org[2]);
	glScalef(p->size, p->size, p->size);
	glRotatef(p->rotangle, vpn[0], vpn[1], vpn[2]);

	glColor4ubv(p->color);
	glBegin(GL_QUADS);

	glTexCoord2f(ptex->coords[p->texindex][0], ptex->coords[p->texindex][3]);
	glVertex3fv(coord[0]);

	glTexCoord2f(ptex->coords[p->texindex][0], ptex->coords[p->texindex][1]);
	glVertex3fv(coord[1]);

	glTexCoord2f(ptex->coords[p->texindex][2], ptex->coords[p->texindex][1]);
	glVertex3fv(coord[2]);

	glTexCoord2f(ptex->coords[p->texindex][2], ptex->coords[p->texindex][3]);
	glVertex3fv(coord[3]);

	glEnd();

	glPopMatrix();
}

void QMB_DrawParticles (void) {
	int	i, j, k, drawncount;
	vec3_t v, up, right, billboard[4], velcoord[4], neworg;
	particle_t *p;
	particle_type_t *pt;
	particle_texture_t *ptex;

	particle_time = cl.time;

	if (!cl.paused)
		QMB_UpdateParticles();

	VectorAdd(vup, vright, billboard[2]);
	VectorSubtract(vright, vup, billboard[3]);
	VectorNegate(billboard[2], billboard[0]);
	VectorNegate(billboard[3], billboard[1]);

	glDepthMask(GL_FALSE);
	glEnable(GL_BLEND);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glShadeModel(GL_SMOOTH);

	for (i = 0; i < num_particletypes; i++) {
		pt = &particle_types[i];
		if (!pt->start)
			continue;

		glBlendFunc(pt->SrcBlend, pt->DstBlend);

		switch(pt->drawtype) {
		case pd_spark:
			glDisable(GL_TEXTURE_2D);
			for (p = pt->start; p; p = p->next) {
				if (particle_time < p->start || particle_time >= p->die)
					continue;

				glBegin(GL_TRIANGLE_FAN);
				glColor4ubv(p->color);
				glVertex3fv(p->org);
				glColor4ub(p->color[0] >> 1, p->color[1] >> 1, p->color[2] >> 1, 0);
				for (j = 7; j >= 0; j--) {
					for (k = 0; k < 3; k++)
						v[k] = p->org[k] - p->vel[k] / 8 + vright[k] * cost[j % 7] * p->size + vup[k] * sint[j % 7] * p->size;
					glVertex3fv(v);
				}
				glEnd();
			}
			break;
		case pd_sparkray:
			glDisable(GL_TEXTURE_2D);
			for (p = pt->start; p; p = p->next) {
				if (particle_time < p->start || particle_time >= p->die)
					continue;

				if (!TraceLineN(p->endorg, p->org, neworg, NULL)) 
					VectorCopy(p->org, neworg);

				glBegin (GL_TRIANGLE_FAN);
				glColor4ubv(p->color);
				glVertex3fv(p->endorg);
				glColor4ub(p->color[0] >> 1, p->color[1] >> 1, p->color[2] >> 1, 0);
				for (j = 7; j >= 0; j--) {
					for (k = 0; k < 3; k++)
						v[k] = neworg[k] + vright[k] * cost[j % 7] * p->size + vup[k] * sint[j % 7] * p->size;
					glVertex3fv (v);
				}
				glEnd();
			}
			break;
		case pd_billboard:
			ptex = &particle_textures[pt->texture];
			glEnable(GL_TEXTURE_2D);
			GL_Bind(ptex->texnum);
			drawncount = 0;
			for (p = pt->start; p; p = p->next) {
				if (particle_time < p->start || particle_time >= p->die)
					continue;

				if (gl_clipparticles.value) {
					if (drawncount >= 3 && VectorSupCompare(p->org, r_origin, 30))
						continue;
					drawncount++;
				}
				QWB_DrawBillboardParticle(ptex, p, billboard);
			}
			break;
		case pd_billboard_vel:
			ptex = &particle_textures[pt->texture];
			glEnable(GL_TEXTURE_2D);
			GL_Bind(ptex->texnum);
			for (p = pt->start; p; p = p->next) {
				if (particle_time < p->start || particle_time >= p->die)
					continue;

				VectorCopy (p->vel, up);
				CrossProduct(vpn, up, right);
				VectorNormalizeFast(right);
				VectorScale(up, pt->custom, up);

				VectorAdd(up, right, velcoord[2]);
				VectorSubtract(right, up, velcoord[3]);
				VectorNegate(velcoord[2], velcoord[0]);
				VectorNegate(velcoord[3], velcoord[1]);

				if (p->rotspeed)
					QWB_DrawBillboardParticleRotate(ptex, p, billboard, vpn);
				else
					QWB_DrawBillboardParticle(ptex, p, billboard);
			}
			break;
		default:
			assert(!"QMB_DrawParticles: unexpected drawtype");
			break;
		}
	}

	glEnable(GL_TEXTURE_2D);
	glDepthMask(GL_TRUE);
	glDisable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

#define	INIT_NEW_PARTICLE(_pt, _p, _color, _size, _time)	\
		_p = free_particles;								\
		free_particles = _p->next;							\
		_p->next = _pt->start;								\
		_pt->start = _p;									\
		_p->size = _size;									\
		_p->hit = 0;										\
		_p->start = cl.time;								\
		_p->die = _p->start + _time;						\
		_p->growth = 0;										\
		_p->rotspeed = 0;									\
		_p->texindex = (rand() % particle_textures[_pt->texture].components);	\
		_p->bounces = 0;									\
		VectorCopy(_color, _p->color);


__inline static void AddParticle(part_type_t type, const vec3_t org, int count, float size, float time, col_t col, vec3_t dir) {
	byte *color;
	int i, j;
	float tempSize;
	particle_t *p;
	particle_type_t *pt;

	if (!qmb_initialized)
		Sys_Error("QMB particle added without initialization");

	assert(size > 0 && time > 0);

	if (type < 0 || type >= num_particletypes)
		Sys_Error("AddParticle: Invalid type (%d)", type);

	pt = &particle_types[particle_type_index[type]];

	for (i = 0; i < count && free_particles; i++) {
		color = col ? col : ColorForParticle(type);
		INIT_NEW_PARTICLE(pt, p, color, size, time);

		switch (type) {
		case p_spark:
			p->size = 1.175;
			VectorCopy(org, p->org);
			tempSize = size * 2;
			p->vel[0] = (rand() % (int) tempSize) - ((int) tempSize / 2);
			p->vel[1] = (rand() % (int) tempSize) - ((int) tempSize / 2);
			p->vel[2] = (rand() % (int) tempSize) - ((int) tempSize / 3);
			break;
		case p_smoke:
			for (j = 0; j < 3; j++)
				p->org[j] = org[j] + ((rand() & 31) - 16) / 2.0;
			for (j = 0; j < 3; j++)
				p->vel[j] = ((rand() % 10) - 5) / 20.0;
			break;
		case p_fire:
			VectorCopy(org, p->org);
			for (j = 0; j < 3; j++)
				p->vel[j] = ((rand() % 160) - 80) * (size / 25.0);
			break;
		case p_bubble:
			p->start += (rand() & 15) / 36.0;
			p->org[0] = org[0] + ((rand() & 31) - 16);
			p->org[1] = org[1] + ((rand() & 31) - 16);
			p->org[2] = org[2] + ((rand() & 63) - 32);
			VectorClear(p->vel);
			break;
		case p_lavasplash:
			VectorCopy(org, p->org);
			VectorCopy(dir, p->vel);	
			break;
		case p_gunblast:
			p->size = 1;
			VectorCopy(org, p->org);
			p->vel[0] = (rand() & 127) - 64;
			p->vel[1] = (rand() & 127) - 64;
			p->vel[2] = (rand() & 127) - 10;
			break;
		case p_chunk:
			VectorCopy(org, p->org);
			p->vel[0] = (rand() % 40) - 20;
			p->vel[1] = (rand() % 40) - 20;
			p->vel[2] = (rand() % 40) - 5;			
			break;
		case p_shockwave:
			VectorCopy(org, p->org);
			VectorCopy(dir, p->vel);	
			break;
		case p_inferno_trail:
			for (j = 0; j < 3; j++)
				p->org[j] = org[j] + (rand() & 15) - 8;
			for (j = 0; j < 3; j++)
				p->vel[j] = (rand() & 3) - 2;
			p->growth = -1.5;
			break;
		case p_inferno_flame:
			VectorCopy(org, p->org);
			VectorClear(p->vel);
			p->growth = -30;
			break;
		case p_sparkray:
			VectorCopy(org, p->endorg);
			VectorCopy(dir, p->org);	
			for (j = 0; j < 3; j++)
				p->vel[j] = (rand() & 127) - 64;
			p->growth = -16;
			break;
		case p_staticbubble:
			VectorCopy(org, p->org);
			VectorClear(p->vel);
			break;
		case p_teleflare:
			VectorCopy(org, p->org);
			VectorCopy(dir, p->vel);	
			p->growth = 1.75;
			break;
		case p_blood1:
		case p_blood2:
			for (j = 0; j < 3; j++)
				p->org[j] = org[j] + (rand() & 15) - 8;
			for (j = 0; j < 3; j++)
				p->vel[j] = (rand() & 63) - 32;
			break;
		case p_blood3:
			p->size = size * (rand() % 20) / 5.0;
			VectorCopy(org, p->org);
			for (j = 0; j < 3; j++)
				p->vel[j] = (rand() % 40) - 20;
			break;
		default:
			assert(!"AddParticle: unexpected type");
			break;
		}
	}
}

__inline static void AddParticleTrail(part_type_t type, vec3_t start, vec3_t end, float size, float time, col_t col) {
	byte *color;
	int i, j,  num_particles;
	float count, length;
	vec3_t point, delta;
	particle_t *p;
	particle_type_t *pt;

	if (!qmb_initialized)
		Sys_Error("QMB particle added without initialization");

	assert(size > 0 && time > 0);

	if (type < 0 || type >= num_particletypes)
		Sys_Error("AddParticle: Invalid type (%d)", type);

	pt = &particle_types[particle_type_index[type]];

	VectorCopy(start, point);
	VectorSubtract(end, start, delta);
	if (!(length = VectorLength(delta)))
		goto done;

	switch(type) {
	case p_trailpart:
		count = length / 1.1;
		break;
	case p_blood3:
		count = length / 8;
		break;
	case p_smoke:
		count = length / 3.8;
		break;
	case p_dpsmoke:
		count = length / 2.5;
		break;
	case p_dpfire:
		count = length / 2.8;
		break;
	default:
		assert(!"AddParticleTrail: unexpected type");
		break;
	}

	if (!(num_particles = (int) count))
		goto done;

	VectorScale(delta, 1.0 / num_particles, delta);

	for (i = 0; i < num_particles && free_particles; i++) {
		color = col ? col : ColorForParticle(type);
		INIT_NEW_PARTICLE(pt, p, color, size, time);

		switch (type) {
		case p_trailpart:
			VectorCopy (point, p->org);
			VectorClear(p->vel);
			p->growth = -size / time;
			break;
		case p_blood3:
			VectorCopy (point, p->org);
			for (j = 0; j < 3; j++)
				p->org[j] += ((rand() & 15) - 8) / 8.0;
			for (j = 0; j < 3; j++)
				p->vel[j] = ((rand() & 15) - 8) / 2.0;
			p->size = size * (rand() % 20) / 10.0;
			p->growth = 6;
			break;
		case p_smoke:
			VectorCopy (point, p->org);
			for (j = 0; j < 3; j++)
				p->org[j] += ((rand() & 7) - 4) / 8.0;
			p->vel[0] = p->vel[1] = 0;
			p->vel[2] = rand() & 3;
			p->growth = 4.5;
			p->rotspeed = (rand() & 63) + 96;
			break;
		case p_dpsmoke:
			VectorCopy (point, p->org);
			for (j = 0; j < 3; j++)
				p->vel[j] = (rand() % 10) - 5;
			p->growth = 3;
			p->rotspeed = (rand() & 63) + 96;
			break;
		case p_dpfire:
			VectorCopy (point, p->org);
			for (j = 0; j < 3; j++)
				p->vel[j] = (rand() % 40) - 20;
			break;
		default:
			assert(!"AddParticleTrail: unexpected type");
			break;
		}

		VectorAdd(point, delta, point);
	}
done:
	VectorCopy (point, trail_stop);
}

#define ONE_FRAME_ONLY	(0.0001)

void QMB_ParticleExplosion (vec3_t org) {
	int contents;

	contents = TruePointContents(org);
	if (ISUNDERWATER(contents)) {
		AddParticle(p_fire, org, 12, 14, 0.8, NULL, zerodir);
		AddParticle(p_bubble, org, 6, 3.0, 2.5, NULL, zerodir);
		AddParticle(p_bubble, org, 4, 2.35, 2.5, NULL, zerodir);
		if (r_explosiontype.value != 1) {
			AddParticle(p_spark, org, 50, 100, 0.75, NULL, zerodir);
			AddParticle(p_spark, org, 25, 60, 0.75, NULL, zerodir);
		}
	} else {
		AddParticle(p_fire, org, 16, 18, 1, NULL, zerodir);
		if (r_explosiontype.value != 1) {
			AddParticle(p_spark, org, 50, 250, 0.925f, NULL, zerodir);
			AddParticle(p_spark,org, 25, 150, 0.925f,  NULL, zerodir);
		}
	}
}

void QMB_RunParticleEffect(const vec3_t org, const vec3_t dir, int col, int count) {
	col_t color;
	vec3_t neworg;
	int i, scale, blastcount, blastsize, sparksize, sparkcount, chunkcount, particlecount, bloodcount;
	float blasttime, sparktime;

	if (col == 73) {			
		bloodcount = Q_rint(count / 28.0);
		bloodcount = bound(2, bloodcount, 8);
		for (i = 0; i < bloodcount; i++) {
			AddParticle(p_blood1, org, 1, 4.5, 2 + (rand() & 15) / 16.0, NULL, zerodir);
			AddParticle(p_blood2, org, 1, 4.5, 2 + (rand() & 15) / 16.0, NULL, zerodir);
		}
		return;
	} else if (col == 225) {	
		VectorClear(color);
		scale = (count > 130) ? 3 : (count > 20) ? 2  : 1;
		scale *= 0.758;
		for (i = 0; i < (count >> 1); i++) {
			neworg[0] = org[0] + scale * ((rand() & 15) - 8);
			neworg[1] = org[1] + scale * ((rand() & 15) - 8);
			neworg[2] = org[2] + scale * ((rand() & 15) - 8);
			color[0] = 50 + (rand() & 63);
			AddParticle(p_blood3, org, 1, 1, 2.3, NULL, zerodir);
		}
		return;
	} else if (col == 20 && count == 30) {	
		color[0] = 51; color[2] = 51; color[1] = 255;
		AddParticle(p_chunk, org, 1, 1, 0.75, color, zerodir);  
		AddParticle(p_spark, org, 12 , 75, 0.4, color, zerodir);
		return;
	} else if (col == 226 && count == 20) {	
		color[0] = 230; color[1] = 204; color[2] = 26;
		AddParticle(p_chunk, org, 1, 1, 0.75, color, zerodir);  
		AddParticle(p_spark, org, 12 , 75, 0.4, color, zerodir);
		return;
	}

	switch (count) {
	case 10:	
		AddParticle(p_spark, org, 4, 70, 0.9, NULL, zerodir);
		break;
	case 20:	
		AddParticle(p_spark, org, 8, 85, 0.9, NULL, zerodir);
		break;
	case 30:	
		AddParticle(p_chunk, org, 10, 1, 4, NULL, zerodir);
		AddParticle(p_spark, org, 8, 105, 0.9, NULL, zerodir);
		break;
	default:	
		if (count > 130) {
			scale = 2.274;
			blastcount = 50; blastsize = 50; blasttime = 0.4;
			sparkcount = 6;	sparksize = 70;	sparktime = 0.6;
			chunkcount = 14;
		} else if (count > 20) {
			scale = 1.516;
			blastcount = 30; blastsize = 35; blasttime = 0.25;
			sparkcount = 4;	sparksize = 60;	sparktime = 0.4;
			chunkcount = 7;
		} else {
			scale = 0.758;
			blastcount = 15; blastsize = 5;	blasttime = 0.15;
			sparkcount = 2;	sparksize = 50;	sparktime = 0.25;
			chunkcount = 3;
		}
		particlecount = max(1, count >> 1);
		AddParticle(p_gunblast, org, blastcount, blastsize, blasttime, NULL, zerodir);
		for (i = 0; i < particlecount; i++) {
			neworg[0] = org[0] + scale * ((rand() & 15) - 8);
			neworg[1] = org[1] + scale * ((rand() & 15) - 8);
			neworg[2] = org[2] + scale * ((rand() & 15) - 8);
			AddParticle(p_smoke, neworg, 1, 4, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
			if (i % (particlecount / chunkcount) == 0)
				AddParticle(p_chunk, neworg, 1, 0.75, 3.75, NULL, zerodir);
		}
	}
}

void QMB_ParticleTrail (vec3_t start, vec3_t end, vec3_t *trail_origin, trail_type_t type) {
	col_t color;

	switch (type) {
		case GRENADE_TRAIL:
			AddParticleTrail(p_smoke, start, end, 1.45, 0.825, NULL);
			break;
		case BLOOD_TRAIL:
		case BIG_BLOOD_TRAIL:
			AddParticleTrail(p_blood3, start, end, type == BLOOD_TRAIL ? 1.35 : 2.4, 2, NULL);
			break;
		case TRACER1_TRAIL:
			color[0] = 0; color[1] = 124; color[2] = 0;
			AddParticleTrail (p_trailpart, start, end, 3.75, 0.5, color);
			break;
		case TRACER2_TRAIL:
			color[0] = 255; color[1] = 77; color[2] = 0;
			AddParticleTrail (p_trailpart, start, end, 3.75, 0.5, color);
			break;
		case VOOR_TRAIL:
			color[0] = 77; color[1] = 0; color[2] = 255;
			AddParticleTrail (p_trailpart, start, end, 3.75, 0.5, color);
			break;
		case ALT_ROCKET_TRAIL:
			AddParticleTrail(p_dpfire, start, end, 3, 0.26, NULL);
			AddParticleTrail(p_dpsmoke, start, end, 3, 0.825, NULL);
			break;
		case ROCKET_TRAIL:
		default:
			color[0] = 255; color[1] = 56; color[2] = 9;
			AddParticleTrail(p_trailpart, start, end, 6.2, 0.31, color);
			AddParticleTrail(p_smoke, start, end, 1.8, 0.825, NULL);
			break;
	}

	VectorCopy(trail_stop, *trail_origin);
}

void QMB_BlobExplosion (vec3_t org) {
	float theta;
	col_t color;
	vec3_t neworg, vel;

	color[0] = 60; color[1] = 100; color[2] = 240;
	AddParticle (p_spark, org, 44, 250, 1.15, color, zerodir);

	color[0] = 90; color[1] = 47; color[2] = 207;
	AddParticle(p_fire, org, 15, 30, 1.4, color, zerodir);

	vel[2] = 0;
	for (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 70) {
		color[0] = (60 + (rand() & 15)); color[1] = (65 + (rand() & 15)); color[2] = (200 + (rand() & 15));

		
		vel[0] = cos(theta) * 125;
		vel[1] = sin(theta) * 125;
		neworg[0] = org[0] + cos(theta) * 6;
		neworg[1] = org[1] + sin(theta) * 6;
		neworg[2] = org[2] + 0 - 10;
		AddParticle(p_shockwave, neworg, 1, 4, 0.8, color, vel);
		neworg[2] = org[2] + 0 + 10;
		AddParticle(p_shockwave, neworg, 1, 4, 0.8, color, vel);

		
		vel[0] *= 1.15;
		vel[1] *= 1.15;
		neworg[0] = org[0] + cos(theta) * 13;
		neworg[1] = org[1] + sin(theta) * 13;
		neworg[2] = org[2] + 0;
		AddParticle(p_shockwave, neworg, 1, 6, 1.0, color, vel);
	}
}

void QMB_LavaSplash (vec3_t org) {
	int	i, j;
	float vel;
	vec3_t dir, neworg;

	for (i = -16; i < 16; i++) {
		for (j = -16; j < 16; j++) {
			dir[0] = j * 8 + (rand() & 7);
			dir[1] = i * 8 + (rand() & 7);
			dir[2] = 256;

			neworg[0] = org[0] + dir[0];
			neworg[1] = org[1] + dir[1];
			neworg[2] = org[2] + (rand() & 63);

			VectorNormalizeFast (dir);
			vel = 50 + (rand() & 63);
			VectorScale (dir, vel, dir);

			AddParticle(p_lavasplash, neworg, 1, 4.5, 2.6 + (rand() & 31) * 0.02, NULL, dir);
		}
	}
}

void QMB_TeleportSplash (vec3_t org) {
	int i, j, k;
	vec3_t neworg, angle;
	col_t color;

	
	for (i = -12; i <= 12; i += 6) {
		for (j = -12; j <= 12; j += 6) {
			for (k = -24; k <= 32; k += 8) {
				neworg[0] = org[0] + i + (rand() & 3) - 1;
				neworg[1] = org[1] + j + (rand() & 3) - 1;
				neworg[2] = org[2] + k + (rand() & 3) - 1;
				angle[0] = (rand() & 15) - 7;
				angle[1] = (rand() & 15) - 7;
				angle[2] = (rand() % 160) - 80;
				AddParticle(p_teleflare, neworg, 1, 1.8, 0.30 + (rand() & 7) * 0.02, NULL, angle);
			}
		}
	}

	
	VectorSet(color, 140, 140, 255);
	VectorClear(angle);
	for (i = 0; i < 5; i++) {
		angle[2] = 0;
		for (j = 0; j < 5; j++) {
			AngleVectors(angle, NULL, NULL, neworg);
			VectorMA(org, 70, neworg, neworg);	
			AddParticle(p_sparkray, org, 1, 6 + (i & 3), 5,  color, neworg);
			angle[2] += 360 / 5;
		}
		angle[0] += 180 / 5;
	}
}

void QMB_DetpackExplosion (vec3_t org) {
	int i, j, contents;
	float theta;
	vec3_t neworg, angle = {0, 0, 0};

	
	contents = TruePointContents(org);
	if (ISUNDERWATER(contents)) {
		AddParticle(p_bubble, org, 8, 2.8, 2.5, NULL, zerodir);
		AddParticle(p_bubble, org, 6, 2.2, 2.5, NULL, zerodir);
		AddParticle(p_fire, org, 10, 25, 0.75, ColorForParticle(p_inferno_flame), zerodir);
	} else {
		AddParticle(p_fire, org, 14, 33, 0.75, ColorForParticle(p_inferno_flame), zerodir);
	}

	
	for (i = 0; i < 5; i++) {
		angle[2] = 0;
		for (j = 0; j < 5; j++) {
			AngleVectors(angle, NULL, NULL, neworg);
			VectorMA(org, 90, neworg, neworg);	
			AddParticle(p_sparkray, org, 1, 9 + (i & 3), 0.75,  NULL, neworg);
			angle[2] += 360 / 5;
		}
		angle[0] += 180 / 5;
	}

	
	angle[2] = 0;
	for (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 90) {
		angle[0] = cos(theta) * 350;
		angle[1] = sin(theta) * 350;
		AddParticle(p_shockwave, org, 1, 14, 0.75, NULL, angle);
	}
}

void QMB_InfernoFlame (vec3_t org) {
	if (cls.frametime) {
		AddParticle(p_inferno_flame, org, 1, 30, 13.125 * cls.frametime,  NULL, zerodir);
		AddParticle(p_inferno_trail, org, 2, 1.75, 45.0 * cls.frametime, NULL, zerodir);
		AddParticle(p_inferno_trail, org, 2, 1.0, 52.5 * cls.frametime, NULL, zerodir);
	}
}

void QMB_StaticBubble (entity_t *ent) {
	AddParticle(p_staticbubble, ent->origin, 1, ent->frame == 1 ? 1.85 : 2.9, ONE_FRAME_ONLY, NULL, zerodir);
}
