/*==========================================================*/
/* Pair.c                                                   */
/*==========================================================*/
/*                                                          */
/* A pair of rotating tetrahedrons with color interpolation */
/*                                                          */
/* Demonstration of use of homogeneous coordinate           */
/* transformations and a simple data structure              */
/*                                                          */
/* Colors are assigned to the verticesL                     */
/* Left mouse button control direction of rotation          */
/* Or specify rotation axis using X, Y, Z                   */
/*                                                          */
/* Author:  Bary W Pollack                                  */
/* Updated: Apr. 8, 2005                                    */
/*                                                          */
/*==========================================================*/

#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int bool;
#define true    (1)
#define false   (0)

/*==========================================================*/

#define dX0 -3.0
#define dY0 +0.0
#define dZ0 +0.0

GLfloat verticesL[][3] =
{
    {-dX0+1.0, dY0-1.0, dZ0-1.0}, 
    {-dX0-1.0, dY0+1.0, dZ0-1.0}, 
    {-dX0+1.0, dY0+1.0, dZ0+1.0}, 
    {-dX0-1.0, dY0-1.0, dZ0+1.0},

    {-dX0+1.0, dY0-1.0, dZ0-1.0}, 
    {-dX0-1.0, dY0+1.0, dZ0-1.0}, 
    {-dX0+1.0, dY0+1.0, dZ0+1.0}, 
    {-dX0-1.0, dY0-1.0, dZ0+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}
};

GLfloat verticesR[][3] =
{
    {+dX0+1.0, dY0-1.0, dZ0-1.0}, 
    {+dX0-1.0, dY0+1.0, dZ0-1.0}, 
    {+dX0+1.0, dY0+1.0, dZ0+1.0}, 
    {+dX0-1.0, dY0-1.0, dZ0+1.0},

    {+dX0+1.0, dY0-1.0, dZ0-1.0}, 
    {+dX0-1.0, dY0+1.0, dZ0-1.0}, 
    {+dX0+1.0, dY0+1.0, dZ0+1.0}, 
    {+dX0-1.0, dY0-1.0, dZ0+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}
};

GLfloat colors[][3] =
{   //    red             green            blue             white
    {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {1.0, 1.0, 1.0}
};

/*==========================================================*/

void polygon (GLfloat vertices[][3], int a, int b, int c)
{
    /* draw a polygon via list of verticesL */
    glBegin (GL_TRIANGLES);
        glColor3fv  (colors[a]);    // For solid color, set this color only
        glVertex3fv (vertices[a]);
        glColor3fv  (colors[b]);
        glVertex3fv (vertices[b]);
        glColor3fv  (colors[c]);
        glVertex3fv (vertices[c]);
    glEnd ();
    /* draw polygon edges in black on right tetrahedron */
    if (vertices == verticesL) {
        glBegin (GL_LINE_LOOP);
            glColor3f (0.0, 0.0, 0.0);
            glVertex3fv (vertices[a]);
            glVertex3fv (vertices[b]);
            glVertex3fv (vertices[c]);
        glEnd ();
    }
}

/*==========================================================*/

void tetrahedron (GLfloat vertices[][3])
{
    /* map verticesL to faces */
    polygon (vertices, 0, 1, 2);
    polygon (vertices, 1, 3, 2);
    polygon (vertices, 0, 3, 1);
    polygon (vertices, 0, 2, 3);
}

/*==========================================================*/

static GLfloat  theta[] = { 0.0, 0.0, 0.0 };
static GLfloat  transL[] = { -dX0, dY0, dZ0 };
static GLfloat  transR[] = { +dX0, dY0, dZ0 };
static GLint    axis = 0;
static bool     bSpinning = true;
static GLfloat  fAmt = 1.0F;
static bool     bGrowing  = false;

/*==========================================================*/

GLfloat newAmt (void)
{
    static int nDir = +1;

    if (nDir > 0)
        fAmt *= 1.001F;
    else
        fAmt /= 1.001F;
    if ((fAmt >= 2.5F) || (fAmt <= 0.5F))
        nDir = -nDir;
    return fAmt;
}

/*==========================================================*/

void display (void)
{
    /* display callback, clear frame buffer and z buffer, 
       rotate figure and draw, swap buffers             */
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity ();
    glTranslatef (-transL[0], -transL[1], -transL[2]);
    glRotatef (+theta[0], 1.0, 0.0, 0.0);       /* x */
    glRotatef (+theta[1], 0.0, 1.0, 0.0);       /* y */
    glRotatef (+theta[2], 0.0, 0.0, 1.0);       /* z */
    glTranslatef (+transL[0], +transL[1], +transL[2]);
    tetrahedron (verticesL);

    glLoadIdentity ();
    glTranslatef (+transR[0], +transR[1], +transR[2]);
    if (bGrowing)
        fAmt = newAmt ();
    glScalef (fAmt, fAmt, fAmt);
    glRotatef (-theta[0], 1.0, 0.0, 0.0);       /* x */
    glRotatef (-theta[1], 0.0, 1.0, 0.0);       /* y */
    glRotatef (-theta[2], 0.0, 0.0, 1.0);       /* z */
    glTranslatef (-transR[0], -transR[1], -transR[2]);
    tetrahedron (verticesR);

    glFlush ();
    glutSwapBuffers ();
}

/*==========================================================*/

void spin (void)
{
    /* Idle callback, rotate tetrahedron 0.1 degrees about selected axis */
    theta[axis] += 0.1;
    if (theta[axis] > 360.0 )
        theta[axis] -= 360.0;
    glutPostRedisplay ();
}

/*==========================================================*/

void mouse (int btn, int state, int x, int y)
{
    char *sAxis [] = { "X-axis", "Y-axis", "Z-axis" };

    if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
        puts ("LEFT-DOWN");
    else if (btn == GLUT_RIGHT_BUTTON  && state == GLUT_DOWN)
        puts ("RIGHT-DOWN");
    else if (btn == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
        puts ("MIDDLE-DOWN");
    else if (btn == GLUT_LEFT_BUTTON   && state == GLUT_UP)
        puts ("LEFT-UP");
    else if (btn == GLUT_RIGHT_BUTTON  && state == GLUT_UP)
        puts ("RIGHT-UP");
    else if (btn == GLUT_MIDDLE_BUTTON && state == GLUT_UP)
        puts ("MIDDLE-UP");

    /* mouse callback, selects an axis about which to rotate */
    if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        axis = (++axis) % 3;
        printf ("Rotate about %s\n", sAxis[axis]);
    }
}

/*==========================================================*/

void keyboard (unsigned char key, int x, int y)
{
    char *sAxis [] = { "X-axis", "Y-axis", "Z-axis" };
    void help (void);
    int  i, j;

    /* keyboard callback, selects an axis about which to rotate */
    /// key = toupper (key);
    switch (key)
    {
      case '0': theta[0] = theta[1] = theta[2] = 0.0;
                fAmt = 1.0F;
                transL[0] = dX0;  transL[1] = dY0;  transL[2] = dZ0;
                transR[0] = dX0;  transR[1] = dY0;  transR[2] = dZ0;
                for (i = 0; i < 4; i++)
                    for (j = 0; j < 3; j++)
                    {
                        verticesL[i][j] = verticesL[i+4][j];
                        verticesR[i][j] = verticesR[i+4][j];
                    }
                glutPostRedisplay ();  break;

      case 'x':
      case 'X': axis = 0;  break;
      case 'y':
      case 'Y': axis = 1;  break;
      case 'z':
      case 'Z': axis = 2;  break;

      case 'g':
      case 'G': bGrowing = !bGrowing;  break;

      case 'u': for (i = 0; i < 4; i++)
                    { verticesL[i][0] += 0.1; verticesR[i][0] += 0.1; }
                break;
      case 'v': for (i = 0; i < 4; i++)
                    { verticesL[i][1] += 0.1; verticesR[i][1] += 0.1; }
                break;
      case 'w': for (i = 0; i < 4; i++)
                    { verticesL[i][2] += 0.1; verticesR[i][2] += 0.1; }
                break;
      case 'U': for (i = 0; i < 4; i++)
                    { verticesL[i][0] -= 0.1; verticesR[i][0] -= 0.1; }
                break;
      case 'V': for (i = 0; i < 4; i++)
                    { verticesL[i][1] -= 0.1; verticesR[i][1] -= 0.1; }
                break;
      case 'W': for (i = 0; i < 4; i++)
                    { verticesL[i][2] -= 0.1; verticesR[i][2] -= 0.1; }
                break;

      case 's':
      case 'S': if (bSpinning = !bSpinning)
                {
                    glutIdleFunc (spin);
                }
                else
                {
                    glutIdleFunc (NULL);
                    key = -9;
                }
                break;
      case 'q':
      case 'Q': printf ("Quit!\n\n");
                exit (0);
      case '?': help ();
      default:  key = -9;
    }
    if (('X' <= key && key <= 'Z') || ('x' <= key && key <= 'z'))
        printf ("Rotate about %s\n", sAxis[axis]);
    else if ('U' <= toupper (key) && toupper (key) <= 'W')
    {
        printf ("Translate along %s\n", sAxis[toupper (key) - 'U']);
        glutPostRedisplay ();
    }
}

/*==========================================================*/

void myReshape (int w, int h)
{
    double dVal = 6.0;

    glViewport (0, 0, w, h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    if (w <= h)
        glOrtho (-dVal, dVal, -dVal * (GLfloat) h / (GLfloat) w, 
                               dVal * (GLfloat) h / (GLfloat) w, -1.5*dVal, 1.5*dVal);
    else
        glOrtho (-dVal * (GLfloat) w / (GLfloat) h, 
                  dVal * (GLfloat) w / (GLfloat) h, -dVal, dVal, -1.5*dVal, 1.5*dVal);
    glMatrixMode (GL_MODELVIEW);
    printf ("Reshaped...\n");
}


/*==========================================================*/

static char *sTitle = "A Pair of Rotating Tetrahedra";

void help (void)
{
    char *sMsg =
            "\nPress left mouse button to change rotation axis\n"
            "Press X, Y, Z to rotate about that axis\n"
            "press 0 to reoriginate;\n"
            "Press U, V, W to translate along +X, +Y, +Z axes\n"
            "Press u, v, w to translate along -X, -Y, -Z axes\n"
            "Press G to start/stop growing/shrinking\n"
            "Press S to start/stop spinning\n"
            "Press Q to quit\n"
            "? to see this message\n\n";
    printf ("\n%s\n\n", sTitle);
    printf (sMsg);
}

/*==========================================================*/

int main (int argc, char **argv)
{
    glutInit (&argc, argv);
    help ();
    /* need both double buffering and z buffer */
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize (500, 400);
    glutCreateWindow (sTitle);
    glutReshapeFunc (myReshape);
    glutDisplayFunc (display);
    glutIdleFunc (spin);
    glutKeyboardFunc (keyboard);
    glutMouseFunc (mouse);
    glEnable (GL_DEPTH_TEST); /* Enable hidden-surface-removal */
    glClearColor  (0.9, 0.9, 0.9, 1.0);

    glutMainLoop ();

    return 0;
}

/*==========================================================*/
