//-----------------------------------------------------------------------------
// FXExplode
//		Emits large particles slowly from center of screen and fades 
//		them out as they reach edge.  Works best with additive shader.
//
//-----------------------------------------------------------------------------
#include "SM_CommonFXPCH.h"
#include "SM_Engine3DPCH.h"
#include "SM_DemoEffect.h"
#include "SM_MaxScene.h"
#include "SM_Main.h"
#include "SM_Shader.h"
#include "SM_FVF.h"
#include "SM_Color.h"
#include "MSystemFunctions.h"
#include "fxmesh.h"
#include "FXParamParser.h"


using namespace ShaderManager;

//-----------------------------------------------------------------------------
// Types and Macros
//-----------------------------------------------------------------------------
typedef Vector3D vec3_t;
typedef Color color_t;
#define LENGTHOF(x) (sizeof(x)/sizeof(x[0]))


//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const float c_pi = 3.1415926535897932384626433f;
const float c_2pi = 3.1415926535897932384626433f*2.0f;

//-----------------------------------------------------------------------------
// Utility Functions
//-----------------------------------------------------------------------------

namespace
{
inline float frand() { return (float)(rand())/(float)RAND_MAX; }
inline float sfrand() { return 2.0*((float)(rand())/(float)RAND_MAX-.5); }


void LookAt(const vec3_t& pos, const vec3_t& at, const vec3_t& up)
{
	Matrix4X4 mat;
	mat.LookAt(pos, at, up);
	SM_D3d::Device()->SetTransform(D3DTS_VIEW, (D3DMATRIX*)&mat);
}

void GetRightUp(vec3_t& right, vec3_t& up)
{
	// get view mat
	D3DMATRIX mat;
	SM_D3d::Device()->GetTransform(D3DTS_VIEW, &mat);

	// make up, right vecs
	right = vec3_t(mat._11, mat._21, mat._31);
	up = vec3_t(mat._12, mat._22, mat._32);
}

void randVec3(vec3_t& v, const float& r)
{
	v.z = sfrand();
	const float t = c_2pi * frand();
	const float tr = r*sqrtf(1-v.z*v.z);
	v.x = tr*cos(t);
	v.y = tr*sin(t);
	v.z = v.z*r;
}

void EnableAddBlend()
{
	SM_D3d::SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	SM_D3d::SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
	SM_D3d::SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
}

void DisableAlpha()
{
	SM_D3d::SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
}

void EnableLighting(BOOL enable)
{ 
	SM_D3d::SetRenderState(D3DRS_LIGHTING, enable); 
}


SM_DemoEffect::Helper LoadHelp[] =
{
	{"shader fov partSize partVel partLife numParts", 
		"shader: what to shade particles with\r\n"
		"fov: camera field of view\r\n"
		"partSize: size of individual particles\r\n"
		"partVel: particle speed\r\n"
		"partLife: how many seconds particles live\r\n"
		"numParts: number of particles\r\n"},

	{"shader=particle fov=60 partSize=1 partVel=.8 partLife=3.5 numParts=200", "Default settings."},
};

SM_DemoEffect::Helper CommandsHelp[] =
{
	{"",""}
};

}


//-----------------------------------------------------------------------------
// ParticleSystem
//-----------------------------------------------------------------------------

class ParticleSystem
{
protected:

	struct Particle
	{
		Particle*	pnext;
		vec3_t		pos;
		vec3_t		vel;
		float		energy;
	};
	
	vec3_t		m_pos;			// location of emitter
	vec3_t		m_target;		// destination of emitter for this frame
	DWORD		m_clr;			// color of parts

	float		m_nextEmit;		// next time to emit a particle

	Particle*	m_pool;
	Particle*	m_liveParts;
	Particle*	m_freeParts;

	FXMesh		m_mesh;

public:
	int		m_numParts;
	float	m_emitRecharge;
	float	m_partSize;
	float	m_partLife;
	float	m_partVel;
	float	m_time;

	ParticleSystem() : m_pool(0), m_liveParts(0), m_freeParts(0), m_numParts(200),
		m_emitRecharge(.02f), m_partSize(1.0f), m_partLife(3.5f), m_partVel(.8f),
		m_clr(0xffffffff)
	{
	}

	void Init(DWORD clr)
	{
		m_clr = clr;

		// allocate particle pool
		m_pool = new Particle[m_numParts];

		for (int i=0; i<m_numParts-1; i++)
		{
			m_pool[i].pnext = &m_pool[i+1];
		}
		m_pool[m_numParts-1].pnext = 0;

		m_freeParts = &m_pool[0];
		m_liveParts = 0;

		m_time = 0.0f;
		m_nextEmit = 0.0f;
		m_pos = m_target = vec3_t(0,0,0);

		m_mesh.Init(sizeof(ctxvertex), ctxvertex_fvf, false);
		m_mesh.ResizeVerts(m_numParts*6);
		m_mesh.Optimize();
	}

	void Update(const float& _time, const float& dt)
	{
		m_time += dt;
		// update particles
		Particle* part = m_liveParts;
		Particle* prev=0;
		while (part)
		{
			part->pos += part->vel * dt;
			part->energy -= dt* (1.0f/m_partLife);

			if (part->energy<0)
			{
				Particle* pnext = part->pnext;

				if (prev)
					prev->pnext = part->pnext;
				else
					m_liveParts = part->pnext;

				part->pnext = m_freeParts;
				m_freeParts = part;

				part = pnext;
			}
			else
			{	
				prev = part;
				part = part->pnext;
			}
		}

		// emit new particles
		while (m_time > m_nextEmit)
		{
			// grab a part from free list
			Particle* p = m_freeParts;
			if (p)
			{
				// take off free list and put on live
				m_freeParts = m_freeParts->pnext;
				p->pnext = m_liveParts;
				m_liveParts = p;

				// init part
				p->pos = m_pos;
				const float v = m_partVel;
				randVec3(p->vel, v);
				p->energy = 1.0f;
			}
			
			m_nextEmit += m_emitRecharge;
		}
	}

	void Render()
	{
		// make up, right vecs
		vec3_t right, up;
		GetRightUp(right, up);
		right *= m_partSize;
		up *= m_partSize;

		ctxvertex v[4];
		v[0] = ctxvertex(-right+up, D3DCOLOR_COLORVALUE(1,1,1,1), 0,0);
		v[1] = ctxvertex(right+up, D3DCOLOR_COLORVALUE(1,1,1,1), 1,0);
		v[2] = ctxvertex(right-up, D3DCOLOR_COLORVALUE(1,1,1,1), 1,1);
		v[3] = ctxvertex(-right-up, D3DCOLOR_COLORVALUE(1,1,1,1), 0,1);

		m_mesh.LockVerts(false);
		ctxvertex* pv = (ctxvertex*)m_mesh.GetVert(0);
		
		// draw each live particle
		Particle* part = m_liveParts;
		int k=0;
		int numparts=0;
		while (part)
		{
			numparts++;
		
			color_t clr = m_clr;
			clr *= part->energy;

			pv[k] = v[0]; pv[k].c = clr; pv[k++].p += part->pos;
			pv[k] = v[1]; pv[k].c = clr; pv[k++].p += part->pos;
			pv[k] = v[2]; pv[k].c = clr; pv[k++].p += part->pos;

			pv[k] = v[0]; pv[k].c = clr; pv[k++].p += part->pos;
			pv[k] = v[2]; pv[k].c = clr; pv[k++].p += part->pos;
			pv[k] = v[3]; pv[k].c = clr; pv[k++].p += part->pos;
			
			part = part->pnext;
		}

		m_mesh.UnlockVerts();

		if (numparts)
		{
			SM_D3d::SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
			SM_D3d::SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
			
			HRESULT hr;
			hr = SM_D3d::Device()->SetVertexShader(ctxvertex_fvf);
			hr = SM_D3d::Device()->SetStreamSource(0, m_mesh.GetVB(), sizeof(ctxvertex));
			hr = SM_D3d::Device()->DrawPrimitive(D3DPT_TRIANGLELIST, 0, (numparts)*2);

			SM_D3d::SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
			SM_D3d::SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);		
		}
	}

	void Destroy()
	{
		// free particle pool
		delete[] m_pool;
		m_pool = m_freeParts = m_liveParts = 0;
		
		m_mesh.Destroy();
	}

	inline void SetPartSize(float s) { m_partSize = s; }
	inline void SetPos(const vec3_t& pos) { m_pos = pos; }
};



//-----------------------------------------------------------------------------
// FXExplode implementation
//-----------------------------------------------------------------------------

class FXExplode : public SM_DemoEffect
{
private:
	int				m_shader;
	ParticleSystem	m_ps;
	float			m_lastTime;
	float			m_fov;


public:
	FXExplode(const char* sz) : SM_DemoEffect(sz), m_lastTime(0), m_fov(60) {}

	int LoadArgumentsHelp     (Helper*& pHelpers)
	{
		pHelpers = LoadHelp;
		return (sizeof(LoadHelp)/sizeof(Helper));
	}

	int CommandArgumentsHelp  (Helper*& pHelpers)
	{
		pHelpers = CommandsHelp;
		return (sizeof(CommandsHelp)/sizeof(Helper));
	}

	
	int Init(const char* pcCommand)
	{
		int   iReturn=0;

		FXParamParser parse;

		parse.Parse(pcCommand);


		m_fov = parse.GetFloat("fov", 60);
		m_ps.m_partSize = parse.GetFloat("partSize", 1);
		m_ps.m_partVel = parse.GetFloat("partVel", .8f);
		m_ps.m_partLife = parse.GetFloat("partLife", 3.5f);
		m_ps.m_numParts = parse.GetInt("numParts", 200);

		// load texture for the ball
		const char* szShader = parse.GetStr("shader", "enviroment");
		m_shader = LoadShader(szShader);

		m_ps.Init(D3DCOLOR_COLORVALUE(1,1,1,1));

		//
		// load shader
		//
		m_shader = LoadShader(szShader);
		if (m_shader == -1)
		{
			::MessageBox(NULL, "Could not load shader", szShader, MB_OK);
			iReturn = -1;
			goto FAILED;
		}

		
	FAILED:

		return iReturn;        
	}

	int Start(float fTime)
	{
		return 0;
	}

	int Stop()
	{
		return 0;
	}

	int Reset()
	{
		return 0;
	}


	int Shutdown()
	{
		m_ps.Destroy();
		return 0;
	}


	int LoadChars()
	{
		return 0;
	}

	int LoadFont(const char* pcToken)
	{
		return 0;
	}


	int Command(float fTime, const char* pcCommand)
	{
		return 0;
	}

	int Run(float time)
	{
		float dt = fabs(time - m_lastTime);
		if (dt > .5f) dt = .5f;

		SM_D3d::Device()->SetTransform(D3DTS_WORLD, (D3DMATRIX*) &Matrix4X4::Identity);

		RenderContext RC;   

	    RC.Set(Vector3D(0.0f, 0.0f, -5),
			   Quaternion(1.0f, 0.0f, 0.0f, 0.0f),
			   m_fov,
			   0.75f,
			   1.0f,
			   200.0f);

	    RC.SetViewport(0, 0, 640.0f, 480.0f);  
	  
		RC.SyncRasterizer();
	    RC.UpdateFrustum();
	
		ShaderManager::SetRenderContext(&RC);

		m_ps.Update(time, dt);
		
		Shader* ps = GetShader(m_shader);
		ASSERT(ps);
		if (ps)
		{
			for (int i=0; i<ps->m_uPasses; i++)
			{
				ps->SetShaderState(i);
				EnableLighting(false);
				m_ps.Render();
			}
		}

		m_lastTime = time;

		return 1;
	}

};

DEFINE_EFFECT(FXExplode)
FXExplode Efecto00("EXPLODE_00");
FXExplode Efecto01("EXPLODE_01");
