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

*/
#include "cg_local.h"

#define MAX_DECALS				256
#define MAX_DECAL_VERTS			128
#define MAX_DECAL_FRAGMENTS		64

typedef struct cdecal_s
{
	struct cdecal_s		*prev, *next;

	unsigned int		die;				// remove after this time
	unsigned int		fadetime;
	float				fadefreq;
	qboolean			fadealpha;

	float				color[4];
	struct shader_s		*shader;

	poly_t				*poly;
} cdecal_t;

static	cdecal_t		cg_decals[MAX_DECALS];
static	cdecal_t		cg_decals_headnode, *cg_free_decals;

static	poly_t			cg_decal_polys[MAX_DECALS];
static	vec3_t			cg_decal_verts[MAX_DECALS][MAX_DECAL_VERTS];
static	vec2_t			cg_decal_stcoords[MAX_DECALS][MAX_DECAL_VERTS];
static	byte_vec4_t		cg_decal_colors[MAX_DECALS][MAX_DECAL_VERTS];

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

	memset( cg_decals, 0, sizeof( cg_decals ) );

	// link decals
	cg_free_decals = cg_decals;
	cg_decals_headnode.prev = &cg_decals_headnode;
	cg_decals_headnode.next = &cg_decals_headnode;
	for( i = 0; i < MAX_DECALS; i++ ) {
		if( i < MAX_DECALS - 1 )
			cg_decals[i].next = &cg_decals[i+1];

		cg_decals[i].poly = &cg_decal_polys[i];
		cg_decals[i].poly->verts = cg_decal_verts[i];
		cg_decals[i].poly->stcoords = cg_decal_stcoords[i];
		cg_decals[i].poly->colors = cg_decal_colors[i];
	}
}

/*
=================
CG_AllocDecal

Returns either a free decal or the oldest one
=================
*/
cdecal_t *CG_AllocDecal( void )
{
	cdecal_t *dl;

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

	// put the decal at the start of the list
	dl->prev = &cg_decals_headnode;
	dl->next = cg_decals_headnode.next;
	dl->next->prev = dl;
	dl->prev->next = dl;

	return dl;
}

/*
=================
CG_FreeDecal
=================
*/
void CG_FreeDecal( cdecal_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_decals;
	cg_free_decals = dl;
}

/*
=================
CG_SpawnDecal
=================
*/
void CG_SpawnDecal( vec3_t origin, vec3_t dir, float orient, float radius,
				 float r, float g, float b, float a, float die, float fadetime, qboolean fadealpha, struct shader_s *shader )
{
	int i, j;
	cdecal_t *dl;
	poly_t *poly;
	vec3_t axis[3];
	vec3_t verts[MAX_DECAL_VERTS];
	byte_vec4_t color;
	fragment_t *fr, fragments[MAX_DECAL_FRAGMENTS];
	int numfragments;
	float dietime, fadefreq;

	if( !cg_addDecals->integer )
		return;

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

	// we don't spawn decals if too far away (we could move there, but players won't notice there should be a decal by then)
	if( DistanceFast(origin, cg.refdef.vieworg) * cg.view_fracDistFOV > 2048 )
		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_DECAL_VERTS, verts, MAX_DECAL_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] );

	dietime = cg.time + die * 1000;
	fadefreq = 0.001f / min( fadetime, die );
	fadetime = cg.time + (die - min( fadetime, die )) * 1000;

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

		// allocate decal
		dl = CG_AllocDecal ();
		dl->die = dietime;
		dl->fadetime = fadetime;
		dl->fadefreq = fadefreq;
		dl->fadealpha = fadealpha;
		dl->shader = shader;
		dl->color[0] = r; 
		dl->color[1] = g;
		dl->color[2] = b;
		dl->color[3] = a;

		// setup polygon for drawing
		poly = dl->poly;
		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;
		}
	}
}

/*
=================
CG_AddDecals
=================
*/
void CG_AddDecals( void )
{
	int			i;
	float		fade;
	cdecal_t	*dl, *next, *hnode;
	poly_t		*poly;
	byte_vec4_t color;

	// add decals in first-spawed - first-drawn order
	hnode = &cg_decals_headnode;
	for( dl = hnode->prev; dl != hnode; dl = next ) {
		next = dl->prev;

		// it's time to DIE
		if( dl->die <= cg.time ) {
			CG_FreeDecal( dl );
			continue;
		}
		poly = dl->poly;

		// fade out
		if( dl->fadetime < cg.time ) {
			fade = (dl->die - cg.time) * dl->fadefreq;

			if( dl->fadealpha ) {
				color[0] = ( qbyte )( dl->color[0] );
				color[1] = ( qbyte )( dl->color[1] );
				color[2] = ( qbyte )( dl->color[2] );
				color[3] = ( qbyte )( dl->color[3]*fade );
			} else {
				color[0] = ( qbyte )( dl->color[0]*fade );
				color[1] = ( qbyte )( dl->color[1]*fade );
				color[2] = ( qbyte )( dl->color[2]*fade );
				color[3] = ( qbyte )( dl->color[3] );
			}

			for( i = 0; i < poly->numverts; i++ )
				*( int * )poly->colors[i] = *( int * )color;
		}

		trap_R_AddPolyToScene( poly );
	}
}
