
#include "../hud.h"
#include "../cl_util.h"
#include "../parsemsg.h"
#include "r_studioint.h"
#include "com_model.h"
#include "r_efx.h"
#include "pm_defs.h"
#include "pmtrace.h"
#include "event_api.h"
#include "fxparticlesystem.h"


// Constructor
CFXParticle::CFXParticle()
{	m_pNext = NULL;
	m_pPrev = NULL;
	Reset();
}

// Reset
void CFXParticle::Reset()
{	setOrigin(vec3_t(0,0,0));
	setPrevOrigin(vec3_t(0,0,0));
	setVelocity(vec3_t(0,0,0));
	setSprite(0);
	setGravity(0);
	setBounceDampen(0);
	setAirResistance(0);
	setStartSize(0);
	setEndSize(0);
	setSize(0);
	setCollision(0);
	setBounceCount(0);
	setSleep(false);
	// unlooping it, suppose to have a slight speed boost?
	float *RGBA = getRGBA();
	float *startRGBA = getStartRGBA();
	float *endRGBA = getEndRGBA();
	startRGBA[0] = 0;
	startRGBA[1] = 0;
	startRGBA[2] = 0;
	startRGBA[3] = 0;
	endRGBA[0] = 0;
	endRGBA[1] = 0;
	endRGBA[2] = 0;
	endRGBA[3] = 0;
	RGBA[0] = 0;
	RGBA[1] = 0;
	RGBA[2] = 0;
	RGBA[3] = 0;
}

// Think
bool CFXParticle::Think( const float &flTimeDelta )
{	// Calculate life
	calculateLife(flTimeDelta);
	if(!isDead())
	{	// Only calculate velocity and position if we're not 'sleeping'
		if(!isSleeping())
		{	calculateVelocity(flTimeDelta);
			calculateCollision(flTimeDelta);
		}
		calculateSize();
		calculateColor();
		return true;
	}
	return false;
}


// Calculate Life
void CFXParticle::calculateLife( const float &flTimeDelta )
{	setCurrentLife(getCurrentLife()-flTimeDelta);
	if(isDead())
	{	setCurrentLife(0);
	}
}

// Calculate Velocity
void CFXParticle::calculateVelocity( const float &flTimeDelta )
{	
	vec3_t vecVelocity = getVelocity();
	// Add air resistance
	float flResistance = getAirResistance()*flTimeDelta;
	if(flResistance > 0)
	{	if(abs(vecVelocity.x) <= flResistance)
			vecVelocity.x = 0;
		else
		{	if(vecVelocity.x > 0)
				vecVelocity.x -= flResistance;
			else
				vecVelocity.x += flResistance;

		}
		if(abs(vecVelocity.y) <= flResistance)
			vecVelocity.y = 0;
		else
		{	if(vecVelocity.y > 0)
				vecVelocity.y -= flResistance;
			else
				vecVelocity.y += flResistance;
		}
	}
	// Add gravity
	float flGravity = getGravity() * flTimeDelta;
	if(abs(flGravity) > 0)
	{	vecVelocity.z -= flGravity;
	}

	// Set new velocity and origin
	setVelocity(vecVelocity);
	setOrigin( getOrigin() + vecVelocity*flTimeDelta );
}

// Calculate Collision
void CFXParticle::calculateCollision( const float &flTimeDelta )
{
	switch(getCollision())
	{
	case FXPCOLLISION_NONE:
		break;
	case FXPCOLLISION_STOP:
		{
			vec3_t origin = getOrigin();
			vec3_t prevOrigin(getPrevOrigin());
			int iContents = gEngfuncs.PM_PointContents(origin,NULL);

			// We hit something
			if(iContents == CONTENTS_SOLID)
			{	// Our previous origin is good, back it up and bounce off wall
				if(prevOrigin != vec3_t(0,0,0) && gEngfuncs.PM_PointContents(prevOrigin,NULL) != CONTENTS_SOLID)
				{	// trace
					pmtrace_t tr;
					gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
					gEngfuncs.pEventAPI->EV_PlayerTrace( prevOrigin,origin, PM_WORLD_ONLY, -1, &tr );

					// Set origin
					setOrigin(tr.endpos + tr.plane.normal);

					// We hit something, stop updating velocity and checking for collisions
					setSleep(true);
				}
			}

			break;
		}
	case FXPCOLLISION_BOUNCE:
		{
			vec3_t origin = getOrigin();
			vec3_t prevOrigin(getPrevOrigin());
			int iContents = gEngfuncs.PM_PointContents(origin,NULL);

			// We hit something
			if(iContents == CONTENTS_SOLID)
			{	// Our previous origin is good, back it up and bounce off wall
				if(prevOrigin != vec3_t(0,0,0) && gEngfuncs.PM_PointContents(prevOrigin,NULL) != CONTENTS_SOLID)
				{	// trace
					pmtrace_t tr;
					gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
					gEngfuncs.pEventAPI->EV_PlayerTrace( prevOrigin,origin, PM_WORLD_ONLY, -1, &tr );

					// Calculate reflected velocity
					vec3_t vecR;
					float n = -DotProduct( tr.plane.normal, getVelocity() );
					VectorMA( getVelocity(), 2.0 * n, tr.plane.normal, vecR );

					// Reset speed and reapply velocity with bouncedamp
					setVelocity(vecR.Normalize() * getVelocity().Length() * getBounceDampen());

					// Set origin
					setOrigin(tr.endpos + tr.plane.normal);

					// Inc bounce count
					incBounceCount();

					// Bounced too many times, stop
					if(getBounceCount() >= 3 && getVelocity().Length() < 32.0)
					{	setSleep(true);
					}
				}
			}

			break;
		}
	}
	// Set previous origin
	setPrevOrigin(getOrigin());
}

// Calculate Size
void CFXParticle::calculateSize( void )
{	setSize(getStartSize() + (getEndSize()-getStartSize())*getPercentLife());
}

// Calculate Color
void CFXParticle::calculateColor( void )
{	float flPercent = getPercentLife();
	float *RGBA = getRGBA();
	float *startRGBA = getStartRGBA();
	float *endRGBA = getEndRGBA();
	for(int i = 0; i < 4; i++)
	{	RGBA[i] = startRGBA[i] + (endRGBA[i]-startRGBA[i])*flPercent;
	}
}

// Get Percent Life
float CFXParticle::getPercentLife( void )
{	return (float(1)-(getCurrentLife()/getStartLife()));
}

// Is dead
bool CFXParticle::isDead( void )
{	if(getCurrentLife()<=0)
		return true;
	return false;
}