/* Rotating cube with color interpolation */

/* Demonstration of use of homogeneous coordinate 
transformations and simple data structure for representing
cube from Chapter 4 */

/*Both normals and colors are assigned to the vertices */
/*Cube is centered at origin so (unnormalized) normals
are the same as the vertex values */

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

#define LIGHTS_ON

class GraphicObject {

protected:

#ifdef LIGHTS_ON
	void compute_normal    (const GLfloat a[3],
				const GLfloat b[3],
				const GLfloat c[3], 
				GLfloat result[3]) {
		GLfloat length;

		result [0] = (b[1]-a[1])*(c[2]-a[2]) -
			     (b[2]-a[2])*(c[1]-a[1]);
		result [1] = (b[2]-a[2])*(c[0]-a[0]) -
			     (b[0]-a[0])*(c[2]-a[2]);
		result [2] = (b[0]-a[0])*(c[1]-a[1]) -
			     (b[1]-a[1])*(c[0]-a[0]);
		length = sqrt (result[0]*result[0] +
			result[1]*result[1] + result[2]*result[2]);
		if (fabs(length) > 0.01) {
			result[0] /= length;
			result[1] /= length;
			result[2] /= length;
			}
		else
			exit(1);
		}
#endif

	void SetColor (GLfloat color[3]) {
#ifdef LIGHTS_ON
		glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, color);
		glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, color);
		glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, color);
#else
		glColor3fv (color);
#endif
		}

	void polygon(int n, const int which[],
		const GLfloat vertices[][3], const GLfloat colors[][3])
	{
	
	/* draw a flat polygon via list of vertices */

#ifdef LIGHTS_ON
	/* use cross product of first three vertices for normal */

		GLfloat normal[3];
	
		compute_normal (vertices[which[0]],
			vertices[which[1]],vertices[which[2]], normal);
		glNormal3fv(normal);
#endif
		
		int i;
	 	glBegin(GL_POLYGON);
			for (i=0; i<n; ++i) {
				SetColor(colors[which[i]]);
				glVertex3fv(vertices[which[i]]);
				}
		glEnd();
	}

	void quadrilateral(int a, int b, int c , int d,
		const GLfloat vertices[][3], const GLfloat colors[][3])
	{
	/* draw a flat quadrilateral via list of vertices */
	
	int which[4];

	which[0] = a;
	which[1] = b;
	which[2] = c;
	which[3] = d;

	polygon (4, which, vertices, colors);
	}

public:
	virtual void drawself (void) = 0;

}; /* end of class GraphicObject */
	
class MovableObject : public GraphicObject {

protected:
	GLfloat location[3];
	GLfloat theta[3];

	void PreDraw (void) {
		glPushMatrix ();
	
		glTranslatef(location[0],location[1],location[2]);

		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);

// Note that rotation is the rightmost transformation, i.e. the first
// one to take effect.  I assume that all objects are drawn as though
// centered at the origin; this way the rotation really is around the
// origin.
		}

	void PostDraw (void) {
		glPopMatrix ();
		}

public:
	MovableObject (void) {
		location[0] = location[1] = location[2] = 0.0;
		theta[0] = theta[1] = theta[2] = 0.0;
		}

	virtual void rotate (int axis, GLfloat degrees) {
		theta[axis] += degrees;
		}

	virtual void translate (int axis, GLfloat howfar) {
		location[axis] += howfar;
		}
	
}; /* end of class MovableObject */

class ground : public GraphicObject {
	static GLfloat vertices[5][3];
	static GLfloat colors[5][3];

public:
	virtual void drawself (void) {
		static const which[] = {0,1,2,3,4};
		polygon (5,which,vertices,colors);
		}
	}; /* end of class ground */

GLfloat ground::vertices[][3] =
		{{-3.0,-3.0,0.0}, {3.0,-3.0,0.0},
		{5.0,-3.0,-4.0}, {0.0,-3.0,-6.0}, {-5.0,-3.0,-4.0}};
GLfloat ground::colors[][3] =
		{{1.0,0.0,0.0}, {0.5,0.5,0.0},
		{0.0,1.0,0.0}, {0.0,0.5,0.5}, {0.0,0.0,1.0}};

class cube : public MovableObject {
	static GLfloat vertices[8][3];

	static GLfloat colors[8][3];
// for some weird reason, initialization here doesn't work,
// and I have to initialize the arrays outside the class declaration.
// See note below.

	void colorcube (void)
	{
	
	/* map vertices to faces */
	
		quadrilateral(0,3,2,1,vertices,colors);
		quadrilateral(2,3,7,6,vertices,colors);
		quadrilateral(0,4,7,3,vertices,colors);
		quadrilateral(1,2,6,5,vertices,colors);
		quadrilateral(4,5,6,7,vertices,colors);
		quadrilateral(0,1,5,4,vertices,colors);
	}

public:

	cube (void) {
		translate (2, -4.0);
		}

	virtual void drawself (void) {
		PreDraw ();
		colorcube ();
		PostDraw ();
	}

}; /* end of class cube */
GLfloat cube::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}};

GLfloat cube::colors[][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}};
// These initializations really should be inside the class declaration,
// but that doesn't link.  See note above.


static cube TheCube;
static ground TheGround;

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

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();
	TheGround.drawself();
 	TheCube.drawself();

 	glFlush();
	glutSwapBuffers();
}

/* mouse callback, selects an axis about which to rotate */
/* And rotates it: positive if shift key down, negative if not */
void mouse(int btn, int state, int x, int y)
{
	float amount;

	int modifiers = glutGetModifiers();

	if (state == GLUT_DOWN) return;

	if (modifiers & GLUT_ACTIVE_SHIFT) {
           amount = 5.0;
	   }
	else {
	   amount = -5.0;
	   }

	if(btn==GLUT_LEFT_BUTTON)
	   TheCube.rotate (0,amount);
	if(btn==GLUT_MIDDLE_BUTTON)
	   TheCube.rotate (1,amount);
	if(btn==GLUT_RIGHT_BUTTON)
	   TheCube.rotate (2,amount);
	
        glutPostRedisplay ();
}

void myReshape(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glFrustum(-2.0, 2.0, -2.0 * (GLfloat) h / (GLfloat) w,
            2.0 * (GLfloat) h / (GLfloat) w, 1.0, 10.0);
    else
        glFrustum(-2.0 * (GLfloat) w / (GLfloat) h,
            2.0 * (GLfloat) w / (GLfloat) h, -2.0, 2.0, 1.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*
void myKeyboard (unsigned char key, int x, int y)
{
    static int number = 0;

    key = tolower (key);

    if (isdigit(key)) {
       number = 10 * number + (key - '0');
       return;
       }
    if (key == 'n') {
       if (number > 0) sides = number;
       else sides = 3;
       }
    else if (key == 
}
*/

void mySpecialKeyboard (int key, int x, int y)
{
	float amount = 0.2;

	if (key == GLUT_KEY_UP)
	   TheCube.translate (1,amount);
	else if (key == GLUT_KEY_DOWN)
	   TheCube.translate (1,-amount);
	else if (key == GLUT_KEY_LEFT)
	   TheCube.translate (0,-amount);
	else if (key == GLUT_KEY_RIGHT)
	   TheCube.translate (0,amount);
	else if (key == GLUT_KEY_PAGE_UP)
	   TheCube.translate (2,-amount);
	else if (key == GLUT_KEY_PAGE_DOWN)
	   TheCube.translate (2,amount);
	
        glutPostRedisplay ();
}

void myinit (void) {
	
/* need double buffering, z buffer */
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(500, 500);
    glutCreateWindow("colorcube");
    glEnable(GL_DEPTH_TEST); /* Enable hidden--surface--removal */

#ifdef LIGHTS_ON
/* let's put some light on the subject */
    static GLfloat ambient[] = {0.4, 0.4, 0.4, 1.0};
    static GLfloat light0pos[] = {10.0, 10.0, 10.0, 0.0};
    static GLfloat light0amb[] = {0.2, 0.2, 0.2, 1.0};
    static GLfloat light0diff[] = {0.6, 0.6, 0.6, 1.0};
    static GLfloat light0spec[] = {0.9, 0.0, 0.0, 1.0};

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);

    glLightfv (GL_LIGHT0, GL_POSITION, light0pos);
//  glLightfv (GL_LIGHT0, GL_AMBIENT, light0amb);
    glLightfv (GL_LIGHT0, GL_DIFFUSE, light0diff);
    glLightfv (GL_LIGHT0, GL_SPECULAR, light0spec);
    glLightModelfv (GL_LIGHT_MODEL_AMBIENT, ambient);
#endif
}

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

    glutReshapeFunc(myReshape);
    glutDisplayFunc(display);
/* 	   glutIdleFunc(spinCube); */
    glutMouseFunc(mouse);
    glutSpecialFunc(mySpecialKeyboard);
    glutMainLoop();
}
