/* split_tetra2.c Rotating split_tetra with color sections   */
/*                8 tetrahedrons build a tetrahedron         */
/*                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           */
/* "s" split each tetrahedron into 4 tetrahedron */

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

static GLfloat vertices[1525][3] = {{0.0, 0.0, 0.0}, /* unused, start with [1] */
       {-0.500,-0.250,-0.250}, { 0.500, -0.250, -0.250},  /* 1,2 */
       { 0.000, 0.616,-0.250}, { 0.000,  0.000,  0.616}};  /* 3,4 */
static int nvert = 4; /* increment before inserting, count not including zero */
static int ivert[1025][4] = {{1,2,3,4}};
static int overt[1025] = {1}; /* offset toward this point */
static int npoly = 1;

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},
	   {0.7,0.5,0.5}, {0.5,0.7,0.5}, {0.5,0.5,0.7}};
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 next_tetra()
{
  int tvert[257][4];
  int tpoly;
  int ipoly, j, k;
  int p1, p2, p3, p4, p12, p13, p14, p23, p24, p34;
  
  tpoly = npoly;
  for(ipoly=0; ipoly<tpoly; ipoly++) /* move old polygons to temp */
    for(j=0; j<4; j++)
      tvert[ipoly][j] = ivert[ipoly][j];

  npoly = 0;
  for(ipoly=0; ipoly<tpoly; ipoly++) /* generate 8 new for each old */
  {
    p1 = tvert[ipoly][0];
	p2 = tvert[ipoly][1];
	p3 = tvert[ipoly][2];
	p4 = tvert[ipoly][3];
	nvert++;
	p12 = nvert;
	vertices[nvert][0] = (vertices[p1][0]+vertices[p2][0])/2.0;
	vertices[nvert][1] = (vertices[p1][1]+vertices[p2][1])/2.0;
	vertices[nvert][2] = (vertices[p1][2]+vertices[p2][2])/2.0;
	nvert++;
	p13 = nvert;
	vertices[nvert][0] = (vertices[p1][0]+vertices[p3][0])/2.0;
	vertices[nvert][1] = (vertices[p1][1]+vertices[p3][1])/2.0;
	vertices[nvert][2] = (vertices[p1][2]+vertices[p3][2])/2.0;
	nvert++;
	p14 = nvert;
	vertices[nvert][0] = (vertices[p1][0]+vertices[p4][0])/2.0;
	vertices[nvert][1] = (vertices[p1][1]+vertices[p4][1])/2.0;
	vertices[nvert][2] = (vertices[p1][2]+vertices[p4][2])/2.0;
	nvert++;
	p23 = nvert;
	vertices[nvert][0] = (vertices[p2][0]+vertices[p3][0])/2.0;
	vertices[nvert][1] = (vertices[p2][1]+vertices[p3][1])/2.0;
	vertices[nvert][2] = (vertices[p2][2]+vertices[p3][2])/2.0;
	nvert++;
	p24 = nvert;
	vertices[nvert][0] = (vertices[p2][0]+vertices[p4][0])/2.0;
	vertices[nvert][1] = (vertices[p2][1]+vertices[p4][1])/2.0;
	vertices[nvert][2] = (vertices[p2][2]+vertices[p4][2])/2.0;
	nvert++;
	p34 = nvert;
	vertices[nvert][0] = (vertices[p3][0]+vertices[p4][0])/2.0;
	vertices[nvert][1] = (vertices[p3][1]+vertices[p4][1])/2.0;
	vertices[nvert][2] = (vertices[p3][2]+vertices[p4][2])/2.0;
	ivert[npoly][0] = p1;
	ivert[npoly][1] = p12;
	ivert[npoly][2] = p13;
	ivert[npoly][3] = p14;
	overt[npoly] = p1;
	npoly++;
	ivert[npoly][0] = p2;
	ivert[npoly][1] = p12;
	ivert[npoly][2] = p23;
	ivert[npoly][3] = p24;
	overt[npoly] = p2;
	npoly++;
	ivert[npoly][0] = p3;
	ivert[npoly][1] = p13;
	ivert[npoly][2] = p23;
	ivert[npoly][3] = p34;
	overt[npoly] = p3;
	npoly++;
	ivert[npoly][0] = p4;
	ivert[npoly][1] = p14;
	ivert[npoly][2] = p24;
	ivert[npoly][3] = p34;
	overt[npoly] = p4;
	npoly++;
	ivert[npoly][0] = p24;
	ivert[npoly][1] = p12;
	ivert[npoly][2] = p13;
	ivert[npoly][3] = p23;
    	/* create new offset node, for plotting only */
    	nvert++;
    	overt[npoly] = nvert;
    	vertices[nvert][0] = (vertices[p12][0]+vertices[p13][0]+vertices[p23][0])/3.0;
    	vertices[nvert][1] = (vertices[p12][1]+vertices[p13][1]+vertices[p23][1])/3.0;
    	vertices[nvert][2] = (vertices[p12][2]+vertices[p13][2]+vertices[p23][2])/3.0;
	npoly++;
	ivert[npoly][0] = p13;
	ivert[npoly][1] = p12;
	ivert[npoly][2] = p14;
	ivert[npoly][3] = p24;
    	/* create new offset node, for plotting only */
    	nvert++;
    	overt[npoly] = nvert;
    	vertices[nvert][0] = (vertices[p13][0]+vertices[p12][0]+vertices[p14][0])/3.0;
    	vertices[nvert][1] = (vertices[p13][1]+vertices[p12][1]+vertices[p14][1])/3.0;
    	vertices[nvert][2] = (vertices[p13][2]+vertices[p12][2]+vertices[p14][2])/3.0;
	npoly++;
	ivert[npoly][0] = p13;
	ivert[npoly][1] = p23;
	ivert[npoly][2] = p24;
	ivert[npoly][3] = p34;
    	/* create new offset node, for plotting only */
    	nvert++;
    	overt[npoly] = nvert;
    	vertices[nvert][0] = (vertices[p13][0]+vertices[p23][0]+vertices[p34][0])/3.0;
    	vertices[nvert][1] = (vertices[p13][1]+vertices[p23][1]+vertices[p34][1])/3.0;
    	vertices[nvert][2] = (vertices[p13][2]+vertices[p23][2]+vertices[p34][2])/3.0;
	npoly++;
	ivert[npoly][0] = p24;
	ivert[npoly][1] = p13;
	ivert[npoly][2] = p14;
	ivert[npoly][3] = p34;
    	/* create new offset node, for plotting only */
    	nvert++;
    	overt[npoly] = nvert;
    	vertices[nvert][0] = (vertices[p13][0]+vertices[p14][0]+vertices[p34][0])/3.0;
    	vertices[nvert][1] = (vertices[p13][1]+vertices[p14][1]+vertices[p34][1])/3.0;
    	vertices[nvert][2] = (vertices[p13][2]+vertices[p14][2]+vertices[p34][2])/3.0;
	npoly++;
  }
}

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 */
  double scale = 2.5;

  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];
      overtices[i][j]*=scale;
      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_tetra(void)
{
  int i;
  
  /* map vertices to tetrahedrons with 4 vertices each */
  /* tetrahedron(0, 1,2,3,4, 1); sample: color, 4 vertices, offset toward */
  for(i=0; i<npoly; i++)
  {
    tetrahedron(i%9, ivert[i][0],ivert[i][1],ivert[i][2],ivert[i][3], ivert[i][0]);
  }
} /* end colorsplit_tetra */

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, "s, tetra split into eight 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_tetra 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_tetra();
  printhelp();
  glFlush();
  glutSwapBuffers();
} /* end display */

static void spinSplit_tetra(void)
{
  /* Idle callback, spin split_tetra '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_tetra */

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;
    case 's':
      next_tetra();
      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(argv[0]);
  glutReshapeFunc(myReshape);      /* enable resize/reshape */
  glutDisplayFunc(display);        /* enable display */
  glutIdleFunc(spinSplit_tetra);    /* 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_tetra.c */

