//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_func_dust.h"
#include "func_dust_shared.h"
#include "c_te_particlesystem.h"
#include "env_wind_shared.h"
#include "engine/IEngineTrace.h"
#include "tier0/vprof.h"
#include "ClientEffectPrecacheSystem.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_Func_Dust, DT_Func_Dust, CFunc_Dust )
	RecvPropInt( RECVINFO(m_Color) ),
	RecvPropInt( RECVINFO(m_SpawnRate) ),
	RecvPropFloat( RECVINFO(m_flSizeMin) ),
	RecvPropFloat( RECVINFO(m_flSizeMax) ),
	RecvPropInt( RECVINFO(m_LifetimeMin) ),
	RecvPropInt( RECVINFO(m_LifetimeMax) ),
	RecvPropInt( RECVINFO(m_DustFlags) ),
	RecvPropInt( RECVINFO(m_SpeedMax) ),
	RecvPropInt( RECVINFO(m_DistMax) ),
	RecvPropInt( RECVINFO( m_nModelIndex ) ),
	RecvPropDataTable( RECVINFO_DT( m_Collision ), 0, &REFERENCE_RECV_TABLE(DT_CollisionProperty) ),
END_RECV_TABLE()



// ------------------------------------------------------------------------------------ //
// CDustEffect implementation.
// ------------------------------------------------------------------------------------ //
#define DUST_ACCEL 50


void CDustEffect::RenderParticles( CParticleRenderIterator *pIterator )
{
	const CFuncDustParticle *pParticle = (const CFuncDustParticle*)pIterator->GetFirst();
	while ( pParticle )
	{
		// Velocity.
		float flAlpha;
		if( m_pDust->m_DustFlags & DUSTFLAGS_FROZEN )
		{
			flAlpha = 1;
		}
		else
		{
			// Alpha.
			float flAngle = (pParticle->m_flLifetime / pParticle->m_flDieTime) * M_PI * 2;
			flAlpha = sin( flAngle - (M_PI * 0.5f) ) * 0.5f + 0.5f;
		}

		Vector tPos;
		TransformParticle( g_ParticleMgr.GetModelView(), pParticle->m_Pos, tPos );
		float sortKey = (int) tPos.z;

		if( -tPos.z <= m_pDust->m_DistMax )
		{
			flAlpha *= 1 + (tPos.z / m_pDust->m_DistMax);

			// Draw it.
			float flSize = pParticle->m_flSize;
			if( m_pDust->m_DustFlags & DUSTFLAGS_SCALEMOTES )
				flSize *= -tPos.z;

			RenderParticle_Color255Size(
				pIterator->GetParticleDraw(),
				tPos,
				Vector( m_pDust->m_Color.r, m_pDust->m_Color.g, m_pDust->m_Color.b ),
				flAlpha * m_pDust->m_Color.a,
				flSize
				);
		}

		pParticle = (const CFuncDustParticle*)pIterator->GetNext( sortKey );
	}
}


void CDustEffect::SimulateParticles( CParticleSimulateIterator *pIterator )
{
	Vector vecWind;
	GetWindspeedAtTime( gpGlobals->curtime, vecWind );


	CFuncDustParticle *pParticle = (CFuncDustParticle*)pIterator->GetFirst();
	while ( pParticle )
	{
		// Velocity.
		if( !(m_pDust->m_DustFlags & DUSTFLAGS_FROZEN) )
		{
			// Kill the particle?
			pParticle->m_flLifetime += pIterator->GetTimeDelta();
			if( pParticle->m_flLifetime >= pParticle->m_flDieTime )
			{
				pIterator->RemoveParticle( pParticle );
			}
			else
			{
				for ( int i = 0 ; i < 2 ; i++ )
				{
					if ( pParticle->m_vVelocity[i] < vecWind[i] )
					{
						pParticle->m_vVelocity[i] += ( gpGlobals->frametime * DUST_ACCEL );

						// clamp
						if ( pParticle->m_vVelocity[i] > vecWind[i] )
							pParticle->m_vVelocity[i] = vecWind[i];
					}
					else if (pParticle->m_vVelocity[i] > vecWind[i] )
					{
						pParticle->m_vVelocity[i] -= ( gpGlobals->frametime * DUST_ACCEL );

						// clamp.
						if ( pParticle->m_vVelocity[i] < vecWind[i] )
							pParticle->m_vVelocity[i] = vecWind[i];
					}
				}

				// Apply velocity.
				pParticle->m_Pos.MulAdd( pParticle->m_Pos, pParticle->m_vVelocity, pIterator->GetTimeDelta() );
			}
		}

		pParticle = (CFuncDustParticle*)pIterator->GetNext();
	}
}


// ------------------------------------------------------------------------------------ //
// C_Func_Dust implementation.
// ------------------------------------------------------------------------------------ //

C_Func_Dust::C_Func_Dust() : m_Effect( "C_Func_Dust" )
{
	m_Effect.m_pDust = this;
	m_Effect.SetDynamicallyAllocated( false ); // So it doesn't try to delete itself.
}


C_Func_Dust::~C_Func_Dust()
{
}

CLIENTEFFECT_REGISTER_BEGIN( PrecacheFuncDust )
CLIENTEFFECT_MATERIAL( "particle/sparkles" )
CLIENTEFFECT_REGISTER_END()

void C_Func_Dust::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );

	if( updateType == DATA_UPDATE_CREATED )
	{
		m_hMaterial = m_Effect.GetPMaterial( "particle/sparkles" );

		m_Effect.SetSortOrigin( WorldSpaceCenter( ) );

		// Let us think each frame.
		SetNextClientThink( CLIENT_THINK_ALWAYS );
		
		// If we're setup to be frozen, just make a bunch of particles initially.
		if( m_DustFlags & DUSTFLAGS_FROZEN )
		{
			for( int i=0; i < m_SpawnRate; i++ )
			{
				AttemptSpawnNewParticle();
			}
		}
	}

	m_Spawner.Init( m_SpawnRate ); // N particles per second
}


void C_Func_Dust::ClientThink()
{
	// If frozen, don't make new particles.
	if( m_DustFlags & DUSTFLAGS_FROZEN )
		return;

	// Spawn particles?
	if( m_DustFlags & DUSTFLAGS_ON )
	{
		float flDelta = min( gpGlobals->frametime, 0.1f );
		while( m_Spawner.NextEvent( flDelta ) )
		{
			AttemptSpawnNewParticle();
		}
	}

	// Tell the particle manager our bbox.
	Vector vWorldMins, vWorldMaxs;
	CollisionProp()->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
	vWorldMins -= Vector( m_flSizeMax, m_flSizeMax, m_flSizeMax );
	vWorldMaxs += Vector( m_flSizeMax, m_flSizeMax, m_flSizeMax );
	m_Effect.GetBinding().SetBBox( vWorldMins, vWorldMaxs );
}


bool C_Func_Dust::ShouldDraw()
{
	return false;
}


void C_Func_Dust::AttemptSpawnNewParticle()
{
	// Find a random spot inside our bmodel.
	static int nTests=10;

	for( int iTest=0; iTest < nTests; iTest++ )
	{
		Vector vPercent = RandomVector( 0, 1 );
		Vector vTest = WorldAlignMins() + (WorldAlignMaxs() - WorldAlignMins()) * vPercent;

		int contents = enginetrace->GetPointContents_Collideable( GetCollideable(), vTest );
		if( contents & CONTENTS_SOLID )
		{
			CFuncDustParticle *pParticle = (CFuncDustParticle*)m_Effect.AddParticle( 10, m_hMaterial, vTest );
			if( pParticle )
			{
				pParticle->m_vVelocity = RandomVector( -m_SpeedMax, m_SpeedMax );

				pParticle->m_flLifetime = 0;
				pParticle->m_flDieTime = RemapVal( rand(), 0, RAND_MAX, m_LifetimeMin, m_LifetimeMax );

				if( m_DustFlags & DUSTFLAGS_SCALEMOTES )
					pParticle->m_flSize = RemapVal( rand(), 0, RAND_MAX, m_flSizeMin/10000.0f, m_flSizeMax/10000.0f );
				else
					pParticle->m_flSize = RemapVal( rand(), 0, RAND_MAX, m_flSizeMin, m_flSizeMax );
			
				pParticle->m_Color = m_Color;
			}

			break;
		}
	}
}

//
// Dust
//

//-----------------------------------------------------------------------------
// Spew out dust!
//-----------------------------------------------------------------------------
void FX_Dust( const Vector &vecOrigin, const Vector &vecDirection, float flSize, float flSpeed )
{
	VPROF_BUDGET( "FX_Dust", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
	CSmartPtr<CSimpleEmitter> pSimple = CSimpleEmitter::Create( "dust" );
	pSimple->SetSortOrigin( vecOrigin );
	pSimple->SetNearClip( 32, 64 );

	SimpleParticle	*pParticle;

	Vector	offset;

	int	numPuffs = (flSize*0.5f);

	if ( numPuffs < 1 )
		numPuffs = 1;
	if ( numPuffs > 32 )
		numPuffs = 32;

	float speed = flSpeed * 0.1f;

	if ( speed < 0 )
		speed = 1.0f;
	
	if (speed > 48.0f )
		speed = 48.0f;

	//FIXME: Better sampling area
	offset = vecOrigin + ( vecDirection * flSize );

	//Find area ambient light color and use it to tint smoke
	Vector	worldLight = WorldGetLightForPoint( offset, true );

	PMaterialHandle	hMaterial[2];

	hMaterial[0] = pSimple->GetPMaterial("particle/particle_smokegrenade");
	hMaterial[1] = pSimple->GetPMaterial("particle/particle_noisesphere");

	//Throw puffs
	for ( int i = 0; i < numPuffs; i++ )
	{
		offset.Random( -(flSize*0.25f), flSize*0.25f );
		offset += vecOrigin + ( vecDirection * flSize );

		pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof(SimpleParticle), hMaterial[random->RandomInt(0,1)], offset );

		if ( pParticle != NULL )
		{			
			pParticle->m_flLifetime		= 0.0f;
			pParticle->m_flDieTime		= random->RandomFloat( 0.4f, 1.5f );
			
			pParticle->m_vecVelocity = vecDirection * random->RandomFloat( speed*0.5f, speed ) * i;

			pParticle->m_vecVelocity[2] = 0.0f;

			int	color = random->RandomInt( 48, 64 );

			pParticle->m_uchColor[0] = (color+16) + ( worldLight[0] * (float) color );
			pParticle->m_uchColor[1] = (color+8) + ( worldLight[1] * (float) color );
			pParticle->m_uchColor[2] = color + ( worldLight[2] * (float) color );

			pParticle->m_uchStartAlpha	= random->RandomInt( 32, 128 );
			pParticle->m_uchEndAlpha	= 0;
			pParticle->m_uchStartSize	= random->RandomInt( 2, 8 );
			pParticle->m_uchEndSize		= random->RandomInt( 24, 48 );
			pParticle->m_flRoll			= random->RandomInt( 0, 360 );
			pParticle->m_flRollDelta	= random->RandomFloat( -1.0f, 1.0f );
		}
	}
}


class C_TEDust: public C_TEParticleSystem
{
public:
	DECLARE_CLASS( C_TEDust, C_TEParticleSystem );
	DECLARE_CLIENTCLASS();

				C_TEDust();
	virtual		~C_TEDust();

public:
	virtual void	PostDataUpdate( DataUpdateType_t updateType );
	virtual	bool	ShouldDraw() { return true; }

public:

	float		m_flSize;
	float		m_flSpeed;
	Vector		m_vecDirection;

protected:
	void		GetDustColor( Vector &color );
};

IMPLEMENT_CLIENTCLASS_EVENT_DT( C_TEDust, DT_TEDust, CTEDust )
	RecvPropFloat(RECVINFO(m_flSize)),
	RecvPropFloat(RECVINFO(m_flSpeed)),
	RecvPropVector(RECVINFO(m_vecDirection)),
END_RECV_TABLE()

//==================================================
// C_TEDust
//==================================================

C_TEDust::C_TEDust()
{
}

C_TEDust::~C_TEDust()
{
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bNewEntity - whether or not to start a new entity
//-----------------------------------------------------------------------------
void C_TEDust::PostDataUpdate( DataUpdateType_t updateType )
{
	FX_Dust( m_vecOrigin, m_vecDirection, m_flSize, m_flSpeed );
}


void TE_Dust( IRecipientFilter& filter, float delay,
			 const Vector &pos, const Vector &dir, float size, float speed )
{
	FX_Dust( pos, dir, size, speed );
}
