/* Rotating cube with color interpolation */
/* Adapted from [Angel, Ch. 4] */
/* This version purposely does not double-buffer */

#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

/* vertices of cube about the origin */
GLfloat vertices[8][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}};

/* colors to be assigned to edges */
GLfloat colors[8][3] =
  {{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0},
   {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, 
   {1.0, 0.0, 1.0}, {1.0, 1.0, 1.0}, {0.0, 1.0, 1.0}};

/* draw a face given four vertex indices */
void face(int a, int b, int c, int d)
{
  glBegin(GL_POLYGON);
    glColor3fv(colors[a]);
    glVertex3fv(vertices[a]);
    glColor3fv(colors[b]);
    glVertex3fv(vertices[b]);
    glColor3fv(colors[c]);
    glVertex3fv(vertices[c]);
    glColor3fv(colors[d]);
    glVertex3fv(vertices[d]);
  glEnd();
}

void colorcube(void)
{
  /* map vertices to faces */
  /* note consistent orientation */
  face(0,3,2,1);
  face(2,3,7,6);
  face(0,4,7,3);
  face(1,2,6,5);
  face(4,5,6,7);
  face(0,1,5,4);
}

GLfloat theta[3] = {0.0, 0.0, 0.0};
GLfloat delta = 2.0;
GLint axis = 2;

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

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  glRotatef(theta[0], 1.0, 0.0, 0.0);
  glRotatef(theta[1], 0.0, 1.0, 0.0);
  glRotatef(theta[2], 0.0, 0.0, 1.0);

  colorcube();
  glFlush();
  glutSwapBuffers();
}

int stop = 0;

/* idle callback */
void spinCube()
{
  /* spin cube delta degrees about selected axis */
  theta[axis] += delta;
  if (theta[axis] > 360.0) theta[axis] -= 360.0;

  /* display result */
  glutPostRedisplay();
}

/* mouse callback */
void mouse(int btn, int state, int x, int y)
{
  /* select an axis about which to rotate */
  if (btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0;
  if (btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1;
  if (btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
}

/* keyboard callback */
void keyboard(unsigned char key, int x, int y)
{
  if (key=='q' || key == 'Q') exit(0);
  if (key==' ') {stop = !stop;};
  if (stop)
    glutIdleFunc(NULL);
  else
    glutIdleFunc(spinCube);
}

/* reshape callback */
void myReshape(int w, int h)
{
    GLfloat aspect = (GLfloat) w / (GLfloat) h;
    /* scale viewport with window */
    glViewport(0, 0, w, h);

    /* prevent distortion */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h) /* aspect <= 1 */
      glOrtho(-2.0, 2.0, -2.0 / aspect, 2.0 / aspect, -10.0, 10.0);
    else /* aspect > 1 */
      glOrtho(-2.0 * aspect, 2.0 * aspect, -2.0, 2.0, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv)
{
    glutInit(&argc, argv);

    /* double buffering for smooth animation */
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(500, 500);
    glutCreateWindow("cube");
    glutReshapeFunc(myReshape);
    glutDisplayFunc(display);
    glutIdleFunc(spinCube);
    glutMouseFunc(mouse);
    glutKeyboardFunc(keyboard);

    /* enable hidden-surface removal */
    glEnable(GL_DEPTH_TEST);
    glutMainLoop();
    return(0);
}
