/*  bezmesh.c
 *  This program renders a lighted, filled Bezier surface,
 *  using two-dimensional evaluators.
 */
#include "cs148.h"

// Screen size
int screenWidth = 640;
int screenHeight = 480;

// Mouse viewing rotation points
const double PI = 3.1415926;
const double DEG_TO_RAD = PI / 180.;
const double MIN_PHI = 1., MAX_PHI = 179.;
const double MIN_RADIUS = 3., MAX_RADIUS = 30.;
double mouse_theta = 25., mouse_phi = 90., mouse_radius = 10.;

// Mouse rotation modes
enum { MOUSE_NONE, MOUSE_ROTATE, MOUSE_ZOOM };
const double ROTATE_RATE = 0.75;
const double ZOOM_RATE = 0.05;
int mouse_mode = MOUSE_NONE;
int mouse_lastx, mouse_lasty;


GLfloat ctrlpoints[4][4][3] = {
   { {-1.5, -1.5, 4.0},
     {-0.5, -1.5, 2.0},
     {0.5, -1.5, -1.0},
     {1.5, -1.5, 2.0}},
   { {-1.5, -0.5, 1.0},
     {-0.5, -0.5, 3.0},
     {0.5, -0.5, 0.0},
     {1.5, -0.5, -1.0}},
   { {-1.5, 0.5, 4.0},
     {-0.5, 0.5, 0.0},
     {0.5, 0.5, 3.0},
     {1.5, 0.5, 4.0}},
   { {-1.5, 1.5, -2.0},
     {-0.5, 1.5, -2.0},
     {0.5, 1.5, 0.0},
     {1.5, 1.5, -1.0}}
};

void initlights(void)
{
   GLfloat ambient[] = {0.2, 0.2, 0.2, 1.0};
   GLfloat position[] = {0.0, 0.0, 2.0, 1.0};
   GLfloat mat_diffuse[] = {0.6, 0.6, 0.6, 1.0};
   GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};
   GLfloat mat_shininess[] = {50.0};

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);

   glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
   glLightfv(GL_LIGHT0, GL_POSITION, position);

   glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glPushMatrix();
   glRotatef(85.0, 1.0, 1.0, 1.0);
   glEvalMesh2(GL_FILL, 0, 20, 0, 20);

   // Draw control points if necessary
   if (0) {
      glPointSize(5.);
	  glDisable(GL_LIGHTING);
      glColor3f(1., 0., 0.);
	  glBegin(GL_POINTS);
	  for (int i = 0; i < 4; i++)
		 for (int j = 0; j < 4; j++)
	        glVertex3fv(ctrlpoints[i][j]);
	  glEnd();
	  glEnable(GL_LIGHTING);
   }

   // Draw control wireframe if necessary
   if (0) {
      glLineWidth(2.);
	  glDisable(GL_LIGHTING);
      glColor3f(0., 1., 0.);
	  for (int i = 0; i < 4; i++) {
         glBegin(GL_LINE_STRIP);
		 for (int j = 0; j < 4; j++)
	        glVertex3fv(ctrlpoints[i][j]);
		 glEnd();
	  }
	  for (i = 0; i < 4; i++) {
         glBegin(GL_LINE_STRIP);
		 for (int j = 0; j < 4; j++)
	        glVertex3fv(ctrlpoints[j][i]);
		 glEnd();
	  }
	  glEnable(GL_LIGHTING);
   }
   glPopMatrix();
   glutSwapBuffers();
}


// Mouse function -- on entry:
//   button = GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, or GLUT_RIGHT_BUTTON
//   state =  GLUT_UP or GLUT_DOWN
//   x, y = mouse location
void 
myMouse(int button, int state, int mousex, int mousey) {
	int x = mousex;
	int y = screenHeight - mousey;

	// Catch left mouse button
	if (button == GLUT_LEFT_BUTTON) {
		if (state == GLUT_DOWN) {
			mouse_mode = MOUSE_ROTATE;
			mouse_lastx = x;
			mouse_lasty = y;
		}
		else
			mouse_mode = MOUSE_NONE;
	}

	// Catch right mouse button
	if (button == GLUT_RIGHT_BUTTON) {
		if (state == GLUT_DOWN) {
			mouse_mode = MOUSE_ZOOM;
			mouse_lastx = x;
			mouse_lasty = y;
		}
		else
			mouse_mode = MOUSE_NONE;
	}
}


void 
myMovedMouse(int mousex, int mousey) {
	int x = mousex;
	int y = screenHeight - mousey;

	// Update rotation or zoom (depending on mode)
	switch (mouse_mode) {
	case MOUSE_ROTATE:
		mouse_theta -= (double)(x - mouse_lastx) * ROTATE_RATE;
		if (mouse_theta < 0.0)
			mouse_theta += 360.;
		if (mouse_theta >= 360.)
			mouse_theta -= 360.;
		mouse_phi += (double)(y - mouse_lasty) * ROTATE_RATE;
		if (mouse_phi >= MAX_PHI)
			mouse_phi = MAX_PHI;
		if (mouse_phi < MIN_PHI)
			mouse_phi = MIN_PHI;
		break;
	case MOUSE_ZOOM:
		mouse_radius += (double)(y - mouse_lasty) * ZOOM_RATE;
		if (mouse_radius >= MAX_RADIUS)
			mouse_radius = MAX_RADIUS;
		if (mouse_radius < MIN_RADIUS)
			mouse_radius = MIN_RADIUS;
		break;
	};
	mouse_lastx = x;
	mouse_lasty = y;
	
	// Recompute lookat (spherical coords)
	double ex, ey, ez;
	ex = mouse_radius * cos(DEG_TO_RAD * mouse_theta) * sin(DEG_TO_RAD * mouse_phi); 
	ey = mouse_radius * sin(DEG_TO_RAD * mouse_theta) * sin(DEG_TO_RAD * mouse_phi); 
	ez = mouse_radius * cos(DEG_TO_RAD * mouse_phi); 
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(ey, ez, ex, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

	// Redraw
	glutPostRedisplay();
}


void reshape(int w, int h)
{
	screenWidth = w;
	screenHeight = h;
	glViewport(0, 0, screenWidth, screenHeight);	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(50.0, (GLdouble)screenWidth / (GLdouble)screenHeight, 1.0, MAX_RADIUS + 5.);
	glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
   }
}


void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glEnable(GL_DEPTH_TEST);
   glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
           0, 1, 12, 4, &ctrlpoints[0][0][0]);
   glEnable(GL_MAP2_VERTEX_3);
   glEnable(GL_AUTO_NORMAL);
   glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
   initlights();       /* for lighted version only */

   mouse_lastx = 0;
   mouse_lasty = screenHeight;
   mouse_mode = MOUSE_ROTATE;
   myMovedMouse(0, 0);
   mouse_mode = MOUSE_NONE;

   // Redraw
   glutPostRedisplay();
}


int main(int argc, char **argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutReshapeFunc(reshape);
   glutDisplayFunc(display);
   glutKeyboardFunc(keyboard);
   glutMouseFunc(myMouse);
   glutMotionFunc(myMovedMouse);
   glutMainLoop();
   return 0;
}

