/*
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.

*/
// gl_rpart.c

#include "quakedef.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_sparkray, p_staticbubble, p_trailpart, p_dpsmoke, p_dpfire, p_teleflare,
	p_blood1, p_blood2, p_blood3, p_q3blood, p_flame, p_lavatrail, p_bubble2, p_streak,
	p_streaktrail, p_streakwave, p_lightningbeam, p_glow, p_missilefire, p_muzzleflash,
	p_q3blood_trail, p_q3blood_decal, p_q3rocketsmoke, p_q3grenadesmoke, p_q3explosion,
	p_q3flame, p_q3gunshot, p_q3teleport,
	num_particletypes
} part_type_t;

typedef	enum
{
	pm_static, pm_normal, pm_bounce, pm_die, pm_nophysics, pm_float, pm_streak, pm_streakwave
} 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, ptex_lightning, ptex_muzzleflash,
	ptex_q3blood, ptex_q3blood_trail, ptex_q3smoke, ptex_q3explosion, ptex_q3flame,
	num_particletextures
} part_tex_t;

typedef	enum
{
	pd_spark, pd_sparkray, pd_billboard, pd_billboard_vel, pd_hide, pd_beam,
	pd_q3flame, pd_q3gunshot, pd_q3teleport
} 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	int		particle_count = 0;
static	float	particle_time;
static	vec3_t	trail_stop;

part_mode_t	particle_mode;
qboolean	qmb_initialized = false;

int			colorStart, colorLength;
qboolean	colorMapped = false;

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

void R_CalcBeamVerts (float *vert, vec3_t org1, vec3_t org2, float width);
void R_DrawQ3Model (entity_t *ent);
void QMB_Q3Gunshot (vec3_t org, int skinnum, float alpha);
void QMB_Q3Teleport (vec3_t org, float alpha);

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

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

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:
	case p_glow:
	case p_muzzleflash:
		color[0] = color[1] = color[2] = 255;
		break;

	case p_q3rocketsmoke:
	case p_q3grenadesmoke:
		color[0] = color[1] = color[2] = 160;
		break;

	case p_q3explosion:
	case p_q3flame:
	case p_q3gunshot:	// not used
	case p_q3teleport:	// not used
		color[0] = color[1] = color[2] = 200;
		break;

	case p_fire:
		color[0] = 255;
		color[1] = 142;
		color[2] = 62;
		break;

	case p_bubble:
	case p_bubble2:
	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_missilefire:
		color[0] = 255;
		color[1] = 56;
		color[2] = 9;
		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;

	case p_q3blood:
	case p_q3blood_trail:
	case p_q3blood_decal:
		color[0] = 180;
		color[1] = color[2] = 0;
		break;

	case p_flame:
		color[0] = 255;
		color[1] = 100;
		color[2] = 25;
		break;

	case p_lavatrail:
		color[0] = 255;
		color[1] = 102;
		color[2] = 25;
		break;

	case p_lightningbeam:
		color[0] = 120;
		color[1] = 140;
		color[2] = 255;
		break;

	case p_streakwave:
		color[0] = 60;
		color[1] = 100;
		color[2] = 240;
		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) / max_s;	\
	particle_textures[_ptex].coords[_texindex][1] = (_t1 + 1) / max_t;	\
	particle_textures[_ptex].coords[_texindex][2] = (_s2 - 1) / max_s;	\
	particle_textures[_ptex].coords[_texindex][3] = (_t2 - 1) / max_t;	\
} 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 QMB_InitParticles (void)
{
	int		i, j, ti, count = 0, particleimage;
	float	max_s, max_t;

	particle_mode = pm_classic;

	Cvar_Register (&gl_bounceparticles);

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

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

	// load the rest of the images

	if (!(particleimage = GL_LoadTextureImage("textures/particles/q3particlefont", "qmb:q3particlefont", 384, 192, TEX_ALPHA | TEX_COMPLAIN)))
		return;

	max_s = 384.0;
	max_t = 192.0;
	for (i = 0, ti = 0 ; i < 2 ; i++)
		for (j = 0 ; j < 4 ; j++, ti++)
			ADD_PARTICLE_TEXTURE(ptex_q3explosion, particleimage, ti, 8, j * 64, i * 64, (j + 1) * 64, (i + 1) * 64);
	for (i = 0 ; i < 5 ; i++)
		ADD_PARTICLE_TEXTURE(ptex_q3blood, particleimage, i, 5, i * 64, 128, (i + 1) * 64, 192);
	ADD_PARTICLE_TEXTURE(ptex_q3smoke, particleimage, 0, 1, 256, 0, 384, 128);
	ADD_PARTICLE_TEXTURE(ptex_q3blood_trail, particleimage, 0, 1, 320, 128, 384, 192);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/q3flames", "qmb:q3flames", 512, 512, TEX_ALPHA | TEX_COMPLAIN)))
		return;

	max_s = max_t = 512.0;
	for (i = 0, ti = 0 ; i < 2 ; i++)
		for (j = 0 ; j < 4 ; j++, ti++)
			ADD_PARTICLE_TEXTURE(ptex_q3flame, particleimage, ti, 8, j * 128, i * 256, (j + 1) * 128, (i + 1) * 256);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/zing1", "qmb:lightning", 256, 128, TEX_ALPHA | TEX_COMPLAIN)))
		return;

	max_s = 256.0;
	max_t = 128.0;
	ADD_PARTICLE_TEXTURE(ptex_lightning, particleimage, 0, 1, 0, 0, 256, 128);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/muzzleflash", "qmb:muzzleflash", 128, 128, TEX_ALPHA | TEX_COMPLAIN)))
		return;	

	max_s = max_t = 128.0;
	ADD_PARTICLE_TEXTURE(ptex_muzzleflash, particleimage, 0, 1, 0, 0, 128, 128);

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

	particles = Hunk_AllocName (r_numparticles * sizeof(particle_t), "qmb:particles");

	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, -1);
	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_missilefire, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 153, 0, 0, pm_static, 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, -1);

	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);

	ADD_PARTICLE_TYPE(p_flame, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 200, 10, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_lavatrail, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 255, 3, 0, pm_normal, 0);
	ADD_PARTICLE_TYPE(p_bubble2, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 1, 0, pm_float, 0);
	ADD_PARTICLE_TYPE(p_streak, pd_hide, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, -64, 0, pm_streak, 1.5);
	ADD_PARTICLE_TYPE(p_streakwave, pd_hide, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, 0, 0, pm_streakwave, 0);
	ADD_PARTICLE_TYPE(p_streaktrail, pd_beam, GL_SRC_ALPHA, GL_ONE, ptex_none, 128, 0, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_lightningbeam, pd_beam, GL_SRC_ALPHA, GL_ONE, ptex_lightning, 255, 0, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_glow, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_generic, 204, 0, 0, pm_die, -1);
	ADD_PARTICLE_TYPE(p_muzzleflash, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_muzzleflash, 204, 0, 0, pm_static, -1);

	ADD_PARTICLE_TYPE(p_q3blood, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_q3blood, 255, 0, 0, pm_static, -1);
	ADD_PARTICLE_TYPE(p_q3blood_trail, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_q3blood_trail, 255, -1.5, 0, pm_die, -1);
	ADD_PARTICLE_TYPE(p_q3rocketsmoke, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_q3smoke, 80, 0, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_q3grenadesmoke, pd_billboard, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, ptex_q3smoke, 80, 0, 0, pm_die, 0);
	ADD_PARTICLE_TYPE(p_q3explosion, pd_billboard, GL_SRC_ALPHA, GL_ONE, ptex_q3explosion, 204, 0, 0, pm_static, -1);
	ADD_PARTICLE_TYPE(p_q3flame, pd_q3flame, GL_SRC_ALPHA, GL_ONE, ptex_q3flame, 204, 0, 0, pm_static, -1);
	ADD_PARTICLE_TYPE(p_q3gunshot, pd_q3gunshot, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, 0, 0, pm_static, -1);
	ADD_PARTICLE_TYPE(p_q3teleport, pd_q3teleport, GL_SRC_ALPHA, GL_ONE, ptex_none, 255, 0, 0, pm_static, -1);

	qmb_initialized = true;
}

void QMB_ClearParticles (void)
{
	int	i;

	if (!qmb_initialized)
		return;

	particle_count = 0;
	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;
}

__inline static void AddParticle (part_type_t type, vec3_t org, int count, float size, float time, col_t col, vec3_t dir);

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

	particle_count = 0;
	frametime = fabs(cl.ctime - cl.oldtime);
	grav = sv_gravity.value / 800.0;

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

		if (pt->start)
		{
			p = pt->start;
			while (p && p->next)
			{
				kill = p->next;
				if (kill->die <= particle_time)
				{
					p->next = kill->next;
					kill->next = free_particles;
					free_particles = kill;
				}
				else
				{
					p = p->next;
				}
			}
			if (pt->start->die <= particle_time)
			{
				kill = pt->start;
				pt->start = kill->next;
				kill->next = free_particles;
				free_particles = kill;
			}
		}

		for (p = pt->start ; p ; p = p->next)
		{
			if (particle_time < p->start)
				continue;

			particle_count++;

			p->size += p->growth * frametime;

			if (p->size <= 0)
			{
				p->die = 0;
				continue;
			}

			switch (pt->id)
			{
			case p_q3blood:	// avoid alpha for q3blood
				p->color[3] = 255;
				break;

			case p_q3explosion:
			case p_q3flame:
			case p_q3gunshot:
				if (particle_time < (p->start + (p->die - p->start) / 2.0))
				{
					if (pt->id == p_q3gunshot && !p->texindex)
						p->color[3] = 255;
					else
						p->color[3] = pt->startalpha * ((particle_time - p->start) / (p->die - p->start) * 2);
				}
				else
				{
					p->color[3] = pt->startalpha * ((p->die - particle_time) / (p->die - p->start) * 2);
				}
				break;

			default:
				p->color[3] = pt->startalpha * ((p->die - particle_time) / (p->die - p->start));
				break;
			}

			p->rotangle += p->rotspeed * frametime;

			if (p->hit)
				continue;

			p->vel[2] += pt->grav * grav * frametime;

			VectorScale (p->vel, 1 + pt->accel * frametime, p->vel);

			switch (pt->move)
			{
			case pm_static:
			/*	VectorCopy (p->org, oldorg);
				VectorMA (p->org, frametime, p->vel, p->org);
				TraceLineN (oldorg, p->org, stop, normal);
				if (stop != p->org && VectorLength(stop) != 0)
				{
					vec3_t	tangent;

					VectorCopy (stop, p->org);
					VectorCopy (normal, p->vel);
					CrossProduct (normal, p->vel, tangent);
					if (pt->id == p_q3explosion && gl_decal_explosions.value)
						R_SpawnDecal (p->org, normal, tangent, decal_burn, 32);
					else if (pt->id == p_q3gunshot && gl_decal_bullets.value)
						R_SpawnDecal (p->org, normal, tangent, decal_mark, 10);
				}
				VectorCopy (oldorg, p->org);
				VectorClear (p->vel);*/
				break;

			case pm_normal:
				VectorCopy (p->org, oldorg);
				VectorMA (p->org, frametime, p->vel, p->org);
				if (CONTENTS_SOLID == TruePointContents(p->org))
				{
					p->hit = 1;
					if (pt->id == p_blood3 && gl_decal_blood.value)
					{
						TraceLineN (oldorg, p->org, stop, normal);
						if (stop != p->org && VectorLength(stop) != 0)
						{
							vec3_t	tangent;

							VectorCopy (stop, p->org);
							VectorCopy (normal, p->vel);
							CrossProduct (normal, p->vel, tangent);
							R_SpawnDecal (p->org, normal, tangent, decal_blood3, 12);
						}
					}
					VectorCopy (oldorg, p->org);
					VectorClear (p->vel);
				}
				break;

			case pm_float:
				VectorMA (p->org, frametime, p->vel, p->org);
				p->org[2] += p->size + 1;
				if (!ISUNDERWATER(TruePointContents(p->org)))
					p->die = 0;
				p->org[2] -= p->size + 1;
				break;

			case pm_nophysics:
				VectorMA (p->org, frametime, p->vel, p->org);
				break;

			case pm_die:
				VectorCopy (p->org, oldorg);
				VectorMA (p->org, frametime, p->vel, p->org);
				if (CONTENTS_SOLID == TruePointContents(p->org))
				{
					TraceLineN (oldorg, p->org, stop, normal);
					if (stop != p->org && VectorLength(stop) != 0)
					{
						vec3_t	tangent;

						VectorCopy (stop, p->org);
						VectorCopy (normal, p->vel);
						CrossProduct (normal, p->vel, tangent);
						if ((pt->id == p_fire || pt->id == p_dpfire) && gl_decal_explosions.value)
							R_SpawnDecal (p->org, normal, tangent, decal_burn, 32);
						else if (pt->id == p_blood1 && gl_decal_blood.value)
							R_SpawnDecal (p->org, normal, tangent, decal_blood1, 12);
						else if (pt->id == p_blood2 && gl_decal_blood.value)
							R_SpawnDecal (p->org, normal, tangent, decal_blood2, 12);
						else if (pt->id == p_q3blood_trail && gl_decal_blood.value)
							R_SpawnDecal (p->org, normal, tangent, decal_q3blood, 48);
					}
					VectorCopy (oldorg, p->org);
					VectorClear (p->vel);
					p->die = 0;
				}
				break;

			case pm_bounce:
				if (!gl_bounceparticles.value || p->bounces)
				{
					VectorMA (p->org, frametime, p->vel, p->org);
					if (CONTENTS_SOLID == TruePointContents(p->org))
						p->die = 0;
				}
				else
				{
					VectorCopy (p->org, oldorg);
					VectorMA (p->org, 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;

			case pm_streak:
				VectorCopy (p->org, oldorg);
				VectorMA (p->org, 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);
					}
				}
				AddParticle (p_streaktrail, oldorg, 1, p->size, 0.2, p->color, p->org);
				if (!VectorLength(p->vel))
					p->die = 0;
				break;

			case pm_streakwave:
				VectorCopy (p->org, oldorg);
				VectorMA (p->org, frametime, p->vel, p->org);
				AddParticle (p_streaktrail, oldorg, 1, p->size, 0.5, p->color, p->org);
				p->vel[0] = 19 * p->vel[0] / 20.0;
				p->vel[1] = 19 * p->vel[1] / 20.0;
				p->vel[2] = 19 * p->vel[2] / 20.0;
				break;

			default:
				assert (!"QMB_UpdateParticles: unexpected pt->move");
				break;
			}
		}
	}
}

#define DRAW_PARTICLE_BILLBOARD(_ptex, _p, _coord)		\
	glPushMatrix ();									\
	glTranslatef (_p->org[0], _p->org[1], _p->org[2]);	\
	glScalef (_p->size, _p->size, _p->size);			\
	if (_p->rotspeed || pt->id == p_q3rocketsmoke || pt->id == p_q3grenadesmoke)\
		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;
	vec3_t		v, up, right, billboard[4], velcoord[4], neworg;
	particle_t	*p;
	particle_type_t	*pt;
	particle_texture_t *ptex;

	if (!qmb_initialized)
		return;

	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_hide:
			break;

		case pd_beam:
			ptex = &particle_textures[pt->texture];
			GL_Bind (ptex->texnum);
			for (p = pt->start ; p ; p = p->next) 
			{
				float	varray_vertex[16];

				if (particle_time < p->start || particle_time >= p->die)
					continue;

				glColor4ubv (p->color);
				R_CalcBeamVerts (varray_vertex, p->org, p->endorg, p->size / 3.0);
				glBegin (GL_QUADS);
				glTexCoord2f (1, 0);
				glVertex3f (varray_vertex[0], varray_vertex[1], varray_vertex[2]);
				glTexCoord2f (1, 1);
				glVertex3f (varray_vertex[4], varray_vertex[5], varray_vertex[6]);
				glTexCoord2f (0, 1);
				glVertex3f (varray_vertex[8], varray_vertex[9], varray_vertex[10]);
				glTexCoord2f (0, 0);
				glVertex3f (varray_vertex[12], varray_vertex[13], varray_vertex[14]);
				glEnd ();
			}
			break;

		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 ();
			}
			glEnable (GL_TEXTURE_2D);
			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 ();
			}
			glEnable (GL_TEXTURE_2D);
			break;

		case pd_billboard:
			ptex = &particle_textures[pt->texture];
			GL_Bind (ptex->texnum);
			for (p = pt->start ; p ; p = p->next)
			{
				if (particle_time < p->start || particle_time >= p->die)
					continue;

				for (j = 0 ; j < cl.maxclients ; j++)
				{
					if (pt->custom != -1 && VectorSupCompare(p->org, cl_entities[1+j].origin, 40))
					{
						p->die = 0;
						continue;
					}
				}
				DRAW_PARTICLE_BILLBOARD(ptex, p, billboard);
			}
			break;

		case pd_billboard_vel:
			ptex = &particle_textures[pt->texture];
			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]);
				DRAW_PARTICLE_BILLBOARD(ptex, p, velcoord);
			}
			break;

		case pd_q3flame:
			ptex = &particle_textures[pt->texture];
			GL_Bind (ptex->texnum);
			for (p = pt->start ; p ; p = p->next)
			{
				float	varray_vertex[16];
				float	xhalf = p->size / 2.0, yhalf = p->size;
			//	vec3_t	org, v, end, normal;

				if (particle_time < p->start || particle_time >= p->die)
					continue;

				glDisable (GL_CULL_FACE);
				for (j=0 ; j<2 ; j++)
				{
					glPushMatrix ();
					glTranslatef (p->org[0], p->org[1], p->org[2]);
					glRotatef (!j ? 45 : -45, 0, 0, 1);
					glColor4ubv (p->color);

			// sigh. The best would be if the flames were always orthogonal to their surfaces
			// but I'm afraid it's impossible to get that work (w/o progs modification of course)
			/*	VectorCopy (cl_entities[cl.viewentity].origin, org);
				VectorSubtract (p->org, org, v);
				VectorScale (v, 2, v);
				VectorAdd (org, v, end);
				if (TraceLineN(org, end, end, normal))
				{
					varray_vertex[0] = normal[0] * xhalf;
					varray_vertex[1] = normal[1] * xhalf;
					varray_vertex[2] = -yhalf;
					varray_vertex[4] = normal[0] * xhalf;
					varray_vertex[5] = normal[1] * xhalf;
					varray_vertex[6] = yhalf;
					varray_vertex[8] = normal[0] * -xhalf;
					varray_vertex[9] = normal[1] * -xhalf;
					varray_vertex[10] = yhalf;
					varray_vertex[12] = normal[0] * -xhalf;
					varray_vertex[13] = normal[1] * -xhalf;
					varray_vertex[14] = -yhalf;
				}*/

					varray_vertex[0] = 0;
					varray_vertex[1] = xhalf;
					varray_vertex[2] = -yhalf;
					varray_vertex[4] = 0;
					varray_vertex[5] = xhalf;
					varray_vertex[6] = yhalf;
					varray_vertex[8] = 0;
					varray_vertex[9] = -xhalf;
					varray_vertex[10] = yhalf;
					varray_vertex[12] = 0;
					varray_vertex[13] = -xhalf;
					varray_vertex[14] = -yhalf;

					glBegin (GL_QUADS);
					glTexCoord2f (ptex->coords[p->texindex][0], ptex->coords[p->texindex][3]);
					glVertex3f (varray_vertex[0], varray_vertex[1], varray_vertex[2]);
					glTexCoord2f (ptex->coords[p->texindex][0], ptex->coords[p->texindex][1]);
					glVertex3f (varray_vertex[4], varray_vertex[5], varray_vertex[6]);
					glTexCoord2f (ptex->coords[p->texindex][2], ptex->coords[p->texindex][1]);
					glVertex3f (varray_vertex[8], varray_vertex[9], varray_vertex[10]);
					glTexCoord2f (ptex->coords[p->texindex][2], ptex->coords[p->texindex][3]);
					glVertex3f (varray_vertex[12], varray_vertex[13], varray_vertex[14]);
					glEnd ();

					glPopMatrix ();
				}
				glEnable (GL_CULL_FACE);
			}
			break;

		case pd_q3gunshot:
			for (p = pt->start ; p ; p = p->next)
				QMB_Q3Gunshot (p->org, (int)p->texindex, (float)p->color[3] / 255.0);
			break;

		case pd_q3teleport:
			for (p = pt->start ; p ; p = p->next)
				QMB_Q3Teleport (p->org, (float)p->color[3] / 255.0);
			break;

		default:
			assert (!"QMB_DrawParticles: unexpected drawtype");
			break;
		}
	}

	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);
	glShadeModel (GL_FLAT);
}

void d8to24col (col_t colourv, int colour)
{
	byte	*colourByte;

	colourByte = (byte *)&d_8to24table[colour];
	colourv[0] = colourByte[0];
	colourv[1] = colourByte[1];
	colourv[2] = colourByte[2];
}

#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, vec3_t org, int count, float size, float time, col_t col, vec3_t dir)
{
	int			i, j, k, colorMod = 0;
	float		tempSize, stage;
	byte		*color;
	col_t		colorMap;
	particle_t	*p;
	particle_type_t	*pt;
	static unsigned long q3blood_texindex = 0;

	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++)
	{
		if (colorMapped)
		{
			d8to24col (colorMap, colorStart + (colorMod++ % colorLength));
			color = colorMap;
		}
		else
		{
			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:
			VectorCopy (org, p->org);
			for (j=0 ; j<2 ; j++)
				p->vel[j] = (rand() & 31) - 16;
			p->vel[2] = (rand() & 63) + 24;
			break;

		case p_lavasplash:
		case p_streak:
		case p_streakwave:
		case p_shockwave:
		case p_q3teleport:
			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_q3explosion:
			p->texindex = 0;
			VectorCopy (org, p->org);
			VectorClear (p->vel);
			p->growth = 50;
			for (j=1 ; j<8 ; j++)
			{
				INIT_NEW_PARTICLE(pt, p, color, size, time);
				p->size = size + j * 2;
				p->start = cl.time + (j * time / 2.0);
				p->die = p->start + time;
				p->texindex = j;
				VectorCopy (org, p->org);
				VectorClear (p->vel);
				p->growth = 50;
			}
			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:
		case p_missilefire:
			VectorCopy (org, p->org);
			VectorClear (p->vel);
			break;

		case p_muzzleflash:
			VectorCopy (org, p->org);
			p->rotspeed = (rand() & 45) - 90;
			p->size = size * (rand() % 6) / 4;
			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;

		case p_q3blood:
			p->texindex = q3blood_texindex++ % 5;
			for (k=0 ; k<3 ; k++)
				p->org[k] = org[k] + (rand() & 15) - 8;
			VectorClear (p->vel);
			for (j=1 ; j<3 ; j++)
			{
				INIT_NEW_PARTICLE(pt, p, color, size, time);
				p->start = cl.time + (j * time);
				p->die = p->start + time;
				p->texindex = q3blood_texindex++ % 5;
				for (k=0 ; k<3 ; k++)
					p->org[k] = org[k] + (rand() & 15) - 8;
				VectorClear (p->vel);
			}
			break;

		case p_flame:
			VectorCopy (org, p->org);
			p->growth = -p->size / 2.0;
			VectorClear (p->vel);
			for (j=0 ; j<2 ; j++)
				p->vel[j] = (rand() % 6) - 3;
			break;

		case p_q3flame:
			stage = cl.time - floor(cl.time);
			if (stage >= 0 && stage < 0.125)
				p->texindex = 0;
			else if (stage >= 0.125 && stage < 0.25)
				p->texindex = 1;
			else if (stage >= 0.25 && stage < 0.375)
				p->texindex = 2;
			else if (stage >= 0.375 && stage < 0.5)
				p->texindex = 3;
			else if (stage >= 0.5 && stage < 0.625)
				p->texindex = 4;
			else if (stage >= 0.625 && stage < 0.75)
				p->texindex = 5;
			else if (stage >= 0.75 && stage < 0.875)
				p->texindex = 6;
			else
				p->texindex = 7;
			VectorCopy (org, p->org);
			VectorClear (p->vel);
			break;

		case p_q3gunshot:
			p->texindex = 0;	// used for animation here
			VectorCopy (org, p->org);
			for (j=1 ; j<3 ; j++)
			{
				INIT_NEW_PARTICLE(pt, p, color, size, time);
				p->start = cl.time + (j * time / 2.0);
				p->die = p->start + time;
				p->texindex = j + 1;
				VectorCopy (org, p->org);
			}
			break;

		case p_glow:
			VectorCopy (org, p->org);
			VectorCopy (dir, p->vel);	
			p->growth = -1.5;
			break;

		case p_streaktrail:
		case p_lightningbeam:
			VectorCopy (org, p->org);
			VectorCopy (dir, p->endorg);
			VectorClear (p->vel);
			p->growth = -p->size / time;
			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)
{
	int			i, j, num_particles;
	float		count, length;
	byte		*color;
	vec3_t		point, delta;
	particle_t	*p;
	particle_type_t	*pt;
	static float rotangle = 0;

	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_q3blood_trail:
	case p_q3rocketsmoke:
		count = length / 40.0;
		break;

	case p_q3grenadesmoke:
		count = length / 12.0;
		break;

	case p_trailpart:
	case p_lavatrail:
		count = length / 1.1;
		break;

	case p_blood3:
		count = length / 8.0;
		break;

	case p_bubble:
	case p_bubble2:
		count = length / 5.0;
		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_q3blood_trail:
			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->growth = 6;
			break;

		case p_bubble:
		case p_bubble2:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() % 10) - 5;
			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_q3rocketsmoke:
		case p_q3grenadesmoke:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->org[j] += ((rand() & 7) - 4) / 8.0;
			VectorClear (p->vel);
			p->growth = 30;
			if (rotangle >= 360)
				rotangle = 0;
			p->rotangle = rotangle;
			rotangle += 30;
			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;

		case p_lavatrail:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->org[j] += (rand() & 7) - 4;
			p->vel[0] = p->vel[1] = 0;
			p->vel[2] = rand() & 3;
			break;

		default:
			assert (!"AddParticleTrail: unexpected type");
			break;
		}

		VectorAdd (point, delta, point);
	}

done:
	VectorCopy (point, trail_stop);
}

void QMB_ParticleExplosion (vec3_t org)
{
	if (ISUNDERWATER(TruePointContents(org)))
	{
		AddParticle (p_bubble, org, 6, 3.0, 2.5, NULL, zerodir);
		AddParticle (p_bubble, org, 4, 2.35, 2.5, NULL, zerodir);
		if (gl_part_explosions.value == 2)
		{
			AddParticle (p_q3explosion, org, 1, 36, 0.2, NULL, zerodir);
		}
		else
		{
			AddParticle (p_fire, org, 12, 14, 0.8, 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
	{
		if (gl_part_explosions.value == 2)
		{
			AddParticle (p_q3explosion, org, 1, 36, 0.2, 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_ColorMappedExplosion (vec3_t org, int start, int length)
{
	colorMapped = true;
	colorStart = start, colorLength = length;
	QMB_ParticleExplosion (org);
	colorMapped = false;
}

void QMB_RunParticleEffect (vec3_t org, vec3_t dir, int col, int count)
{
	int		i, j, particlecount;
	vec3_t	neworg, newdir;
	col_t	color;

	if (col == 73)
	{
		if (gl_part_blood.value == 2)
		{
			AddParticle (p_q3blood, org, 1, 15, 0.18, NULL, zerodir);
		}
		else
		{
			int	bloodcount;

			bloodcount = Q_rint (count / 28);
			bloodcount = bound(2, bloodcount, 8);
			for (i = 0 ; i < bloodcount ; i++)
			{
				AddParticle (p_blood1, org, 1, 4.5, 2 + (rand() & 15) / 16, NULL, zerodir);
				AddParticle (p_blood2, org, 1, 4.5, 2 + (rand() & 15) / 16, NULL, zerodir);
			}
		}
		return;
	}
	else if (col == 225)
	{
		if (gl_part_blood.value == 2)
		{
			AddParticle (p_q3blood, org, 1, 15, 0.18, NULL, zerodir);
		}
		else
		{
			int	scale;

			scale = (count > 130) ? 3 : (count > 20) ? 2 : 1;
			scale *= 0.758;
			for (i = 0 ; i < (count >> 1) ; i++)
			{
				for (j = 0 ; j < 3 ; j++)
					neworg[j] = org[j] + scale * ((rand() & 15) - 8);
				AddParticle (p_blood3, neworg, 1, 1, 2.3, NULL, zerodir);
			}
		}
		return;
	}
	else if (col == 20 && count == 30)
	{
		color[0] = 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 9:
	case 10:
		if (nehahra && count == 10)	// ventillation's wind
		{
			for (i = 0 ; i < count ; i++)
			{
				for (j = 0 ; j < 3 ; j++)
				{
					neworg[j] = org[j] + ((rand() % 24) - 12);
					newdir[j] = dir[j] * (10 + (rand() % 5));
				}
				d8to24col (color, (col & ~7) + (rand() & 7));
				AddParticle (p_glow, neworg, 1, 3, 0.3 + 0.1 * (rand() % 3), color, newdir);
			}
		}
		else
		{
			if (gl_part_spikes.value == 2)
				AddParticle (p_q3gunshot, org, 1, 1, 0.3, NULL, zerodir);
			else
				AddParticle (p_spark, org, 6, 70, 0.6, NULL, zerodir);

			if (gl_decal_bullets.value)
				R_SpawnDecalStatic (org, decal_mark, 8);
		}
		break;

	case 20:
		if (gl_part_spikes.value == 2)
			AddParticle (p_q3gunshot, org, 1, 1, 0.3, NULL, zerodir);
		else
			AddParticle (p_spark, org, 12, 85, 0.6, NULL, zerodir);
		if (gl_decal_bullets.value)
			R_SpawnDecalStatic (org, decal_mark, 10);
		break;

	case 21:	// gunshot
		if (gl_part_gunshots.value == 2)
		{
			AddParticle (p_q3gunshot, org, 1, 1, 0.3, NULL, zerodir);
		}
		else
		{
			particlecount = count >> 1;
			AddParticle (p_gunblast, org, 15, 5, 0.15, NULL, zerodir);
			for (i = 0 ; i < particlecount ; i++)
			{
				for (j = 0 ; j < 3 ; j++)
					neworg[j] = org[j] + ((rand() & 15) - 8);
				AddParticle (p_smoke, neworg, 1, 4, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
				if ((i % particlecount) == 0)
					AddParticle (p_chunk, neworg, 1, 0.75, 3.75, NULL, zerodir);
			}
		}
		if (gl_decal_bullets.value)
			R_SpawnDecalStatic (org, decal_mark, 8);
		break;

	case 30:
		if (gl_part_spikes.value == 2)
		{
			AddParticle (p_q3gunshot, org, 1, 1, 0.3, NULL, zerodir);
		}
		else
		{
			AddParticle (p_chunk, org, 10, 1, 4, NULL, zerodir);
			AddParticle (p_spark, org, 8, 105, 0.9, NULL, zerodir);
		}
		break;

	default:
		if (hipnotic)
		{
			switch (count)
			{
			case 1:		// particlefields
			case 2:
			case 3:
			case 4:
				d8to24col (color, (col & ~7) + (rand() & 7));
				AddParticle (p_gunblast, org, 15, 5, 0.1 * (rand() % 5), color, zerodir);
				return;
			}
		}
		else if (nehahra)
		{
			switch (count)
			{
			case 25:	// slime barrel chunks
				return;

			case 244:	// sewer
				for (i=0 ; i < (count >> 3) ; i++)
				{
					for (j=0 ; j<3 ; j++)
						neworg[j] = org[j] + ((rand() % 24) - 12);
					newdir[0] = dir[0] * (10 + (rand() % 5));
					newdir[1] = dir[1] * (10 + (rand() % 5));
					newdir[2] = dir[2] * 15;
					d8to24col (color, (col & ~7) + (rand() & 7));
					AddParticle (p_glow, neworg, 1, 3.5, 0.5 + 0.1 * (rand() % 3), color, newdir);
				}
				return;
			}
		}

		particlecount = max(1, count >> 1);
		for (i = 0 ; i < particlecount ; i++)
		{
			for (j = 0 ; j < 3 ; j++)
			{
				neworg[j] = org[j] + ((rand() % 20) - 10);
				newdir[j] = dir[j] * (10 + (rand() % 5));
			}
			d8to24col (color, (col & ~7) + (rand() & 7));
			AddParticle (p_glow, neworg, 1, 3, 0.2 + 0.1 * (rand() % 4), color, newdir);
		}
		break;
	}
}

void QMB_RocketTrail (vec3_t start, vec3_t end, vec3_t *trail_origin, trail_type_t type)
{
	col_t	color;
	
	switch (type)
	{
	case GRENADE_TRAIL:
		if (ISUNDERWATER(TruePointContents(start)))
			AddParticleTrail (p_bubble, start, end, 1.8, 1.5, NULL);
		if (gl_part_trails.value == 2)
		{
			AddParticleTrail (p_q3grenadesmoke, start, end, 5, 0.75, NULL);
		}
		else
		{
			if (r_grenadetrail.value == 3)
			{
				AddParticleTrail (p_dpfire, start, end, 3, 0.26, NULL);
				AddParticleTrail (p_dpsmoke, start, end, 3, 0.825, NULL);
			}
			else if (r_grenadetrail.value == 2)	// QMB rocket
			{
				AddParticleTrail (p_trailpart, start, end, 6.2, 0.31, ColorForParticle(p_missilefire));
				AddParticleTrail (p_smoke, start, end, 1.8, 0.825, NULL);
			}
			else
			{
				AddParticleTrail (p_smoke, start, end, 1.45, 0.825, NULL);
			}
		}
		break;

	case BLOOD_TRAIL:
	case BIG_BLOOD_TRAIL:
		if (gl_part_trails.value == 2)
			AddParticleTrail (p_q3blood_trail, start, end, 15, 2, NULL);
		else
			AddParticleTrail (p_blood3, start, end, type == BLOOD_TRAIL ? 1.35 : 2.4, 2, NULL);
		break;

	case TRACER1_TRAIL:
		color[0] = color[2] = 0;
		color[1] = 124;
		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 LAVA_TRAIL:
		AddParticleTrail (p_lavatrail, start, end, 5, 1, NULL);
		break;

	case BUBBLE_TRAIL:
		if (ISUNDERWATER(TruePointContents(start)))
			AddParticleTrail (p_bubble2, start, end, 1.5, 0.825, NULL);
		break;

	case NEHAHRA_SMOKE:
		AddParticleTrail (p_smoke, start, end, 0.8, 0.825, NULL);
		break;

	case ROCKET_TRAIL:
	default:
		if (ISUNDERWATER(TruePointContents(start)))
			AddParticleTrail (p_bubble, start, end, 1.8, 1.5, NULL);
		if (gl_part_trails.value == 2)
		{
			AddParticleTrail (p_q3rocketsmoke, start, end, 5, 1.5, NULL);
		}
		else
		{
			if (r_rockettrail.value == 3)
			{
				AddParticleTrail (p_dpfire, start, end, 3, 0.26, NULL);
				AddParticleTrail (p_dpsmoke, start, end, 3, 0.825, NULL);
			}
			else if (r_rockettrail.value == 2)	// QMB grenade
			{
				AddParticleTrail (p_smoke, start, end, 1.45, 0.825, NULL);
			}
			else
			{
				AddParticleTrail (p_trailpart, start, end, 6.2, 0.31, ColorForParticle(p_missilefire));
				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] - 10;
		AddParticle (p_shockwave, neworg, 1, 4, 0.8, color, vel);
		neworg[2] = org[2] + 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];
		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;

	if (gl_part_telesplash.value == 2)
	{
		AddParticle (p_q3teleport, org, 1, 1, 1.0, NULL, zerodir);
		return;
	}

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

#define ONE_FRAME_ONLY	(0.0001)

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

void QMB_TorchFlame (vec3_t org, float size, float time)
{
	if (fabs(cl.ctime - cl.oldtime))
		AddParticle (p_flame, org, 1, size, time, NULL, zerodir);
}

void QMB_Q3TorchFlame (vec3_t org, float size)
{
	static double flametime = 0;

	if (flametime + 0.125 < cl.time || flametime >= cl.time)
		flametime = cl.time;
	else
		return;

	if (fabs(cl.ctime - cl.oldtime))
		AddParticle (p_q3flame, org, 1, size, 0.25, NULL, zerodir);
}

void QMB_MissileFire (vec3_t org)
{
	if (fabs(cl.ctime - cl.oldtime))
		AddParticle (p_missilefire, org, 1, 20, ONE_FRAME_ONLY, NULL, zerodir);
}

void QMB_ShamblerCharge (vec3_t org)
{
	int		i;
	float	time, len;
	vec3_t	pos, vec, dir;

	for (i = 0 ; i < 5 ; i++)
	{
		VectorClear (vec);
		VectorClear (dir);

		VectorCopy (org, pos);
		pos[0] += (rand() % 200) - 100;
		pos[1] += (rand() % 200) - 100;
		pos[2] += (rand() % 200) - 100;

		VectorSubtract (pos, org, vec);
		len = VectorLength (vec);
		VectorNormalize (vec);
		VectorMA (dir, -200, vec, dir);
		time = len / 200.0;

		AddParticle (p_streakwave, pos, 1, 3, time, NULL, dir);
	}
}

void R_CalcBeamVerts (float *vert, vec3_t org1, vec3_t org2, float width)
{
	vec3_t	right1, right2, diff, normal;

	VectorSubtract (org2, org1, normal);
	VectorNormalize (normal);

	// calculate 'right' vector for start
	VectorSubtract (r_origin, org1, diff);
	VectorNormalize (diff);
	CrossProduct (normal, diff, right1);

	// calculate 'right' vector for end
	VectorSubtract (r_origin, org2, diff);
	VectorNormalize (diff);
	CrossProduct (normal, diff, right2);

	vert[0] = org1[0] + width * right1[0];
	vert[1] = org1[1] + width * right1[1];
	vert[2] = org1[2] + width * right1[2];
	vert[4] = org1[0] - width * right1[0];
	vert[5] = org1[1] - width * right1[1];
	vert[6] = org1[2] - width * right1[2];
	vert[8] = org2[0] - width * right2[0];
	vert[9] = org2[1] - width * right2[1];
	vert[10] = org2[2] - width * right2[2];
	vert[12] = org2[0] + width * right2[0];
	vert[13] = org2[1] + width * right2[1];
	vert[14] = org2[2] + width * right2[2];
}

void QMB_LightningSplash (vec3_t org)
{
	int		i, j;
	vec3_t	neworg, angle;
	col_t	color = {200, 100, 100};

	VectorClear (angle);

	for (i = 0 ; i < 5 ; i++)
	{
		angle[2] = 0;
		for (j = 0 ; j < 5 ; j++)
		{
			AngleVectors (angle, NULL, NULL, neworg);
			VectorMA (org, 20, neworg, neworg);
			AddParticle (p_spark, org, 2, 85, 0.05, NULL, zerodir);
			AddParticle (p_spark, org, 2, 100, 0.1, color, neworg);
			angle[2] += 360 / 5;
		}
		angle[0] += 180 / 5;
	}
} 

void QMB_LightningBeam (vec3_t start, vec3_t end)
{
	float	frametime = fabs(cl.ctime - cl.oldtime);
	vec3_t	neworg;

	if (frametime)
	{
		AddParticle (p_lightningbeam, start, 1, 80, frametime * 2, NULL, end);

		if (TraceLineN(start, end, neworg, NULL))
		{
			QMB_LightningSplash (neworg);
			if (gl_decal_sparks.value)
				R_SpawnDecalStatic (neworg, decal_glow, 10);
		}
	}
}

void QMB_Q3Gunshot (vec3_t org, int skinnum, float alpha)
{
	vec3_t		neworg, normal, v, newend;
	entity_t	*ent;
	extern model_t *cl_q3gunshot_mod;

	if (!(ent = CL_NewTempEntity()))
		return;

	VectorCopy (org, ent->origin);
	ent->model = cl_q3gunshot_mod;

	VectorCopy (cl_entities[cl.viewentity].origin, neworg);
	VectorSubtract (ent->origin, neworg, v);
	VectorScale (v, 2, v);
	VectorAdd (neworg, v, newend);
	if (TraceLineN(neworg, newend, newend, normal))
		vectoangles (normal, ent->angles);

	ent->skinnum = skinnum;
	ent->transparency = alpha;
	ent->istransparent = true;

	R_DrawQ3Model (ent);
}

void QMB_Q3Teleport (vec3_t org, float alpha)
{
	entity_t	*ent;
	extern model_t *cl_q3teleport_mod;

	if (!(ent = CL_NewTempEntity()))
		return;

	VectorCopy (org, ent->origin);
	ent->model = cl_q3teleport_mod;
	ent->transparency = alpha;
	ent->istransparent = true;

	R_DrawQ3Model (ent);
}

void QMB_MuzzleFlash (vec3_t org)
{
	float	frametime = fabs(cl.ctime - cl.oldtime);

	if (frametime)
		AddParticle (p_muzzleflash, org, 1, 8, frametime * 2, NULL, zerodir);
}

void R_GetParticleMode (void)
{
	if (!gl_part_explosions.value && !gl_part_trails.value && !gl_part_spikes.value && 
	    !gl_part_gunshots.value && !gl_part_blood.value && !gl_part_telesplash.value && 
	    !gl_part_blobs.value && !gl_part_lavasplash.value && !gl_part_flames.value && 
	    !gl_part_lightning.value)
		particle_mode = pm_classic;
	else if (gl_part_explosions.value == 1 && gl_part_trails.value == 1 && gl_part_spikes.value == 1 && 
		gl_part_gunshots.value == 1 && gl_part_blood.value == 1 && gl_part_telesplash.value == 1 && 
		gl_part_blobs.value == 1 && gl_part_lavasplash.value == 1 && gl_part_flames.value == 1 && 
		gl_part_lightning.value == 1)
		particle_mode = pm_qmb;
	else if (gl_part_explosions.value == 2 && gl_part_trails.value == 2 && gl_part_spikes.value == 2 && 
		gl_part_gunshots.value == 2 && gl_part_blood.value == 2 && gl_part_telesplash.value == 2 && 
		gl_part_blobs.value == 2 && gl_part_lavasplash.value == 2 && gl_part_flames.value == 2 && 
		gl_part_lightning.value == 2)
		particle_mode = pm_quake3;
	else
		particle_mode = pm_mixed;
}

void R_SetParticleMode (part_mode_t val)
{
	particle_mode = val;

	Cvar_SetValue (&gl_part_explosions, particle_mode);
	Cvar_SetValue (&gl_part_trails, particle_mode);
	Cvar_SetValue (&gl_part_spikes, particle_mode);
	Cvar_SetValue (&gl_part_gunshots, particle_mode);
	Cvar_SetValue (&gl_part_blood, particle_mode);
	Cvar_SetValue (&gl_part_telesplash, particle_mode);
	Cvar_SetValue (&gl_part_blobs, particle_mode);
	Cvar_SetValue (&gl_part_lavasplash, particle_mode);
	Cvar_SetValue (&gl_part_flames, particle_mode);
	Cvar_SetValue (&gl_part_lightning, particle_mode);
}

char *R_NameForParticleMode (void)
{
	char *name;

	switch (particle_mode)
	{
	case pm_classic:
		name = "Classic";
		break;

	case pm_qmb:
		name = "QMB";
		break;

	case pm_quake3:
		name = "Quake3";
		break;

	case pm_mixed:
		name = "mixed";
		break;
	}

	return name;
}

/*
===============
R_ToggleParticles_f

function that toggles between classic and QMB particles
===============
*/
void R_ToggleParticles_f (void)
{
	if (cmd_source != src_command)
		return;

	if (particle_mode == pm_classic)
		R_SetParticleMode (pm_qmb);
	else if (particle_mode == pm_qmb)
		R_SetParticleMode (pm_quake3);
	else
		R_SetParticleMode (pm_classic);

	if (key_dest != key_menu)
		Con_Printf ("Using %s particles\n", R_NameForParticleMode());
}
