#ifdef _DEBUG
	#include <stdlib.h>
	#include "../mmgr.h"
#endif

#include <math.h>

#include "BoyC.hpp"
#include "../mathematics.hpp"
#include "../primitives.hpp"

// grid X resolution
#define GRIDX 40
// grid Y resolution
#define GRIDY 30
// grid Z resolution, this should be left at 4
#define GRIDZ 4
// tunnel depth
#define TDEPTH 100
// threshold value for the blobs
#define THRESHOLD 10.0f
// number of tunnels
#define TUNNELS 10

float RADTHETA=3.1415f/180.0f; //guess what, deg->rad help constant
float oldcampos[2]; // previous camera position

int TunnelPos=0; //starting position of the tunnel
int Precalc=TDEPTH;

float LightCol[4]={1,1,1,1}; //light color
GLuint Tex, //the texture
       TexImage; //buffer used for the feedback effect

// marching tetraeders algo
typedef struct
{
	float Value;
	float Pos[3];
	float Normal[3];
	int Inside;
} GRIDPOINT;

//yeah the tetraeder lookup tables are a bit smaller than the marching cubes ones ;)
unsigned char Tetraeders[6][4] = 
{
	//6 tetraeders that make up the cube (cube vertex indices)
	{3,7,2,1},
	{3,0,4,1},
	{3,4,7,1},
	{1,4,5,7},
	{1,5,2,7},
	{2,5,6,7}
};

unsigned char TetraMap[16][4] =
{
    //tetraeder vertex: 0 1 2 3     - 0 = inside, 1= outside
	{0,0,0,0}, //       0 0 0 0
	{3,5,6,0}, //       0 0 0 1
	{2,4,6,0}, //       0 0 1 0
	{2,3,5,4}, //       0 0 1 1
	{1,4,5,0}, //       0 1 0 0
	{1,4,6,3}, //       0 1 0 1
	{1,2,6,5}, //       0 1 1 0
	{1,2,3,0}  //       0 1 1 1     next 8 are the same but in reverse order
	           // mappos = vertex0.inside*8+vertex1.inside*4+vertex2.inside*2+vertex3.inside;
};

unsigned char TriMap[6] = {0,1,2,0,2,3};

float TetraederEdges[6][3]; //6 possible intersection points of the edges. mapped by TetraMap[value]-1, if TetraMap[value] is 0, end
float TetraederNormals[6][3];
GRIDPOINT Cube[8]; //8 grid points of the cube

GRIDPOINT Grid[GRIDX][GRIDY][GRIDZ];

float Linear(float a, float b, float t) //linear interpolation
{
	return a+(b-a)*t;
}


void CalculateCube() //calculate one cube of the marching tetraeder grid
{
	int e1,e2;
	int x;
	int z;
	for (x=0; x<6; x++) //for each tetraeder
	{
		unsigned char Map=(Cube[Tetraeders[x][0]].Inside<<3)
			             +(Cube[Tetraeders[x][1]].Inside<<2)
						 +(Cube[Tetraeders[x][2]].Inside<<1)
						 +Cube[Tetraeders[x][3]].Inside;
		int e=0;

		for (e1=0; e1<3; e1++)
			for (e2=e1+1; e2<4; e2++)
			{
				GRIDPOINT p1,p2;
				p1=Cube[Tetraeders[x][e1]];
				p2=Cube[Tetraeders[x][e2]];
				if (p1.Inside!=p2.Inside)
				{
					float t=(THRESHOLD-p1.Value)/(p2.Value-p1.Value);
					int k;
					for (k=0; k<3; k++)
					{
						TetraederEdges[e][k]=Linear(p1.Pos[k],p2.Pos[k],t);
						TetraederNormals[e][k]=Linear(p1.Normal[k],p2.Normal[k],t);
					}
				}
				e++;
			}

		for (z=0; z<6; z++) 
		if ((TetraMap[Map][0] && z<3) || (TetraMap[Map][3] && z>=3))
		{
			int t=TetraMap[Map][TriMap[z]]-1;
			glTexCoord2f(TetraederEdges[t][0]/(float)GRIDX*3,((-TetraederEdges[t][2]+TunnelPos)/(float)GRIDX*3));
			glNormal3fv(TetraederNormals[t]);
			glVertex3fv(TetraederEdges[t]);
		}

	}
}

GLuint Lists[TDEPTH];

typedef struct //tunnel structure, holds the pre generated values for the path
{
	float xa,ya,xs,ys;
	float bxa,bya,bxs,bys;
	float cxa,cya,cxs,cys;
} TS;

TS ts[TUNNELS+1];

typedef struct //stores the current position of the tunnel
{
	float x,y;
	float r2,rs,ra,rd;
} TUNNEL;

TUNNEL Tunnels[TUNNELS];

__forceinline void InitTunnels() // generates amplitude and size parameters for the tunnels
{
	int x;
	float a=140/25.5f;
	float b=255/25.5f;
	float c=209/255.0f;
	float d=167/255.0f;
	float e=223/25.5f;
	float f=139/25.5f;
	float g=177/255.0f;
	float h=232/255.0f;
	float radius=153/255.0f;

	for (x=0; x<TDEPTH; x++) Lists[x]=glGenLists(1); // we create the display lists here

	srand(81);
	for (x=0; x<=TUNNELS; x++)
	{
		do
		ts[x].xs=rand()/(float)RAND_MAX*a-a/2.0f; //7
		while (fabs(ts[x].xs)<0.1);
		
		do
		ts[x].ys=rand()/(float)RAND_MAX*b-b/2.0f; //7
		while (fabs(ts[x].ys)<0.1);

		do
		ts[x].xa=rand()/(float)RAND_MAX*c-c/2.0f; //0.6
		while (fabs(ts[x].xa)<0.1);
		
		do
		ts[x].ya=rand()/(float)RAND_MAX*d-d/2.0f; //0.6
		while (fabs(ts[x].ya)<0.1);
		
		ts[x].bxs=(rand()/(float)RAND_MAX*e-e/2.0f);
		ts[x].bys=(rand()/(float)RAND_MAX*f-f/2.0f);
		ts[x].bxa=(rand()/(float)RAND_MAX*g-g/2.0f)/2.0f;
		ts[x].bya=(rand()/(float)RAND_MAX*h-h/2.0f)/2.0f;
		Tunnels[x].rd=rand()/(float)RAND_MAX*(GRIDX*4.0f/3.0f)*radius+5;
		Tunnels[x].rs=rand()/(float)RAND_MAX*7.0f-3.5f;
		Tunnels[x].ra=rand()/(float)RAND_MAX*(GRIDX*4.0f/3.0f)/2.0f*radius;

	}
	Tunnels[0].rd=70;
}

__forceinline float CheckPoint(int x, int y) // calculate potential for one point based on the tunnel positions
{
	float p=0;
	int z;
	if (x==1 || x==GRIDX-1 || y==1 || y==GRIDY-1) return -100000; // the edge of the grid should be solid
	for (z=0; z<TUNNELS; z++) p+=Tunnels[z].r2/((x-Tunnels[z].x)*(x-Tunnels[z].x)+(y-Tunnels[z].y)*(y-Tunnels[z].y));
	return p;
}


void BoyC::draw()
{
	const float pos = (time - startTime) / (endTime - startTime);
	float alpha = 1.0f;

	const float fadeinstart = 0.0f;
	const float fadeinstop = 0.1f;
	const float fadeoutstart = 0.90f;
	const float fadeoutstop = 1.0f;

	if (pos >= fadeinstart && pos <= fadeinstop)
		alpha *= (pos-fadeinstart) / (fadeinstop-fadeinstart);
	if (pos >= fadeoutstart && pos <= fadeoutstop)
		alpha *= 1-(pos-fadeoutstart) / (fadeoutstop-fadeoutstart);

//    filter.init(true);
	renderScene(pos, alpha);
//    filter.glow(8, 0.005f, 0.005f, 0.92f, -1.0f, 1.0f);
}

void BoyC::renderScene(float pos, float alpha)
{
		int m;
		float v1,v2;
		float campos[2];
		float Pos[4];
		float zoom = 1/255.0f;
		float asp=4/3.0f;
		float x1=0,y2=0,x2=dmsGetWindowWidth()/1024.0f,y1=dmsGetWindowHeight()/1024.0f;
		int i;

        // init opengl
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_NORMALIZE);
    glDisable(GL_BLEND);

//        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
//	    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
//	    glEnable(GL_TEXTURE_GEN_S);
//	    glEnable(GL_TEXTURE_GEN_T);

	glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, dmsGetTexture("49.jpg")->getID());

    
		for (;Precalc;Precalc--)
		{
			int x,y,z;
			TunnelPos++;

			//calculate current tunnel positions
			for (x=0; x<TUNNELS; x++)
			{
				float TR=TunnelPos*RADTHETA;
				Tunnels[x].x=GRIDX*((float)sin(TR*ts[x].xs)*ts[x].xa+0.5f+(float)cos(TR*ts[x].bxs)*ts[x].bxa);
				Tunnels[x].y=GRIDY*((float)cos(TR*ts[x].ys)*ts[x].ya+0.5f+(float)sin(TR*ts[x].bys)*ts[x].bya);
				Tunnels[x].r2=max(5,Tunnels[x].rd+(float)sin(TR*Tunnels[x].rs)*Tunnels[x].ra);
			}

			//calculate grid values
			for (x=0; x<GRIDX; x++)
				for (y=0; y<GRIDY; y++)
					for (z=0; z<GRIDZ; z++)
					{
						Grid[x][y][z].Pos[0]=x-GRIDX/2.0f;
						Grid[x][y][z].Pos[1]=y-GRIDY/2.0f;
						Grid[x][y][z].Pos[2]=(float)-(z-1);

						if (z<GRIDZ-1) Grid[x][y][z].Value=Grid[x][y][z+1].Value;
						else Grid[x][y][z].Value=CheckPoint(x,y);

						Grid[x][y][z].Inside=Grid[x][y][z].Value<=THRESHOLD;
					}

			//calculate grid normals
			for (x=1; x<GRIDX-1; x++)
				for (y=1; y<GRIDY-1; y++)
					for (z=1; z<GRIDZ-1; z++)
					{
						Grid[x][y][z].Normal[0]= (Grid[x+1][y  ][z  ].Value-Grid[x-1][y  ][z  ].Value)/2.0f;
						Grid[x][y][z].Normal[1]= (Grid[x  ][y+1][z  ].Value-Grid[x  ][y-1][z  ].Value)/2.0f;
						Grid[x][y][z].Normal[2]=-(Grid[x  ][y  ][z+1].Value-Grid[x  ][y  ][z-1].Value)/2.0f;
					}


			//cycle display lists
			for (x=0; x<TDEPTH; x++) Lists[x]=Lists[(x+1)%(TDEPTH)];

			//create display list for new tunnel slice
			glNewList(Lists[TDEPTH-1],GL_COMPILE);
			glBegin(GL_TRIANGLES);
			for (x=1; x<GRIDX-1; x++)
				for (y=1; y<GRIDY-1; y++)
					{
						for (z=0; z<8; Cube[z++]=Grid[x+(((z+1)&2)>>1)][y+((z&2)>>1)][(z<4)+1]);
						CalculateCube();
					}
			glEnd();
			glTranslatef(0,0,-1);
			glEndList();
		}
		Precalc=1;


		m=3;

		v1=(TunnelPos-TDEPTH+m)*RADTHETA;
		v2=(TunnelPos-TDEPTH+m-1)*RADTHETA;

		campos[0]=GRIDX*(float)sin(v1*ts[0].xs)*ts[0].xa+GRIDX*(float)cos(v1*ts[0].bxs)*ts[0].bxa;
		campos[1]=GRIDY*(float)cos(v1*ts[0].ys)*ts[0].ya+GRIDY*(float)sin(v1*ts[0].bys)*ts[0].bya;

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(90,4/3.0f,0.1,2000.0);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		gluLookAt(oldcampos[0],oldcampos[1],-m,campos[0],campos[1],-m-1,0,1,0);
		oldcampos[0]=campos[0];
		oldcampos[1]=campos[1];
		/************ cam calculation done ****************/

		// place light
		Pos[0]=Pos[2]=1;
		Pos[1]=-1;
		Pos[3]=0;
		glLightfv(GL_LIGHT0,GL_POSITION,Pos);

		// draw tunnel
///		glBindTexture(GL_TEXTURE_2D,Tex);
		for (i=0; i<TDEPTH; i++) glCallList(Lists[i]);

	    glDisable(GL_TEXTURE_GEN_S);
	    glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_LIGHTING);
}




BoyC::BoyC()
{	
    InitTunnels();
	int x,y;
	for (x=0; x<8; x++) for (y=0; y<4; y++) TetraMap[15-x][y]=TetraMap[x][y]; 
}

BoyC::~BoyC()
{
}


bool BoyC::init(unsigned long s, unsigned long e)
{
    
		for (;Precalc;Precalc--)
		{
			int x,y,z;
			TunnelPos++;

			//calculate current tunnel positions
			for (x=0; x<TUNNELS; x++)
			{
				float TR=TunnelPos*RADTHETA;
				Tunnels[x].x=GRIDX*((float)sin(TR*ts[x].xs)*ts[x].xa+0.5f+(float)cos(TR*ts[x].bxs)*ts[x].bxa);
				Tunnels[x].y=GRIDY*((float)cos(TR*ts[x].ys)*ts[x].ya+0.5f+(float)sin(TR*ts[x].bys)*ts[x].bya);
				Tunnels[x].r2=max(5,Tunnels[x].rd+(float)sin(TR*Tunnels[x].rs)*Tunnels[x].ra);
			}

			//calculate grid values
			for (x=0; x<GRIDX; x++)
				for (y=0; y<GRIDY; y++)
					for (z=0; z<GRIDZ; z++)
					{
						Grid[x][y][z].Pos[0]=x-GRIDX/2.0f;
						Grid[x][y][z].Pos[1]=y-GRIDY/2.0f;
						Grid[x][y][z].Pos[2]=(float)-(z-1);

						if (z<GRIDZ-1) Grid[x][y][z].Value=Grid[x][y][z+1].Value;
						else Grid[x][y][z].Value=CheckPoint(x,y);

						Grid[x][y][z].Inside=Grid[x][y][z].Value<=THRESHOLD;
					}

			//calculate grid normals
			for (x=1; x<GRIDX-1; x++)
				for (y=1; y<GRIDY-1; y++)
					for (z=1; z<GRIDZ-1; z++)
					{
						Grid[x][y][z].Normal[0]= (Grid[x+1][y  ][z  ].Value-Grid[x-1][y  ][z  ].Value)/2.0f;
						Grid[x][y][z].Normal[1]= (Grid[x  ][y+1][z  ].Value-Grid[x  ][y-1][z  ].Value)/2.0f;
						Grid[x][y][z].Normal[2]=-(Grid[x  ][y  ][z+1].Value-Grid[x  ][y  ][z-1].Value)/2.0f;
					}


			//cycle display lists
			for (x=0; x<TDEPTH; x++) Lists[x]=Lists[(x+1)%(TDEPTH)];

			//create display list for new tunnel slice
			glNewList(Lists[TDEPTH-1],GL_COMPILE);
			glBegin(GL_TRIANGLES);
			for (x=1; x<GRIDX-1; x++)
				for (y=1; y<GRIDY-1; y++)
					{
						for (z=0; z<8; Cube[z++]=Grid[x+(((z+1)&2)>>1)][y+((z&2)>>1)][(z<4)+1]);
						CalculateCube();
					}
			glEnd();
			glTranslatef(0,0,-1);
			glEndList();
		}
		Precalc=1;

	startTime = s;
	endTime = e;
	return true;
}

