//-----------------------------------------------------------------------------
// FXDistortBall
//		Deforms sphere by spline separately along x,y,z axes
//
//-----------------------------------------------------------------------------
#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 "MSystemFunctions.h"
#include "fxmesh.h"
#include "FXParamParser.h"

using namespace ShaderManager;

//-----------------------------------------------------------------------------
// Types and Macros
//-----------------------------------------------------------------------------
typedef Vector3D vec3_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
{

template<typename T>
T spline(float a, const T* ctrl, int numctrl)
{
	ASSERT(a>=0);
	ASSERT(a<=1);

	int numKnots = numctrl - 3;

	// figure out which knot we're on
	float t= a * (float)numKnots;
	int knot = floorf(t);

	// find u
	float u = t - (float)knot;
	float u2 = u*u;
	float u3 = u2*u;
	float ui = 1.0f - u;

	// find basis vals
	float b[4];
	b[0] = ui*ui*ui;
	b[1] = 3.0f*u3 - 6.0f*u2 + 4.0f;
	b[2] = -3.0f*u3 + 3.0f*u2 + 3.0f*u + 1.0f;
	b[3] = u3;
	
	T v = b[0]*ctrl[knot];
	v += b[1]*ctrl[knot+1];
	v += b[2]*ctrl[knot+2];
	v += b[3]*ctrl[knot+3];
	
	v *= 1.0f/6.0f;

	return v;
}

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

SM_DemoEffect::Helper LoadHelp[] =
{
	{"shader thetaSteps phiSteps", 
		"shader: what to shade ball with\r\n"
		"thetaSteps: steps along theta\r\n"
		"phiSteps: steps along phi\r\n"},

	{"shader=distortBall thetaSteps=40 phiSteps=40", "Default settings."},
};

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

}


//-----------------------------------------------------------------------------
// FXDistortBall implementation
//-----------------------------------------------------------------------------

class FXDistortBall : public SM_DemoEffect
{
private:
	FXMesh m_mesh;
	float m_s1[6];
	float m_s2[6];
	int m_shader;
	int m_thetaSteps;
	int m_phiSteps;


public:
	FXDistortBall(const char* sz) : SM_DemoEffect(sz) {}

	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_thetaSteps = parse.GetFloat("thetaSteps", 40);
		m_phiSteps = parse.GetFloat("phiSteps", 40);

		// load texture for the ball
		m_shader = LoadShader(szShader);

		// init mesh
		m_mesh.Init(sizeof(ntxvertex), ntxvertex_fvf, false);
		m_mesh.PrimType() = D3DPT_TRIANGLESTRIP;
		m_mesh.TriStripIndices(m_thetaSteps, m_phiSteps);

		//
		// 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_mesh.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)
	{
		Update(time);

		float a = time*3.5f;
		float tr = 2.0f+4.0f;//*sin(a/3.333);
		
		//
		// update splines
		//
		m_s1[0] = .5*(1.5 + sin(0   *tr*c_pi + a));
		m_s1[1] = .5*(1.5 + sin(.2  *tr*c_pi + a));
		m_s1[2] = .5*(1.5 + sin(.4  *tr*c_pi + a));
		m_s1[3] = .5*(1.5 + sin(.6  *tr*c_pi + a));
		m_s1[4] = .5*(1.5 + sin(.8  *tr*c_pi + a));
		m_s1[5] = .5*(1.5 + sin(1   *tr*c_pi + a));
		
		m_s2[0] = 1+sin(0  *4.0f*c_pi + .3*a);
		m_s2[1] = 1+sin(.2 *4.0f*c_pi + .5*a);
		m_s2[2] = 1+sin(.4 *4.0f*c_pi + 1.1*a);
		m_s2[3] = 1+sin(.6 *4.0f*c_pi + 2.1*a);
		m_s2[4] = 1+sin(.8 *4.0f*c_pi + 1.1*a);
		m_s2[5] = 1+sin(1  *4.0f*c_pi + .5*a);


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


#if 1
		const float r = 2.5f;
		const vec3_t c = vec3_t(0,0,0);
		const vec3_t pos = vec3_t(r*cos(time*.45f), r*sin(.35f*time+c_pi/3.0f), r*sin(time*.45f));
		LookAt(pos+c, c, vec3_t(0,1,0));
#else
		SM_D3d::Device()->SetTransform(D3DTS_WORLD, (D3DMATRIX*) &Matrix4X4::Identity);
#endif

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

		return 1;
	}

private:

	void Update(float time)
	{
		m_mesh.LockVerts();
		for (int iphi=0; iphi<m_phiSteps; iphi++)
		{
			float v = (float)iphi / (float)(m_phiSteps-1);
			const float phi = -c_pi*.5f + v*c_pi;
			for (int itheta=0; itheta<m_thetaSteps; itheta++)
			{
				const float u = (float)itheta / (float)(m_thetaSteps-1);
				const float theta = c_2pi * u;

				const float fx = cos(phi)*sin(theta);
				const float fy = sin(phi);
				const float fz = cos(phi)*cos(theta);

				float s = spline((fz+1.0f)/2.0f, m_s1, LENGTHOF(m_s1));
				float t = spline((fx+1.0f)/2.0f, m_s2, LENGTHOF(m_s2));
				const float r = s*t;

				m_mesh.AddVert(&ntxvertex(vec3_t(r*fx,r*fy,r*fz), vec3_t(0,0,0), u,v));
			}
		}
		m_mesh.GenNormals(m_thetaSteps, m_phiSteps, true);
		m_mesh.UnlockVerts();
	}

};

DEFINE_EFFECT(FXDistortBall)
FXDistortBall Efecto00("DISTORTBALL_00");
FXDistortBall Efecto01("DISTORTBALL_01");
