//-----------------------------------------------------------------------------
// FXTunnel1
//-----------------------------------------------------------------------------
#include "SM_CommonFXPCH.h"
#include "SM_Engine3DPCH.h"
#include "SM_DemoEffect.h"
#include "SM_FVF.h"
#include "SM_Shader.h"
#include "fxmesh.h"
#include "FXParamParser.h"


using namespace ShaderManager;

//-----------------------------------------------------------------------------
// Types and Macros
//-----------------------------------------------------------------------------
typedef Vector3D vec3_t;


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

namespace
{

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 SetWireFrame(bool wire)
{
	SM_D3d::Device()->SetRenderState(D3DRS_FILLMODE, wire?D3DFILL_WIREFRAME:D3DFILL_SOLID);
}


SM_DemoEffect::Helper LoadHelp[] =
{
	{"shader ringRes numRings tunnelLength pinch", 
		"shader: what to shade tunnel with\r\n"
		"ringRes: resolution of ring (number of points)\r\n"
		"numRings: how many rings tunnel has\r\n"
		"tunnelLength: length of tunnel\r\n"
		"pinch: if non-zero then tunnel is closed\r\n"
	},

	{"shader=tunnel1 ringRes=64 numRings=50 tunnelLength=50 pinch=0", "Default settings"},
	{"shader=enviroment ringRes=64 numRings=50 tunnelLength=50 pinch=1", "Environment mapped and pinched"},
};

SM_DemoEffect::Helper CommandsHelp[] =
{
	{"SETSPOKES n delay","Set number of ridges (spokes) on tunnel wall, transition takes 'delay' seconds"},
	{"SETINNER radius delay", "Set inner radius of tunnel"},
	{"SETOUTER radius delay", "Set outer radius of tunnel"},
	{"TIMEMULT multiplier delay", "Make time move faster (>1) or slower (<1)"}
};

class CommandVal
{
	float prev;
	float cur;
	float transTime;
	float totalTime;

public:

	CommandVal() : prev(0), cur(0), transTime(1), totalTime(1) {}
	CommandVal(float initial) : prev(initial), cur(initial), transTime(1), totalTime(1) {}
	
	void NewVal(float val, float delay) 
	{ 
		prev = cur; 
		cur = val; 
		totalTime = delay; 
		transTime = 0; 
		if (totalTime == 0) 
		{
			// avoid div by 0
			totalTime = 1;
			transTime = 1;
		}
	}
	
	float GetInterpFactor()
	{
		const float t = transTime/totalTime;
		return (t <= 1) ? t : 1;
	}

	float GetInterpVal()
	{
		const float t = GetInterpFactor();
		return prev + t*(cur-prev);
	}

	float GetPrev() { return prev; }
	float GetCur() { return cur; }
	float GetTransTime() { return transTime; }
	void Update(float dt) { transTime += dt; }
};


}

//-----------------------------------------------------------------------------
// FXTunnel1
//-----------------------------------------------------------------------------
class FXTunnel1 : public SM_DemoEffect
{
private:
	FXMesh					m_mesh;
	int						m_shader;
	int						m_numCtrl;
	int						m_numRings;
	float					m_t1length;
	CommandVal				m_spokes;
	CommandVal				m_innerRadius;
	CommandVal				m_outerRadius;
	CommandVal				m_timeMult;
	float					m_lastTime;
	bool					m_bPinch;
	float					m_lastTimeWarp;

public:              
	FXTunnel1(char const* pcName) 
		: SM_DemoEffect(pcName)
		, m_spokes(1)
		, m_lastTime(0)
		, m_innerRadius(3)
		, m_outerRadius(6)
		, m_timeMult(1)
		, m_bPinch(true)
		, m_lastTimeWarp(0)
	{
	}

	virtual ~FXTunnel1()
	{
	}

	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);

		const char* szShader = parse.GetStr("shader", "enviroment");
		m_numCtrl = parse.GetInt("ringRes", 64);
		m_numRings = parse.GetInt("numRings", 50);
		m_t1length = parse.GetFloat("tunnelLength", 50);
		m_bPinch = parse.GetBool("pinch", false);

		//
		// init vb and ib
		//

		m_mesh.Init(sizeof(cntxvertex), cntxvertex_fvf, false);
		m_mesh.PrimType() = D3DPT_TRIANGLELIST;

		// init vb
		m_mesh.ResizeVerts(m_numCtrl*m_numRings);

		// init ib

		int k=0;
		for (int ring=0; ring<m_numRings-1; ring++)
		{
			for (int i=0; i<m_numCtrl; i++)
			{
				const int o = ring * m_numCtrl;
				
				m_mesh.AddIndex(o + (i+1)%m_numCtrl);
				m_mesh.AddIndex(o + i);
				m_mesh.AddIndex(o + i + m_numCtrl);
				
				m_mesh.AddIndex(o+(i+1)%m_numCtrl);
				m_mesh.AddIndex(o+i + m_numCtrl);
				m_mesh.AddIndex(o+(i+1)%m_numCtrl + m_numCtrl);
			}
		}

		//
		// 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 Shutdown()
	{
		m_mesh.Destroy();
		return 0;
	}

	int Start(float fTime)
	{
		return 0;
	}

	int Stop()
	{
		return 0;
	}

	int Reset()
	{
		return 0;
	}

	int Run(float fTime)
	{
		float time = fTime;
		float dt = fabs(time - m_lastTime);
		if (dt > .5f) dt = .5f;
		m_lastTime = time;
		
		m_spokes.Update(dt);
		m_innerRadius.Update(dt);
		m_outerRadius.Update(dt);
		m_timeMult.Update(dt);

		time += (time - m_lastTimeWarp)*m_timeMult.GetInterpVal();
		
		const float mytime = GetTimeFromZ(2.0f, time);
		vec3_t o, at;
		GetOffset(o, mytime);
		GetOffset(at, GetTimeFromZ(-1.0f, time));
		o.z = 4.0f;
		at.z = -1.0f;
		at = o+o-at;

		// make geom
		unsigned int si=0, si2=0;
		m_mesh.LockVerts(false);
		cntxvertex* pv = (cntxvertex*)m_mesh.GetVert(0);

		for (int ring=0; ring<m_numRings; ring++)
		{
			const float tr = (float)ring/(float)(m_numRings-1);
			const float z = tr*0.0f + (1.0f-tr)*m_t1length;
			const float mytime = GetTimeFromZ(z, time);//time*5.0f - tr * 12.0f;

			const float tblend = .5f*(1.0f+sin(mytime));
		
			vec3_t o;
			GetOffset(o, mytime);
			
			float radiusPinch = 1.0f;
			if (m_bPinch && tr < .1f)
				radiusPinch = tr/.1f;
			
			for (int i=0; i<m_numCtrl; i++)
			{
				const float t = (float)i/(float)(m_numCtrl-1);
				const float a = t * c_2pi;
				const float r = radiusPinch * GetRadius(mytime, a);
							
				const float urep = 8.0f;
				const float vrep = 4.0f;
				cntxvertex v(o+vec3_t(r*cos(a), r*sin(a), z), vec3_t(0,0,0), D3DCOLOR_COLORVALUE(tr*1,tr*1,tr*1,1), t*urep+mytime*.1f, mytime*.2f*vrep);
				pv[ring*m_numCtrl + i] = v;
			}

		}

		m_mesh.GenNormals(m_numCtrl, m_numRings, true);
		m_mesh.UnlockVerts();
		
#if 1
	    RenderContext RC;   
		RC.Set(Vector3D(0.0f, 0.0f, -2.5f),
			   Quaternion(1.0f, 0.0f, 0.0f, 0.0f),
			   90,
			   0.75f,
			   1.0f,
			   200.0f);

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

		// does rc have to be updated?  most likely, but best
		// way to do that?
		LookAt(o, at, vec3_t(0,1,0));

		Shader* ps = GetShader(m_shader);
		if (ps)
		{
			for (int i=0; i<ps->m_uPasses; i++)
			{
				ps->SetShaderState(i);
				m_mesh.Render();
			}
		}

		return 1;
	}

	int Command(float fTime, const char* pcCommand)
	{
		SM_DemoEffect::Command(fTime, pcCommand);
		char* pcCopy =0;
		char* pcToken;
		int   iReturn=-1;

		pcCopy=new char[strlen(pcCommand)+1];
		strcpy(pcCopy, pcCommand);

		pcToken=strtok(pcCopy, " \t");

		if (pcToken)
		{
			if (stricmp(pcToken, "SETSPOKES")==0)
			{
				int n = SafeAtoi(strtok(0, " \t"));
				float delay = SafeAtof(strtok(0, " \t"));
				m_spokes.NewVal(n, delay);
			}
			else if (stricmp(pcToken, "SETINNER")==0)
			{
				float r = SafeAtof(strtok(0, " \t"));
				float delay = SafeAtof(strtok(0, " \t"));
				m_innerRadius.NewVal(r, delay);
			}
			else if (stricmp(pcToken, "SETOUTER")==0)
			{
				float r = SafeAtof(strtok(0, " \t"));
				float delay = SafeAtof(strtok(0, " \t"));
				m_outerRadius.NewVal(r, delay);
			}
			else if (stricmp(pcToken, "TIMEMULT")==0)
			{
				float v = SafeAtof(strtok(0, " \t"));
				float delay = SafeAtof(strtok(0, " \t"));
				m_timeMult.NewVal(v, delay);
				m_lastTimeWarp = fTime;
			}
		}

		iReturn  = 0;

		if (pcCopy) { delete[] pcCopy; pcCopy=0; }
		return iReturn;
	}  

private:
	void GetOffset(vec3_t& v, const float& time)
	{
		const float or = 1.0f;
	/*
		v.x = or*cos(-time)*cos(-time*.05234f)+.05f*or*cos(-time*2.342f);
		v.y = or*sin(-time)*cos(-time*.0123f);
		v.z = 0;
	*/
		const float t = time*.51233f;
		const float r = 5.0f;
		v.x = cos(t)+cos(.5f*t);
		v.y = sin(t)*sin(.345f*t);
		v.z = 0;
		v *= r;
	}

	float GetTimeFromZ(const float& z, const float& time)
	{
		return time*4.0f + z*.1123f;
	}

	float GetRadius(const float& ttime, const float& a)
	{
		const float time = ttime*.25f;
		const float innerRadius = m_innerRadius.GetInterpVal();
		const float outerRadius = m_outerRadius.GetInterpVal();
		const float r1 = innerRadius + (.5f*sin(a*m_spokes.GetPrev()+time*2.0f) + .5f)*(outerRadius-innerRadius);
		const float r2 = innerRadius + (.5f*sin(a*m_spokes.GetCur() +time*2.0f) + .5f)*(outerRadius-innerRadius);
		return r1 + m_spokes.GetInterpFactor()*(r2-r1);
#if 0
  		const float t1 = 4;
  		const float t2 = 8;
  		const float t3 = 12;
  		if (time <= t1)
  		{
  			const float t = time / t1;
  			const float r1 = 5.0f + 2.0f*sin(a*3.0f+time*2.0f);
  			const float r2 = 5.0f + 1.0f*sin(a*5.0f-time*8.7f);
  			return (1.0f-t)*r1 + t*r2;
  		}
  		else if (time <= t2)
  		{
  			const float t = (time-t1)/(t2-t1);
  			const float r1 = 5.0f + 2.0f*sin(a*5.0f-time*8.7f);
  			const float r2 = 10.0f + 2.0f*sin(a*2.0f+time);
  			return (1.0f-t)*r1 + t*r2;
  		}
  		else if (time <= t3)
  		{
  			const float t = (time-t2)/(t3-t2);
  			const float r1 = 10.0f + 2.0f*sin(a*2.0f+time);
  			const float r2 = 5.0f + 2.0f*sin(a*4.0f)*cos(a*2.0f-time*5.0f);
  			return (1.0f-t)*r1 + t*r2;
  		}
  
  		return 5.0f + 2.0f*sin(a*4.0f)*cos(a*2.0f-time*5.0f);
#endif
	}
};

DEFINE_EFFECT(FXTunnel1)
FXTunnel1 Efecto00("TUNNEL1_00");
FXTunnel1 Efecto01("TUNNEL1_01");

