/**************************************************************
 * file: rotate.c
 *
 * Author: Chen Li <chenli@cs.nyu.edu>
 *	   Homework 1 for Chee's Visualization Course, 1998
 *	   Modified by Chee for Visualization Course, 2001
 *
 * Synopsis:
 *	   This program displays two cubes, one inside the
 *	   other.  The outer one is transparent so we can see
 *	   the inner cube.  The vertices of the cube have
 *	   different colors.  Both cubes can rotate independently.
 *	   You can control the speed of rotation using the mouse.
 *	   Three radio buttons allow you to control lighting,
 *	   shading, and turn on/off rotation.
 *	  
 *
 *
 * $Id: rotate.html,v 1.1 2001/09/26 21:46:27 yap Exp yap $
 **************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

// define some global consts
#define MY_CUBE1_LIST 1
#define MY_CUBE2_LIST 2

int mainWin, win1, win2, win3;

// define some labels shown with the buttons
char *label1 = "FLAT";
char *label2 = "ROTATE";
char *label3 = "LIGHT";

// define some color constants
#define BLACK   {0.0,0.0,0.0}
#define RED     {1.0,0.0,0.0}
#define GREEN   {0.0,1.0,0.0}
#define BLUE    {0.0,0.0,1.0}
#define YELLOW  {1.0,1.0,0.0}
#define MAGENTA {1.0,0.0,1.0}
#define CYAN    {0.0,1.0,1.0}
#define WHITE   {1.0,1.0,1.0}
#define UNKNOWN {0.2,0.3,0.5}

// define some cube constants
GLfloat vertices[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0},
			 {1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, 
			 {1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
// define the normals on vertices
GLfloat normals[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0},
			{1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, 
			{1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};

// define the colors on vertices
GLfloat cube1_colors[][3] = {UNKNOWN, RED, YELLOW, GREEN,
			     BLUE, MAGENTA, WHITE, CYAN };

// define a different set of colors for another cube
GLfloat cube2_colors[][3] = {{0.3,0.2,0.5}, {.8,0.1,0.1}, {.4,.4,0.2}, 
			     {0.1,.8,0.1}, {.1,.1,.8} ,{.4,0.2,.4}, 
			     {1.0,1.0,.3}, {0.0,.9 ,1.0} };

// define some global states
static GLfloat axis1[] = {1.0,1.0,1.0};
static GLfloat w1[] = {1.0, 1.0, 1.0};
static GLfloat theta1[] = {0.0, 0.0, 0.0};

static GLfloat axis2[] = {0.0,1.0,0.0};
static GLfloat w2[] = {1.0, 1.0, 1.0};
static GLfloat theta2[] = {0.0, 0.0, 0.0};

int spinOn=1, smoothShading=1, lightOff=1;

// define light parameters
GLfloat global_ambient[] = {0.2, 0.2, 0.2, 1.0}; // Global ambient term

GLfloat light0_diffuse[] = {0.0, 1.0, 0.0, 1.0};  // Green diffuse light
GLfloat light0_specular[] = {1.0, 1.0, 1.0, 0.0}; // White specular light
GLfloat light0_position[] = {1.0, 1.0, 1.0, 0.0}; // Infinite light location

GLfloat light1_diffuse[] = {1.0, 0.0, 0.0, 1.0};  // Red diffuse light
GLfloat light1_specular[] = {1.0, 1.0, 1.0, 0.0}; // White specular light
GLfloat light1_position[] = {-1.0, 1.0, 1.0, 0.0}; // Infinite light location

GLfloat light2_diffuse[] = {0.0, 0.0, 1.0, 1.0};  // Blue diffuse light
GLfloat light2_specular[] = {1.0, 1.0, 1.0, 0.0}; // White specular light
GLfloat light2_position[] = {0.0, -1.0, 1.0, 0.0}; // Infinite light location

// declare some functions in advance
void polygon1(int a, int b, int c , int d);
void polygon2(int a, int b, int c , int d);
void drawButton();
void drawPressedButton();
void buildCube();
void calculateRotationForCube1();
void calculateRotationForCube2();
GLfloat* crossProduct(float x1, float y1, float z1,
		      float x2, float y2, float z2);
float dotProduct(float x1, float y1, float z1,
		 float x2, float y2, float z2);
float getAngle(float x1, float y1, float z1,
	       float x2, float y2, float z2);

/***************************************************************
 *              OpenGL/GLUT callback functions
 ***************************************************************/

static void Idle( void )
{
  /* update animation vars */
  if (spinOn) {
    calculateRotationForCube1();
    calculateRotationForCube2();
  }

  // redisplay the subwindows
  glutSetWindow(win1);
  glutPostRedisplay();
  glutSetWindow(win2);
  glutPostRedisplay();
  glutSetWindow(win3);
  glutPostRedisplay();

  // set the current window back to the main window and mark redisplay
  glutSetWindow(mainWin);
  glutPostRedisplay();
}


static void Display( void )
{
  /* display callback, clear frame buffer and z buffer,
     rotate cube and draw, swap buffers */

  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

  // use smooth or flat shadings
  if (smoothShading) glShadeModel(GL_SMOOTH);
  else glShadeModel(GL_FLAT);

  // enable/disable lightings
  if (lightOff) glDisable(GL_LIGHTING);
  else glEnable(GL_LIGHTING);

  // draw the inner cube
  glPushMatrix();  
  glRotatef(theta1[0], 1.0, 0.0, 0.0);
  glRotatef(theta1[1], 0.0, 1.0, 0.0);
  glRotatef(theta1[2], 0.0, 0.0, 1.0);
  glPolygonMode(GL_FRONT, GL_FILL); 
  glCallList(MY_CUBE1_LIST);
  glPopMatrix();

  // draw the outer cube
  glPushMatrix();
  glScalef(3.0, 3.0, 3.0);
  glRotatef(theta2[0], 1.0, 0.0, 0.0);
  glRotatef(theta2[1], 0.0, 1.0, 0.0);
  glRotatef(theta2[2], 0.0, 0.0, 1.0);
  glPolygonMode(GL_FRONT, GL_LINE);
  glCallList(MY_CUBE2_LIST);
  glPopMatrix();

  // force refresh
  glFlush();
  glutSwapBuffers();
}


static void Reshape( int width, int height )
{
  //  printf("width=%d, height=%d \n", width, height);
  glViewport( 0, 0, width, height );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  //  glFrustum( -1.0, 1.0, -1.0, 1.0, 5.0, 25.0 );
  glFrustum( -2.0, 2.0, -2.0, 2.0, 5.0, 25.0 );
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();
  glTranslatef( 0.0, 0.0, -15.0 );
}


static void Key( unsigned char key, int x, int y )
{
  switch (key) {
  case 27:
    exit(0);
    break;
  case 'r':
  case 'R':
    spinOn = 1 - spinOn; //toggle between rotate and spin mode
    break;
  case 's':
  case 'S':
    smoothShading = 1- smoothShading; //toggle between the shading modes
    break;
  case 'l':
  case 'L':
    lightOff = 1 - lightOff; // turn on/off light
    break;
  }
  glutPostRedisplay();
}


static void Mouse(int button, int state, int x, int y) {
  /* mouse callback, specify the axis and degree of rotation */
  
  static int x1, y1, x2, y2, deltaX, deltaY;
  GLfloat *tmp = NULL;
  float angSpeed;
  int i;

  if (((button==GLUT_MIDDLE_BUTTON) || (button==GLUT_LEFT_BUTTON))
      && state == GLUT_DOWN) {
    x1 =  x - 200;
    y1 = - y + 200;
    //    printf("x1=%d, y1=%d\n", x1, y1);
  }
  
  if(button==GLUT_MIDDLE_BUTTON && state == GLUT_UP) {
    x2 = x - 200;
    y2 = -y + 200;
    deltaX = x2 - x1;
    deltaY = y2 - y1;
    if ((abs(deltaX) <=1 ) && (abs(deltaY) <=1 ) ) return;
    glPushMatrix();
    tmp = crossProduct(x1, y1, 150.0, x2, y2, 150.0);
    axis1[0] = tmp[0];
    axis1[1] = tmp[1];
    axis1[2] = tmp[2];

    angSpeed = (spinOn)? getAngle(x1, y1, 150, x2, y2, 150) / 20.0 :
      getAngle(x1, y1, 150, x2, y2, 150) / 4.0;

    for (i=0; i<3; i++) {
      w1[i] = angSpeed*axis1[i];
    }
    /*
      printf("x1=%d, y1=%d, x2=%d, y2=%d \n", x1, y1, x2, y2);
      printf("The axis for rotation one is %f, %f, %f. \n", 
      axis1[0], axis1[1], axis1[2]);
      printf("The inner rotation speed is %f %f %f \n\n", w1[0], w1[1], w1[2]);
      */
    if (!spinOn) {
      // we do one-time rotation
      calculateRotationForCube1();
      glutPostRedisplay();
      // restore to angular speed
      for (i=0; i<3; i++)  w1[i] /= 5.0;
    }
  }

  if(button==GLUT_LEFT_BUTTON && state == GLUT_UP) {
    x2 = x - 200;
    y2 = -y + 200;
    deltaX = x2 - x1;
    deltaY = y2 - y1;
    if ((abs(deltaX) <=1 ) && (abs(deltaY) <=1 ) ) return;
    tmp = crossProduct(x1, y1, 150.0, x2, y2, 150.0);
    axis2[0] = tmp[0];
    axis2[1] = tmp[1];
    axis2[2] = tmp[2];

    angSpeed = (spinOn)? getAngle(x1, y1, 150, x2, y2, 150) / 20.0 :
      getAngle(x1, y1, 150, x2, y2, 150) / 4.0;

    for (i=0; i<3; i++) {
      w2[i] = angSpeed*axis2[i];
    }
    /*
      printf("x1=%d, y1=%d, x2=%d, y2=%d \n", x1, y1, x2, y2);
      printf("The axis for rotation two is %f, %f, %f. \n", 
      axis2[0], axis2[1], axis2[2]);
      printf("The outer rotation speed is %f %f %f\n\n", w2[0], w2[1], w2[2]);
      */
    if (!spinOn) {
      // we do one-time rotation
      calculateRotationForCube2();
      glutPostRedisplay();
      // restore to angular speed
      for (i=0; i<3; i++)  w2[i] /= 5.0;
    }
  }
}

static void Init( void )
{
  /* setup lighting, etc */
  // glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glEnable(GL_LIGHT2);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
  glMaterialf(GL_FRONT, GL_SHININESS, 100.0);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
  glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
  glLightfv(GL_LIGHT1, GL_SPECULAR, light1_specular);
  glLightfv(GL_LIGHT1, GL_POSITION, light1_position);
  glLightfv(GL_LIGHT2, GL_DIFFUSE, light2_diffuse);
  glLightfv(GL_LIGHT2, GL_SPECULAR, light2_specular);
  glLightfv(GL_LIGHT2, GL_POSITION, light2_position);

  // build some display lists
  buildCube();
}

void right_menu(int id)
{
  glutIdleFunc(NULL);
  if(id == 1) exit(0);
  else Display();
  glutIdleFunc(Idle);
}

void displayButton(char* label, int state) {
  int len, i;

  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glColor3f(1.0, 1.0, 0.0);

  glPushMatrix();
  glTranslatef(-40, 0, 0);
  glScalef(8.0, 8.0, 1.0);
  if (state)  drawButton();
  else drawPressedButton();
  glScalef(.125, .125, 1.0);
  glTranslatef(20, -5.0, 0.0);
  glScalef(0.1, 0.1, 0.1);
  len = (int) strlen(label);
  for (i = 0; i < len; i++) {
    glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, label[i]);
  }
  glPopMatrix();

  glFlush();  
  glutSwapBuffers();
}


void displayButton1(void)
{
  displayButton(label1, smoothShading);
}

static void flatButtonMouse(int button, int state, int x, int y) {
  /* mouse callback, to toggle the flat shading option */
  if (state == GLUT_DOWN) {
    smoothShading = 1 - smoothShading;
  }
}

void displayButton2(void)
{
  displayButton(label2, spinOn);
}

static void spinButtonMouse(int button, int state, int x, int y) {
  /* mouse callback, to toggle the spin/rotate mode */
  if (state == GLUT_DOWN) {
    spinOn = 1 - spinOn;
  }
}

void displayButton3(void)
{
  displayButton(label3, lightOff);
}

static void lightButtonMouse(int button, int state, int x, int y) {
  /* mouse callback, to toggle light on/off mode */
  if (state == GLUT_DOWN) {
    lightOff = 1 - lightOff;
  }
}

static void buttonReshape( int width, int height )
{
  //  printf("width=%d, height=%d \n", width, height);
  glViewport( 0, 0, width, height );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluOrtho2D(0, width, 0, height);
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();
  glTranslatef( width/2, height/2, 0.0 );
}


int main( int argc, char *argv[] )
{
  glutInit( &argc, argv );
  glutInitWindowSize( 400, 400 );

  glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );

  //define the top level window
  mainWin = glutCreateWindow(argv[0]);

  Init();

  // define a menu
  glutCreateMenu(right_menu);
  glutAddMenuEntry("Quit",1);
  glutAddMenuEntry("Reset",2);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
  // end menu definition

  glutReshapeFunc( Reshape );
  glutKeyboardFunc( Key );
  glutMouseFunc( Mouse );
  //  glutSpecialFunc( SpecialKey );
  glutDisplayFunc( Display );
  glutIdleFunc( Idle );
  glEnable(GL_DEPTH_TEST); // Enable hidden--surface--removal

  // define some sub-windows to contain the radio buttons

  // flat/smooth shading button
  win1 = glutCreateSubWindow(mainWin, 0, 0, 100, 20);
  glutDisplayFunc(displayButton1);
  glClearColor(0.0, 0.0, 0.5, 1.0);
  glutReshapeFunc( buttonReshape );
  glutMouseFunc( flatButtonMouse );

  // spin/rotate button
  win2 = glutCreateSubWindow(mainWin, 150, 0, 100, 20);
  glutDisplayFunc(displayButton2);
  glClearColor(0.0, 0.0, 0.5, 0.0);
  glutReshapeFunc( buttonReshape );
  glutMouseFunc( spinButtonMouse );

  // light on/off button
  win3 = glutCreateSubWindow(mainWin, 300, 0, 100, 20);
  glutDisplayFunc(displayButton3);
  glClearColor(0.0, 0.0, 0.5, 0.0);
  glutReshapeFunc( buttonReshape );
  glutMouseFunc( lightButtonMouse );

  // end of subwins definitions

  // set the current window back to the top level one
  glutSetWindow(mainWin);
  // start the events handling loop
  glutMainLoop();
}


/*********************************************************
 *     define some supporting functions
 *********************************************************/

void drawButton()
{
  // draw a square of size two
  glBegin(GL_LINE_LOOP);
  glVertex3f(1.0, 1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0);
  glVertex3f(1.0, -1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0);
  glVertex3f(-1.0, -1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0);
  glVertex3f(-1.0, 1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0);
  glEnd();
}

void drawPressedButton() {
  drawButton();
  // draw a cross in the square 
  glBegin(GL_LINES);
  glVertex3f(-1.0, -1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0); 
  glVertex3f(1.0, 1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0);
  glVertex3f(-1.0, 1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0); 
  glVertex3f(1.0, -1.0, 0.0);
  glColor3f(1.0, 1.0, 0.0);
  glEnd();
}

void mapVertexAttr(int tag, int i) {
  if (tag == MY_CUBE1_LIST) {
    glColor3fv(cube1_colors[i]);
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, cube1_colors[i]);
  } else {
    glColor3fv(cube2_colors[i]);
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, cube2_colors[i]);
  }
  glNormal3fv(normals[i]);
}

void polygon(int tag, int a, int b, int c , int d)
{

  /* draw a polygon via list of vertices */

  glBegin(GL_POLYGON);
  mapVertexAttr(tag, a);
  glVertex3fv(vertices[a]);
  mapVertexAttr(tag, b);
  glVertex3fv(vertices[b]);
  mapVertexAttr(tag, c);
  glVertex3fv(vertices[c]);
  mapVertexAttr(tag, d);
  glVertex3fv(vertices[d]);
  glEnd();
}

void buildCube(void)
{

  //define two display lists with difference color scheme
  /* map vertices to faces */
  glNewList(MY_CUBE1_LIST, GL_COMPILE);
  polygon(MY_CUBE1_LIST, 0,3,2,1);
  polygon(MY_CUBE1_LIST, 2,3,7,6);
  polygon(MY_CUBE1_LIST, 7,3,0,4);
  polygon(MY_CUBE1_LIST, 1,2,6,5);
  polygon(MY_CUBE1_LIST, 4,5,6,7);
  polygon(MY_CUBE1_LIST, 5,4,0,1);
  glEndList();

  glNewList(MY_CUBE2_LIST, GL_COMPILE);
  polygon(MY_CUBE2_LIST, 0,3,2,1);
  polygon(MY_CUBE2_LIST, 2,3,7,6);
  polygon(MY_CUBE2_LIST, 7,3,0,4);
  polygon(MY_CUBE2_LIST, 1,2,6,5);
  polygon(MY_CUBE2_LIST, 4,5,6,7);
  polygon(MY_CUBE2_LIST, 5,4,0,1);
  glEndList();

};

void calculateRotationForCube1() {
  theta1[0] += w1[0];
  theta1[0] = (theta1[0] >= 360)? theta1[0]-360 : theta1[0];
  theta1[1] += w1[1];
  theta1[1] = (theta1[1] >= 360)? theta1[1]-360 : theta1[1];
  theta1[2] += w1[2];
  theta1[2] = (theta1[2] >= 360)? theta1[2]-360 : theta1[2];
}

void calculateRotationForCube2() {
  theta2[0] += w2[0];
  theta2[0] = (theta2[0] >= 360)? theta2[0]-360 : theta2[0];
  theta2[1] += w2[1];
  theta2[1] = (theta2[1] >= 360)? theta2[1]-360 : theta2[1];
  theta2[2] += w2[2];
  theta2[2] = (theta2[2] >= 360)? theta2[2]-360 : theta2[2];
}

// compute the cross product of two vectors
GLfloat* crossProduct(float x1, float y1, float z1,
		      float x2, float y2, float z2) {
  GLfloat *r;
  double d;

  if ((r=(GLfloat*)malloc(sizeof(GLfloat) * 3)) == NULL) {
    perror("Memory allocation failed -- Chen Li");
    exit(1);
  }

  // CrossProduct of ([x1, y1, z1], [x2, y2, z2]) = 
  //     [y1 z2 - z1 y2, z1 x2 - x1 z2, x1 y2 - y1 x2]
  r[0] = y1*z2 - z1*y2;
  r[1] = z1*x2 - x1*z2;
  r[2] = x1*y2 - y1*x2;

  // normalize it
  d = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]);
  r[0] /= d;
  r[1] /= d;
  r[2] /= d;

  return(r);
}

// compute the dot product of two vectors  
float dotProduct(float x1, float y1, float z1,
		 float x2, float y2, float z2) {
  return (x1*x2 + y1*y2 + z1*z2);
}

// compute the length of a vector
float lengthOfVector(float x, float y, float z) {
  return sqrt(x*x+y*y+z*z);
}

// compute the angle between two vectors
float getAngle(float x1, float y1, float z1,
	       float x2, float y2, float z2) {
  float cos; 
  cos = dotProduct(x1, y1, z1, x2, y2, z2) / 
    (lengthOfVector(x1, y1, z1) * lengthOfVector(x2, y2, z2));
  //  printf("cos(theta) is %f, acos is %f \n", cos, acos(cos));
  return(acos(cos)*360/(2*3.1415926)); // convert to degrees
}