/* split_cube.c  Rotating split_cube with color sections    */
/*               5 tetrahedrons build a cube                */
/*               color of faces must be shaded to see edges */
/*               tetrahedrons must be offset to see "split" */
/*               does wireframe make it better or worse?    */
/*               give command line "x" to start worse case  */

/* Mouse buttons control direction of rotation */
/* "O" and "o" make offset bigger and smaller  */
/* "C" and "c" make color distinct or closer   */
/* "F" and "f" make rotation faster or slower  */
/* "W" and "w" make wireframe or solid         */

#include <stdlib.h>
#include <string.h>
#include <GL/glut.h>

static GLfloat vertices[][3] = {
       {-1.0,-1.0,-1.0}, {-1.0,-1.0, 1.0},                   /* 0,1   */
       {-1.0, 1.0,-1.0}, {-1.0, 1.0, 1.0}, { 1.0,-1.0,-1.0}, /* 2,3,4 */
       { 1.0,-1.0, 1.0}, { 1.0, 1.0,-1.0}, { 1.0, 1.0, 1.0}, /* 5,6,7 */
       { 0.0, 0.0, 0.0}};                                    /* 8     */
static GLfloat colors[][3] = {
       {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}, {0.0,1.0,1.0}};
static GLfloat theta[] = {0.0,0.0,0.0};
static GLint axis = 2;
static double colorset = 0.1;  /* 'C' or 'c' */
static double offset = 0.2;    /* 'O' or 'o' */
static double rotation = 0.5;  /* 'F' or 'f' */
static int wireframe = 0;      /* 'W' or 'w' */
static int hide = 0;

/* no function prototypes used here because the functions are defined */
/* before they are used */

static void triangle(int color, int a, int b, int c, int off)
{
  int i,j,k;
  GLfloat overtices[3][3]; /* offset vertices */
  GLfloat ocolors[3][3];   /* offset face colors */

  k=a;
  for(i=0; i<3; i++)
  {
    if(i==1) k=b;
    if(i==2) k=c;
    for(j=0; j<3; j++)
    {
      overtices[i][j]=vertices[k][j]+offset*vertices[off][j];
      ocolors[i][j]=colors[color][j]-colorset*(double)i;
    }
  }
  /* draw a triangle face via list of vertices */
  if(!wireframe)
  {
    glBegin(GL_POLYGON);
      glColor3fv(ocolors[0]);
      glVertex3fv(overtices[0]);
      glColor3fv(ocolors[1]);
      glVertex3fv(overtices[1]);
      glColor3fv(ocolors[2]);
      glVertex3fv(overtices[2]);
    glEnd();
  }
  else /* wireframe, three lines */
  {
    glLineWidth(2.0);
    glBegin(GL_LINES);
      glColor3fv(colors[color]);
      glVertex3fv(overtices[0]);
      glVertex3fv(overtices[1]);
      glVertex3fv(overtices[0]);
      glVertex3fv(overtices[2]);
      glVertex3fv(overtices[1]);
      glVertex3fv(overtices[2]);
    glEnd();
  }
} /* end triangle */

static void tetrahedron(int color, int a, int b, int c, int d, int off)
{
  /* draw a tetrahedron via list of vertices (a,b,c,d) */
  triangle(color, a,b,c, off);
  triangle(color, b,a,d, off);
  triangle(color, b,d,c, off);
  triangle(color, d,c,a, off);
} /* end tetrahedron */

static void colorsplit_cube(void)
{
  /* map vertices to 5 tetrahedrons with 4 faces each */
  tetrahedron(0, 0,2,6,3, 2); /* color, 4 vertices, offset */
  tetrahedron(1, 3,7,6,5, 7);
  tetrahedron(2, 0,6,4,5, 4);
  tetrahedron(3, 0,1,5,3, 1);
  tetrahedron(4, 3,0,5,6, 8);
} /* end colorsplit_cube */

static void printstring(void *font, char *string)
{
   int len, i;

   len = (int) strlen(string);
   for (i = 0; i < len; i++)
      glutBitmapCharacter(font, string[i]);
} /* end printstring */

static void printhelp(void)
{
  /* aqua, lower left */
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
    glLoadIdentity();
    glOrtho(0, 500, 0, 500, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
      glLoadIdentity();
      glColor3f(0.0, 1.0, 1.0);
      /* hide for graphics class, use  -x */
      if(!hide)
      {
        glRasterPos2i(100, 480);
        printstring(GLUT_BITMAP_HELVETICA_18, "Cube split into five tetrahedrons");
      }
      glRasterPos2i(5, 65);
      printstring(GLUT_BITMAP_HELVETICA_12, "O or o changes offset");
      glRasterPos2i(5, 53);
      printstring(GLUT_BITMAP_HELVETICA_12, "F or f changes fast rotation");
      glRasterPos2i(5, 41);
      printstring(GLUT_BITMAP_HELVETICA_12, "C or c changes color blending");
      glRasterPos2i(5, 29);
      printstring(GLUT_BITMAP_HELVETICA_12, "W or w changes to wire frame");
      glRasterPos2i(5, 17);
      printstring(GLUT_BITMAP_HELVETICA_12, "arrow keys change rotation axis");
      glRasterPos2i(5, 5);
      printstring(GLUT_BITMAP_HELVETICA_12, "mouse button selects rotation axis");
      glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
} /* end printhelp */

static void display(void)
{
  /* display callback, clear frame buffer and z buffer, */
  /* rotate split_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);
  colorsplit_cube();
  printhelp();
  glFlush();
  glutSwapBuffers();
} /* end display */

static void spinSplit_Cube(void)
{
  /* Idle callback, spin split_cube 'rotation' degrees about selected axis */
  theta[axis] = theta[axis] + rotation;
  if(theta[axis] > 360.0) theta[axis] = theta[axis] - 360.0;
  /* display(); */
  glutPostRedisplay();
} /* end spinSplit_Cube */

static void mouse(int btn, int state, int x, int y)
{
  /* mouse callback, selects 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;
} /* end mouse */

static void keyboard(unsigned char key, int x, int y)
{
  switch (key)
  {
    case 'O':
      offset=offset+0.05;
      if(offset>0.5) offset=0.5;
      break;
    case 'o':
      offset=offset-0.05;
      if(offset<0.0) offset=0.0;
      break;
    case 'C':
      colorset=colorset+0.02;
      if(colorset>0.5) colorset=0.5;
      break;
    case 'c':
      colorset=colorset-0.02;
      if(colorset<0.0) colorset=0.0;
      break;
    case 'F':
      rotation=rotation+0.25;
      if(rotation>5.0) rotation=5.0;
      break;
    case 'f':
      rotation=rotation-0.25;
      if(rotation<0.0) rotation=0.0;
      break;
    case 'W':
      wireframe=1;
      break;
    case 'w':
      wireframe=0;
      break;
   }
} /* end keyboard */

static void special(int k, int x, int y)
{
  switch(k)
  {
    case GLUT_KEY_LEFT:
      axis = 1;
      break;
    case GLUT_KEY_RIGHT:
      axis = 2;
      break;
    case GLUT_KEY_DOWN:
      axis = 0;
      break;
    case GLUT_KEY_UP:
      wireframe = 1-wireframe;
      break;
  }
}

static void myReshape(int w, int h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if(w <= h) /* 2.0 -> 2.5 makes objects smaller */
      glOrtho(-2.5, 2.5, -2.5 * (GLfloat)h / (GLfloat)w,
              2.5 * (GLfloat)h / (GLfloat)w, -10.0, 10.0);
  else
      glOrtho(-2.5 * (GLfloat)w / (GLfloat)h,
              2.5 * (GLfloat)w / (GLfloat)h, -2.5, 2.5, -10.0, 10.0);
  glMatrixMode(GL_MODELVIEW);
} /* end myReshape */

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  if(argc>1)
  {
    if(argv[1][0]=='x' || argv[1][1]=='x') /* start with worse recognition */
    {                                      /* for graphics class demo      */
      colorset = 0.0;
      offset = 0.0;
      rotation = 5.0;
      hide = 1;
    }
  }
  /* need both double buffering and z buffer */
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(500, 500);
  glutCreateWindow("split_cube");
  glutReshapeFunc(myReshape);      /* enable resize/reshape */
  glutDisplayFunc(display);        /* enable display */
  glutIdleFunc(spinSplit_Cube);    /* enable idle function */
  glutMouseFunc(mouse);            /* enable mouse */
  glutKeyboardFunc(keyboard);      /* enable keyboard */
  glutSpecialFunc(special);        /* enable arrow keys */
  glEnable(GL_DEPTH_TEST);         /* enable hidden surface removal */
  glutMainLoop();
  return 0;
} /* end split_cube.c */

