/*
Copyright (C) 1996-1997 Id Software, Inc.

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.

*/

/*
The aim of this particle system is to have as much as possible configurable.
Some parts still fail here, and are marked FIXME
Effects are flushed on new maps.
The engine has a few builtins.
*/

#include "quakedef.h"
#ifdef SWQUAKE
#include "r_local.h"
#endif
#ifdef RGLQUAKE
#include "glquake.h"//hack
#endif

#include "renderque.h"

#include "r_partset.h"

int pt_explosion,
	pt_pointfile,
	pt_entityparticles,
	pt_darkfield,
	pt_blob,
	pt_blood,
	pt_lightningblood,
	pt_gunshot,
	pt_wizspike,
	pt_knightspike,
	pt_spike,
	pt_superspike,
	pt_lavasplash,
	pt_teleportsplash,
	pt_blasterparticles,
	pt_superbullet,
	pt_bullet,
	pt_spark,
	pt_plasma,
	pt_smoke;

int pe_default,
	pe_size2,
	pe_size3;

int rt_blastertrail,
	rt_railtrail,
	rt_bubbletrail,
	rt_rocket,
	rt_grenade,
	rt_gib,
	rt_lightning1,
	rt_lightning2,
	rt_lightning3,
	pt_lightning1_end,
	pt_lightning2_end,
	pt_lightning3_end;

//triangle fan sparks use these.
static double sint[7] = {0.000000, 0.781832,  0.974928,  0.433884, -0.433884, -0.974928, -0.781832};
static double cost[7] = {1.000000, 0.623490, -0.222521, -0.900969, -0.900969, -0.222521,  0.623490};


#define crand() (rand()%32767/16383.5f-1)

void D_DrawParticleTrans (particle_t *pparticle);
void D_DrawSparkTrans (particle_t *pparticle, vec3_t src, vec3_t dest);

void P_ReadPointFile_f (void);

#define MAX_BEAMSEGS             2048   // default max # of beam segments
#define MAX_PARTICLES			32768	// default max # of particles at one
										//  time
#define MAX_DECALS				 4096	// this is going to be expensive
#define MAX_TRAILSTATES           512   // default max # of trailstates

//int		ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
//int		ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
//int		ramp3[8] = {0x6d, 0x6b, 6,	  5,    4,    3,    2,    1};

particle_t	*free_particles;
particle_t	*particles;	//contains the initial list of alloced particles.
int			r_numparticles;

beamseg_t   *free_beams;
beamseg_t   *beams;
int			r_numbeams;

clippeddecal_t	*free_decals;
clippeddecal_t	*decals;
int			r_numdecals;

trailstate_t *trailstates;
int			ts_cycle; // current cyclic index of trailstates
int			r_numtrailstates;

vec3_t			r_pright, r_pup, r_ppn;

extern cvar_t r_bouncysparks;
extern cvar_t r_part_rain;
extern cvar_t gl_part_explosionheart, gl_part_emp;
extern cvar_t r_bloodstains;

cvar_t r_particlesdesc = {"r_particlesdesc", "spikeset", NULL, CVAR_LATCH|CVAR_SEMICHEAT};

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

cvar_t r_rockettrail = {"r_rockettrail", "1"};
cvar_t r_grenadetrail = {"r_grenadetrail", "1"};

cvar_t r_particle_tracelimit = {"r_particle_tracelimit", "250"};

static float particletime;

typedef struct skytris_s {
	struct skytris_s *next;
	vec3_t org;
	vec3_t x;
	vec3_t y;
	float area;
	float nexttime;
	msurface_t *face;
} skytris_t;

//these could be deltas or absolutes depending on ramping mode.
typedef struct {
	vec3_t rgb;
	float alpha;
	float scale;
	float rotation;
} ramp_t;

typedef struct part_type_s {
	char name[MAX_QPATH];
	char texname[MAX_QPATH];
	vec3_t rgb;
	vec3_t rgbchange;
	vec3_t rgbrand;
	int colorindex;
	int colorrand;
	qboolean citracer;
	float rgbchangetime;
	vec3_t rgbrandsync;
	float scale, alpha;
	float alphachange;
	float die, randdie;
	float randomvel, veladd;
	float offsetspread;
	float offsetspreadvert;
	float randomvelvert;
	float randscale;

	float timelimit;

	enum {PT_NORMAL, PT_BEAM, PT_DECAL} type;
	enum {BM_MERGE, BM_ADD, BM_SUBTRACT} blendmode;

	float rotationstartmin, rotationstartrand;
	float rotationmin, rotationrand;

	float scaledelta;
	float count;
	int texturenum;
	int assoc;
	int cliptype;
	float clipcount;
	int emit;
	float emittime;
	float emitrand;
	float emitstart;

	float areaspread;
	float areaspreadvert;
	float scalefactor;
	float invscalefactor;

	float spawnparam1;
	float spawnparam2;
/*	float spawnparam3; */

	float offsetup; // make this into a vec3_t later with dir, possibly for mdls

	enum {SM_BOX, SM_CIRCLE, SM_BALL, SM_SPIRAL, SM_TRACER, SM_TELEBOX, SM_LAVASPLASH, SM_UNICIRCLE, SM_FIELD} spawnmode;	
	//box = even spread within the area
	//circle = around edge of a circle
	//ball = filled sphere
	//spiral = spiral trail
	//tracer = tracer trail
	//telebox = q1-style telebox
	//lavasplash = q1-style lavasplash
	//unicircle = uniform circle
	//field = synced field (brightfield, etc)

	float gravity;
	vec3_t friction;
	float clipbounce;
	int stains;

	enum {RAMP_NONE, RAMP_DELTA, RAMP_ABSOLUTE} rampmode;
	int rampindexes;
	ramp_t *ramp;

	int loaded;
	particle_t	*particles;
	clippeddecal_t *clippeddecals;
	beamseg_t *beams;
	skytris_t *skytris;

	unsigned int flags;
#define PT_VELOCITY	1
#define PT_FRICTION 2
#define PT_CHANGESCOLOUR 4
} part_type_t;
int numparticletypes;
part_type_t *part_type;

static part_type_t *P_GetParticleType(char *name)
{
	int i;
	part_type_t *ptype;
	for (i = 0; i < numparticletypes; i++)
	{
		ptype = &part_type[i];
		if (!strcmp(ptype->name, name))
			return ptype;
	}
	part_type = BZ_Realloc(part_type, sizeof(part_type_t)*(numparticletypes+1));
	ptype = &part_type[numparticletypes++];
	strcpy(ptype->name, name);
	ptype->assoc=-1;
	ptype->cliptype = -1;
	ptype->emit = -1;
/*
	Due to BZ_Realloc we can assume all of this anyway
	ptype->loaded = 0;
	ptype->ramp = NULL;
	ptype->particles = NULL;
	ptype->beams = NULL;
*/
	return ptype;
}

int P_AllocateParticleType(char *name)	//guarentees that the particle type exists, returning it's index.
{
	return P_GetParticleType(name) - part_type;
}

int P_ParticleTypeForName(char *name)
{
	int to;

	to = P_GetParticleType(name) - part_type;
	if (to < 0 || to >= numparticletypes)
	{
		return -1;
	}

	return to;
}

int P_FindParticleType(char *name)	//checks if particle description 'name' exists, returns -1 if not.
{
	int i;
	for (i = 0; i < numparticletypes; i++)
	{
		if (!strcmp(part_type[i].name, name))
			return i;
	}

	return -1;
}

qboolean P_DescriptionIsLoaded(char *name)
{
	int i = P_FindParticleType(name);
	part_type_t *ptype;
	if (i < 0)
		return false;
	ptype = &part_type[i];
	return ptype->loaded;
}

static void P_SetModified(void)	//called when the particle system changes (from console).
{
	if (Cmd_FromGamecode())
		return;	//server stuffed particle descriptions don't count.

	f_modified_particles = true;

	if (care_f_modified)
	{
		care_f_modified = false;
		Cbuf_AddText("say particles description has changed\n", RESTRICT_LOCAL);
	}
}
static int CheckAssosiation(char *name, int from)
{
	int to, orig;

	orig = to = P_FindParticleType(name);
	if (to < 0)
	{
		return -1;
	}

	while(to != -1)
	{
		if (to == from)
		{
			Con_Printf("Assosiation would cause infinate loop\n");
			return -1;
		}
		to = part_type[to].assoc;
	}
	return orig;
}

#ifdef RGLQUAKE
void P_LoadTexture(part_type_t *ptype, qboolean warn)
{
	if (strcmp(ptype->texname, "default"))
	{
		ptype->texturenum = Mod_LoadHiResTexture(ptype->texname, "particles", true, true, true);
	
		if (!ptype->texturenum)
		{
			if (warn)
				Con_DPrintf("Couldn't load texture %s for particle effect %s\n", ptype->texname, ptype->name);

			if (strstr(ptype->texname, "glow") || strstr(ptype->texname, "ball"))
				ptype->texturenum = balltexture;
			else
				ptype->texturenum = explosiontexture;
		}
	}
	else
		ptype->texturenum = explosiontexture;
}
#endif

//Uses FTE's multiline console stuff.
//This is the function that loads the effect descriptions (via console).
void P_ParticleEffect_f(void)
{
	char *var, *value;
	char *buf;
	particle_t *parts;
	beamseg_t *beamsegs;
	skytris_t *st;

	part_type_t *ptype;
	int pnum, assoc;

	if (Cmd_Argc()!=2)
	{
		Con_Printf("No name for particle effect\n");
		return;
	}

	buf = Cbuf_GetNext(Cmd_ExecLevel);
	while (*buf && *buf <= ' ')
		buf++;	//no whitespace please.
	if (*buf != '{')
	{
		Cbuf_InsertText(buf, Cmd_ExecLevel);
		Con_Printf("This is a multiline command and should be used within config files\n");
		return;
	}

	ptype = P_GetParticleType(Cmd_Argv(1));
	if (!ptype)
	{
		Con_Printf("Bad name\n");
		return;
	}

	P_SetModified();

	pnum = ptype-part_type;

	st = ptype->skytris;
	if (ptype->ramp)
		BZ_Free(ptype->ramp);

	while (ptype->particles) // empty particle list
	{
		parts = ptype->particles->next;
		ptype->particles->next = free_particles;
		free_particles = ptype->particles;
		ptype->particles = parts;
	}

	// go with a lazy clear of list.. mark everything as DEAD and let
	// the beam rendering handle removing nodes
	beamsegs = ptype->beams;
	while (beamsegs)
	{
		beamsegs->flags |= BS_DEAD;
		beamsegs = beamsegs->next;
	}

	beamsegs = ptype->beams;
	memset(ptype, 0, sizeof(*ptype));
//	ptype->particles = parts;
	ptype->beams = beamsegs;
	ptype->skytris = st;
	strcpy(ptype->name, Cmd_Argv(1));
	ptype->assoc=-1;
	ptype->cliptype = -1;
	ptype->emit = -1;
	ptype->alpha = 1;
	ptype->alphachange = 1;
	ptype->clipbounce = 0.8;
	ptype->colorindex = -1;
	ptype->rotationstartmin = -M_PI;	//start with a random angle
	ptype->rotationstartrand = M_PI-ptype->rotationstartmin;
	ptype->rotationmin = 0;				//but don't spin
	ptype->rotationrand = 0;

	while(1)
	{
		buf = Cbuf_GetNext(Cmd_ExecLevel);
		while (*buf && *buf <= ' ')
			buf++;	//no whitespace please.
		if (*buf == '}')
			break;
		if (!*buf)
		{
			Con_Printf("Unexpected End Of Buffer\n");
			return;
		}

		Cmd_TokenizeString(buf, true, true);
		var = Cmd_Argv(0);
		value = Cmd_Argv(1);

		if (!strcmp(var, "texture"))
			Q_strncpyz(ptype->texname, value, sizeof(ptype->texname));

		else if (!strcmp(var, "rotationstart"))
		{
			ptype->rotationstartmin = atof(value)*M_PI/180;
			if (Cmd_Argc()>2)
				ptype->rotationstartrand = atof(Cmd_Argv(2))*M_PI/180-ptype->rotationstartmin;
			else
				ptype->rotationstartrand = 0;
		}
		else if (!strcmp(var, "rotationspeed"))
		{
			ptype->rotationmin = atof(value)*M_PI/180;
			if (Cmd_Argc()>2)
				ptype->rotationrand = atof(Cmd_Argv(2))*M_PI/180-ptype->rotationmin;
			else
				ptype->rotationrand = 0;
		}

		else if (!strcmp(var, "scale"))
			ptype->scale = atof(value);
		else if (!strcmp(var, "scalerand"))
			ptype->randscale = atof(value);

		else if (!strcmp(var, "scalefactor"))
			ptype->scalefactor = atof(value);
		else if (!strcmp(var, "scaledelta"))
			ptype->scaledelta = atof(value);

		else if (!strcmp(var, "step"))
			ptype->count = 1/atof(value);
		else if (!strcmp(var, "count"))
			ptype->count = atof(value);

		else if (!strcmp(var, "alpha"))
			ptype->alpha = atof(value);
		else if (!strcmp(var, "alphachange"))
			ptype->alphachange = atof(value);
		else if (!strcmp(var, "die"))
			ptype->die = atof(value);
		else if (!strcmp(var, "diesubrand"))
			ptype->randdie = atof(value);

		else if (!strcmp(var, "randomvel"))
		{
			ptype->randomvel = atof(value);
			if (Cmd_Argc()>2)
				ptype->randomvelvert = atof(Cmd_Argv(2));
			else
				ptype->randomvelvert = ptype->randomvel;
		}
		else if (!strcmp(var, "veladd"))
			ptype->veladd = atof(value);
		else if (!strcmp(var, "friction"))
		{
			ptype->friction[2] = ptype->friction[1] = ptype->friction[0] = atof(value);

			if (Cmd_Argc()>3)
			{
				ptype->friction[2] = atof(Cmd_Argv(3));
				ptype->friction[1] = atof(Cmd_Argv(2));
			}
			else if (Cmd_Argc()>2)
			{
				ptype->friction[2] = atof(Cmd_Argv(2));
			}
		}
		else if (!strcmp(var, "gravity"))
			ptype->gravity = atof(value);
		else if (!strcmp(var, "clipbounce"))
			ptype->clipbounce = atof(value);

		else if (!strcmp(var, "assoc"))
		{
			assoc = CheckAssosiation(value, pnum);	//careful - this can realloc all the particle types
			ptype = &part_type[pnum];
			ptype->assoc = assoc;
		}

		else if (!strcmp(var, "colorindex"))
			ptype->colorindex = atoi(value);
		else if (!strcmp(var, "colorrand"))
			ptype->colorrand = atoi(value);
		else if (!strcmp(var, "citracer"))
			ptype->citracer = true; 

		else if (!strcmp(var, "red"))
			ptype->rgb[0] = atof(value)/255;
		else if (!strcmp(var, "green"))
			ptype->rgb[1] = atof(value)/255;
		else if (!strcmp(var, "blue"))
			ptype->rgb[2] = atof(value)/255;
		else if (!strcmp(var, "rgb"))
		{
			ptype->rgb[0] = ptype->rgb[1] = ptype->rgb[2] = atof(value)/255;
			if (Cmd_Argc()>3)
			{
				ptype->rgb[1] = atof(Cmd_Argv(2))/255;
				ptype->rgb[2] = atof(Cmd_Argv(3))/255;
			}
		}

		else if (!strcmp(var, "reddelta"))
		{
			ptype->rgbchange[0] = atof(value)/255;
			if (!ptype->rgbchangetime)
				ptype->rgbchangetime = ptype->die;
		}
		else if (!strcmp(var, "greendelta"))
		{
			ptype->rgbchange[1] = atof(value)/255;
			if (!ptype->rgbchangetime)
				ptype->rgbchangetime = ptype->die;
		}
		else if (!strcmp(var, "bluedelta"))
		{
			ptype->rgbchange[2] = atof(value)/255;
			if (!ptype->rgbchangetime)
				ptype->rgbchangetime = ptype->die;
		}
		else if (!strcmp(var, "rgbdelta"))
		{
			ptype->rgbchange[0] = ptype->rgbchange[1] = ptype->rgbchange[2] = atof(value)/255;
			if (Cmd_Argc()>3)
			{
				ptype->rgbchange[1] = atof(Cmd_Argv(2))/255;
				ptype->rgbchange[2] = atof(Cmd_Argv(3))/255;
			}
			if (!ptype->rgbchangetime)
				ptype->rgbchangetime = ptype->die;
		}
		else if (!strcmp(var, "rgbdeltatime"))
			ptype->rgbchangetime = atof(value);

		else if (!strcmp(var, "redrand"))
			ptype->rgbrand[0] = atof(value)/255;
		else if (!strcmp(var, "greenrand"))
			ptype->rgbrand[1] = atof(value)/255;
		else if (!strcmp(var, "bluerand"))
			ptype->rgbrand[2] = atof(value)/255;
		else if (!strcmp(var, "rgbrand"))
		{
			ptype->rgbrand[0] = ptype->rgbrand[1] = ptype->rgbrand[2] = atof(value)/255;
			if (Cmd_Argc()>3)
			{
				ptype->rgbrand[1] = atof(Cmd_Argv(2))/255;
				ptype->rgbrand[2] = atof(Cmd_Argv(3))/255;
			}
		}

		else if (!strcmp(var, "rgbrandsync"))
		{
			ptype->rgbrandsync[0] = ptype->rgbrandsync[1] = ptype->rgbrandsync[2] = atof(value);
			if (Cmd_Argc()>3)
			{
				ptype->rgbrandsync[1] = atof(Cmd_Argv(2));
				ptype->rgbrandsync[2] = atof(Cmd_Argv(3));
			}
		}
		else if (!strcmp(var, "redrandsync"))
			ptype->rgbrandsync[0] = atof(value);
		else if (!strcmp(var, "greenrandsync"))
			ptype->rgbrandsync[1] = atof(value);
		else if (!strcmp(var, "bluerandsync"))
			ptype->rgbrandsync[2] = atof(value);

		else if (!strcmp(var, "stains"))
			ptype->stains = atoi(value);
		else if (!strcmp(var, "blend"))
		{
			if (!strcmp(value, "add"))
				ptype->blendmode = BM_ADD;
			else if (!strcmp(value, "subtract"))
				ptype->blendmode = BM_SUBTRACT;
			else
				ptype->blendmode = BM_MERGE;
				
		}
		else if (!strcmp(var, "spawnmode"))
		{
			if (!strcmp(value, "circle"))
				ptype->spawnmode = SM_CIRCLE;
			else if (!strcmp(value, "ball"))
				ptype->spawnmode = SM_BALL;
			else if (!strcmp(value, "spiral"))
				ptype->spawnmode = SM_SPIRAL;
			else if (!strcmp(value, "tracer"))
				ptype->spawnmode = SM_TRACER;
			else if (!strcmp(value, "telebox"))
				ptype->spawnmode = SM_TELEBOX;
			else if (!strcmp(value, "lavasplash"))
				ptype->spawnmode = SM_LAVASPLASH;
			else if (!strcmp(value, "uniformcircle"))
				ptype->spawnmode = SM_UNICIRCLE;
			else if (!strcmp(value, "syncfield"))
				ptype->spawnmode = SM_FIELD;
			else
				ptype->spawnmode = SM_BOX;

		}
		else if (!strcmp(var, "type"))
		{
			if (!strcmp(value, "beam"))
				ptype->type = PT_BEAM;
			else if (!strcmp(value, "decal"))
				ptype->type = PT_DECAL;
			else
				ptype->type = PT_NORMAL;

		}
		else if (!strcmp(var, "isbeam"))
		{
			Con_DPrintf("isbeam is deprechiated, use type beam\n");
			ptype->type = PT_BEAM;
		}
		else if (!strcmp(var, "timelimit"))
			ptype->timelimit = atof(value);
		else if (!strcmp(var, "cliptype"))
		{
			assoc = P_ParticleTypeForName(value);//careful - this can realloc all the particle types
			ptype = &part_type[pnum];
			ptype->cliptype = assoc;
		}
		else if (!strcmp(var, "clipcount"))
			ptype->clipcount = atof(value);

		else if (!strcmp(var, "emit"))
		{
			assoc = P_ParticleTypeForName(value);//careful - this can realloc all the particle types
			ptype = &part_type[pnum];
			ptype->emit = assoc;
		}
		else if (!strcmp(var, "emitinterval"))
			ptype->emittime = atof(value);
		else if (!strcmp(var, "emitintervalrand"))
			ptype->emitrand = atof(value);
		else if (!strcmp(var, "emitstart"))
			ptype->emitstart = atof(value);

		// old names
		else if (!strcmp(var, "areaspread"))
		{
			Con_DPrintf("areaspread is deprechiated, use spawnorg\n");
			ptype->areaspread = atof(value);
		}
		else if (!strcmp(var, "areaspreadvert"))
		{
			Con_DPrintf("areaspreadvert is deprechiated, use spawnorg\n");
			ptype->areaspreadvert = atof(value);
		}
		else if (!strcmp(var, "offsetspread"))
		{
			Con_DPrintf("offsetspread is deprechiated, use spawnvel\n");
			ptype->offsetspread = atof(value);
		}
		else if (!strcmp(var, "offsetspreadvert"))
		{
			Con_DPrintf("offsetspreadvert is deprechiated, use spawnvel\n");
			ptype->offsetspreadvert  = atof(value);
		}

		// new names
		else if (!strcmp(var, "spawnorg"))
		{
			ptype->areaspreadvert = ptype->areaspread = atof(value);

			if (Cmd_Argc()>2)
				ptype->areaspreadvert = atof(Cmd_Argv(2));
		}
		else if (!strcmp(var, "spawnvel"))
		{
			ptype->offsetspreadvert = ptype->offsetspread = atof(value);

			if (Cmd_Argc()>2)
				ptype->offsetspreadvert = atof(Cmd_Argv(2));
		}

		// spawn mode param fields
		else if (!strcmp(var, "spawnparam1"))
			ptype->spawnparam1 = atof(value);
		else if (!strcmp(var, "spawnparam2"))
			ptype->spawnparam2 = atof(value);
/*		else if (!strcmp(var, "spawnparam3"))
			ptype->spawnparam3 = atof(value); */

		else if (!strcmp(var, "up"))
			ptype->offsetup = atof(value);
		else if (!strcmp(var, "rampmode"))
		{
			if (!strcmp(value, "none"))
				ptype->rampmode = RAMP_NONE;
			else if (!strcmp(value, "absolute"))
				ptype->rampmode = RAMP_ABSOLUTE;
			else //if (!strcmp(value, "delta"))
				ptype->rampmode = RAMP_DELTA;
		}		
		else if (!strcmp(var, "rampindexlist"))
		{ // better not use this with delta ramps...
			int cidx, i;

			i = 1;
			while (i <= Cmd_Argc())
			{
				ptype->ramp = BZ_Realloc(ptype->ramp, sizeof(ramp_t)*(ptype->rampindexes+1));

				cidx = atoi(Cmd_Argv(i));
				ptype->ramp[ptype->rampindexes].alpha = cidx > 255 ? 0.5 : 1;

				cidx = d_8to24rgbtable[cidx];
				ptype->ramp[ptype->rampindexes].rgb[0] = (cidx & 0xff) * (1/255.0);
				ptype->ramp[ptype->rampindexes].rgb[1] = (cidx >> 8 & 0xff) * (1/255.0);
				ptype->ramp[ptype->rampindexes].rgb[2] = (cidx >> 16 & 0xff) * (1/255.0);

				ptype->ramp[ptype->rampindexes].scale = ptype->scale;

				ptype->rampindexes++;
				i++;
			}
		}
		else if (!strcmp(var, "rampindex"))
		{
			int cidx;
			ptype->ramp = BZ_Realloc(ptype->ramp, sizeof(ramp_t)*(ptype->rampindexes+1));

			cidx = atoi(value);
			ptype->ramp[ptype->rampindexes].alpha = cidx > 255 ? 0.5 : 1;

			if (Cmd_Argc() > 2) // they gave alpha
				ptype->ramp[ptype->rampindexes].alpha *= atof(Cmd_Argv(2));

			cidx = d_8to24rgbtable[cidx];
			ptype->ramp[ptype->rampindexes].rgb[0] = (cidx & 0xff) * (1/255.0);
			ptype->ramp[ptype->rampindexes].rgb[1] = (cidx >> 8 & 0xff) * (1/255.0);
			ptype->ramp[ptype->rampindexes].rgb[2] = (cidx >> 16 & 0xff) * (1/255.0);

			if (Cmd_Argc() > 3) // they gave scale
				ptype->ramp[ptype->rampindexes].scale = atof(Cmd_Argv(3));
			else
				ptype->ramp[ptype->rampindexes].scale = ptype->scale;


			ptype->rampindexes++;
		}
		else if (!strcmp(var, "ramp"))
		{
			ptype->ramp = BZ_Realloc(ptype->ramp, sizeof(ramp_t)*(ptype->rampindexes+1));

			ptype->ramp[ptype->rampindexes].rgb[0] = atof(value)/255;
			if (Cmd_Argc()>3)	//seperate rgb
			{
				ptype->ramp[ptype->rampindexes].rgb[1] = atof(Cmd_Argv(2))/255;
				ptype->ramp[ptype->rampindexes].rgb[2] = atof(Cmd_Argv(3))/255;

				if (Cmd_Argc()>4)	//have we alpha and scale changes?
				{
					ptype->ramp[ptype->rampindexes].alpha = atof(Cmd_Argv(4));
					if (Cmd_Argc()>5)	//have we scale changes?
						ptype->ramp[ptype->rampindexes].scale = atof(Cmd_Argv(5));
					else
						ptype->ramp[ptype->rampindexes].scale = ptype->scaledelta;
				}
				else
				{
					ptype->ramp[ptype->rampindexes].alpha = ptype->alpha;
					ptype->ramp[ptype->rampindexes].scale = ptype->scaledelta;
				}
			}
			else	//they only gave one value
			{
				ptype->ramp[ptype->rampindexes].rgb[1] = ptype->ramp[ptype->rampindexes].rgb[0];
				ptype->ramp[ptype->rampindexes].rgb[2] = ptype->ramp[ptype->rampindexes].rgb[0];

				ptype->ramp[ptype->rampindexes].alpha = ptype->alpha;
				ptype->ramp[ptype->rampindexes].scale = ptype->scaledelta;
			}

			ptype->rampindexes++;
		}
		else
			Con_DPrintf("%s is not a recognised particle type field (in %s)\n", var, ptype->name);
	}
	ptype->invscalefactor = 1-ptype->scalefactor;
	ptype->loaded = 1;
	if (ptype->clipcount < 1)
		ptype->clipcount = 1;

	ptype->flags = 0;
	//if there is a chance that it moves
	if (ptype->randomvel || ptype->gravity || ptype->veladd || ptype->offsetspread || ptype->offsetspreadvert)
		ptype->flags |= PT_VELOCITY;
	//if it has friction
	if (ptype->friction)
		ptype->flags |= PT_FRICTION;

	if (ptype->rampmode && !ptype->ramp)
	{
		ptype->rampmode = RAMP_NONE;
		Con_Printf("Particle type %s has a ramp mode but no ramp\n", ptype->name);
	}
	else if (ptype->ramp && !ptype->rampmode)
	{
		Con_Printf("Particle type %s has a ramp but no ramp mode\n", ptype->name);
	}

#ifdef RGLQUAKE
	if (qrenderer == QR_OPENGL)
	{
		P_LoadTexture(ptype, true);
	}
#endif
}

//assosiate a point effect with a model.
//the effect will be spawned every frame with count*frametime
//has the capability to hide models.
void P_AssosiateEffect_f (void)
{
	char *modelname = Cmd_Argv(1);
	char *effectname = Cmd_Argv(2);
	int effectnum;
	model_t *model;

	if (!cls.demoplayback && (
		strstr(modelname, "player") || 
		strstr(modelname, "eyes") || 
		strstr(modelname, "flag") ||
		strstr(modelname, "tf_stan") ||
		strstr(modelname, ".bsp") ||
		strstr(modelname, "turr")))
	{
		Con_Printf("Sorry: Not allowed to attach effects to model \"%s\"\n", modelname);
		return;
	}

	model = Mod_FindName(modelname);
	if (!cls.demoplayback && (model->flags & EF_ROTATE))
	{
		Con_Printf("Sorry: You may not assosiate effects with item model \"%s\"\n", modelname);
		return;
	}
	effectnum = P_AllocateParticleType(effectname);
	model->particleeffect = effectnum;
	model->particleengulphs = atoi(Cmd_Argv(3));

	P_SetModified();	//make it appear in f_modified.
}

//assosiate a particle trail with a model.
//the effect will be spawned between two points when an entity with the model moves.
void P_AssosiateTrail_f (void)
{
	char *modelname = Cmd_Argv(1);
	char *effectname = Cmd_Argv(2);
	int effectnum;
	model_t *model;
	
	if (!cls.demoplayback && (
		strstr(modelname, "player") ||
		strstr(modelname, "eyes") ||
		strstr(modelname, "flag") ||
		strstr(modelname, "tf_stan")))
	{
		Con_Printf("Sorry, you can't assosiate trails with model \"%s\"\n", modelname);
		return;
	}

	model = Mod_FindName(modelname);
	effectnum = P_AllocateParticleType(effectname);
	model->particletrail = effectnum;
	model->nodefaulttrail = true;	//we could have assigned the trail to a model that wasn't loaded.

	P_SetModified();	//make it appear in f_modified.
}

void P_DefaultTrail (model_t *model)
{
	if (model->nodefaulttrail == true)
		return;

	if (model->flags & EF_ROCKET)
	{
		switch((int)r_rockettrail.value)
		{
		case 0:
			model->particletrail = P_AllocateParticleType("t_null");
			break;
		case 1:
		default:
			model->particletrail = rt_rocket;//q2 models do this without flags.
			break;
		case 2:
			model->particletrail = rt_grenade;
			break;
		case 3:
			model->particletrail = P_AllocateParticleType("t_altrocket");
			break;
		case 4:
			model->particletrail = rt_gib;
			break;
		case 5:
			model->particletrail = P_AllocateParticleType("t_zomgib");
			break;
		case 6:
			model->particletrail = P_AllocateParticleType("t_tracer");
			break;
		case 7:
			model->particletrail = P_AllocateParticleType("t_tracer2");
			break;
		case 8:
			model->particletrail = P_AllocateParticleType("t_tracer3");
			break;
		}
	}
	else if (model->flags & EF_GRENADE)
	{
		if (r_grenadetrail.value)
			model->particletrail = rt_grenade;
		else
			model->particletrail = P_AllocateParticleType("t_null");
	}
	else if (model->flags & EF_GIB)
		model->particletrail = rt_gib;
	else if (model->flags & EF_TRACER)
		model->particletrail = P_AllocateParticleType("t_tracer");
	else if (model->flags & EF_ZOMGIB)
		model->particletrail = P_AllocateParticleType("t_zomgib");
	else if (model->flags & EF_TRACER2)
		model->particletrail = P_AllocateParticleType("t_tracer2");
	else if (model->flags & EF_TRACER3)
		model->particletrail = P_AllocateParticleType("t_tracer3");

	else if (model->flags & EF_BLOODSHOT)	//these are the hexen2 ones.
		model->particletrail = P_AllocateParticleType("t_bloodshot");
	else if (model->flags & EF_FIREBALL)
		model->particletrail = P_AllocateParticleType("t_fireball");
	else if (model->flags & EF_ACIDBALL)
		model->particletrail = P_AllocateParticleType("t_acidball");
	else if (model->flags & EF_ICE)
		model->particletrail = P_AllocateParticleType("t_ice");
	else if (model->flags & EF_SPIT)
		model->particletrail = P_AllocateParticleType("t_spit");
	else if (model->flags & EF_SPELL)
		model->particletrail = P_AllocateParticleType("t_spell");
	else if (model->flags & EF_VORP_MISSILE)
		model->particletrail = P_AllocateParticleType("t_vorpmissile");
	else if (model->flags & EF_SET_STAFF)
		model->particletrail = P_AllocateParticleType("t_setstaff");
	else if (model->flags & EF_MAGICMISSILE)
		model->particletrail = P_AllocateParticleType("t_magicmissile");
	else if (model->flags & EF_BONESHARD)
		model->particletrail = P_AllocateParticleType("t_boneshard");
	else if (model->flags & EF_SCARAB)
		model->particletrail = P_AllocateParticleType("t_scarab");
	else
		model->particletrail = -1;
}

#if _DEBUG
// R_BeamInfo_f - debug junk
void P_BeamInfo_f (void)
{
	beamseg_t *bs;
	int i, j, k, l, m;

	i = 0;

	for (bs = free_beams; bs; bs = bs->next)
		i++;

	Con_Printf("%i free beams\n", i);

	for (i = 0; i < numparticletypes; i++)
	{
		m = l = k = j = 0;
		for (bs = part_type[i].beams; bs; bs = bs->next)
		{
			if (!bs->p)
				k++;

			if (bs->flags & BS_DEAD)
				l++;

			if (bs->flags & BS_LASTSEG)
				m++;

			j++;
		}

		if (j)
			Con_Printf("Type %i = %i NULL p, %i DEAD, %i LASTSEG, %i total\n", i, k, l, m, j);
	}
}

void P_PartInfo_f (void)
{
	particle_t *p;

	int i, j;

	i = 0;

	for (p = free_particles; p; p = p->next)
		i++;

	Con_Printf("%i free particles\n", i);

	for (i = 0; i < numparticletypes; i++)
	{
		j = 0;
		for (p = part_type[i].particles; p; p = p->next)
			j++;

		if (j)
			Con_Printf("Type %s = %i total\n", part_type[i].name, j);
	}
}
#endif

/*
===============
R_InitParticles
===============
*/
void P_InitParticles (void)
{
	char *particlecvargroupname = "Particle effects";
	int		i;

	if (r_numparticles)	//already inited
		return;

	i = COM_CheckParm ("-particles");

	if (i)
	{
		r_numparticles = (int)(Q_atoi(com_argv[i+1]));
	}
	else
	{
		r_numparticles = MAX_PARTICLES;
	}

	r_numbeams = MAX_BEAMSEGS;

	r_numdecals = MAX_DECALS;

	r_numtrailstates = MAX_TRAILSTATES;

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

	beams = (beamseg_t *)
			Hunk_AllocName (r_numbeams * sizeof(beamseg_t), "beamsegs");

	decals = (clippeddecal_t *)
			Hunk_AllocName (r_numdecals * sizeof(clippeddecal_t), "decals");

	trailstates = (trailstate_t *)
			Hunk_AllocName (r_numtrailstates * sizeof(trailstate_t), "trailstates");
	ts_cycle = 0;
	
	Cmd_AddCommand("pointfile", P_ReadPointFile_f);	//load the leak info produced from qbsp into the particle system to show a line. :)

	Cmd_AddCommand("r_part", P_ParticleEffect_f);
	Cmd_AddCommand("r_effect", P_AssosiateEffect_f);
	Cmd_AddCommand("r_trail", P_AssosiateTrail_f);

#if _DEBUG
	Cmd_AddCommand("r_partinfo", P_PartInfo_f);
	Cmd_AddCommand("r_beaminfo", P_BeamInfo_f);
#endif

	//particles
	Cvar_Register(&r_particlesdesc, particlecvargroupname);
	Cvar_Register(&r_bouncysparks, particlecvargroupname);
	Cvar_Register(&r_part_rain, particlecvargroupname);

	Cvar_Register(&r_part_rain_quantity, particlecvargroupname);

	Cvar_Register(&r_particle_tracelimit, particlecvargroupname);

	Cvar_Register(&r_rockettrail, particlecvargroupname);
	Cvar_Register(&r_grenadetrail, particlecvargroupname);

	pt_explosion		= P_AllocateParticleType("te_explosion");
	pt_pointfile		= P_AllocateParticleType("pe_pointfile");
	pt_entityparticles	= P_AllocateParticleType("ef_entityparticles");
	pt_darkfield		= P_AllocateParticleType("ef_darkfield");
	pt_blob				= P_AllocateParticleType("te_blob");

	pt_blood			= P_AllocateParticleType("te_blood");
	pt_lightningblood	= P_AllocateParticleType("te_lightningblood");
	pt_gunshot			= P_AllocateParticleType("te_gunshot");
	pt_lavasplash		= P_AllocateParticleType("te_lavasplash");
	pt_teleportsplash	= P_AllocateParticleType("te_teleportsplash");
	pt_superbullet		= P_AllocateParticleType("te_superbullet");
	pt_bullet			= P_AllocateParticleType("te_bullet");
	pt_blasterparticles = P_AllocateParticleType("te_blasterparticles");
	pt_wizspike			= P_AllocateParticleType("te_wizspike");
	pt_knightspike		= P_AllocateParticleType("te_knightspike");
	pt_spike			= P_AllocateParticleType("te_spike");
	pt_superspike		= P_AllocateParticleType("te_superspike");

	rt_railtrail		= P_AllocateParticleType("te_railtrail");
	rt_bubbletrail		= P_AllocateParticleType("te_bubbletrail");
	rt_blastertrail		= P_AllocateParticleType("t_blastertrail");
	rt_rocket			= P_AllocateParticleType("t_rocket");
	rt_grenade			= P_AllocateParticleType("t_grenade");
	rt_gib				= P_AllocateParticleType("t_gib");

	rt_lightning1		= P_AllocateParticleType("te_lightning1");
	rt_lightning2		= P_AllocateParticleType("te_lightning2");
	rt_lightning3		= P_AllocateParticleType("te_lightning3");

	pt_lightning1_end	= P_AllocateParticleType("te_lightning1_end");
	pt_lightning2_end	= P_AllocateParticleType("te_lightning2_end");
	pt_lightning3_end	= P_AllocateParticleType("te_lightning3_end");

	pt_spark			= P_AllocateParticleType("te_spark");
	pt_plasma			= P_AllocateParticleType("te_plasma");
	pt_smoke			= P_AllocateParticleType("te_smoke");

	pe_default			= P_AllocateParticleType("pe_default");
	pe_size2			= P_AllocateParticleType("pe_size2");
	pe_size3			= P_AllocateParticleType("pe_size3");
}


/*
===============
P_ClearParticles
===============
*/
void P_ClearParticles (void)
{
	int		i;
	
	free_particles = &particles[0];
	for (i=0 ;i<r_numparticles ; i++)
		particles[i].next = &particles[i+1];
	particles[r_numparticles-1].next = NULL;

	free_decals = &decals[0];
	for (i=0 ;i<r_numdecals ; i++)
		decals[i].next = &decals[i+1];
	decals[r_numdecals-1].next = NULL;

	free_beams = &beams[0];
	for (i=0 ;i<r_numbeams ; i++)
	{
		beams[i].p = NULL;
		beams[i].flags = BS_DEAD;
		beams[i].next = &beams[i+1];
	}
	beams[r_numbeams-1].next = NULL;

	particletime = cl.time;

#ifdef RGLQUAKE
	if (qrenderer == QR_OPENGL)
	{
		for (i = 0; i < numparticletypes; i++)
		{
			P_LoadTexture(&part_type[i], false);
		}
	}
#endif

	for (i = 0; i < numparticletypes; i++)
	{
		part_type[i].clippeddecals = NULL;
		part_type[i].particles = NULL;
		part_type[i].beams = NULL;
		part_type[i].skytris = NULL;
	}
}

void P_NewServer(void)
{
	extern model_t	mod_known[];
	extern int		mod_numknown;

	model_t *mod;
	int i;

	for (i = 0; i < numparticletypes; i++)
	{
		*part_type[i].texname = '\0';
		part_type[i].scale = 0;
		part_type[i].loaded = 0;
		if (part_type->ramp)
			BZ_Free(part_type->ramp);
		part_type->ramp = NULL;
	}



	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
	{
		mod->particleeffect = -1;
		mod->particletrail = -1;
		mod->nodefaulttrail = false;

		P_DefaultTrail(mod);
	}

	f_modified_particles = false;

	//particle descriptions submitted by the server are deemed to not be cheats but game configs.
	if (!stricmp(r_particlesdesc.string, "none"))
		return;
	else if (!stricmp(r_particlesdesc.string, "faithful") || !*r_particlesdesc.string)
		Cbuf_AddText(particle_set_faithful, RESTRICT_SERVER);
	else if (!stricmp(r_particlesdesc.string, "spikeset"))
		Cbuf_AddText(particle_set_spikeset, RESTRICT_SERVER);
	else if (!stricmp(r_particlesdesc.string, "highfps"))
		Cbuf_AddText(particle_set_highfps, RESTRICT_SERVER);
	else
	{
		char *file = COM_LoadMallocFile(va("particles/%s.cfg", r_particlesdesc.string));
		if (!file)
			file = COM_LoadMallocFile(va("%s.cfg", r_particlesdesc.string));
		if (file)
		{
			Cbuf_AddText(file, RESTRICT_LOCAL);
			Cbuf_AddText("\n", RESTRICT_LOCAL);	//I'm paranoid.
			BZ_Free(file);
		}
		else
		{
			Con_Printf("Couldn't find particle description, using spikeset\n");
			Cbuf_AddText(particle_set_spikeset, RESTRICT_SERVER);
		}
/*#if defined(_DEBUG) && defined(WIN32)	//expand the particles cfg into a C style quoted string, and copy to clipboard so I can paste it in.
		{
			char *TL_ExpandToCString(char *in);
			extern HWND mainwindow;
			char *file = COM_LoadTempFile(va("%s.cfg", r_particlesdesc.string));
			char *lptstrCopy, *buf, temp;
			int len;
			HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, 
				com_filesize*2); 
			lptstrCopy = GlobalLock(hglbCopy); 
			while(file && *file)
			{
				len = strlen(file)+1;
				if (len > 1024)
					len = 1024;
				temp = file[len-1];
				file[len-1] = '\0';
				buf = TL_ExpandToCString(file);
				file[len-1] = temp;
				len-=1;
				com_filesize -= len;
				file+=len;

				len = strlen(buf);
				memcpy(lptstrCopy, buf, len);
				lptstrCopy+=len;
			}
			*lptstrCopy = '\0';
			GlobalUnlock(hglbCopy); 

			if (!OpenClipboard(mainwindow)) 
				return; 
			EmptyClipboard();

			SetClipboardData(CF_TEXT, hglbCopy); 
			CloseClipboard();
		}
#endif*/
	}
}

void P_ReadPointFile_f (void)
{
	FILE	*f;
	vec3_t	org;
	int		r;
	int		c;
	char	name[MAX_OSPATH];
	
	COM_StripExtension(cl.worldmodel->name, name);
	strcat(name, ".pts");

	COM_FOpenFile (name, &f);
	if (!f)
	{
		Con_Printf ("couldn't open %s\n", name);
		return;
	}

	P_ClearParticles();	//so overflows arn't as bad.
	
	Con_Printf ("Reading %s...\n", name);
	c = 0;
	for ( ;; )
	{
		r = fscanf (f,"%f %f %f\n", &org[0], &org[1], &org[2]);
		if (r != 3)
			break;
		c++;

		if (c%8)
			continue;
		
		if (!free_particles)
		{
			Con_Printf ("Not enough free particles\n");
			break;
		}
		P_RunParticleEffectType(org, NULL, 1, pt_pointfile);
	}

	fclose (f);
	Con_Printf ("%i points read\n", c);
}

void P_AddRainParticles(void)
{
	float x;
	float y;
	static float skipped;
	static float lastrendered;
	int ptype;

	vec3_t org, vdist;

	skytris_t *st;

	if (!r_part_rain.value || !r_part_rain_quantity.value)
	{
		skipped = true;
		return;
	}

	if (lastrendered < particletime - 0.5)
		skipped = true;	//we've gone for half a sec without any new rain. This would cause some strange effects, so reset times.

	if (skipped)
	{
		for (ptype = 0; ptype<numparticletypes; ptype++)
		{
			for (st = part_type[ptype].skytris; st; st = st->next)
			{
				st->nexttime = particletime;
			}
		}
	}
	skipped = false;

	lastrendered = particletime;
/*
{
	int i;

glDisable(GL_TEXTURE_2D);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glDisable(GL_DEPTH_TEST);
glBegin(GL_TRIANGLES);

	st = skytris;
	for (i = 0; i < r_part_rain_quantity.value; i++)
		st = st->next;
		glVertex3f(st->org[0], st->org[1], st->org[2]);
		glVertex3f(st->org[0]+st->x[0], st->org[1]+st->x[1], st->org[2]+st->x[2]);
		glVertex3f(st->org[0]+st->y[0], st->org[1]+st->y[1], st->org[2]+st->y[2]);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBegin(GL_POINTS);
		for (i = 0; i < 1000; i++)
		{
			x = frandom()*frandom();
			y = frandom() * (1-x);
			VectorMA(st->org, x, st->x, org);
			VectorMA(org, y, st->y, org);

			glVertex3f(org[0], org[1], org[2]);
		}
glEnd();
glEnable(GL_DEPTH_TEST);
}
*/
	for (ptype = 0; ptype<numparticletypes; ptype++)
	{
		if (!part_type[ptype].loaded)	//woo, batch skipping.
			continue;

		for (st = part_type[ptype].skytris; st; st = st->next)
		{
	//		if (st->face->visframe != r_framecount)
	//			continue;

			if (st->face->visframe != r_framecount)
			{
				st->nexttime = particletime;
				continue;
			}

			while (st->nexttime < particletime)
			{
				if (!free_particles)
					return;

				st->nexttime += 10000/(st->area*r_part_rain_quantity.value);

				x = frandom()*frandom();
				y = frandom() * (1-x);
				VectorMA(st->org, x, st->x, org);
				VectorMA(org, y, st->y, org);


				VectorSubtract(org, r_refdef.vieworg, vdist);

				if (Length(vdist) > (1024+512)*frandom())
					continue;

				VectorMA(org, 0.5, st->face->normal, org);				
				if (!(cl.worldmodel->hulls->funcs.HullPointContents(cl.worldmodel->hulls, org) & FTECONTENTS_SOLID))
				{
					if (st->face->flags & SURF_PLANEBACK)
					{
						vdist[0] = -st->face->plane->normal[0];
						vdist[1] = -st->face->plane->normal[1];
						vdist[2] = -st->face->plane->normal[2];
						P_RunParticleEffectType(org, vdist, 1, ptype);
					}
					else
						P_RunParticleEffectType(org, st->face->plane->normal, 1, ptype);
				}
			}
		}
	}
}


void R_Part_SkyTri(float *v1, float *v2, float *v3, msurface_t *surf)
{
	float dot;
	float xm;
	float ym;
	float theta;
	vec3_t xd;
	vec3_t yd;

	skytris_t *st;

	st = Hunk_Alloc(sizeof(skytris_t));
	st->next = part_type[surf->texinfo->texture->parttype].skytris;
	VectorCopy(v1, st->org);
	VectorSubtract(v2, st->org, st->x);
	VectorSubtract(v3, st->org, st->y);

	VectorCopy(st->x, xd);
	VectorCopy(st->y, yd);
/*
	xd[2] = 0;	//prevent area from being valid on vertical surfaces
	yd[2] = 0;
*/
	xm = Length(xd);
	ym = Length(yd);

	dot = DotProduct(xd, yd);
	theta = acos(dot/(xm*ym));
	st->area = sin(theta)*xm*ym;
	st->nexttime = particletime;
	st->face = surf;

	if (st->area<=0)
		return;//bummer.

	part_type[surf->texinfo->texture->parttype].skytris = st;
}



void P_EmitSkyEffectTris(model_t *mod, msurface_t 	*fa)
{
	vec3_t		verts[64];
	int v1;
	int v2;
	int v3;
	int numverts;
	int i, lindex;
	float *vec;

	//
	// convert edges back to a normal polygon
	//
	numverts = 0;
	for (i=0 ; i<fa->numedges ; i++)
	{
		lindex = mod->surfedges[fa->firstedge + i];

		if (lindex > 0)
			vec = mod->vertexes[mod->edges[lindex].v[0]].position;
		else
			vec = mod->vertexes[mod->edges[-lindex].v[1]].position;
		VectorCopy (vec, verts[numverts]);
		numverts++;

		if (numverts>=64)
		{
			Con_Printf("Too many verts on sky surface\n");
			return;
		}
	}

	v1 = 0;
	v2 = 1;
	for (v3 = 2; v3 < numverts; v3++)
	{
		R_Part_SkyTri(verts[v1], verts[v2], verts[v3], fa);

		v2 = v3;
	}
}

//FIXME!
void P_DarkFieldParticles (float *org, qbyte colour)
{
	int			i, j, k;
	vec3_t		dir, norg;

	for (i=-16 ; i<16 ; i+=8)
		for (j=-16 ; j<16 ; j+=8)
			for (k=0 ; k<32 ; k+=8)
			{
				if (!free_particles)
					return;

				norg[0] = org[0] + i + (rand()&3);
				norg[1] = org[1] + j + (rand()&3);
				norg[2] = org[2] + k + (rand()&3);

				dir[0] = j;
				dir[1] = i;
				dir[2] = k;

				P_RunParticleEffectType(norg, dir, 1, pt_darkfield);
			}
}

/*
===============
R_EntityParticles
===============
*/

#define NUMVERTEXNORMALS	162
float	r_avertexnormals[NUMVERTEXNORMALS][3] = {
#include "anorms.h"
};
vec2_t	avelocities[NUMVERTEXNORMALS];
#define BEAMLENGTH 16
// vec3_t	avelocity = {23, 7, 3};
// float	partstep = 0.01;
// float	timescale = 0.01;

void P_EntityParticles (float *org, qbyte colour, float *radius)
{
	part_type[pt_entityparticles].areaspread = radius[0]*0.5 + radius[1]*0.5;
	part_type[pt_entityparticles].areaspreadvert = radius[2];
	part_type[pt_entityparticles].colorindex = colour;

	P_RunParticleEffectType(org, NULL, 1, pt_entityparticles);
}

/*
===============
R_ParticleExplosion

===============
*/
void P_ParticleExplosion (vec3_t org)
{
//	int			i, j;
//	particle_t	*p;

	P_RunParticleEffectType(org, NULL, 1, pt_explosion);

	R_AddStain(org, -1, -1, -1, 100);
/*
	for (i=0 ; i<r_particles_in_explosion.value ; i++)
	{
		if (!free_particles)
			return;
		if ((rand()&3)==3)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_sparks;
			active_sparks = p;

			p->scale = 1;
			p->alpha = 0.8 + (rand()&15)/(15.0*5);
			p->die = particletime + 7;
			p->color = ramp1[0];
			p->ramp = rand()&3;
			if (i & 1)
			{
				p->type = st_shrapnal;
				for (j=0 ; j<3 ; j++)
				{
					p->org[j] = org[j];// + ((rand()%32)-16);
					p->vel[j] = ((rand()%512)-256)*r_particle_explosion_speed.value;
				}
			}
			else
			{
				p->type = pt_grav;
				for (j=0 ; j<3 ; j++)
				{
					p->org[j] = org[j];// + ((rand()%32)-16);
					p->vel[j] = ((rand()%512)-256)*r_particle_explosion_speed.value;
				}
			}
		}
		else
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_sparks;
			active_sparks = p;

			p->scale = 1;
			p->alpha = 0.8;
			p->die = particletime + 7;
			p->color = ramp1[0];
			p->ramp = rand()&3;
			if (i & 1)
			{
				p->type = st_shrapnal;
				for (j=0 ; j<3 ; j++)
				{
					p->org[j] = org[j];// + ((rand()%32)-16);
					p->vel[j] = ((rand()%512)-256)*r_particle_explosion_speed.value;
				}
			}
			else
			{
				p->type = st_shrapnal;
				for (j=0 ; j<3 ; j++)
				{
					p->org[j] = org[j];// + ((rand()%32)-16);
					p->vel[j] = ((rand()%512)-256)*r_particle_explosion_speed.value;
				}
			}
		}
	}*/
}

/*
===============
R_BlobExplosion

===============
*/
void P_BlobExplosion (vec3_t org)
{
	P_RunParticleEffectType(org, NULL, 1, pt_blob);
}

int P_RunParticleEffectTypeString (vec3_t org, vec3_t dir, float count, char *name)
{
	int type = P_FindParticleType(name);
	if (type < 0)
		return 1;

	return P_RunParticleEffectType(org, dir, count, type);
}

int Q1BSP_ClipDecal(vec3_t center, vec3_t normal, vec3_t tangent, float size, float **out);
int P_RunParticleEffectType (vec3_t org, vec3_t dir, float count, int typenum)
{
	part_type_t *ptype = &part_type[typenum];
	int i, j, k, l, spawnspc;
	float m;
	particle_t	*p;
	beamseg_t *b, *bfirst;
	vec3_t ofsvec, arsvec; // offsetspread vec, areaspread vec

	if (typenum < 0)
		return 1;

	if (!ptype->loaded)
		return 1;

	if (ptype->type == PT_DECAL)
	{
		clippeddecal_t *d;
		int decalcount;
		float dist;
		vec3_t tangent, t2;
		vec3_t vec={0.5, 0.5, 0.5};
		static vec3_t up = {0,0.73,-0.73};
		float *decverts;
		int i;

		if (!free_decals)
			return 0;

		if (!dir)
			dir = up;

		VectorNormalize(vec);
		CrossProduct(dir, vec, tangent);
		CrossProduct(dir, tangent, t2);

		decalcount = Q1BSP_ClipDecal(org, dir, tangent, ptype->scale, &decverts);
		while(decalcount)
		{
			if (!free_decals)
				break;

			d = free_decals;
			free_decals = d->next;
			d->next = ptype->clippeddecals;
			ptype->clippeddecals = d;

			VectorCopy((decverts+0), d->vertex[0]);
			VectorCopy((decverts+3), d->vertex[1]);
			VectorCopy((decverts+6), d->vertex[2]);

			for (i = 0; i < 3; i++)
			{
				VectorSubtract(d->vertex[i], org, vec);
				dist = DotProduct(vec, dir)/ptype->scale;
				d->texcoords[i][0] = ((DotProduct(vec, t2)*(1-dist))/ptype->scale)+0.5;
				d->texcoords[i][1] = ((DotProduct(vec, tangent)*(1-dist))/ptype->scale)+0.5;
			}

			d->die = ptype->randdie*frandom();

			if (ptype->die)
				d->alpha = ptype->alpha-d->die*(ptype->alpha/ptype->die)*ptype->alphachange;
			else
				d->alpha = ptype->alpha;

			if (ptype->colorindex >= 0)
			{
				int cidx;
				cidx = ptype->colorrand > 0 ? rand() % ptype->colorrand : 0;
				cidx = ptype->colorindex + cidx;
				if (cidx > 255)
					d->alpha = d->alpha / 2; // Hexen 2 style transparency
				cidx = d_8to24rgbtable[cidx & 0xff];
				d->rgb[0] = (cidx & 0xff) * (1/255.0);
				d->rgb[1] = (cidx >> 8 & 0xff) * (1/255.0);
				d->rgb[2] = (cidx >> 16 & 0xff) * (1/255.0);
			}
			else
				VectorCopy(ptype->rgb, d->rgb);

			vec[2] = frandom();
			vec[0] = vec[2]*ptype->rgbrandsync[0] + frandom()*(1-ptype->rgbrandsync[0]);
			vec[1] = vec[2]*ptype->rgbrandsync[1] + frandom()*(1-ptype->rgbrandsync[1]);
			vec[2] = vec[2]*ptype->rgbrandsync[2] + frandom()*(1-ptype->rgbrandsync[2]);
			d->rgb[0] += vec[0]*ptype->rgbrand[0] + ptype->rgbchange[0]*d->die;
			d->rgb[1] += vec[1]*ptype->rgbrand[1] + ptype->rgbchange[1]*d->die;
			d->rgb[2] += vec[2]*ptype->rgbrand[2] + ptype->rgbchange[2]*d->die;

			d->die = particletime + ptype->die - d->die;

			decverts += 3*3;
			decalcount--;
		}

		return 0;
	}

	// get msvc to shut up
	j = k = l = 0;
	m = 0;

	while(ptype)
	{
		// init spawn specific variables
		b = bfirst = NULL;
		spawnspc = 8;

		switch (ptype->spawnmode)
		{
		case SM_UNICIRCLE:
			m = (count*ptype->count);
			if (ptype->type == PT_BEAM)
				m--;

			if (m < 1)
				m = 0;
			else
				m = (M_PI*2)/m;

			if (ptype->spawnparam1) /* use for weird shape hacks */
				m *= ptype->spawnparam1;
			break;
		case SM_TELEBOX:
			spawnspc = 4;
			l = -ptype->areaspreadvert;
		case SM_LAVASPLASH:
			j = k = -ptype->areaspread;
			if (ptype->spawnparam1)
				m = ptype->spawnparam1;
			else
				m = 0.55752; /* default weird number for tele/lavasplash used in vanilla Q1 */

			if (ptype->spawnparam2)
				spawnspc = (int)ptype->spawnparam2;
			break;
		case SM_FIELD:
			if (!avelocities[0][0])
			{
				for (j=0 ; j<NUMVERTEXNORMALS*2 ; j++)
					avelocities[0][j] = (rand()&255) * 0.01;
			}

			j = 0;
			m = 0;
			break;
		default:	//others don't need intitialisation
			break;
		}
		
		// particle spawning loop
		for (i = 0; i < count*ptype->count; i++)
		{
			if (!free_particles)
				break;
			p = free_particles;
			if (ptype->type == PT_BEAM)
			{
				if (!free_beams)
					break;
				if (b)
				{
					b = b->next = free_beams;
					free_beams = free_beams->next;
				}
				else
				{
					b = bfirst = free_beams;
					free_beams = free_beams->next;
				}
				b->texture_s = i; // TODO: FIX THIS NUMBER
				b->flags = 0;
				b->p = p;
				VectorClear(b->dir);
			}
			free_particles = p->next;
			p->next = ptype->particles;
			ptype->particles = p;

			p->die = ptype->randdie*frandom();
			p->scale = ptype->scale+ptype->randscale*frandom();
			if (ptype->die)
				p->alpha = ptype->alpha-p->die*(ptype->alpha/ptype->die)*ptype->alphachange;
			else
				p->alpha = ptype->alpha;
			p->color = 0;
			p->nextemit = particletime + ptype->emitstart - p->die;
			if (ptype->emittime < 0)
				p->trailstate = NULL;

			p->rotationspeed = ptype->rotationmin + frandom()*ptype->rotationrand;
			p->angle = ptype->rotationstartmin + frandom()*ptype->rotationstartrand;

			if (ptype->colorindex >= 0)
			{
				int cidx;
				cidx = ptype->colorrand > 0 ? rand() % ptype->colorrand : 0;
				cidx = ptype->colorindex + cidx;
				if (cidx > 255)
					p->alpha = p->alpha / 2; // Hexen 2 style transparency
				cidx = d_8to24rgbtable[cidx & 0xff];
				p->rgb[0] = (cidx & 0xff) * (1/255.0);
				p->rgb[1] = (cidx >> 8 & 0xff) * (1/255.0);
				p->rgb[2] = (cidx >> 16 & 0xff) * (1/255.0);
			}
			else
				VectorCopy(ptype->rgb, p->rgb);

			// use org temporarily for rgbsync
			p->org[2] = frandom();
			p->org[0] = p->org[2]*ptype->rgbrandsync[0] + frandom()*(1-ptype->rgbrandsync[0]);
			p->org[1] = p->org[2]*ptype->rgbrandsync[1] + frandom()*(1-ptype->rgbrandsync[1]);
			p->org[2] = p->org[2]*ptype->rgbrandsync[2] + frandom()*(1-ptype->rgbrandsync[2]);

			p->rgb[0] += p->org[0]*ptype->rgbrand[0] + ptype->rgbchange[0]*p->die;
			p->rgb[1] += p->org[1]*ptype->rgbrand[1] + ptype->rgbchange[1]*p->die;
			p->rgb[2] += p->org[2]*ptype->rgbrand[2] + ptype->rgbchange[2]*p->die;

			// randomvel
			p->vel[0] = crandom()*ptype->randomvel;
			p->vel[1] = crandom()*ptype->randomvel;
			p->vel[2] = crandom()*ptype->randomvelvert;

			// handle spawn modes (org/vel)
			switch (ptype->spawnmode)
			{
			case SM_BOX:
				ofsvec[0] = crandom();
				ofsvec[1] = crandom();
				ofsvec[2] = crandom();

				arsvec[0] = ofsvec[0]*ptype->areaspread;
				arsvec[1] = ofsvec[1]*ptype->areaspread;
				arsvec[2] = ofsvec[2]*ptype->areaspreadvert;
				break;
			case SM_TELEBOX:
				ofsvec[0] = k;
				ofsvec[1] = j;
				ofsvec[2] = l+4;
				VectorNormalize(ofsvec);
				VectorScale(ofsvec, 1.0-(frandom())*m, ofsvec);

				// org is just like the original
				arsvec[0] = j + (rand()%spawnspc);
				arsvec[1] = k + (rand()%spawnspc);
				arsvec[2] = l + (rand()%spawnspc);

				// advance telebox loop
				j += spawnspc;
				if (j >= ptype->areaspread)
				{
					j = -ptype->areaspread;
					k += spawnspc;
					if (k >= ptype->areaspread)
					{
						k = -ptype->areaspread;
						l += spawnspc;
						if (l >= ptype->areaspreadvert)
							l = -ptype->areaspreadvert;
					}
				}
				break;
			case SM_LAVASPLASH:
				// calc directions, org with temp vector
				ofsvec[0] = k + (rand()%spawnspc);
				ofsvec[1] = j + (rand()%spawnspc);
				ofsvec[2] = 256;

				arsvec[0] = ofsvec[0];
				arsvec[1] = ofsvec[1];
				arsvec[2] = frandom()*ptype->areaspreadvert;

				VectorNormalize(ofsvec);
				VectorScale(ofsvec, 1.0-(frandom())*m, ofsvec);

				// advance splash loop
				j += spawnspc;
				if (j >= ptype->areaspread)
				{
					j = -ptype->areaspread;
					k += spawnspc;
					if (k >= ptype->areaspread)
						k = -ptype->areaspread;
				}
				break;
			case SM_UNICIRCLE:
				ofsvec[0] = cos(m*i);
				ofsvec[1] = sin(m*i);
				ofsvec[2] = 0;
				VectorScale(ofsvec, ptype->areaspread, arsvec);
				break;
			case SM_FIELD:
				arsvec[0] = cl.time * (avelocities[i][0] + m);
				arsvec[1] = cl.time * (avelocities[i][1] + m);
				arsvec[2] = cos(arsvec[1]);

				ofsvec[0] = arsvec[2]*cos(arsvec[0]);
				ofsvec[1] = arsvec[2]*sin(arsvec[0]);
				ofsvec[2] = -sin(arsvec[1]);
				
				arsvec[0] = r_avertexnormals[j][0]*ptype->areaspread + ofsvec[0]*BEAMLENGTH;
				arsvec[1] = r_avertexnormals[j][1]*ptype->areaspread + ofsvec[1]*BEAMLENGTH;
				arsvec[2] = r_avertexnormals[j][2]*ptype->areaspreadvert + ofsvec[2]*BEAMLENGTH;

				VectorNormalize(ofsvec);

				j++;
				if (j >= NUMVERTEXNORMALS)
				{
					j = 0;
					m += 0.1762891; // some BS number to try to "randomize" things
				}
				break;
			default: // SM_BALL, SM_CIRCLE
				ofsvec[0] = hrandom();
				ofsvec[1] = hrandom();
				if (ptype->areaspreadvert)
					ofsvec[2] = hrandom();
				else
					ofsvec[2] = 0;

				VectorNormalize(ofsvec);
				if (ptype->spawnmode != SM_CIRCLE)
					VectorScale(ofsvec, frandom(), ofsvec);

				arsvec[0] = ofsvec[0]*ptype->areaspread;
				arsvec[1] = ofsvec[1]*ptype->areaspread;
				arsvec[2] = ofsvec[2]*ptype->areaspreadvert;
				break;
			}

			// apply arsvec+ofsvec
			if (dir)
			{
				p->vel[0] += dir[0]*ptype->veladd+ofsvec[0]*ptype->offsetspread;
				p->vel[1] += dir[1]*ptype->veladd+ofsvec[1]*ptype->offsetspread;
				p->vel[2] += dir[2]*ptype->veladd+ofsvec[2]*ptype->offsetspreadvert;
			}
			else
			{
				p->vel[0] += ofsvec[0]*ptype->offsetspread;
				p->vel[1] += ofsvec[1]*ptype->offsetspread;
				p->vel[2] += ofsvec[2]*ptype->offsetspreadvert - ptype->veladd;
			}
			p->org[0] = org[0] + arsvec[0];
			p->org[1] = org[1] + arsvec[1];
			p->org[2] = org[2] + arsvec[2] + ptype->offsetup;

			p->die = particletime + ptype->die - p->die;
		}

		// update beam list
		if (ptype->type == PT_BEAM)
		{
			if (b)
			{
				// update dir for bfirst for certain modes since it will never get updated
				switch (ptype->spawnmode)
				{
				case SM_UNICIRCLE:
					// kinda hackish here, assuming ofsvec contains the point at i-1
					arsvec[0] = cos(m*(i-2));
					arsvec[1] = sin(m*(i-2));
					arsvec[2] = 0;
					VectorSubtract(ofsvec, arsvec, bfirst->dir);
					VectorNormalize(bfirst->dir);			
					break;
				default:
					break;
				}

				b->flags |= BS_NODRAW;
				b->next = ptype->beams;
				ptype->beams = bfirst;
			}
		}

		// go to next associated effect
		if (ptype->assoc < 0)
			break;
		ptype = &part_type[ptype->assoc];
	}

	return 0;
}

/*
===============
R_RunParticleEffect

===============
*/
void P_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
{
	int ptype;

	ptype = P_FindParticleType(va("pe_%i", color));
	if (P_RunParticleEffectType(org, dir, count, ptype))
	{
		color &= ~0x7;
		if (count > 130 && part_type[pe_size3].loaded)
		{
			part_type[pe_size3].colorindex = color;
			part_type[pe_size3].colorrand = 8;
			P_RunParticleEffectType(org, dir, count, pe_size3);
			return;
		}
		if (count > 20 && part_type[pe_size2].loaded)
		{
			part_type[pe_size2].colorindex = color;
			part_type[pe_size2].colorrand = 8;
			P_RunParticleEffectType(org, dir, count, pe_size2);
			return;
		}
		part_type[pe_default].colorindex = color;
		part_type[pe_default].colorrand = 8;
		P_RunParticleEffectType(org, dir, count, pe_default);
		return;
	}
}

//h2 stylie
void P_RunParticleEffect2 (vec3_t org, vec3_t dmin, vec3_t dmax, int color, int effect, int count)
{
	int			i, j;
	float		num;
	float invcount;
	vec3_t	nvel;

	int ptype = P_FindParticleType(va("pe2_%i_%i", effect, color));
	if (ptype < 0)
	{
		ptype = P_FindParticleType(va("pe2_%i", effect));
		if (ptype < 0)
			ptype = pe_default;

		part_type[ptype].colorindex = color;
	}

	invcount = 1/part_type[ptype].count; // using this to get R_RPET to always spawn 1
	count = count * part_type[ptype].count;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;

		for (j=0 ; j<3 ; j++)
		{
			num = rand() / (float)RAND_MAX;
			nvel[j] = dmin[j] + ((dmax[j] - dmin[j]) * num);
		}
		P_RunParticleEffectType(org, nvel, invcount, ptype);

	}
}

/*
===============
R_RunParticleEffect3

===============
*/
//h2 stylie
void P_RunParticleEffect3 (vec3_t org, vec3_t box, int color, int effect, int count)
{
	int			i, j;
	vec3_t	nvel;
	float		num;
	float invcount;

	int ptype = P_FindParticleType(va("pe3_%i_%i", effect, color));
	if (ptype < 0)
	{
		ptype = P_FindParticleType(va("pe3_%i", effect));
		if (ptype < 0)
			ptype = pe_default;

		part_type[ptype].colorindex = color;
	}

	invcount = 1/part_type[ptype].count; // using this to get R_RPET to always spawn 1
	count = count * part_type[ptype].count;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;

		for (j=0 ; j<3 ; j++)
		{
			num = rand() / (float)RAND_MAX;
			nvel[j] = (box[j] * num * 2) - box[j];
		}

		P_RunParticleEffectType(org, nvel, invcount, ptype);
	}
}

/*
===============
R_RunParticleEffect4

===============
*/
//h2 stylie
void P_RunParticleEffect4 (vec3_t org, float radius, int color, int effect, int count)
{
	int			i, j;
	vec3_t	nvel;
	float		num;
	float invcount;

	int ptype = P_FindParticleType(va("pe4_%i_%i", effect, color));
	if (ptype < 0)
	{
		ptype = P_FindParticleType(va("pe4_%i", effect));
		if (ptype < 0)
			ptype = pe_default;

		part_type[ptype].colorindex = color;
	}

	invcount = 1/part_type[ptype].count; // using this to get R_RPET to always spawn 1
	count = count * part_type[ptype].count;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;

		for (j=0 ; j<3 ; j++)
		{
			num = rand() / (float)RAND_MAX;
			nvel[j] = (radius * num * 2) - radius;
		}
		P_RunParticleEffectType(org, nvel, invcount, ptype);
	}
}





/*
===============
R_LavaSplash

===============
*/
void P_LavaSplash (vec3_t org)
{
	P_RunParticleEffectType(org, NULL, 1, pt_lavasplash);
}

/*
===============
R_TeleportSplash

===============
*/
void CLQ2_BlasterTrail (vec3_t start, vec3_t end)
{
	P_ParticleTrail(start, end, rt_blastertrail, NULL);
}

void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up)
{
	float		d;

	// this rotate and negat guarantees a vector
	// not colinear with the original
	right[1] = -forward[0];
	right[2] = forward[1];
	right[0] = forward[2];

	d = DotProduct (right, forward);
	VectorMA (right, -d, forward, right);
	VectorNormalize (right);
	CrossProduct (right, forward, up);
}

void CLQ2_RailTrail (vec3_t start, vec3_t end)
{
	P_ParticleTrail(start, end, rt_railtrail, NULL);
}

// Trailstate functions
static void P_CleanTrailstate(trailstate_t *ts)
{
	// clear LASTSEG flag from lastbeam so it can be reused
	if (ts->lastbeam)
	{
		ts->lastbeam->flags &= ~BS_LASTSEG;
		ts->lastbeam->flags |= BS_NODRAW;
	}

	// clean structure
	memset(ts, 0, sizeof(trailstate_t));
}

void P_DelinkTrailstate(trailstate_t **tsk)
{
	trailstate_t *ts;
	trailstate_t *assoc;

	if (*tsk == NULL)
		return; // not linked to a trailstate

	ts = *tsk; // store old pointer
	*tsk = NULL; // clear pointer

	if (ts->key != tsk)
		return; // prevent overwrite

	assoc = ts->assoc; // store assoc
	P_CleanTrailstate(ts); // clean directly linked trailstate

	// clean trailstates assoc linked
	while (assoc)
	{
		ts = assoc->assoc;
		P_CleanTrailstate(assoc);
		assoc = ts;
	}
}

static trailstate_t *P_NewTrailstate(trailstate_t **key)
{
	trailstate_t *ts;

	// bounds check here in case r_numtrailstates changed
	if (ts_cycle >= r_numtrailstates)
		ts_cycle = 0;

	// get trailstate
	ts = trailstates + ts_cycle;

	// clear trailstate
	P_CleanTrailstate(ts);

	// set key
	ts->key = key;

	// advance index cycle
	ts_cycle++;

	// return clean trailstate
	return ts;
}

int P_ParticleTrail (vec3_t startpos, vec3_t end, int type, trailstate_t **tsk)
{
	vec3_t	vec, vstep, right, up, start;
	float	len;
	int			tcount;
	particle_t	*p;
	part_type_t *ptype = &part_type[type];
	beamseg_t   *b;
	beamseg_t   *bfirst;
	trailstate_t *ts;

	float veladd = -ptype->veladd;
	float randvel = ptype->randomvel;
	float step;
	float stop;
	float tdegree = 1/50; /* MSVC whine */

	if (!ptype->loaded)
		return 1;

	VectorCopy(startpos, start);

	// trailstate allocation/deallocation
	if (tsk)
	{
		// if *tsk = NULL get a new one
		if (*tsk == NULL)
		{
			ts = P_NewTrailstate(tsk);
			*tsk = ts;
		}
		else
		{
			ts = *tsk;

			if (ts->key != tsk) // trailstate was overwritten
			{
				ts = P_NewTrailstate(tsk); // so get a new one
				*tsk = ts;
			}
		}
	}
	else
		ts = NULL;

	if (ptype->assoc>=0)
	{
		if (ts)
			P_ParticleTrail(start, end, ptype->assoc, &(ts->assoc));
		else
			P_ParticleTrail(start, end, ptype->assoc, NULL);
	} 

	// time limit for trails
	if (ptype->timelimit && ts)
	{
		if (ts->statetime > particletime)
			return 0; // timelimit still in effect

		ts->statetime = particletime + ptype->timelimit; // record old time
		ts = NULL; // clear trailstate so we don't save length/lastseg
	}

	if (!ptype->die)
		ts = NULL;
	
	step = 1/ptype->count;

	if (step < 0.01)
		step = 0.01;

	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);
	VectorScale(vec, step, vstep);

	// add offset
	start[2] += ptype->offsetup;

	if (ptype->spawnmode == SM_SPIRAL)
	{
		VectorVectors(vec, right, up);

		// precalculate degree of rotation
		if (ptype->spawnparam1)
			tdegree = 2*M_PI/ptype->spawnparam1; /* distance per rotation inversed */
	}

	// store last stop here for lack of a better solution besides vectors
	if (ts)
	{
		ts->laststop = stop = ts->laststop + len;	//when to stop
		len = ts->lastdist;
	}
	else
	{
		stop = len;
		len = 0;
	}

//	len = ts->lastdist/step;
//	len = (len - (int)len)*step;
//	VectorMA (start, -len, vec, start);

	b = bfirst = NULL;

	while (len < stop)
	{
		len += step;

		if (!free_particles)
		{
			len = stop;
			break;
		}

		p = free_particles;
		if (ptype->type == PT_BEAM)
		{
			if (!free_beams)
			{
				len = stop;
				break;
			}
			if (b)
			{
				b = b->next = free_beams;
				free_beams = free_beams->next;
			}
			else
			{
				b = bfirst = free_beams;
				free_beams = free_beams->next;
			}
			b->texture_s = len; // not sure how to calc this
			b->flags = 0;
			b->p = p;
			VectorCopy(vec, b->dir);
		}

		free_particles = p->next;
		p->next = ptype->particles;
		ptype->particles = p;

		p->die = ptype->randdie*frandom();
		p->scale = ptype->scale+ptype->randscale*frandom();
		if (ptype->die)
			p->alpha = ptype->alpha-p->die*(ptype->alpha/ptype->die)*ptype->alphachange;
		else
			p->alpha = ptype->alpha;
		p->color = 0;

//		if (ptype->spawnmode == SM_TRACER)
		if (ptype->spawnparam1)
			tcount = (int)(len * ptype->count / ptype->spawnparam1);
		else
			tcount = (int)(len * ptype->count);

		if (ptype->colorindex >= 0)
		{
			int cidx;
			cidx = ptype->colorrand > 0 ? rand() % ptype->colorrand : 0;
			if (ptype->citracer) // colorindex behavior as per tracers in std Q1
				cidx += ((tcount & 4) << 1);

			cidx = ptype->colorindex + cidx;
			if (cidx > 255)
				p->alpha = p->alpha / 2;
			cidx = d_8to24rgbtable[cidx & 0xff];
			p->rgb[0] = (cidx & 0xff) * (1/255.0);
			p->rgb[1] = (cidx >> 8 & 0xff) * (1/255.0);
			p->rgb[2] = (cidx >> 16 & 0xff) * (1/255.0);
		}
		else
			VectorCopy(ptype->rgb, p->rgb);

		// use org temporarily for rgbsync
		p->org[2] = frandom();
		p->org[0] = p->org[2]*ptype->rgbrandsync[0] + frandom()*(1-ptype->rgbrandsync[0]);
		p->org[1] = p->org[2]*ptype->rgbrandsync[1] + frandom()*(1-ptype->rgbrandsync[1]);
		p->org[2] = p->org[2]*ptype->rgbrandsync[2] + frandom()*(1-ptype->rgbrandsync[2]);

		p->rgb[0] += p->org[0]*ptype->rgbrand[0] + ptype->rgbchange[0]*p->die;
		p->rgb[1] += p->org[1]*ptype->rgbrand[1] + ptype->rgbchange[1]*p->die;
		p->rgb[2] += p->org[2]*ptype->rgbrand[2] + ptype->rgbchange[2]*p->die;

		VectorCopy (vec3_origin, p->vel);
		p->nextemit = particletime + ptype->emitstart - p->die;
		if (ptype->emittime < 0)
			p->trailstate = NULL; // init trailstate

		p->rotationspeed = ptype->rotationmin + frandom()*ptype->rotationrand;
		p->angle = ptype->rotationstartmin + frandom()*ptype->rotationstartrand;

		switch(ptype->spawnmode)
		{
		case SM_TRACER:
			if (tcount & 1)
			{
				p->vel[0] = vec[1]*ptype->offsetspread;
				p->vel[1] = -vec[0]*ptype->offsetspread;
				p->org[0] = vec[1]*ptype->areaspread;
				p->org[1] = -vec[0]*ptype->areaspread;
			}
			else
			{
				p->vel[0] = -vec[1]*ptype->offsetspread;
				p->vel[1] = vec[0]*ptype->offsetspread;
				p->org[0] = -vec[1]*ptype->areaspread;
				p->org[1] = vec[0]*ptype->areaspread;
			}

			p->vel[0] += vec[0]*veladd+crandom()*randvel;
			p->vel[1] += vec[1]*veladd+crandom()*randvel;
			p->vel[2] = vec[2]*veladd+crandom()*randvel;

			p->org[0] += start[0];
			p->org[1] += start[1];
			p->org[2] = start[2];
			break;
		case SM_SPIRAL:
			{
				float tsin, tcos;

				tcos = cos(len*tdegree)*ptype->areaspread;
				tsin = sin(len*tdegree)*ptype->areaspread;

				p->org[0] = start[0] + right[0]*tcos + up[0]*tsin;
				p->org[1] = start[1] + right[1]*tcos + up[1]*tsin;
				p->org[2] = start[2] + right[2]*tcos + up[2]*tsin;

				tcos = cos(len*tdegree)*ptype->offsetspread;
				tsin = sin(len*tdegree)*ptype->offsetspread;

				p->vel[0] = vec[0]*veladd+crandom()*randvel + right[0]*tcos + up[0]*tsin;
				p->vel[1] = vec[1]*veladd+crandom()*randvel + right[1]*tcos + up[1]*tsin;
				p->vel[2] = vec[2]*veladd+crandom()*randvel + right[2]*tcos + up[2]*tsin;
			}
			break;
		default:
			p->org[0] = crandom();
			p->org[1] = crandom();
			p->org[2] = crandom();

			p->vel[0] = vec[0]*veladd+crandom()*randvel + p->org[0]*ptype->offsetspread;
			p->vel[1] = vec[1]*veladd+crandom()*randvel + p->org[1]*ptype->offsetspread;
			p->vel[2] = vec[2]*veladd+crandom()*randvel + p->org[2]*ptype->offsetspreadvert;

			p->org[0] = p->org[0]*ptype->areaspread + start[0];
			p->org[1] = p->org[1]*ptype->areaspread + start[1];
			p->org[2] = p->org[2]*ptype->areaspreadvert + start[2];
			break;
		}

		VectorAdd (start, vstep, start);

		p->die = particletime + ptype->die - p->die;
	}

	if (ts)
	{
		ts->lastdist = len;

		// update beamseg list
		if (ptype->type == PT_BEAM)
		{
			if (b)
			{
				if (ptype->beams)
				{
					if (ts->lastbeam)
					{
						b->next = ts->lastbeam->next;
						ts->lastbeam->next = bfirst;
						ts->lastbeam->flags &= ~BS_LASTSEG;
					}
					else
					{
						b->next = ptype->beams;
						ptype->beams = bfirst;
					}
				}
				else
				{
					ptype->beams = bfirst;
					b->next = NULL;
				}

				b->flags |= BS_LASTSEG;
				ts->lastbeam = b;
			}

			if ((!free_particles || !free_beams) && ts->lastbeam)
			{
				ts->lastbeam->flags &= ~BS_LASTSEG;
				ts->lastbeam->flags |= BS_NODRAW;
				ts->lastbeam = NULL;
			}
		}
	}
	else if (ptype->type == PT_BEAM)
	{
		if (b)
		{
			b->flags |= BS_NODRAW;
			b->next = ptype->beams;
			ptype->beams = bfirst;
		}
	}

	return 0;
}

void P_TorchEffect (vec3_t pos, int type)
{
#ifdef SIDEVIEWS
	if (r_secondaryview)	//this is called when the models are actually drawn.
		return;
#endif
	if (cl.paused)
		return;

 	P_RunParticleEffectType(pos, NULL, host_frametime, type);
}

void CLQ2_BubbleTrail (vec3_t start, vec3_t end)
{
	P_ParticleTrail(start, end, rt_bubbletrail, NULL);
}

#ifdef Q2BSPS
qboolean Q2TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
{
	vec3_t nul = {0,0,0};
	trace_t trace = CM_BoxTrace(start, end, nul, nul, pmove.physents[0].model->hulls[0].firstclipnode, MASK_SOLID);

	if (trace.fraction < 1)
	{
		VectorCopy (trace.plane.normal, normal);
		VectorCopy (trace.endpos, impact);
		return true;
	}
	return false;
}
#endif

qboolean DoomTraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
{
	return false;
}

#if 1	//extra code to make em bounce of doors.
qboolean TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
{
	trace_t		trace;
	float len, bestlen;
	int i;
	vec3_t delta, ts, te;
	physent_t *pe;
	hull_t *hull;
	qboolean clipped=false;

	memset (&trace, 0, sizeof(trace));

	VectorSubtract(end, start, delta);
	bestlen = Length(delta);

	VectorCopy (end, impact);

	for (i=0 ; i< pmove.numphysent ; i++)
	{
		pe = &pmove.physents[i];	
		if (pe->model)
		{
			hull = &pe->model->hulls[0];
			memset (&trace, 0, sizeof(trace));
			VectorSubtract(start, pe->origin, ts);
			VectorSubtract(end, pe->origin, te);
			if (!hull->funcs.RecursiveHullCheck (hull, hull->firstclipnode, 0, 1, ts, te, &trace))
			{
				VectorSubtract(trace.endpos, ts, delta);
				len = Length(delta);
				if (len < bestlen)
				{
					bestlen = len;
					VectorCopy (trace.plane.normal, normal);
					VectorAdd (pe->origin, trace.endpos, impact);
				}

				clipped=true;
			}
			if (trace.startsolid)
			{
				VectorNormalize(delta);
				normal[0] = -delta[0];
				normal[1] = -delta[1];
				normal[2] = -delta[2];
				VectorCopy (start, impact);
				return true;
			}

		}
	}

	if (clipped)
	{
		return true;
	}
	else
	{
		return false;
	}
}

#else	//basic (faster)
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 (cl.worldmodel->hulls->funcs.RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace))
		return false;

	if (trace.startsolid)
		return true;

	VectorCopy (trace.endpos, impact);
	VectorCopy (trace.plane.normal, normal);

	return true;
}
#endif


part_type_t *lasttype;
static vec3_t pright, pup;
float pframetime;
#ifdef RGLQUAKE
void GL_DrawTexturedParticle(particle_t *p, part_type_t *type)
{
	float x,y;
	float scale;

	if (lasttype != type)
	{
		if (type-part_type>=numparticletypes||type-part_type<0)	//FIXME:! Work out why this is needed...
		{
			Con_Printf("Serious bug alert\n");
			return;
		}

		lasttype = type;
		qglEnd();
		qglEnable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_FLAT);
		qglBegin(GL_QUADS);
	}
	
	scale = (p->org[0] - r_origin[0])*vpn[0] + (p->org[1] - r_origin[1])*vpn[1]
		+ (p->org[2] - r_origin[2])*vpn[2];
	scale = (scale*p->scale)*(type->invscalefactor) + p->scale * (type->scalefactor*250);
	if (scale < 20)
		scale = 0.25;
	else
		scale = 0.25 + scale * 0.001;
	
	qglColor4f (p->rgb[0],
				p->rgb[1],
				p->rgb[2],
				p->alpha);

	if (p->angle)
	{
		x = sin(p->angle)*scale;
		y = cos(p->angle)*scale;
	}
	else
	{
		x = 0;
		y = scale;
	}
	qglTexCoord2f(0,0);
	qglVertex3f (p->org[0] - x*pright[0] - y*pup[0], p->org[1] - x*pright[1] - y*pup[1], p->org[2] - x*pright[2] - y*pup[2]);
	qglTexCoord2f(0,1);
	qglVertex3f (p->org[0] - y*pright[0] + x*pup[0], p->org[1] - y*pright[1] + x*pup[1], p->org[2] - y*pright[2] + x*pup[2]);
	qglTexCoord2f(1,1);
	qglVertex3f (p->org[0] + x*pright[0] + y*pup[0], p->org[1] + x*pright[1] + y*pup[1], p->org[2] + x*pright[2] + y*pup[2]);
	qglTexCoord2f(1,0);
	qglVertex3f (p->org[0] + y*pright[0] - x*pup[0], p->org[1] + y*pright[1] - x*pup[1], p->org[2] + y*pright[2] - x*pup[2]);
}


void GL_DrawSketchParticle(particle_t *p, part_type_t *type)
{
	float x,y;
	float scale;

	int quant;

	if (lasttype != type)
	{
		lasttype = type;
		qglEnd();
		qglDisable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
//		if (type->blendmode == BM_ADD)		//addative
//			glBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
		qglBegin(GL_LINES);
	}

	scale = (p->org[0] - r_origin[0])*vpn[0] + (p->org[1] - r_origin[1])*vpn[1]
		+ (p->org[2] - r_origin[2])*vpn[2];
	scale = (scale*p->scale)*(type->invscalefactor) + p->scale * (type->scalefactor*250);
	if (scale < 20)
		scale = 0.25;
	else
		scale = 0.25 + scale * 0.001;
	
	qglColor4f (p->rgb[0]/2,
				p->rgb[1]/2,
				p->rgb[2]/2,
				p->alpha*2);

	quant = scale;

	if (p->angle)
	{
		x = sin(p->angle)*scale;
		y = cos(p->angle)*scale;
	}
	else
	{
		x = 0;
		y = scale;
	}
	qglVertex3f (p->org[0] - x*pright[0] - y*pup[0], p->org[1] - x*pright[1] - y*pup[1], p->org[2] - x*pright[2] - y*pup[2]);
	qglVertex3f (p->org[0] + x*pright[0] + y*pup[0], p->org[1] + x*pright[1] + y*pup[1], p->org[2] + x*pright[2] + y*pup[2]);
	qglVertex3f (p->org[0] + y*pright[0] - x*pup[0], p->org[1] + y*pright[1] - x*pup[1], p->org[2] + y*pright[2] - x*pup[2]);
	qglVertex3f (p->org[0] - y*pright[0] + x*pup[0], p->org[1] - y*pright[1] + x*pup[1], p->org[2] - y*pright[2] + x*pup[2]);
}

void GL_DrawTrifanParticle(particle_t *p, part_type_t *type)
{
	int i;
	vec3_t v;
	float scale;

	qglEnd();

	if (lasttype != type)
	{
		lasttype = type;
		qglDisable(GL_TEXTURE_2D);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
	}

	scale = (p->org[0] - r_origin[0])*vpn[0] + (p->org[1] - r_origin[1])*vpn[1]
		+ (p->org[2] - r_origin[2])*vpn[2];
	scale = (scale*p->scale)*(type->invscalefactor) + p->scale * (type->scalefactor*250);
	if (scale < 20)
		scale = 0.05;
	else
		scale = 0.05 + scale * 0.0001;
/*
	if ((p->vel[0]*p->vel[0]+p->vel[1]*p->vel[1]+p->vel[2]*p->vel[2])*2*scale > 30*30)
		scale = 1+1/30/Length(p->vel)*2;*/
	
	qglBegin (GL_TRIANGLE_FAN);
	qglColor4f (p->rgb[0],
				p->rgb[1],
				p->rgb[2],
				p->alpha);
	qglVertex3fv (p->org);
	qglColor4f (p->rgb[0]/2,
				p->rgb[1]/2,
				p->rgb[2]/2,
				0);
	for (i=7 ; i>=0 ; i--)
	{
		v[0] = p->org[0] - p->vel[0]*scale + vright[0]*cost[i%7]*p->scale + vup[0]*sint[i%7]*p->scale;
		v[1] = p->org[1] - p->vel[1]*scale + vright[1]*cost[i%7]*p->scale + vup[1]*sint[i%7]*p->scale;
		v[2] = p->org[2] - p->vel[2]*scale + vright[2]*cost[i%7]*p->scale + vup[2]*sint[i%7]*p->scale;
		qglVertex3fv (v);
	}
	qglEnd ();
	qglBegin (GL_TRIANGLES);
}

void GL_DrawSparkedParticle(particle_t *p, part_type_t *type)
{
	if (lasttype != type)
	{
		lasttype = type;
		qglEnd();
		qglDisable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
		qglBegin(GL_LINES);
	}

	qglColor4f (p->rgb[0],
				p->rgb[1],
				p->rgb[2],
				p->alpha);
	qglVertex3f (p->org[0], p->org[1], p->org[2]);

	qglColor4f (p->rgb[0],
				p->rgb[1],
				p->rgb[2],
				0);
	qglVertex3f (p->org[0]-p->vel[0]/10, p->org[1]-p->vel[1]/10, p->org[2]-p->vel[2]/10);

}

void GL_DrawSketchSparkParticle(particle_t *p, part_type_t *type)
{
	if (lasttype != type)
	{
		lasttype = type;
		qglEnd();
		qglDisable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
		qglBegin(GL_LINES);
	}

	qglColor4f (p->rgb[0],
				p->rgb[1],
				p->rgb[2],
				p->alpha);
	qglVertex3f (p->org[0], p->org[1], p->org[2]);

	qglColor4f (p->rgb[0],
				p->rgb[1],
				p->rgb[2],
				0);
	qglVertex3f (p->org[0]-p->vel[0]/10, p->org[1]-p->vel[1]/10, p->org[2]-p->vel[2]/10);
}

void GL_DrawParticleBeam_Textured(beamseg_t *b, part_type_t *type)
{
	vec3_t v, point;
	vec3_t cr;
	beamseg_t *c;
	particle_t *p;
	particle_t *q;
	float ts;

//	if (!b->next)
//		return;

	c = b->next;

	q = c->p;
//	if (!q)
//		return;

	p = b->p;
//	if (!p)
//		return;

	if (lasttype != type)
	{
		lasttype = type;
		qglEnd();
		qglEnable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
		qglBegin(GL_QUADS);
	}
	qglColor4f(q->rgb[0],
			  q->rgb[1],
			  q->rgb[2],
			  q->alpha);
//	qglBegin(GL_LINE_LOOP);
	VectorSubtract(r_refdef.vieworg, q->org, v);
	VectorNormalize(v);
	CrossProduct(c->dir, v, cr);
	ts = (c->texture_s*type->rotationstartmin + particletime*type->rotationmin)/754;

	VectorMA(q->org, -q->scale, cr, point);
	qglTexCoord2f(ts, 0);
	qglVertex3fv(point);
	VectorMA(q->org, q->scale, cr, point);
	qglTexCoord2f(ts, 1);
	qglVertex3fv(point);

	qglColor4f(p->rgb[0],
			  p->rgb[1],
			  p->rgb[2],
			  p->alpha);

	VectorSubtract(r_refdef.vieworg, p->org, v);
	VectorNormalize(v);
	CrossProduct(b->dir, v, cr); // replace with old p->dir?
	ts = (b->texture_s*type->rotationstartmin + particletime*type->rotationmin)/754;

	VectorMA(p->org, p->scale, cr, point);
	qglTexCoord2f(ts, 1);
	qglVertex3fv(point);
	VectorMA(p->org, -p->scale, cr, point);
	qglTexCoord2f(ts, 0);
	qglVertex3fv(point);
//	qglEnd();
}

void GL_DrawParticleBeam_Untextured(beamseg_t *b, part_type_t *type)
{
	vec3_t v;
	vec3_t cr;
	beamseg_t *c;
	particle_t *p;
	particle_t *q;

	vec3_t point[4];

//	if (!b->next)
//		return;

	c = b->next;

	q = c->p;
//	if (!q)
//		return;

	p = b->p;
//	if (!p)
//		return;

	if (lasttype != type)
	{
		lasttype = type;
		qglEnd();
		qglDisable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
		qglBegin(GL_QUADS);
	}

//	qglBegin(GL_LINE_LOOP);
	VectorSubtract(r_refdef.vieworg, q->org, v);
	VectorNormalize(v);
	CrossProduct(c->dir, v, cr);

	VectorMA(q->org, -q->scale, cr, point[0]);
	VectorMA(q->org, q->scale, cr, point[1]);


	VectorSubtract(r_refdef.vieworg, p->org, v);
	VectorNormalize(v);
	CrossProduct(b->dir, v, cr); // replace with old p->dir?

	VectorMA(p->org, p->scale, cr, point[2]);
	VectorMA(p->org, -p->scale, cr, point[3]);


	//one half
	//back out
	//back in
	//front in
	//front out
	qglColor4f(q->rgb[0],
		  q->rgb[1],
		  q->rgb[2],
		  0);
	qglVertex3fv(point[0]);
	qglColor4f(q->rgb[0],
		  q->rgb[1],
		  q->rgb[2],
		  q->alpha);
	qglVertex3fv(q->org);

	qglColor4f(p->rgb[0],
		  p->rgb[1],
		  p->rgb[2],
		  p->alpha);
	qglVertex3fv(p->org);
	qglColor4f(p->rgb[0],
		  p->rgb[1],
		  p->rgb[2],
		  0);
	qglVertex3fv(point[3]);

	//front out
	//front in
	//back in
	//back out
	qglColor4f(p->rgb[0],
		  p->rgb[1],
		  p->rgb[2],
		  0);
	qglVertex3fv(point[2]);
	qglColor4f(p->rgb[0],
		  p->rgb[1],
		  p->rgb[2],
		  p->alpha);
	qglVertex3fv(p->org);
	
	qglColor4f(q->rgb[0],
		  q->rgb[1],
		  q->rgb[2],
		  q->alpha);
	qglVertex3fv(q->org);
	qglColor4f(q->rgb[0],
		  q->rgb[1],
		  q->rgb[2],
		  0);
	qglVertex3fv(point[1]);

//	qglEnd();
}

void GL_DrawClippedDecal(clippeddecal_t *d, part_type_t *type)
{
	if (lasttype != type)
	{
		lasttype = type;
		qglEnd();
		qglEnable(GL_TEXTURE_2D);
		GL_Bind(type->texturenum);
		if (type->blendmode == BM_ADD)		//addative
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE);
//		else if (type->blendmode == BM_SUBTRACT)	//subtractive
//			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		else
			qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglShadeModel(GL_SMOOTH);
		qglBegin(GL_TRIANGLES);
	}

	qglColor4f(d->rgb[0],
		  d->rgb[1],
		  d->rgb[2],
		  d->alpha);

	qglTexCoord2fv(d->texcoords[0]);
	qglVertex3fv(d->vertex[0]);
	qglTexCoord2fv(d->texcoords[1]);
	qglVertex3fv(d->vertex[1]);
	qglTexCoord2fv(d->texcoords[2]);
	qglVertex3fv(d->vertex[2]);
}

#endif
#ifdef SWQUAKE
void SWD_DrawParticleSpark(particle_t *p, part_type_t *type)
{
	float speed;
	vec3_t src, dest;

	int r,g,b;	//if you have a cpu with mmx, good for you...
	r = p->rgb[0]*255;
	if (r < 0)
		r = 0;
	else if (r > 255)
		r = 255;
	g = p->rgb[1]*255;
	if (g < 0)
		g = 0;
	else if (g > 255)
		g = 255;
	b = p->rgb[2]*255;
	if (b < 0)
		b = 0;
	else if (b > 255)
		b = 255;
	p->color = GetPalette(r, g, b);

	speed = Length(p->vel);	
	if ((speed) < 1)
	{
		VectorCopy(p->org, src);
		VectorCopy(p->org, dest);
	}
	else
	{	//causes flickers with lower vels (due to bouncing in physics)
		if (speed < 50)
			speed *= 50/speed;
		VectorMA(p->org, 2.5/(speed), p->vel, src);
		VectorMA(p->org, -2.5/(speed), p->vel, dest);
	}

	D_DrawSparkTrans(p, src, dest);
}
void SWD_DrawParticleBlob(particle_t *p, part_type_t *type)
{
	int r,g,b;	//This really shouldn't be like this. Pitty the 32 bit renderer...
	r = p->rgb[0]*255;
	if (r < 0)
		r = 0;
	else if (r > 255)
		r = 255;
	g = p->rgb[1]*255;
	if (g < 0)
		g = 0;
	else if (g > 255)
		g = 255;
	b = p->rgb[2]*255;
	if (b < 0)
		b = 0;
	else if (b > 255)
		b = 255;
	p->color = GetPalette(r, g, b);
	D_DrawParticleTrans(p);
}
void SWD_DrawParticleBeam(beamseg_t *beam, part_type_t *type)
{
	int r,g,b;	//if you have a cpu with mmx, good for you...
	beamseg_t *c;
	particle_t *p;
	particle_t *q;

//	if (!b->next)
//		return;

	c = beam->next;

	q = c->p;
//	if (!q)
//		return;

	p = beam->p;

	r = p->rgb[0]*255;
	if (r < 0)
		r = 0;
	else if (r > 255)
		r = 255;
	g = p->rgb[1]*255;
	if (g < 0)
		g = 0;
	else if (g > 255)
		g = 255;
	b = p->rgb[2]*255;
	if (b < 0)
		b = 0;
	else if (b > 255)
		b = 255;
	p->color = GetPalette(r, g, b);
	D_DrawSparkTrans(p, p->org, q->org );
}
#endif

void DrawParticleTypes (void texturedparticles(particle_t *,part_type_t*), void sparklineparticles(particle_t*,part_type_t*), void sparkfanparticles(particle_t*,part_type_t*), void beamparticlest(beamseg_t*,part_type_t*), void beamparticlesut(beamseg_t*,part_type_t*), void drawdecalparticles(clippeddecal_t*,part_type_t*))
{
	RSpeedMark();

	qboolean (*tr) (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal);
	void *pdraw;

	int i;
	vec3_t oldorg;
	vec3_t stop, normal;
	part_type_t *type;
	particle_t		*p, *kill;
	clippeddecal_t *d, *dkill;
	ramp_t *ramp;
	float grav;
	vec3_t friction;
	float dist;
	particle_t *kill_list, *kill_first;	//the kill list is to stop particles from being freed and reused whilst still in this loop
										//which is bad because beams need to find out when particles died. Reuse can do wierd things.
										//remember that they're not drawn instantly either.
	beamseg_t *b, *bkill;

	int traces=r_particle_tracelimit.value;

	lasttype = NULL;

	pframetime = host_frametime;	
	if (cl.paused || r_secondaryview)
		pframetime = 0;

	VectorScale (vup, 1.5, pup);
	VectorScale (vright, 1.5, pright);
#ifdef SWQUAKE
	VectorScale (vright, xscaleshrink, r_pright);
	VectorScale (vup, yscaleshrink, r_pup);
	VectorCopy (vpn, r_ppn);
#endif

#ifdef Q2BSPS
	if (cl.worldmodel->fromgame == fg_quake2 || cl.worldmodel->fromgame == fg_quake3)
		tr = Q2TraceLineN;
	else
#endif
		tr = TraceLineN;

	kill_list = kill_first = NULL;

	for (i = 0, type = &part_type[i]; i < numparticletypes; i++, type++)
	{
		if (type->clippeddecals)
		{
			for ( ;; ) 
			{
				dkill = type->clippeddecals;
				if (dkill && dkill->die < particletime)
				{
					type->clippeddecals = dkill->next;
					free_decals = 


					dkill->next = (clippeddecal_t *)kill_list;
					kill_list = (particle_t*)dkill;
					if (!kill_first)
						kill_first = kill_list;
					continue;
				}
				break;
			}
			for (d=type->clippeddecals ; d ; d=d->next)
			{
				for ( ;; )
				{
					dkill = d->next;
					if (dkill && dkill->die < particletime)
					{
						d->next = dkill->next;
						dkill->next = (clippeddecal_t *)kill_list;
						kill_list = (particle_t*)dkill;
						if (!kill_first)
							kill_first = kill_list;
						continue;
					}
					break;
				}



				switch (type->rampmode)
				{
				case RAMP_ABSOLUTE:
					ramp = type->ramp + (int)(type->rampindexes * (type->die - (d->die - particletime)) / type->die);
					VectorCopy(ramp->rgb, d->rgb);
					d->alpha = ramp->alpha;
					break;
				case RAMP_DELTA:	//particle ramps
					ramp = type->ramp + (int)(type->rampindexes * (type->die - (d->die - particletime)) / type->die);
					VectorMA(d->rgb, pframetime, ramp->rgb, d->rgb);
					d->alpha -= pframetime*ramp->alpha;
					break;
				case RAMP_NONE:	//particle changes acording to it's preset properties.
					if (particletime < (d->die-type->die+type->rgbchangetime))
					{
						d->rgb[0] += pframetime*type->rgbchange[0];
						d->rgb[1] += pframetime*type->rgbchange[1];
						d->rgb[2] += pframetime*type->rgbchange[2];
					}
					d->alpha -= pframetime*(type->alpha/type->die)*type->alphachange;
				}

				drawdecalparticles(d, type);
			}
		}

		if (!type->particles)
			continue;

		if (type->type == PT_BEAM)
		{
			if (*type->texname)
				pdraw = beamparticlest;
			else
				pdraw = beamparticlesut;
		}
		else
		{
			if (*type->texname)
				pdraw = texturedparticles;
			else if (type->scale || type->scaledelta)
				pdraw = sparkfanparticles;
			else
				pdraw = sparklineparticles;
		}

		if (!type->die)
		{
			while ((p=type->particles))
			{
				if (type->type == PT_NORMAL)
					RQ_AddDistReorder(pdraw, p, type, p->org);

				// make sure emitter runs at least once
				if (type->emit >= 0 && type->emitstart <= 0)
					P_RunParticleEffectType(p->org, p->vel, 1, type->emit);

				// make sure stain effect runs
				if (type->stains && r_bloodstains.value)
				{
					if (traces-->0&&tr(oldorg, p->org, stop, normal))
					{
						R_AddStain(stop,	(p->rgb[1]*-10+p->rgb[2]*-10),
											(p->rgb[0]*-10+p->rgb[2]*-10),
											(p->rgb[0]*-10+p->rgb[1]*-10),
											30*p->alpha*type->stains);
					}
				}

				type->particles = p->next;
//				p->next = free_particles;
//				free_particles = p;
				p->next = kill_list;
				kill_list = p;
				if (!kill_first) // branch here is probably faster than list traversal later
					kill_first = p;
			}

			if (type->beams)
			{
				b = type->beams;
			}

			while ((b=type->beams) && (b->flags & BS_DEAD))
			{
				type->beams = b->next;
				b->next = free_beams;
				free_beams = b;
			}

			while (b)
			{
				if (!(b->flags & BS_NODRAW))
				{
					// no BS_NODRAW implies b->next != NULL
					// BS_NODRAW should imply b->next == NULL or b->next->flags & BS_DEAD
					VectorCopy(b->next->p->org, stop);
					VectorCopy(b->p->org, oldorg);
					VectorSubtract(stop, oldorg, b->next->dir);
					VectorNormalize(b->next->dir);
					VectorAdd(stop, oldorg, stop);
					VectorScale(stop, 0.5, stop);

					RQ_AddDistReorder(pdraw, b, type, stop);

				}

				// clean up dead entries ahead of current
				for ( ;; )
				{
					bkill = b->next;
					if (bkill && (bkill->flags & BS_DEAD))
					{
						b->next = bkill->next;
						bkill->next = free_beams;
						free_beams = bkill;
						continue;
					}
					break;
				}

				b->flags |= BS_DEAD;
				b = b->next;
			}

			continue;
		}

		//kill off early ones.
		if (type->emittime < 0)
		{
			for ( ;; ) 
			{
				kill = type->particles;
				if (kill && kill->die < particletime)
				{
					P_DelinkTrailstate(&kill->trailstate);
					type->particles = kill->next;
					kill->next = kill_list;
					kill_list = kill;
					if (!kill_first)
						kill_first = kill;
					continue;
				}
				break;
			}
		}
		else
		{
			for ( ;; ) 
			{
				kill = type->particles;
				if (kill && kill->die < particletime)
				{
					type->particles = kill->next;
					kill->next = kill_list;
					kill_list = kill;
					if (!kill_first)
						kill_first = kill;
					continue;
				}
				break;
			}
		}

		grav = type->gravity*pframetime;
		VectorScale(type->friction, pframetime, friction);

		for (p=type->particles ; p ; p=p->next)
		{
			if (type->emittime < 0)
			{
				for ( ;; )
				{
					kill = p->next;
					if (kill && kill->die < particletime)
					{
						P_DelinkTrailstate(&kill->trailstate);
						p->next = kill->next;
						kill->next = kill_list;
						kill_list = kill;
						if (!kill_first)
							kill_first = kill;
						continue;
					}
					break;
				}
			}
			else
			{
				for ( ;; )
				{
					kill = p->next;
					if (kill && kill->die < particletime)
					{
						p->next = kill->next;
						kill->next = kill_list;
						kill_list = kill;
						if (!kill_first)
							kill_first = kill;
						continue;
					}
					break;
				}
			}

			VectorCopy(p->org, oldorg);
			if (type->flags & PT_VELOCITY)
			{
				p->org[0] += p->vel[0]*pframetime;
				p->org[1] += p->vel[1]*pframetime;
				p->org[2] += p->vel[2]*pframetime;
				if (type->flags & PT_FRICTION)
				{
					p->vel[0] -= friction[0]*p->vel[0];
					p->vel[1] -= friction[1]*p->vel[1];
					p->vel[2] -= friction[2]*p->vel[2];
				}
				p->vel[2] -= grav;
			}

			p->angle += p->rotationspeed*pframetime;

			switch (type->rampmode)
			{
			case RAMP_ABSOLUTE:
				ramp = type->ramp + (int)(type->rampindexes * (type->die - (p->die - particletime)) / type->die);
				VectorCopy(ramp->rgb, p->rgb);
				p->alpha = ramp->alpha;
				p->scale = ramp->scale;
				break;
			case RAMP_DELTA:	//particle ramps
				ramp = type->ramp + (int)(type->rampindexes * (type->die - (p->die - particletime)) / type->die);
				VectorMA(p->rgb, pframetime, ramp->rgb, p->rgb);
				p->alpha -= pframetime*ramp->alpha;
				p->scale += pframetime*ramp->scale;
				break;
			case RAMP_NONE:	//particle changes acording to it's preset properties.
				if (particletime < (p->die-type->die+type->rgbchangetime))
				{
					p->rgb[0] += pframetime*type->rgbchange[0];
					p->rgb[1] += pframetime*type->rgbchange[1];
					p->rgb[2] += pframetime*type->rgbchange[2];
				}
				p->alpha -= pframetime*(type->alpha/type->die)*type->alphachange;
				p->scale += pframetime*type->scaledelta;
			}

			if (type->emit >= 0)
			{
				if (type->emittime < 0)
					P_ParticleTrail(oldorg, p->org, type->emit, &p->trailstate);
				else if (p->nextemit < particletime)
				{
					p->nextemit = particletime + type->emittime + frandom()*type->emitrand;
					P_RunParticleEffectType(p->org, p->vel, 1, type->emit);
				}
			}

			if (type->cliptype>=0 && r_bouncysparks.value)
			{
				if (traces-->0&&tr(oldorg, p->org, stop, normal))
				{
					if (type->stains && r_bloodstains.value)
						R_AddStain(stop,	p->rgb[1]*-10+p->rgb[2]*-10,
											p->rgb[0]*-10+p->rgb[2]*-10,
											p->rgb[0]*-10+p->rgb[1]*-10,
											30*p->alpha);

					if (type->cliptype == i)
					{	//bounce
						dist = DotProduct(p->vel, normal) * (-1-(rand()/(float)0x7fff)/2);
				
						VectorMA(p->vel, dist, normal, p->vel);
						VectorCopy(stop, p->org);
						p->vel[0] *= type->clipbounce;
						p->vel[1] *= type->clipbounce;
						p->vel[2] *= type->clipbounce;

						if (!*type->texname && Length(p->vel)<1000*pframetime && type->type == PT_NORMAL)
							p->die = -1;
					}
					else
					{
						p->die = -1;
						VectorNormalize(p->vel);
						P_RunParticleEffectType(stop, p->vel, type->clipcount/part_type[type->cliptype].count, type->cliptype);
					}

					continue;
				}
			}
			else if (type->stains && r_bloodstains.value)
			{
				if (traces-->0&&tr(oldorg, p->org, stop, normal))
				{
					R_AddStain(stop,	(p->rgb[1]*-10+p->rgb[2]*-10),
										(p->rgb[0]*-10+p->rgb[2]*-10),
										(p->rgb[0]*-10+p->rgb[1]*-10),
										30*p->alpha*type->stains);
					p->die = -1;
					continue;
				}
			}

			if (type->type == PT_NORMAL)
				RQ_AddDistReorder((void*)pdraw, p, type, p->org);
		}

		// beams are dealt with here

		// kill early entries
		for ( ;; ) 
		{
			bkill = type->beams;
			if (bkill && (bkill->flags & BS_DEAD || bkill->p->die < particletime) && !(bkill->flags & BS_LASTSEG))
			{
				type->beams = bkill->next;
				bkill->next = free_beams;
				free_beams = bkill; 
				continue;
			}
			break;
		}


		b = type->beams;
		if (!b)
			continue;

		for ( ;; )
		{
			if (b->next)
			{
				// mark dead entries
				if (b->flags & (BS_LASTSEG|BS_DEAD|BS_NODRAW))
				{
					// kill some more dead entries
					for ( ;; )
					{
						bkill = b->next;
						if (bkill && (bkill->flags & BS_DEAD) && !(bkill->flags & BS_LASTSEG))
						{
							b->next = bkill->next;
							bkill->next = free_beams;
							free_beams = bkill;
							continue;
						}
						break;
					}

					if (!bkill) // have to check so we don't hit NULL->next
						continue;
				}
				else
				{
					if (!(b->next->flags & BS_DEAD))
					{
						VectorCopy(b->next->p->org, stop);
						VectorCopy(b->p->org, oldorg);
						VectorSubtract(stop, oldorg, b->next->dir);
						VectorNormalize(b->next->dir);
						VectorAdd(stop, oldorg, stop);
						VectorScale(stop, 0.5, stop);

						if (beamparticlest!=NULL)
							RQ_AddDistReorder(pdraw, b, type, stop);
					}			

//					if (b->p->die < particletime)
//						b->flags |= BS_DEAD;
				}
			}
			else
			{
				if (b->p->die < particletime) // end of the list check
					b->flags |= BS_DEAD;

				break;
			}

			if (b->p->die < particletime)
				b->flags |= BS_DEAD;

			b = b->next;
		}
	}

	RSpeedEnd(RSPEED_PARTICLES);

	// lazy delete for particles is done here
	if (kill_list)
	{
		kill_first->next = free_particles;
		free_particles = kill_list;
	}

	particletime += pframetime;
}

/*
===============
R_DrawParticles
===============
*/
void P_DrawParticles (void)
{
	RSpeedMark();

	P_AddRainParticles();
#if defined(RGLQUAKE)
	if (qrenderer == QR_OPENGL)
	{
		extern cvar_t r_drawflat;
		qglDepthMask(0);
		
		qglDisable(GL_ALPHA_TEST);
		qglEnable (GL_BLEND);
		GL_TexEnv(GL_MODULATE);
		qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		if (qglPolygonOffset)
			qglPolygonOffset(-1, 0);
		qglEnable(GL_POLYGON_OFFSET_FILL);
		qglBegin(GL_QUADS);
		if (r_drawflat.value == 2)
			DrawParticleTypes(GL_DrawSketchParticle, GL_DrawSketchSparkParticle, GL_DrawSketchSparkParticle, GL_DrawParticleBeam_Textured, GL_DrawParticleBeam_Untextured, GL_DrawClippedDecal);
		else
			DrawParticleTypes(GL_DrawTexturedParticle, GL_DrawSparkedParticle, GL_DrawTrifanParticle, GL_DrawParticleBeam_Textured, GL_DrawParticleBeam_Untextured, GL_DrawClippedDecal);
		qglEnd();
		qglDisable(GL_POLYGON_OFFSET_FILL);

		RSpeedRemark();
		qglBegin(GL_QUADS);
		RQ_RenderDistAndClear();
		qglEnd();
		RSpeedEnd(RSPEED_PARTICLESDRAW);

		qglEnable(GL_TEXTURE_2D);

		GL_TexEnv(GL_MODULATE);
		qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		qglDepthMask(1);
		return;
	}
#endif
#ifdef SWQUAKE
	if (qrenderer == QR_SOFTWARE)
	{
		DrawParticleTypes(SWD_DrawParticleBlob, SWD_DrawParticleSpark, SWD_DrawParticleSpark, SWD_DrawParticleBeam, SWD_DrawParticleBeam, NULL);

		RSpeedRemark();
		D_StartParticles();
		RQ_RenderDistAndClear();
		D_EndParticles();
		RSpeedEnd(RSPEED_PARTICLESDRAW);
		return;
	}
#endif
}


