#include "tesSphere.h"

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

#define STACK_SIZE 100000

// Give point P on a unit sphere, calculate texture coords [u, v]
void tesSphere::calcTexCoord(vertex *P, vertexTexCoord *tc)
{
	// using spherical coords
	// a is the angle from z axis [0, pi]
	// t is the angle from x-axis [0, 2pi]
	float a, t;

	a = msys_acosf(P->z);
	float tmp = sinf(a);
	// check for poles
	if(abs(tmp) < 0.001f) {
		t = 0.0f;
	}
	else {
		float tmp2 = P->x/tmp;
		// clamp to [-1, 1] - due to small numerical errors
		tmp2 = min(max(tmp2, -1.0f), 1.0f);
		t = msys_acosf(tmp2);  
		if(P->y < 0.0f) {
			t = (float)(2.0f* M_PI - t);
		}
	} 
	// convert to [0, 1]
	tc->v = float(1.0f - a/M_PI);
	tc->u = float(t/(2.0f*M_PI));
}

// add a triangle
void tesSphere::addTriangle(tesSphere *tSphere, vertex* V1, vertex* V2, vertex* V3) 
{
	float r = tSphere->_radius;
	// get index to insert
	int index = tSphere->vertices->count;

	// for a sphere, the normals are vertices for r=1
	tSphere->normals->push(V1);
	tSphere->normals->push(V2);
	tSphere->normals->push(V3);

	// store vertices
	vertex v[3];
	populateVertex(&v[0], V1->x*r, V1->y*r, V1->z*r);
	populateVertex(&v[1], V2->x*r, V2->y*r, V2->z*r);
	populateVertex(&v[2], V3->x*r, V3->y*r, V3->z*r);

	tSphere->vertices->push(&v[0]);
	tSphere->vertices->push(&v[1]);
	tSphere->vertices->push(&v[2]);

	// store indices
	tSphere->indices->push(index);
	tSphere->indices->push(index+1);
	tSphere->indices->push(index+2);

	// store tex coords
	vertexTexCoord tc[3];
	calcTexCoord(V1, &tc[0]);
	calcTexCoord(V2, &tc[1]);
	calcTexCoord(V3, &tc[2]);
	// are all vertices on the same texture?
	// filter for +Y-Z half-plane
	if(V1->x > 0.0f && V2->x > 0.0f && V3->x > 0.0f) 
	{
		// y coordinates should be all of same sign
		// else the triangle is at the texture seam
		if(!((V1->y < 0.0 && V2->y < 0.0 && V3->y < 0.0) ||
			(V1->y > 0.0 && V2->y > 0.0 && V3->y > 0.0))) 
		{
				// add 1.0 to the u tex coords for which y > 0
				// so texture is repeated
				if (V1->y>= 0.0f) tc[0].u+=1.0f;
				if (V2->y >= 0.0f) tc[1].u+=1.0f;
				if (V3->y >= 0.0f) tc[2].u+=1.0f;
		}
	}
	tSphere->texCoords->push(&tc[0]);
	tSphere->texCoords->push(&tc[1]);
	tSphere->texCoords->push(&tc[2]);

}

void tesSphere::subDivide(tesSphere *tSphere, vertex* v1, vertex* v2, vertex* v3, int depth)
{
	if (depth == 0)
	{
		addTriangle(tSphere, v1, v2, v3);
		return;
	}

	vertex v12, v23, v31;
	v12.x = v1->x + v2->x;
	v23.x = v2->x + v3->x;
	v31.x = v3->x + v1->x;

	v12.y = v1->y + v2->y;
	v23.y = v2->y + v3->y;
	v31.y = v3->y + v1->y;

	v12.z = v1->z + v2->z;
	v23.z = v2->z + v3->z;
	v31.z = v3->z + v1->z;

	normalizev(&v12);
	normalizev(&v23);
	normalizev(&v31);

	subDivide(tSphere, v1, &v12, &v31, depth-1);
	subDivide(tSphere, v2, &v23, &v12, depth-1);
	subDivide(tSphere, v3, &v31, &v23, depth-1);
	subDivide(tSphere, &v12, &v23, &v31, depth-1);

}

// normalize vector passed in 
void tesSphere::normalizev(vertex* v) 
{    
	float d = sqrt(v->x*v->x+v->y*v->y+v->z*v->z); 
	if (abs(d) < 0.000001f) {
		return;
	}
	v->x /= d; 
	v->y /= d; 
	v->z /= d; 
}

tesSphere::tesSphere(float radius, int depth)
{
	msys_debugPrintf("Init tesSphere\n");
	// start with an icosahedron
	float X = 0.525731112119133606f;
	float Z = 0.850650808352039932f;

	_depth = depth;
	_radius = radius;

	int numVerts = 12;
	int numIndices = 12 * 5;

	vertices = new vertexStack(STACK_SIZE);
	normals = new vertexStack(STACK_SIZE);
	texCoords = new texCoordStack(STACK_SIZE);
	indices = new indexStack(STACK_SIZE*3);

	float tvertices [] = {   
		-X, 0.0, Z, X, 0.0, Z, -X, 0.0, -Z, X, 0.0, -Z,    
			0.0, Z, X, 0.0, Z, -X, 0.0, -Z, X, 0.0, -Z, -X,    
			Z, X, 0.0, -Z, X, 0.0, Z, -X, 0.0, -Z, -X, 0.0
	};

	vertex icoVerts[12];

	for (int i=0; i<numVerts; i++)
	{
		icoVerts[i].x = tvertices[(i*3)+0];
		icoVerts[i].y = tvertices[(i*3)+1];
		icoVerts[i].z = tvertices[(i*3)+2];
	}
	
	int indicesVals [] = 
	{
		0,4,1, 
		0,9,4, 
		9,5,4, 
		4,5,8, 
		4,8,1,    
		8,10,1, 
		8,3,10, 
		5,3,8, 
		5,2,3, 
		2,7,3,    
		7,10,3, 
		7,6,10, 
		7,11,6, 
		11,0,6, 
		0,1,6, 
		6,1,10, 
		9,0,11, 
		9,11,2, 
		9,2,5, 
		7,2,11
	};
	
	for(int i=0; i<numIndices; i+=3)
	{
		vertex v1  = icoVerts[indicesVals[i]];
		vertex v2  = icoVerts[indicesVals[i+1]];
		vertex v3  = icoVerts[indicesVals[i+2]];

		subDivide(this, &v1, &v2, &v3, depth);
	}

	oglGenBuffersARB(4, &vboId[0]);

	// upload vertex data to vbo
	oglBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId[0]);
	oglBufferDataARB(GL_ARRAY_BUFFER_ARB, vertices->count*sizeof(float)*3, vertices->items, GL_STATIC_DRAW_ARB);
	oglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

	// upload normals data to vbo
	oglBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId[1]);
	oglBufferDataARB(GL_ARRAY_BUFFER_ARB, normals->count*sizeof(float)*3, normals->items, GL_STATIC_DRAW_ARB);
	oglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

	// upload texcoord data to vbo
	oglBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId[2]);
	oglBufferDataARB(GL_ARRAY_BUFFER_ARB, texCoords->count*sizeof(float)*2, texCoords->items, GL_STATIC_DRAW_ARB);
	oglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

	LogGlError();
}

void tesSphere::Draw()
{

	glEnableClientState(GL_VERTEX_ARRAY);	
	glEnableClientState(GL_NORMAL_ARRAY);	
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	oglBindBufferARB( GL_ARRAY_BUFFER_ARB, vboId[0] );
	glVertexPointer(3, GL_FLOAT, 0, 0);
	
	oglBindBufferARB( GL_ARRAY_BUFFER_ARB, vboId[1] );
	glNormalPointer(GL_FLOAT, 0, 0);
	
	oglBindBufferARB( GL_ARRAY_BUFFER_ARB, vboId[2] );
	glTexCoordPointer(2, GL_FLOAT, 0, 0);

	glDrawElements(GL_TRIANGLES, vertices->count, GL_UNSIGNED_INT, indices->items);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	/*
	glBegin(GL_TRIANGLES);
	//glBegin(GL_LINE_LOOP);
		for(int i=0; i<indices->count; i++)
		{
			int index = indices->items[i];
			glTexCoord2f(texCoords->items[index].u, texCoords->items[index].v);
			glNormal3f(normals->items[index].x, normals->items[index].y, normals->items[index].z);
			glVertex3f(vertices->items[index].x, vertices->items[index].y, vertices->items[index].z);
		}
	glEnd();*/
}


tesSphere::~tesSphere(void)
{
}
