//-----------------------------------------------------------------------------
// FXTunnel2
//-----------------------------------------------------------------------------
#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;

struct FrenetFrame
{
	vec3_t p,n,t,b;
};

inline void Cross(vec3_t& o, const vec3_t& a, const vec3_t& b) { o = Vector3D::Cross(a,b); }
inline void Normalize(vec3_t& v) { v.Normalize(); }
inline float Dot(const vec3_t& a, const vec3_t& b) { return Vector3D::Dot(a, b);}
inline float Length(const vec3_t& v) { return v.Length(); }
inline float SqrLength(const vec3_t& v) { return v.SquaredLength(); }


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

// temp
namespace
{

	float Radius1 = 10.0f;
	float P = 3.0f;
	float Q = 4.0f;
	float WarpHeight = -.5f;
	float WarpFreq = c_2pi;

}

//-----------------------------------------------------------------------------
// Util
//-----------------------------------------------------------------------------
typedef void (*CurveFn_t)(float u, vec3_t& p);

void Circle(float t, vec3_t& p)
{
	float x,y;
	_asm
	{
		fld t			;// FPU: t
		fsincos			;// FPU: sin(t), cos(t)
		fstp x			;// FPU: sin(t)
		fstp y			;// FPU: 
	}
	
	p.x = x;
	p.y = y;
	p.z = 0.0f;
}

//-----------------------------------------------------------------------------
// Name: ParamKnot
// Desc: u : (0,1)
//-----------------------------------------------------------------------------
void ParamKnot(float t, vec3_t& p)
{
	float fac = (2+sin(Q*t))*.5f;
	p.x = Radius1 * fac * cos(P * t);
	p.z = Radius1 * fac * sin(P * t);
	p.y = Radius1 * fac * cos(Q * t);
}

//-----------------------------------------------------------------------------
// Name: GetFrenetFrame
// Desc: 
//-----------------------------------------------------------------------------
void GetFrenetFrame(CurveFn_t Curve, float t, float deltaT, FrenetFrame& ff)
{
	vec3_t f0,f1,t0,n0;
	
	Curve(t, f0);
	Curve(t+deltaT, f1);

	// TODO: instead of 2 curve evaluations use previous and next point
	// or sample a little behind and a little ahead

	t0 = f1 - f0;
	n0 = f1 + f0;

	Cross(ff.b, t0, n0);
	Cross(ff.n, ff.b, t0);

	float lengthN = Length(ff.n);
	float lengthB = Length(ff.b);
	if (lengthB != 0.0f)
		ff.b = (1.0f / lengthB) * ff.b;
	if (lengthN != 0.0f)
		ff.n = (1.0f / lengthN) * ff.n;

	ff.p = f0;
	ff.t = t0;
}

#pragma warning (disable: 4035) // just want to disable for this fun... how?
float sinpa2(float t, float one_period, float amp)
{
	_asm
	{
		fld c_2pi			;// FPU: 2pi
		fmul one_period		;// FPU: 2pi*(1/period)
		fmul t				;// FPU: 2pi/period*t
		fsin				;// FPU: sin(2pi/period*t)
		fmul amp			;// FPU: sin(2pi/period*t)*amp
	}
	//float r2 = amp*sin(t*(c_2pi/period));
	//return r;
}


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 P Q external", 
		"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"
		"P,Q: params of pq-torus\r\n"
		"external: if non-zero view torus from outside\r\n"
	},

	{"shader=enviroment ringRes=8 numRings=200 P=3 Q=4 external=0", "Default settings (inside)"},
	{"shader=enviroment ringRes=8 numRings=200 P=3 Q=4 external=1", "Default settings (outside)"},
};

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

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


}

//-----------------------------------------------------------------------------
// FXTunnel2
//-----------------------------------------------------------------------------
class FXTunnel2 : public SM_DemoEffect
{
private:
	FXMesh					m_mesh;
	int						m_shader;
	int						m_p;
	int						m_q;
	int						m_numCtrl;
	int						m_numRings;
	float					m_lastTime;
	bool					m_bOutside;

public:              
	FXTunnel2(char const* pcName) 
		: SM_DemoEffect(pcName)
	{
	}

	virtual ~FXTunnel2()
	{
	}

	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_numCtrl = parse.GetInt("ringRes", 8);
		m_numRings = parse.GetInt("numRings", 200);
		m_p = parse.GetInt("P", 3);
		m_q = parse.GetInt("Q", 4);
		m_bOutside = parse.GetBool("external", false);
		const char* szShader = parse.GetStr("shader", "enviroment");

		//
		// init vb and ib
		//
		m_mesh.Init(sizeof(ntxvertex), ntxvertex_fvf, false);
		m_mesh.PrimType() = D3DPT_TRIANGLESTRIP;

		// init vb
		InitVB();

		// init ib
		InitIB();

		m_mesh.Optimize(false);

		//
		// 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;
		
#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?
		if (m_bOutside)
		{
			const float r = 30.0f;
			LookAt(vec3_t(r*cos(time*c_pi), 0, r*sin(time*c_pi)), vec3_t(0,0,0), vec3_t(0,1,0));
		}
		else
		{
			// move along torus
			FrenetFrame ff;
			GetFrenetFrame(ParamKnot, -time*.3f/*fmodf(time, c_2pi)*/, 1.0f/float(m_numRings-1), ff);
			
			const vec3_t& pos = ff.p;
			const vec3_t& dir = ff.t;
			const vec3_t& up = ff.b;
			LookAt(pos, pos + dir, up);
		}

//		SetWireFrame(true);

		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 0
			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;
			}
#endif
		}

		iReturn  = 0;

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

private:

	void InitIB()
	{
		m_mesh.TriStripIndices(m_numCtrl, m_numRings);
	}

	void InitVB()
	{
		const float time = 0;
		P = m_p;
		Q = m_q;

		for (int y=0; y<m_numRings; y++)
		{
			const float v = (float)y / (float)(m_numRings-1);
			const float v2pi = c_2pi*v;

			FrenetFrame ff;
			GetFrenetFrame(ParamKnot, v2pi, 1.0f/float(m_numRings-1), ff);
	#if 1
			const float r3 = sinpa2(v2pi+time*.55f, 1.0f/(c_pi/32), .55f);
			const float r = 2;//r3+2.5+sinpa2(v2pi+time*.55f, 1.0f/(c_pi/6), 1);
	#else
			const float r3 = sinpa2(v2pi+time*.55f, 1.0f/(c_pi/32), .55f);
			const float r = r3+2.5+sinpa2(v2pi+time*.55f, 1.0f/(c_pi/32), 1);
	#endif

			for (int x=0; x<m_numCtrl; x++)
			{
				const float u = (float)x / (float)(m_numCtrl-1);
					
				vec3_t p;
		
				float p2x,p2y;
				const float t = u*c_2pi;
				_asm
				{
					fld t			;// FPU: t
					fsincos			;// FPU: sin(t), cos(t)
					fstp p2x		;// FPU: sin(t)
					fstp p2y		;// FPU: 
				}

				p = ff.p + (r*p2y)*ff.n + (r*p2x)*ff.b;
				m_mesh.AddVert(&ntxvertex(p, vec3_t(0,0,0), u,v));
			}
		}
		m_mesh.GenNormals(m_numCtrl, m_numRings, true);
		
	}
};

DEFINE_EFFECT(FXTunnel2)
FXTunnel2 Efecto00("TUNNEL2_00");
FXTunnel2 Efecto01("TUNNEL2_01");

