//Base code for the Catmull-Clark subdivion assignment for cpe 572
//Use a 3d array of Vector3s, 2 dimensions are the surface the third is the level of subdivision
//Only supports subdivision up to 4 levels (starts out at level 0)
//only draws wireframe and uses aswd camera movement but no rotations
//ASSIGNMENT: compute the correct re-positioning of geometric values per level given the Catmull-Clark rules
//ZJW 4/29/10

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <GLUT/glut.h>
#include <OPENGL/gl.h>
#include <OPENGL/glext.h>
#include <assert.h>

using namespace std;

/* structure to store a 3d point, note that for this assignment, the vector also stores a level which is used to
   denote which level of subdivion has been computed on that point (to not displace a point twice), given the 
   data structure we are using */
typedef struct Vector3 {
	float x;
	float y;
	float z;
	
	int level;
	Vector3(float in_x, float in_y, float in_z) : x(in_x), y(in_y), z(in_z), level(0) {}
	Vector3() : x(0), y(0), z(0), level(0) {}
	Vector3(const Vector3& in_v) : x(in_v.x), y(in_v.y), z(in_v.z), level(0) {}
	Vector3(float in) : x(in), y(in), z(in), level(0) {}
	inline Vector3& operator =(const Vector3& v2) { x = v2.x; y=v2.y; z=v2.z; level =v2.level; return *this;}
	inline void set(float in_x, float in_y, float in_z) { x = in_x; y = in_y; z = in_z; level = 0;}
} Vector3;

#define FLT_MIN 1.1754E-38F
#define FLT_MAX 1.1754E+38F

int GW;
int GH;

//for camera changes instead
Vector3 eye, la;
char strPop[256];

//helper functions for working with vectors
Vector3 cross(const Vector3& a, const Vector3& b) {
	Vector3 c;
	c.x = a.y*b.z - a.z*b.y;
	c.y = a.z*b.x - a.x*b.z;
	c.z = a.x*b.y - a.y*b.x;
	
	return(c);
}

float dot(const Vector3& a, const Vector3& b) {
	return(a.x*b.x + a.y*b.y + a.z*b.z);
}

float length(const Vector3& a) {
	return(sqrt(dot(a, a)));
}

Vector3 normalize(const Vector3& a) {
	Vector3 u;
	float mag_a = length(a);
	u.x = a.x/mag_a;
	u.y = a.y/mag_a;
	u.z = a.z/mag_a;
	return(u);
}

void PrintVec(const Vector3 v1) {
	printf("vec: %f %f %f\n", v1.x, v1.y, v1.z);
}

void renderBitmapCharacher(float x, float y, float z, void *font,char *string)
{
	char *c;
	glRasterPos3f(x, y,z);
	for (c=string; *c != '\0'; c++) {
		glutBitmapCharacter(font, *c);
	}
}

//storage for the data points for our Catmull-Clark Subdivision surface (up to 4 levels of subdivision)
Vector3 CCSurf[17][17][4];
//Globals for the global extents of the patch
int L, R, T, B;
int DrawLevel;
/*assumes ordering as
 3--------2
 |        |
 |        |
 0--------1
 */

void InitCCSurf() {
	int level=0;
	L=B=0;
	R=T=16;
	CCSurf[L][B][level] = Vector3(-1, 0, 1);
	CCSurf[R][B][level] = Vector3(1, 0, 1);
	CCSurf[R][T][level] = Vector3(1, 0, -1);
	CCSurf[L][T][level] = Vector3(-1, 0, -1);
    //setting center for interesting geometry and edge midpoints - kind of weird
	CCSurf[(R-L)/2][(T-B)/2][level] = Vector3(0, 1, 0);
	CCSurf[(R-L)/2][B][level] = Vector3(0, 0, 1);
	CCSurf[R][(T-B)/2][level] = Vector3(1, 0, 0);
	CCSurf[(R-L)/2][T][level] = Vector3(0, 0, -1);
	CCSurf[L][(T-B)/2][level] = Vector3(-1, 0, 0);
 }
 
/*Routine to draw a given portion of a patch at a given level*/
void DrawPatch(int Lev, int Lft, int Rght, int Bot, int Top) {
	int i, j, skip;
	Vector3 v1, v2,norm;
	skip = (Rght-Lft)/powf(2,(Lev+1));
	
	for (i=Lft; i <Rght; i += skip) {
		for (j=Bot; j<Top; j+=skip) {
		  printf("draw called for i %d j %d for level %d with a skip of %d\n", i, j, Lev, skip);
		  glBegin(GL_LINE_LOOP);
			glColor3f(0, 0, 0);
			glVertex3f(CCSurf[i][j][Lev].x, CCSurf[i][j][Lev].y, CCSurf[i][j][Lev].z);
			glVertex3f(CCSurf[i+skip][j][Lev].x, CCSurf[i+skip][j][Lev].y, CCSurf[i+skip][j][Lev].z);
			glVertex3f(CCSurf[i+skip][j+skip][Lev].x, CCSurf[i+skip][j+skip][Lev].y, CCSurf[i+skip][j+skip][Lev].z);
			glVertex3f(CCSurf[i][j+skip][Lev].x, CCSurf[i][j+skip][Lev].y, CCSurf[i][j+skip][Lev].z);	
		  glEnd();
			
		 /* debugging statements can be pulled */	
		  PrintVec(CCSurf[i][j][Lev]);
		  PrintVec(CCSurf[i+skip][j][Lev]);
		  PrintVec(CCSurf[i+skip][j+skip][Lev]);
		  PrintVec(CCSurf[i][j+skip][Lev]);
		}
	}
}



/* Set the face points and compute the edge midpoints */
void SubdivFace(int L1, int R1, int B1, int T1, int Lev) {
	//compute the sum of edges using previous level positions!
	float div =2;
	//bottom edge
	if (CCSurf[L1+(R1-L1)/2][B1][Lev].level < Lev) {
		CCSurf[L1+(R1-L1)/2][B1][Lev] = Vector3(CCSurf[L1][B1][Lev-1].x+CCSurf[R1][B1][Lev-1].x,
										 CCSurf[L1][B1][Lev-1].y+CCSurf[R1][B1][Lev-1].y,
										 CCSurf[L1][B1][Lev-1].z+CCSurf[R1][B1][Lev-1].z);
		CCSurf[L1+(R1-L1)/2][B1][Lev].x /= div;
		CCSurf[L1+(R1-L1)/2][B1][Lev].y /= div;
		CCSurf[L1+(R1-L1)/2][B1][Lev].z /= div;
		CCSurf[L1+(R1-L1)/2][B1][Lev].level = Lev;
		/* debugging statements can be pulled */
		printf("bot edge point for i %d j %d after\n", L1+(R1-L1)/2, B1);
		PrintVec(CCSurf[L1+(R1-L1)/2][B1][Lev]);
	}
	//top edge
	if (CCSurf[L1+(R1-L1)/2][T1][Lev].level < Lev) {
		CCSurf[L1+(R1-L1)/2][T1][Lev] = Vector3(CCSurf[L1][T1][Lev-1].x+CCSurf[R1][T1][Lev-1].x,
										 CCSurf[L1][T1][Lev-1].y+CCSurf[R1][T1][Lev-1].y,
										 CCSurf[L1][T1][Lev-1].z+CCSurf[R1][T1][Lev-1].z);
		CCSurf[L1+(R1-L1)/2][T1][Lev].x /= div;
		CCSurf[L1+(R1-L1)/2][T1][Lev].y /= div;
		CCSurf[L1+(R1-L1)/2][T1][Lev].z /= div;
		CCSurf[L1+(R1-L1)/2][T1][Lev].level = Lev;
		/* debugging statements can be pulled */
		printf("top edge point for i %d j %d after\n", L1+(R1-L1)/2, T1);
		PrintVec(CCSurf[L1+(R1-L1)/2][T1][Lev]);
	}
	//leftedge
	if (CCSurf[L1][B1+(T1-B1)/2][Lev].level < Lev) {
		CCSurf[L1][B1+(T1-B1)/2][Lev] = Vector3(CCSurf[L1][B1][Lev-1].x+CCSurf[L1][T1][Lev-1].x,
										 CCSurf[L1][B1][Lev-1].y+CCSurf[L1][T1][Lev-1].y,
										 CCSurf[L1][B1][Lev-1].z+CCSurf[L1][T1][Lev-1].z);

		CCSurf[L1][B1+(T1-B1)/2][Lev].x /= div;
		CCSurf[L1][B1+(T1-B1)/2][Lev].y /=div;
		CCSurf[L1][B1+(T1-B1)/2][Lev].z /= div;
		CCSurf[L1][B1+(T1-B1)/2][Lev].level = Lev;
		/* debugging statements can be pulled */
		printf("left edge point for i %d j %d after\n", L1, T1, B1+(T1-B1)/2);
		PrintVec(CCSurf[L1][B1+(T1-B1)/2][Lev]);
	}
	//right edge
	if (CCSurf[R1][B1+(T1-B1)/2][Lev].level < Lev) {
		CCSurf[R1][B1+(T1-B1)/2][Lev] = Vector3((CCSurf[R1][B1][Lev-1].x+CCSurf[R1][T1][Lev-1].x),
										 (CCSurf[R1][B1][Lev-1].y+CCSurf[R1][T1][Lev-1].y),
										 (CCSurf[R1][B1][Lev-1].z+CCSurf[R1][T1][Lev-1].z));
		CCSurf[R1][B1+(T1-B1)/2][Lev].x /= div;
		CCSurf[R1][B1+(T1-B1)/2][Lev].y /= div;
		CCSurf[R1][B1+(T1-B1)/2][Lev].z /= div;
		CCSurf[R1][B1+(T1-B1)/2][Lev].level = Lev;
		/* debugging statements can be pulled */
		printf("rght edge point for i %d j %d after\n", L1, T1, B1+(T1-B1)/2);
		PrintVec(CCSurf[R1][B1+(T1-B1)/2][Lev]);
	}

	//set the face point to centroid (note same as bi-linear on 2 edges, using left and right)
	CCSurf[L1+(R1-L1)/2][B1+(T1-B1)/2][Lev] = Vector3((CCSurf[L1][B1+(T1-B1)/2][Lev].x+CCSurf[R1][B1+(T1-B1)/2][Lev].x)/2.0,
												(CCSurf[L1][B1+(T1-B1)/2][Lev].y+CCSurf[R1][B1+(T1-B1)/2][Lev].y)/2.0,
												(CCSurf[L1][B1+(T1-B1)/2][Lev].z+CCSurf[R1][B1+(T1-B1)/2][Lev].z)/2.0);
	/* debugging statements can be pulled */
	printf("new face point for i %d j%d\n", L1+(R1-L1)/2, B1+(T1-B1)/2);
	PrintVec(CCSurf[L1+(R1-L1)/2][B1+(T1-B1)/2][Lev]);
		
	/*set current level 'original' to same as previous - replace in assignment*/
	CCSurf[R1][B1][Lev] = CCSurf[R1][B1][Lev-1];
	CCSurf[R1][T1][Lev] = CCSurf[R1][T1][Lev-1];
	CCSurf[L1][B1][Lev] = CCSurf[L1][B1][Lev-1];
	CCSurf[L1][T1][Lev] = CCSurf[L1][T1][Lev-1];
}


/*Routine to subdivide a given portion of a patch at a given level*/
void SubDivPatch(int Lev, int Lft, int Rght, int Bot, int Top) {
	int i, j, skip;
	Vector3 v1, v2,norm;
	skip = (Rght-Lft)/powf(2,Lev);
	
	//subdivide the faces - including the edges right now
	for (i=Lft; i <Rght; i +=skip) {
		for (j=Bot; j<Top; j+=skip) {
			printf("subdiv face called for i %d j %d for level %d with skip of %d\n", i, j, Lev, skip);
			SubdivFace(i, i+skip, j, j+skip, Lev);
		}
	}
}


void display() {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);

	glPushMatrix();
		//set up the camera
		gluLookAt(0, 0, 2.5, 
			0, 0, 0, 
			0, 1, 0);
		renderBitmapCharacher(-1.3,1.3,0,(void *)GLUT_BITMAP_HELVETICA_12,strPop);
	glPopMatrix();

	glPushMatrix();
		//set up the camera, aswd keys work but no rotation
		gluLookAt(eye.x, eye.y, eye.z, la.x, la.y, la.z, 0, 1,0);

		//rotate patch just for nice angle of viewing
		glRotatef(45, 1, 0, 0);
		DrawPatch(DrawLevel, L, R, B, T);

	glPopMatrix();
	
	glutSwapBuffers();  /*not sure why I am needing this on mac??*/
	glFlush();

}

void keyboard(unsigned char key, int x, int y) {

  float delta = 0.1;
  Vector3 gaze,left;
  switch(key) {
	  case 'a':
		gaze.x = la.x - eye.x;
		gaze.y = la.y - eye.y;
		gaze.z = la.z - eye.z;
		left = cross(Vector3(0, 1, 0), gaze);
		eye.x += delta*left.x;
		eye.y += delta*left.y;
		eye.z += delta*left.z;
		la.x += delta*left.x;
		la.y += delta*left.y;
		la.z += delta*left.z;
		break;
	  case 'd':
		gaze.x = la.x - eye.x;
		gaze.y = la.y - eye.y;
		gaze.z = la.z - eye.z;
		left = cross(Vector3(0, 1, 0), gaze);
		eye.x -= delta*left.x;
		eye.y -= delta*left.y;
		eye.z -= delta*left.z;
		la.x -= delta*left.x;
		la.y -= delta*left.y;
		la.z -= delta*left.z;
		glutPostRedisplay();
		break;
	  case 's':
		gaze.x = la.x - eye.x;
		gaze.y = la.y - eye.y;
		gaze.z = la.z - eye.z;
		eye.x -= delta*gaze.x;
		eye.y -= delta*gaze.y;
		eye.z -= delta*gaze.z;
		la.x -= delta*gaze.x;
		la.y -= delta*gaze.y;
		la.z -= delta*gaze.z;
		glutPostRedisplay();
		break;
	   case 'w':
		gaze.x = la.x - eye.x;
		gaze.y = la.y - eye.y;
		gaze.z = la.z - eye.z;
		eye.x += delta*gaze.x;
		eye.y += delta*gaze.y;
		eye.z += delta*gaze.z;
		la.x += delta*gaze.x;
		la.y += delta*gaze.y;
		la.z += delta*gaze.z;
		glutPostRedisplay();
		break;
	  case 'q':
		  exit( EXIT_SUCCESS );
		  break;
	  case 'p':
		  DrawLevel++;
		  if (DrawLevel < 4) {
		    SubDivPatch(DrawLevel,L,R, B, T);
			sprintf(strPop, "Catmull-Clark Subdivision Level %d", DrawLevel);
		  } else {
			DrawLevel--;
			sprintf(strPop, "Max Level reached!");
		  }
		    glutPostRedisplay();
		  break;
	 
	  default:
		  break;
	}
	glutPostRedisplay();
}

void reshape(int w, int h) {
	GW = w;
	GH = h;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (float)w/h, 0.1, 50);
	glMatrixMode(GL_MODELVIEW);
	glViewport(0, 0, w, h);

	glutPostRedisplay();
}

int main( int argc, char** argv ) {

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(500, 500); 
	glutInitWindowPosition(100, 100);
	glutCreateWindow("Subdivision");
	glClearColor(1.0, 1.0, 1.0, 1.0);

	glutDisplayFunc( display );
	glutReshapeFunc( reshape );
	glutKeyboardFunc(keyboard);
	glEnable(GL_DEPTH_TEST);

	GW=GH = 500;
	InitCCSurf();
	DrawLevel = 0;

	eye.x = 0.0; eye.y = 0.0; eye.z = 4.0;
	la.x = 0; la.y = 0.0; la.z = 0;

	sprintf(strPop, "Catmull-Clark Subdivision Level %d", DrawLevel);

	glutMainLoop();
}