/*
Copyright (C) 1997-2001 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.

*/
// cg_effects.c -- entity effects parsing and management

#include "cg_local.h"

/*
==============================================================

LIGHT STYLE MANAGEMENT

==============================================================
*/

typedef struct
{
	int		length;
	float	value[3];
	float	map[MAX_QPATH];
} cg_lightStyle_t;

cg_lightStyle_t	cg_lightStyle[MAX_LIGHTSTYLES];
static	int		lastofs;

/*
================
CG_ClearLightStyles
================
*/
void CG_ClearLightStyles( void )
{
	memset( cg_lightStyle, 0, sizeof( cg_lightStyle ) );
	lastofs = -1;
}

/*
================
CG_RunLightStyles
================
*/
void CG_RunLightStyles( void )
{
	int		i;
	int		ofs;
	cg_lightStyle_t	*ls;

	ofs = cg.time / 100;
	if( ofs == lastofs )
		return;
	lastofs = ofs;

	for( i = 0, ls = cg_lightStyle; i < MAX_LIGHTSTYLES; i++, ls++ ) {
		if( !ls->length ) {
			ls->value[0] = ls->value[1] = ls->value[2] = 1.0;
			continue;
		}
		if( ls->length == 1 )
			ls->value[0] = ls->value[1] = ls->value[2] = ls->map[0];
		else
			ls->value[0] = ls->value[1] = ls->value[2] = ls->map[ofs % ls->length];
	}
}

/*
================
CG_SetLightStyle
================
*/
void CG_SetLightStyle( int i )
{
	int		j, k;
	char	*s;

	s = cgs.configStrings[i+CS_LIGHTS];

	j = strlen( s );
	if( j >= MAX_QPATH )
		CG_Error( "CL_SetLightstyle length = %i", j );
	cg_lightStyle[i].length = j;

	for( k = 0; k < j; k++ )
		cg_lightStyle[i].map[k] = (float)(s[k]-'a') / (float)('m'-'a');
}

/*
================
CG_AddLightStyles
================
*/
void CG_AddLightStyles( void )
{
	int		i;
	cg_lightStyle_t	*ls;

	for( i = 0, ls = cg_lightStyle; i < MAX_LIGHTSTYLES; i++, ls++ )
		trap_R_AddLightStyleToScene( i, ls->value[0], ls->value[1], ls->value[2] );
}

/*
==============================================================

DLIGHT MANAGEMENT

==============================================================
*/

typedef struct cdlight_s
{
	struct cdlight_s *prev, *next;
	vec3_t	color;
	vec3_t	origin;
	float	radius;
	struct shader_s *shader;
} cdlight_t;

cdlight_t		cg_dlights[MAX_DLIGHTS];
cdlight_t		cg_dlights_headnode, *cg_free_dlights;

/*
================
CG_ClearDlights
================
*/
void CG_ClearDlights( void )
{
	int i;

	memset( cg_dlights, 0, sizeof( cg_dlights ) );

	// link dynamic lights
	cg_free_dlights = cg_dlights;
	cg_dlights_headnode.prev = &cg_dlights_headnode;
	cg_dlights_headnode.next = &cg_dlights_headnode;
	for( i = 0; i < MAX_DLIGHTS - 1; i++ )
		cg_dlights[i].next = &cg_dlights[i+1];
}

/*
===============
CG_AllocDlight
===============
*/
void CG_AllocDlight( vec3_t origin, float radius, float r, float g, float b, struct shader_s *shader )
{
	cdlight_t	*dl;

	if( radius <= 0 )
		return;

	if( cg_free_dlights ) {	// take a free light if possible
		dl = cg_free_dlights;
		cg_free_dlights = dl->next;
	} else {					// grab the oldest one otherwise
		dl = cg_dlights_headnode.prev;
		dl->prev->next = dl->next;
		dl->next->prev = dl->prev;
	}

	dl->radius = radius;
	VectorCopy( origin, dl->origin );
	//VectorCopy( color, dl->color );
	dl->color[0] = r;
	dl->color[1] = g;
	dl->color[2] = b;
	dl->shader = shader;

	// put the light at the start of the list
	dl->prev = &cg_dlights_headnode;
	dl->next = cg_dlights_headnode.next;
	dl->next->prev = dl;
	dl->prev->next = dl;
}

void CG_AddLightToScene( vec3_t org, float intensity, float r, float g, float b, struct shader_s *shader )
{
	CG_AllocDlight( org, intensity, r, g, b, shader );
}

/*
=================
CG_FreeDlight
=================
*/
static void CG_FreeDlight( cdlight_t *dl )
{
	// remove from linked active list
	dl->prev->next = dl->next;
	dl->next->prev = dl->prev;

	// insert into linked free list
	dl->next = cg_free_dlights;
	cg_free_dlights = dl;
}

/*
===============
CG_AddDlights
===============
*/
void CG_AddDlights( void )
{
	cdlight_t	*dl, *hnode, *next;

	hnode = &cg_dlights_headnode;
	for( dl = hnode->next; dl != hnode; dl = next ) {
		next = dl->next;

		trap_R_AddLightToScene( dl->origin, dl->radius, dl->color[0], dl->color[1], dl->color[2], dl->shader );
		CG_FreeDlight( dl );
	}
}

/*
==============================================================

BLOB SHADOWS MANAGEMENT

==============================================================
*/
#ifdef CGAMEGETLIGHTORIGIN

// I don't think I will really need a linked list for this, but
// copy/pasting the dlights code is the easier thing right now

#define MAX_CGSHADEBOXES MAX_EDICTS
typedef struct cgshadebox_s
{
	struct cgshadebox_s *prev, *next;
	vec3_t	origin;
	vec3_t	mins;
	vec3_t	maxs;
	int		entNum;
	struct shader_s *shader;

} cgshadebox_t;

cgshadebox_t		cg_shadeboxes[MAX_CGSHADEBOXES];
cgshadebox_t		cg_shadeboxes_headnode, *cg_free_shadeboxes;

// ok, to not use decals space we need these arrays to store
// the polygons info. We do not need the linked list nor
// registration, tho, since they are linked in the renderer as
// they are created and they aren't reused in the next frame

#define MAX_BLOBSHADOW_VERTS 128
#define MAX_BLOBSHADOW_FRAGMENTS 64

static int numBlobShadows = 0; // cleared each frame

static	vec3_t			cg_blobshadow_verts[MAX_CGSHADEBOXES][MAX_BLOBSHADOW_VERTS];
static	vec2_t			cg_blobshadow_stcoords[MAX_CGSHADEBOXES][MAX_BLOBSHADOW_VERTS];
static	byte_vec4_t		cg_blobshadow_colors[MAX_CGSHADEBOXES][MAX_BLOBSHADOW_VERTS];


void CG_AddBlobShadow( vec3_t origin, vec3_t dir, float orient, float radius,
				   float r, float g, float b, float a, struct shader_s *shader )
{
	int i, j;
	poly_t poly;
	vec3_t axis[3];
	vec3_t verts[MAX_BLOBSHADOW_VERTS];
	byte_vec4_t color;
	fragment_t *fr, fragments[MAX_BLOBSHADOW_FRAGMENTS];
	int numfragments;

	if( numBlobShadows >= MAX_CGSHADEBOXES )
		return;

	// invalid
	if( radius <= 0 || VectorCompare( dir, vec3_origin ) )
		return;

	// calculate orientation matrix
	VectorNormalize2( dir, axis[0] );
	PerpendicularVector( axis[1], axis[0] );
	RotatePointAroundVector( axis[2], axis[0], axis[1], orient );
	CrossProduct( axis[0], axis[2], axis[1] );

	numfragments = trap_R_GetClippedFragments( origin, radius, axis, // clip it
		MAX_BLOBSHADOW_VERTS, verts, MAX_BLOBSHADOW_FRAGMENTS, fragments );

	// no valid fragments
	if( !numfragments )
		return;

	// clamp and scale colors
	if( r < 0 ) r = 0; else if( r > 1 ) r = 255; else r *= 255;
	if( g < 0 ) g = 0; else if( g > 1 ) g = 255; else g *= 255;
	if( b < 0 ) b = 0; else if( b > 1 ) b = 255; else b *= 255;
	if( a < 0 ) a = 0; else if( a > 1 ) a = 255; else a *= 255;

	color[0] = ( qbyte )( r );
	color[1] = ( qbyte )( g );
	color[2] = ( qbyte )( b );
	color[3] = ( qbyte )( a );

	radius = 0.5f / radius;
	VectorScale( axis[1], radius, axis[1] );
	VectorScale( axis[2], radius, axis[2] );

	for( i = 0, fr = fragments; i < numfragments; i++, fr++ ) {
		if( fr->numverts > MAX_BLOBSHADOW_VERTS )
			return;
		else if( fr->numverts <= 0 )
			continue;

		// link the poly to its arrays & setup for drawing
		memset( &poly, 0, sizeof(poly) );
		poly.verts = cg_blobshadow_verts[numBlobShadows];
		poly.stcoords = cg_blobshadow_stcoords[numBlobShadows];
		poly.colors = cg_blobshadow_colors[numBlobShadows];
		poly.shader = shader;
		poly.numverts = fr->numverts;

		for( j = 0; j < fr->numverts; j++ ) {
			vec3_t v;

			VectorCopy( verts[fr->firstvert+j], poly.verts[j] );
			VectorSubtract( poly.verts[j], origin, v );
			poly.stcoords[j][0] = DotProduct( v, axis[1] ) + 0.5f;
			poly.stcoords[j][1] = DotProduct( v, axis[2] ) + 0.5f;
			*( int * )poly.colors[j] = *( int * )color;
		}

		trap_R_AddPolyToScene( &poly );
		numBlobShadows++;
	}
}

/*
================
CG_ClearShadeBoxes
================
*/
void CG_ClearShadeBoxes( void )
{
	int i;

	memset( cg_shadeboxes, 0, sizeof( cg_shadeboxes ) );

	// link shadeboxes
	cg_free_shadeboxes = cg_shadeboxes;
	cg_shadeboxes_headnode.prev = &cg_shadeboxes_headnode;
	cg_shadeboxes_headnode.next = &cg_shadeboxes_headnode;
	for( i = 0; i < MAX_CGSHADEBOXES - 1; i++ )
		cg_shadeboxes[i].next = &cg_shadeboxes[i+1];
}



/*
===============
CG_AllocShadeBox
===============
*/
void CG_AllocShadeBox( int entNum, const vec3_t origin, const vec3_t mins, const vec3_t maxs, struct shader_s *shader )
{
	cgshadebox_t	*sb;
	float dist;
	vec3_t dir;

	if( cg_shadows->integer != 1 )
		return;

	// Kill if behind the view or if too far away
	VectorSubtract( origin, cg.refdef.vieworg, dir );
	dist = VectorNormalize2( dir, dir ) * cg.view_fracDistFOV;
	if( dist > 1024 )
		return;

	if( DotProduct( dir, cg.v_forward ) < 0 )
		return;

	if( cg_free_shadeboxes ) {	// take a free light if possible
		sb = cg_free_shadeboxes;
		cg_free_shadeboxes = sb->next;
	} else {					// grab the oldest one otherwise
		sb = cg_shadeboxes_headnode.prev;
		sb->prev->next = sb->next;
		sb->next->prev = sb->prev;
	}

	VectorCopy( origin, sb->origin );
	VectorCopy( mins, sb->mins );
	VectorCopy( maxs, sb->maxs );
	sb->entNum = entNum;
	sb->shader = shader;
	if( !sb->shader )
		sb->shader = CG_MediaShader( cgs.media.shaderPlayerShadow );

	// put the light at the start of the list
	sb->prev = &cg_shadeboxes_headnode;
	sb->next = cg_shadeboxes_headnode.next;
	sb->next->prev = sb;
	sb->prev->next = sb;
}

/*
=================
CG_FreeShadeBox
=================
*/
static void CG_FreeShadeBox( cgshadebox_t *sb )
{
	// remove from linked active list
	sb->prev->next = sb->next;
	sb->next->prev = sb->prev;

	// insert into linked free list
	sb->next = cg_free_shadeboxes;
	cg_free_shadeboxes = sb;
}

/*
===============
CG_AddShadeBoxes - Which in reality means CalcBlobShadows
Note:	This function should be called after every dynamic light has been added to the rendering list.
		ShadeBoxes exist for the solely reason of waiting until all dlights are sent before doing the shadows.
===============
*/
#define SHADOW_PROJECTION_DISTANCE 96
#define SHADOW_MAX_SIZE 100
#define SHADOW_MIN_SIZE 24

void CG_AddShadeBoxes( void )
{
	// ok, what we have to do here is finding the light direction of each of the shadeboxes origins
	cgshadebox_t	*sb, *hnode, *next;
	vec3_t			lightdir, end, sborigin;
	trace_t			trace;

	if( cg_shadows->integer != 1 )
		return;

	// clean up the polygons list from old frames
	numBlobShadows = 0;

	hnode = &cg_shadeboxes_headnode;
	for( sb = hnode->next; sb != hnode; sb = next ) {
		next = sb->next;

		VectorClear( lightdir );
		trap_R_LightGridForOrigin( sb->origin, lightdir, NULL, NULL, RadiusFromBounds( sb->mins, sb->maxs ) );

		// move the point we will project close to the bottom of the bbox (so shadow doesn't dance much to the sides)
		VectorSet( sborigin, sb->origin[0], sb->origin[1], sb->origin[2] + sb->mins[2] + 8 );
		VectorMA( sborigin, -SHADOW_PROJECTION_DISTANCE, lightdir, end );
		//CG_DrawTestLine( sb->origin, end ); // lightdir testline
		CG_Trace( &trace, sborigin, vec3_origin, vec3_origin, end, sb->entNum, MASK_OPAQUE );
		if( trace.fraction < 1.0f ) { // we have a shadow
			float	blobradius;
			float	alpha, maxalpha = 0.95f;
			vec3_t	shangles;

			VecToAngles( lightdir, shangles );
			blobradius = SHADOW_MIN_SIZE + trace.fraction * (SHADOW_MAX_SIZE-SHADOW_MIN_SIZE);

			alpha = (1.0f - trace.fraction) * maxalpha;

			CG_AddBlobShadow( trace.endpos, trace.plane.normal, shangles[YAW], blobradius,
				1, 1, 1, alpha, sb->shader );
		}

		CG_FreeShadeBox( sb ); // remove box from list
	}
}
#endif

/*
==============================================================

PARTICLE MANAGEMENT

==============================================================
*/

typedef struct particle_s
{
	float		time;

	vec3_t		org;
	vec3_t		vel;
	vec3_t		accel;
	vec3_t		color;
	float		alpha;
	float		alphavel;
	float		scale;

	poly_t		poly;
	vec3_t		pVerts[4];
	vec2_t		pStcoords[4];
	byte_vec4_t	pColor[4];

	struct shader_s	*shader;
} cparticle_t;

#define	PARTICLE_GRAVITY	40

#define MAX_PARTICLES		2048

static vec3_t avelocities [NUMVERTEXNORMALS];

cparticle_t	particles[MAX_PARTICLES];
int			cg_numparticles;

/*
===============
CG_ClearParticles
===============
*/
void CG_ClearParticles( void )
{
	int i;
	cparticle_t	*p;

	cg_numparticles = 0;
	memset( particles, 0, sizeof(cparticle_t) * MAX_PARTICLES );

	for( i = 0, p = particles; i < MAX_PARTICLES; i++, p++ ) {
		p->pStcoords[0][0] = 0; p->pStcoords[0][1] = 1;
		p->pStcoords[1][0] = 0; p->pStcoords[1][1] = 0;
		p->pStcoords[2][0] = 1; p->pStcoords[2][1] = 0;
		p->pStcoords[3][0] = 1; p->pStcoords[3][1] = 1;
	}
}

/*
#define CG_InitParticle(p,s,a,r,g,b) \
	( \
	(p)->time = cg.time, \
	(p)->scale = (s), \
	(p)->alpha = (a), \
	(p)->color[0] = (r), \
	(p)->color[1] = (g), \
	(p)->color[2] = (b) \
	)*/
static void CG_InitParticle( cparticle_t *p, float s, float a, float r, float g, float b, struct shader_s *shader )
{
	p->time = cg.time;
	p->scale = (s);
	p->alpha = (a);
	p->color[0] = (r);
	p->color[1] = (g);
	p->color[2] = (b);
	p->shader = shader;
}

extern cvar_t *cg_playerTrailsColor;

void CG_AddLinearTrail( centity_t *cent, float lifetime ) {
	cparticle_t	*p;
	float r,g,b;

	// only allow trail in race mode or if watching a demos
	if(!(cg.demoPlaying
			|| cg.frame.playerState.stats[STAT_GAMETYPE]==GAMETYPE_RACE)
			)
		return;

	if( cg_numparticles + 1 > MAX_PARTICLES )
		return;
	
	if(cg_playerTrailsColor->string!=NULL 
		&& sscanf(cg_playerTrailsColor->string,"%f %f %f",&r,&g,&b)==3)
	{
		if(r<0.0f)
			r=0.0f;
		if(r>1.0f)
			r=1.0f;
		if(g<0.0f)
			g=0.0f;
		if(g>1.0f)
			g=1.0f;
		if(b<0.0f)
			b=0.0f;
		if(b>1.0f)
			b=1.0f;
	}
	else
	{
		r=0.0f;
		g=1.0f;
		b=0.0f;
	}

	p = &particles[cg_numparticles++];
	CG_InitParticle( p, 1.0f, 1.0f, r, g, b, NULL );
	VectorCopy( cent->ent.origin, p->org );
	p->alphavel = -(1.0f / lifetime);
}

/*
===============
CG_ParticleEffect

Wall impact puffs
===============
*/
void CG_ParticleEffect( vec3_t org, vec3_t dir, float r, float g, float b, int count )
{
	int			j;
	cparticle_t	*p;
	float		d;

	if( !cg_particles->integer )
		return;

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1, 1, r + random()*0.1, g + random()*0.1, b + random()*0.1, NULL );

		d = rand () & 31;
		for( j = 0; j < 3; j++ ) {
			p->org[j] = org[j] + ((rand()&7) - 4) + d * dir[j];
			p->vel[j] = crandom() * 20;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alphavel = -1.0 / (0.5 + random() * 0.3);
	}
}


/*
===============
CG_ParticleEffect2
===============
*/
void CG_ParticleEffect2( vec3_t org, vec3_t dir, float r, float g, float b, int count )
{
	int			j;
	float		d;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1, 1, r, g, b, NULL );

		d = rand()&7;
		for( j = 0; j < 3; j++ ) {
			p->org[j] = org[j] + ((rand()&7) - 4) + d * dir[j];
			p->vel[j] = crandom() * 20;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alphavel = -1.0 / (0.5 + random() * 0.3);
	}
}


/*
===============
CG_BlasterParticles

Wall impact puffs
===============
*/
void CG_BlasterParticles( vec3_t org, vec3_t dir )
{
	int			j, count = 40;
	float		d;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1, 1, 1.0f, 0.8f, 0, NULL );

		d = rand() & 15;
		for( j = 0; j < 3; j++ ) {
			p->org[j] = org[j] + ((rand()&7) - 4) + d * dir[j];
			p->vel[j] = dir[j] * 30 + crandom() * 40;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alphavel = -1.0 / (0.5 + random() * 0.3);
	}
}

/*
===============
CG_BlasterTrail
===============
*/
void CG_BlasterTrail( vec3_t start, vec3_t end )
{
	int			j, count;
	vec3_t		move, vec;
	float		len;
	//const float	dec = 5.0f;
	const float	dec = 3.0f;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	VectorCopy( start, move );
	VectorSubtract( end, start, vec );
	len = VectorNormalize( vec );
	VectorScale( vec, dec, vec );

	count = (int)(len / dec) + 1;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 2.5f, 0.25f, 1.0f, 0.85f, 0, NULL );

		p->alphavel = -1.0 / (0.1 + random() * 0.2);
		for( j = 0; j < 3; j++ ) {
			p->org[j] = move[j] + crandom();
			p->vel[j] = crandom() * 5;
		}

		VectorClear( p->accel );
		VectorAdd( move, vec, move );
	}
}


/*
===============
CG_ElectroWeakTrail
===============
*/
void CG_ElectroWeakTrail( vec3_t start, vec3_t end )
{
	int			j, count;
	vec3_t		move, vec;
	float		len;
	const float	dec = 5;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	VectorCopy( start, move );
	VectorSubtract( end, start, vec );
	len = VectorNormalize( vec );
	VectorScale( vec, dec, vec );

	count = (int)(len / dec) + 1;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 2.0f, 0.8f, 1.0f, 1.0f, 1.0f, NULL );

		p->alphavel = -1.0 / (0.2 + random() * 0.1);
		for( j = 0; j < 3; j++ ) {
			p->org[j] = move[j] + random();/* + crandom();*/
			p->vel[j] = crandom() * 2;
		}

		VectorClear( p->accel );
		VectorAdd( move, vec, move );
	}
}

/*
===============
CG_ImpactPufParticles
Wall impact puffs
===============
*/
void CG_ImpactPufParticles( vec3_t org, vec3_t dir, int count, float scale, float r, float g, float b, float a, struct shader_s *shader )
{
	int			j;
	float		d;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, scale, a, r, g, b, shader );

		d = rand() & 15;
		for( j = 0; j < 3; j++ ) {
			p->org[j] = org[j] + ((rand()&7) - 4) + d * dir[j];
			p->vel[j] = dir[j] * 30 + crandom() * 40;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alphavel = -1.0 / (0.5 + random() * 0.3);
	}
}

/*
===============
CG_ElectroIonsTrail
===============
*/
void CG_ElectroIonsTrail( vec3_t start, vec3_t end )
{
	int			i, count;
	vec3_t		move, vec;
	float		len;
	const float	dec2 = 8.0f;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	VectorSubtract( end, start, vec );
	len = VectorNormalize( vec );
	VectorScale( vec, dec2, vec );
	VectorCopy( start, move );

	count = (int)(len / dec2) + 1;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1.2f, 1, 0.8f + crandom()*0.1, 0.8f + crandom()*0.1, 0.8f + crandom()*0.1, NULL );

		for( i = 0; i < 3; i++ ) {
			p->org[i] = move[i];
			p->vel[i] = crandom()*4;
		}
		p->alphavel = -1.0 / (0.6 + random()*0.6);
		VectorClear( p->accel );
		VectorAdd( move, vec, move );
	}
}

#define	BEAMLENGTH			16

/*
===============
CG_FlyParticles
===============
*/
void CG_FlyParticles( vec3_t origin, int count )
{
	int			i;
	float		angle, sr, sp, sy, cr, cp, cy;
	vec3_t		forward, dir;
	float		dist, ltime;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	if( count > NUMVERTEXNORMALS )
		count = NUMVERTEXNORMALS;

	if( !avelocities[0][0] ) {
		for( i = 0; i < NUMVERTEXNORMALS*3; i++ )
			avelocities[0][i] = (rand()&255) * 0.01;
	}

	i = 0;
	ltime = (float)cg.time / 1000.0;

	count /= 2;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1, 1, 0, 0, 0, NULL );

		angle = ltime * avelocities[i][0];
		sy = sin( angle );
		cy = cos( angle );
		angle = ltime * avelocities[i][1];
		sp = sin( angle );
		cp = cos( angle );
		angle = ltime * avelocities[i][2];
		sr = sin( angle );
		cr = cos( angle );
	
		forward[0] = cp*cy;
		forward[1] = cp*sy;
		forward[2] = -sp;

		dist = sin( ltime + i ) * 64;
		ByteToDir( i, dir );
		p->org[0] = origin[0] + dir[0]*dist + forward[0]*BEAMLENGTH;
		p->org[1] = origin[1] + dir[1]*dist + forward[1]*BEAMLENGTH;
		p->org[2] = origin[2] + dir[2]*dist + forward[2]*BEAMLENGTH;

		VectorClear( p->vel );
		VectorClear( p->accel );
		p->alphavel = -100;

		i += 2;
	}
}

/*
===============
CG_FlyEffect
===============
*/
void CG_FlyEffect( centity_t *ent, vec3_t origin )
{
	int		n;
	int		count;
	int		starttime;

	if( !cg_particles->integer )
		return;

	if( ent->fly_stoptime < cg.time ) {
		starttime = cg.time;
		ent->fly_stoptime = cg.time + 60000;
	} else {
		starttime = ent->fly_stoptime - 60000;
	}

	n = cg.time - starttime;
	if( n < 20000 ) {
		count = n * 162 / 20000.0;
	} else {
		n = ent->fly_stoptime - cg.time;
		if( n < 20000 )
			count = n * 162 / 20000.0;
		else
			count = 162;
	}

	CG_FlyParticles( origin, count );
}

// wsw specific particle effects below here
//============================================================


//===============
//CG_BloodDamageParticles
//===============
/*
void CG_BloodDamageParticles( vec3_t org, vec3_t dir, int damage )
{
	int			j;
	float		d;
	cparticle_t	*p;
	int			count;

	if( !cg_particles->integer )
		return;

	count = max( damage, 20 );

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		//CG_InitParticle( p, 2, 1, r + random()*0.1, g + random()*0.1, b + random()*0.1 );
		CG_InitParticle( p, 2.0f, 1, 0.9 + crandom() * 0.5, 0, 0, NULL );

		d = rand() & 15;
		for( j = 0; j < 3; j++ ) {
			p->org[j] = org[j] + ((rand()&7) - 4) + d * dir[j];
			p->vel[j] = dir[j] * 30 + crandom() * 40;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alphavel = -1.0 / (0.5 + random() * 0.3);
	}
}
*/
/*
===============
CG_AddParticles
===============
*/
void CG_AddParticles( void )
{
	int				i, j;
	float			alpha;
	float			time, time2;
	vec3_t			org;
	vec3_t			corner;
	byte_vec4_t		color;
	//struct shader_s *shader;
	int				maxparticle, activeparticles;
	float			alphaValues[MAX_PARTICLES];
	cparticle_t		*p, *free_particles[MAX_PARTICLES];

	if( !cg_numparticles )
		return;

	j = 0;
	maxparticle = -1;
	activeparticles = 0;
	//shader = CG_MediaShader( cgs.media.shaderParticle );

	for( i = 0, p = particles; i < cg_numparticles; i++, p++ ) {
		time = (cg.time - p->time) * 0.001f;
		alpha = alphaValues[i] = p->alpha + time * p->alphavel;

		if( alpha <= 0 ) {		// faded out
			free_particles[j++] = p;
			continue;
		}

		maxparticle = i;
		activeparticles++;

		time2 = time * time * 0.5f;

		org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2;
		org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2;
		org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2;

		color[0] = (qbyte)( bound( 0, p->color[0], 1.0f ) * 255 );
		color[1] = (qbyte)( bound( 0, p->color[1], 1.0f ) * 255 );
		color[2] = (qbyte)( bound( 0, p->color[2], 1.0f ) * 255 );
		color[3] = (qbyte)( bound( 0, alpha, 1.0f ) * 255 );

		*(int *)p->pColor[0] = *(int *)color;
		*(int *)p->pColor[1] = *(int *)color;
		*(int *)p->pColor[2] = *(int *)color;
		*(int *)p->pColor[3] = *(int *)color;

		corner[0] = org[0];
		corner[1] = org[1] - 0.5f * p->scale;
		corner[2] = org[2] - 0.5f * p->scale;

		VectorSet( p->pVerts[0], corner[0], corner[1] + p->scale, corner[2] + p->scale );
		VectorSet( p->pVerts[1], corner[0], corner[1], corner[2] + p->scale );
		VectorSet( p->pVerts[2], corner[0], corner[1], corner[2] );
		VectorSet( p->pVerts[3], corner[0], corner[1] + p->scale, corner[2] );

		p->poly.numverts = 4;
		p->poly.verts = p->pVerts;
		p->poly.stcoords = p->pStcoords;
		p->poly.colors = p->pColor;
		p->poly.shader = (p->shader == NULL) ? CG_MediaShader( cgs.media.shaderParticle ) : p->shader;

		trap_R_AddPolyToScene( &p->poly );
	}

	i = 0;
	while( maxparticle >= activeparticles )	{
		*free_particles[i++] = particles[maxparticle--];

		while( maxparticle >= activeparticles )	{
			if( alphaValues[maxparticle] <= 0 )
				maxparticle--;
			else
				break;
		}
	}

	cg_numparticles = activeparticles;
}

/*
==============
CG_ClearEffects
==============
*/
void CG_ClearEffects( void )
{
	CG_ClearParticles();
	CG_ClearDlights();
	CG_ClearLightStyles();
#ifdef CGAMEGETLIGHTORIGIN
	CG_ClearShadeBoxes();
#endif
}


// =======================================================
//			Unused code below
// =======================================================


#ifdef THIS_IS_DISABLED

/*
===============
CG_BfgParticles
===============
*/
void CG_BfgParticles( vec3_t origin )
{
	int			i, count;
	float		angle, sr, sp, sy, cr, cp, cy;
	vec3_t		v, forward, dir;
	float		dist, ltime;
	cparticle_t	*p;

	if( !cg_particles->integer )
		return;

	if( !avelocities[0][0] ) {
		for( i = 0; i < NUMVERTEXNORMALS*3; i++ )
			avelocities[0][i] = (rand()&255) * 0.01;
	}

	i = 0;
	ltime = (float)cg.time / 1000.0;

	count = NUMVERTEXNORMALS;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		angle = ltime * avelocities[i][0];
		sy = sin( angle );
		cy = cos( angle );
		angle = ltime * avelocities[i][1];
		sp = sin( angle );
		cp = cos( angle );
		angle = ltime * avelocities[i][2];
		sr = sin( angle );
		cr = cos( angle );

		forward[0] = cp*cy;
		forward[1] = cp*sy;
		forward[2] = -sp;

		dist = sin( ltime + i ) * 64;
		ByteToDir( i, dir );
		p->org[0] = origin[0] + dir[0]*dist + forward[0]*BEAMLENGTH;
		p->org[1] = origin[1] + dir[1]*dist + forward[1]*BEAMLENGTH;
		p->org[2] = origin[2] + dir[2]*dist + forward[2]*BEAMLENGTH;

		VectorClear( p->vel );
		VectorClear( p->accel );

		VectorSubtract( p->org, origin, v );
		dist = VectorLength( v ) / 90.0;

		CG_InitParticle( p, 1.5f, 1.0f - dist, 0, 1.5f * dist, 0, NULL );
		if( p->color[1] > 1 )
			p->color[1] = 1;

		p->alphavel = -100;

		i++;
	}
}

//===============
//CG_BigTeleportParticles
//===============
void CG_BigTeleportParticles( vec3_t org )
{
	int			count = 4096;
	float		angle, dist;
	static float colortable[4] = {0.0625, 0.40625, 0.65625, 0.5625};
	cparticle_t	*p;

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1, 1, colortable[rand()&3], 0, 0 );

		angle = M_TWOPI * (rand()&1023)/1023.0;
		dist = rand() & 31;

		p->org[0] = org[0] + cos( angle ) * dist;
		p->vel[0] = cos( angle ) * (70 + (rand() & 63));
		p->accel[0] = -cos( angle ) * 100;

		p->org[1] = org[1] + sin( angle ) * dist;
		p->vel[1] = sin( angle ) * (70 + (rand() & 63));
		p->accel[1] = -sin( angle ) * 100;

		p->org[2] = org[2] + 8 + (rand()%90);
		p->vel[2] = -100 + (rand() & 31);
		p->accel[2] = PARTICLE_GRAVITY * 4;

		p->alphavel = -0.3 / (0.5 + random() * 0.3);
	}
}

/*
===============
CG_RailTrail
===============
*/
void CG_RailTrail( vec3_t start, vec3_t end )
{
	int			i, j, count;
	vec3_t		move, vec;
	float		d, c, s, len;
	vec3_t		right, up, dir;
	const float	dec1 = 3.2f, dec2 = 2.6f;
	cparticle_t	*p;

	VectorSubtract( end, start, vec );
	len = VectorNormalize( vec );
	MakeNormalVectors( vec, right, up );
	VectorScale( vec, dec1, vec );
	VectorCopy( start, move );

	i = 0;
	count = (int)(len / dec1) + 1;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		d = i++ * 0.1;
		c = cos( d );
		s = sin( d );

		VectorScale( right, c, dir );
		VectorMA( dir, s, up, dir );

		if( random() > 0.9 )
			CG_InitParticle( p, 2.0f, 1, 0.7 + crandom()*0.1, 0.3, 0.4 + crandom()*0.1 );
		else
			CG_InitParticle( p, 2.0f, 1, 0.3 + crandom()*0.1, 0.3, 0.8 + crandom()*0.1 );

		for( j = 0; j < 3; j++ ) {
			p->org[j] = move[j] + dir[j]*3;
			p->vel[j] = dir[j]*6;
		}

		p->alphavel = -1.0 / (1 + random()*0.2);
		VectorClear( p->accel );
		VectorAdd ( move, vec, move );
	}

	VectorSubtract( end, start, vec );
	len = VectorNormalize( vec );
	VectorScale( vec, dec2, vec );
	VectorCopy( start, move );

	count = (int)(len / dec2) + 1;
	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1.2f, 1, 0.7f + crandom()*0.1, 0.7f + crandom()*0.1, 0.7f + crandom()*0.1 );

		for( j = 0; j < 3; j++ ) {
			p->org[j] = move[j];
			p->vel[j] = crandom()*3;
		}

		p->alphavel = -1.0 / (0.6 + random()*0.2);
		VectorClear( p->accel );
		VectorAdd( move, vec, move );
	}
}

/*
===============
CG_BFGExplosionParticles
===============
*/
void CG_BFGExplosionParticles( vec3_t org )
{
	int			j, count = 256;
	cparticle_t	*p;

	if( cg_numparticles + count > MAX_PARTICLES )
		count = MAX_PARTICLES - cg_numparticles;
	for( p = &particles[cg_numparticles], cg_numparticles += count; count > 0; count--, p++ ) {
		CG_InitParticle( p, 1.5f, 1.0f, 0, 0.8f, 0 );

		for( j = 0; j < 3; j++ ) {
			p->org[j] = org[j] + ((rand() & 31) - 16);
			p->vel[j] = (rand() & 383) - 192;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		p->alphavel = -0.8 / (0.5 + random() * 0.3);
	}
}

#endif





