// cg_localents.c -- every frame, generate renderer commands for locally
// processed entities, like smoke puffs, gibs, shells, etc.

// this line must stay at top so the whole PCH thing works...
#include "cg_headers.h"


#include "cg_media.h"

#ifdef _XBOX
#define	MAX_LOCAL_ENTITIES	128
#else
#define	MAX_LOCAL_ENTITIES	512
#endif
localEntity_t	cg_localEntities[MAX_LOCAL_ENTITIES];
localEntity_t	cg_activeLocalEntities;		// double linked list
localEntity_t	*cg_freeLocalEntities;		// single linked list

/*
===================
CG_InitLocalEntities

This is called at startup and for tournement restarts
===================
*/

void	CG_InitLocalEntities( void ) {
	int		i;

	memset( cg_localEntities, 0, sizeof( cg_localEntities ) );
	cg_activeLocalEntities.next = &cg_activeLocalEntities;
	cg_activeLocalEntities.prev = &cg_activeLocalEntities;
	cg_freeLocalEntities = cg_localEntities;
	for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) {
		cg_localEntities[i].next = &cg_localEntities[i+1];
	}
}


/*
==================
CG_FreeLocalEntity
==================
*/
void CG_FreeLocalEntity( localEntity_t *le ) {
	if ( !le->prev ) {
		CG_Error( "CG_FreeLocalEntity: not active" );
	}

	// remove from the doubly linked active list
	le->prev->next = le->next;
	le->next->prev = le->prev;

	// the free list is only singly linked
	le->next = cg_freeLocalEntities;
	cg_freeLocalEntities = le;
}

/*
===================
CG_AllocLocalEntity

Will allways succeed, even if it requires freeing an old active entity
===================
*/
localEntity_t	*CG_AllocLocalEntity( void ) {
	localEntity_t	*le;

	if ( !cg_freeLocalEntities ) {
		// no free entities, so free the one at the end of the chain
		// remove the oldest active entity
		CG_FreeLocalEntity( cg_activeLocalEntities.prev );
	}

	le = cg_freeLocalEntities;
	cg_freeLocalEntities = cg_freeLocalEntities->next;

	memset( le, 0, sizeof( *le ) );

	// link into the active list
	le->next = cg_activeLocalEntities.next;
	le->prev = &cg_activeLocalEntities;
	cg_activeLocalEntities.next->prev = le;
	cg_activeLocalEntities.next = le;
	le->ownerGentNum = -1;
	return le;
}


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

FRAGMENT PROCESSING

A fragment localentity interacts with the environment in some way (hitting walls),
or generates more localentities along a trail.

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


/*
================
CG_FragmentBounceSound
================
*/
void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) 
{
	// half the fragments will make a bounce sounds
	if ( rand() & 1 ) 
	{
		sfxHandle_t	s = 0;

		switch( le->leBounceSoundType )
		{
		case LEBS_ROCK:
			s = cgs.media.rockBounceSound[Q_irand(0,1)];
			break;
		case LEBS_METAL:
			s = cgs.media.metalBounceSound[Q_irand(0,1)];// FIXME: make sure that this sound is registered properly...might still be rock bounce sound....
			break;
		}

		if ( s )
		{
			cgi_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s );
		}

		// bouncers only make the sound once...
		// FIXME: arbitrary...change if it bugs you
		le->leBounceSoundType = LEBS_NONE;
	} 
	else if ( rand() & 1 )
	{
		// we may end up bouncing again, but each bounce reduces the chance of playing the sound again or they may make a lot of noise when they settle
		// FIXME: maybe just always do this??
		le->leBounceSoundType = LEBS_NONE;
	}
}


/*
================
CG_ReflectVelocity
================
*/
void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) 
{
	vec3_t	velocity;
	float	dot;
	int		hitTime;

	// reflect the velocity on the trace plane
	hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction;
	EvaluateTrajectoryDelta( &le->pos, hitTime, velocity );
	dot = DotProduct( velocity, trace->plane.normal );
	VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta );

	VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta );

	VectorCopy( trace->endpos, le->pos.trBase );
	le->pos.trTime = cg.time;

	// check for stop, making sure that even on low FPS systems it doesn't bobble
	if ( trace->allsolid || 
		( trace->plane.normal[2] > 0 && 
		( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) 
	{
		le->pos.trType = TR_STATIONARY;
	}
}

/*
================
CG_AddFragment
================
*/
void CG_AddFragment( localEntity_t *le ) 
{
	vec3_t	newOrigin;
	trace_t	trace;
	// used to sink into the ground, but it looks better to maybe just fade them out
	int		t;
	
	t = le->endTime - cg.time;

	if ( t < FRAG_FADE_TIME ) 
	{
		le->refEntity.renderfx |= RF_ALPHA_FADE;
		le->refEntity.shaderRGBA[0] = le->refEntity.shaderRGBA[1] = le->refEntity.shaderRGBA[2] = 255;
		le->refEntity.shaderRGBA[3] = ((float)t / FRAG_FADE_TIME) * 255.0f;
	}

	if ( le->pos.trType == TR_STATIONARY ) 
	{
		if ( !(cgi_CM_PointContents( le->refEntity.origin, 0 ) & CONTENTS_SOLID )) 
		{
			// thing is no longer in solid, so let gravity take it back
			VectorCopy( le->refEntity.origin, le->pos.trBase );
			VectorClear( le->pos.trDelta );
			le->pos.trTime = cg.time;
			le->pos.trType = TR_GRAVITY;
		}

		cgi_R_AddRefEntityToScene( &le->refEntity );

		return;
	}

	// calculate new position
	EvaluateTrajectory( &le->pos, cg.time, newOrigin );

	le->refEntity.renderfx |= RF_LIGHTING_ORIGIN;
	VectorCopy( newOrigin, le->refEntity.lightingOrigin );
	
	// trace a line from previous position to new position
	CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, le->ownerGentNum, CONTENTS_SOLID );
	if ( trace.fraction == 1.0 ) {
		// still in free fall
		VectorCopy( newOrigin, le->refEntity.origin );

		if ( le->leFlags & LEF_TUMBLE ) {
			vec3_t angles;

			EvaluateTrajectory( &le->angles, cg.time, angles );
			AnglesToAxis( angles, le->refEntity.axis );
			for(int k = 0; k < 3; k++)
			{
				VectorScale(le->refEntity.axis[k], le->radius, le->refEntity.axis[k]);
			}

		}

		cgi_R_AddRefEntityToScene( &le->refEntity );

		return;
	}

	// if it is in a nodrop zone, remove it
	// this keeps gibs from waiting at the bottom of pits of death
	// and floating levels
	if ( cgi_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) 
	{
		CG_FreeLocalEntity( le );
		return;
	}

	// do a bouncy sound
	CG_FragmentBounceSound( le, &trace );

	// reflect the velocity on the trace plane
	CG_ReflectVelocity( le, &trace );
	//FIXME: if LEF_TUMBLE, change avelocity too?

	cgi_R_AddRefEntityToScene( &le->refEntity );
}

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

TRIVIAL LOCAL ENTITIES

These only do simple scaling or modulation before passing to the renderer
=====================================================================
*/

/*
** CG_AddTeleporterEffect
*/
void CG_AddTeleporterEffect( localEntity_t *le ) {
	refEntity_t *re;
	float c;

	re = &le->refEntity;

	c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime );

	re->shaderRGBA[0] =
	re->shaderRGBA[1] =
	re->shaderRGBA[2] =
	re->shaderRGBA[3] = 0xff * c;

	cgi_R_AddRefEntityToScene( re );
}

/*
** CG_AddFadeRGB
*/
void CG_AddFadeRGB( localEntity_t *le ) {
	refEntity_t *re;
	float c;

	re = &le->refEntity;

	c = ( le->endTime - cg.time ) * le->lifeRate;
	c *= 0xff;

	re->shaderRGBA[0] = le->color[0] * c;
	re->shaderRGBA[1] = le->color[1] * c;
	re->shaderRGBA[2] = le->color[2] * c;
	re->shaderRGBA[3] = le->color[3] * c;

	cgi_R_AddRefEntityToScene( re );
}

/*
==================
CG_AddPuff
==================
*/
static void CG_AddPuff( localEntity_t *le ) {
	refEntity_t	*re;
	float		c;
	vec3_t		delta;
	float		len;

	re = &le->refEntity;

	// fade / grow time
	c = ( le->endTime - cg.time ) / (float)( le->endTime - le->startTime );

	re->shaderRGBA[0] = le->color[0] * c;
	re->shaderRGBA[1] = le->color[1] * c;
	re->shaderRGBA[2] = le->color[2] * c;

	if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) {
		re->radius = le->radius * ( 1.0 - c ) + 8;
	}

	EvaluateTrajectory( &le->pos, cg.time, re->origin );

	// if the view would be "inside" the sprite, kill the sprite
	// so it doesn't add too much overdraw
	VectorSubtract( re->origin, cg.refdef.vieworg, delta );
	len = VectorLength( delta );
	if ( len < le->radius ) {
		CG_FreeLocalEntity( le );
		return;
	}

	cgi_R_AddRefEntityToScene( re );
}

/*
================
CG_AddLocalLight
================
*/
static void CG_AddLocalLight( localEntity_t *le ) 
{
	// There should be a light if this is being used, but hey...
	if ( le->light ) 
	{
		float		light;

		light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );

		if ( light < 0.5 ) 
		{
			light = 1.0;
		} 
		else 
		{
			light = 1.0 - ( light - 0.5 ) * 2;
		}

		light = le->light * light;

		cgi_R_AddLightToScene( le->refEntity.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] );
	}
}

//---------------------------------------------------
static void CG_AddFadeModel( localEntity_t *le )
{
	refEntity_t	*ent = &le->refEntity;

	if ( cg.time < le->startTime )
	{
		CG_FreeLocalEntity( le );
		return;
	}

	float frac = 1.0f - ((float)( cg.time - le->startTime )/(float)( le->endTime - le->startTime ));

	ent->shaderRGBA[0] = le->color[0] * frac;
	ent->shaderRGBA[1] = le->color[1] * frac;
	ent->shaderRGBA[2] = le->color[2] * frac;
	ent->shaderRGBA[3] = le->color[3] * frac;

	EvaluateTrajectory( &le->pos, cg.time, ent->origin );

	// add the entity
	cgi_R_AddRefEntityToScene( ent );
}

// NOTE: this is 100% for the demp2 alt-fire effect, so changes to the visual effect will affect game side demp2 code
//---------------------------------------------------
static void CG_AddFadeScaleModel( localEntity_t *le )
{
	refEntity_t	*ent = &le->refEntity;

	float frac = ( cg.time - le->startTime )/((float)( le->endTime - le->startTime ));

	frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end

	ent->nonNormalizedAxes = qtrue;

	AxisCopy( axisDefault, ent->axis );

	VectorScale( ent->axis[0], le->radius * frac, ent->axis[0] );
	VectorScale( ent->axis[1], le->radius * frac, ent->axis[1] );
	VectorScale( ent->axis[2], le->radius * 0.5f * frac, ent->axis[2] );

	frac = 1.0f - frac;

	ent->shaderRGBA[0] = le->color[0] * frac;
	ent->shaderRGBA[1] = le->color[1] * frac;
	ent->shaderRGBA[2] = le->color[2] * frac;
	ent->shaderRGBA[3] = le->color[3] * frac;

	// add the entity
	cgi_R_AddRefEntityToScene( ent );
}

// create a quad that doesn't use a refEnt.  Currently only for use with the DebugNav drawing so it doesn't have to use fx
//------------------------------------------
static void CG_AddQuad( localEntity_t *le )
{
	polyVert_t	verts[4];

	VectorCopy( le->refEntity.origin, verts[0].xyz );
	verts[0].xyz[0] -= le->radius;
	verts[0].xyz[1] -= le->radius;
	verts[0].st[0] = 0;
	verts[0].st[1] = 0;

	for ( int i = 0; i < 4; i++ )
	{
		verts[i].modulate[0] = le->color[0];
		verts[i].modulate[1] = le->color[1];
		verts[i].modulate[2] = le->color[2];
		verts[i].modulate[3] = le->color[3];
	}

	VectorCopy( le->refEntity.origin, verts[1].xyz );
	verts[1].xyz[0] -= le->radius;
	verts[1].xyz[1] += le->radius;
	verts[1].st[0] = 0;
	verts[1].st[1] = 1;

	VectorCopy( le->refEntity.origin, verts[2].xyz );
	verts[2].xyz[0] += le->radius;
	verts[2].xyz[1] += le->radius;
	verts[2].st[0] = 1;
	verts[2].st[1] = 1;

	VectorCopy( le->refEntity.origin, verts[3].xyz );
	verts[3].xyz[0] += le->radius;
	verts[3].xyz[1] -= le->radius;
	verts[3].st[0] = 1;
	verts[3].st[1] = 0;

	cgi_R_AddPolyToScene( le->refEntity.customShader, 4, verts );
}

// create a sprite that doesn't use a refEnt.  Currently only for use with the DebugNav drawing so it doesn't have to use fx
//------------------------------------------
static void CG_AddSprite( localEntity_t *le )
{
	polyVert_t	verts[4];

	VectorCopy( le->refEntity.origin, verts[0].xyz );
	VectorMA( verts[0].xyz, -le->radius, cg.refdef.viewaxis[2], verts[0].xyz );
	VectorMA( verts[0].xyz, -le->radius, cg.refdef.viewaxis[1], verts[0].xyz );
	verts[0].st[0] = 0;
	verts[0].st[1] = 0;

	for ( int i = 0; i < 4; i++ )
	{
		verts[i].modulate[0] = le->color[0];
		verts[i].modulate[1] = le->color[1];
		verts[i].modulate[2] = le->color[2];
		verts[i].modulate[3] = le->color[3];
	}

	VectorCopy( le->refEntity.origin, verts[1].xyz );
	VectorMA( verts[1].xyz, -le->radius, cg.refdef.viewaxis[2], verts[1].xyz );
	VectorMA( verts[1].xyz, le->radius, cg.refdef.viewaxis[1], verts[1].xyz );
	verts[1].st[0] = 0;
	verts[1].st[1] = 1;

	VectorCopy( le->refEntity.origin, verts[2].xyz );
	VectorMA( verts[2].xyz, le->radius, cg.refdef.viewaxis[2], verts[2].xyz );
	VectorMA( verts[2].xyz, le->radius, cg.refdef.viewaxis[1], verts[2].xyz );
	verts[2].st[0] = 1;
	verts[2].st[1] = 1;

	VectorCopy( le->refEntity.origin, verts[3].xyz );
	VectorMA( verts[3].xyz, le->radius, cg.refdef.viewaxis[2], verts[3].xyz );
	VectorMA( verts[3].xyz, -le->radius, cg.refdef.viewaxis[1], verts[3].xyz );
	verts[3].st[0] = 1;
	verts[3].st[1] = 0;

	cgi_R_AddPolyToScene( le->refEntity.customShader, 4, verts );
}

/*
===================
CG_AddLine

for beams and the like.
===================
*/
void CG_AddLine( localEntity_t *le )
{
	refEntity_t	*re;

	re = &le->refEntity;

	re->reType = RT_LINE;

	cgi_R_AddRefEntityToScene( re );
}

//==============================================================================

/*
===================
CG_AddLocalEntities

===================
*/
void CG_AddLocalEntities( void ) 
{
	localEntity_t	*le, *next;

	// walk the list backwards, so any new local entities generated
	// (trails, marks, etc) will be present this frame
	le = cg_activeLocalEntities.prev;
	for ( ; le != &cg_activeLocalEntities ; le = next ) {
		// grab next now, so if the local entity is freed we
		// still have it
		next = le->prev;

		if ( cg.time >= le->endTime ) {
			CG_FreeLocalEntity( le );
			continue;
		}
		switch ( le->leType ) {
		default:
			CG_Error( "Bad leType: %i", le->leType );
			break;

		case LE_MARK:
			break;

		case LE_FADE_MODEL:
			CG_AddFadeModel( le );
			break;

		case LE_FADE_SCALE_MODEL:
			CG_AddFadeScaleModel( le );
			break;

		case LE_FRAGMENT:
			CG_AddFragment( le );
			break;

		case LE_PUFF:
			CG_AddPuff( le );
			break;

		case LE_FADE_RGB:		// teleporters, railtrails
			CG_AddFadeRGB( le );
			break;

		case LE_LIGHT:
			CG_AddLocalLight( le );
			break;

		case LE_LINE:					// oriented lines for FX
			CG_AddLine( le );
			break;

		// Use for debug only
		case LE_QUAD:
			CG_AddQuad( le );
			break;

		case LE_SPRITE:
			CG_AddSprite( le );
		}
	}
}




