/*
	White cube in an environment with three colored lights, to illustrate how
	light color works

	Example for intro computer graphics course, (c)2000 Steve Cunningham
*/
#include "glut.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

typedef GLfloat point3[3];
typedef GLfloat color [4];

static char ch; // keyboard callback return character
static GLfloat saveState[16] = {1.0,0.0,0.0,0.0,
                                0.0,1.0,0.0,0.0,
                                0.0,0.0,1.0,0.0,
                                0.0,0.0,0.0,1.0},
               viewProj[16]; // hold transforms

// function prototypes
	void myinit( void );
	void cube( float, float, float );
	void display( void );
	void reshape( int ,int );
	void keyboard(unsigned char, int, int );

void myinit(void)
{
	GLfloat light_pos0[]={  0.0, 10.0,  2.0, 1.0 }; // light 1: up y-axis
	GLfloat light_col0[]={  1.0,  0.0,  0.0, 1.0 }; // light is red
	GLfloat amb_color0[]={  0.3,  0.0,  0.0, 1.0 }; // even ambiently

	GLfloat light_pos1[]={  5.0, -5.0,  2.0, 1.0 }; // light 2: lower right
	GLfloat light_col1[]={  0.0,  1.0,  0.0, 1.0 }; // light is green
	GLfloat amb_color1[]={  0.0,  0.3,  0.0, 1.0 }; // even ambiently
	
	GLfloat light_pos2[]={ -5.0,  5.0,  2.0, 1.0 }; // light 3: lower left
	GLfloat light_col2[]={  0.0,  0.0,  1.0, 1.0 }; // light is blue
	GLfloat amb_color2[]={  0.0,  0.0,  0.3, 1.0 }; // even ambiently

//	set up overall light data
	GLfloat mat_specular[]  ={ 0.8, 0.8, 0.8, 1.0 };
	long i = 1;

	glClearColor( 0.0, 0.0, 0.0, 0.0 );
	glShadeModel(GL_SMOOTH);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular );

	glLightfv(GL_LIGHT0, GL_POSITION, light_pos0 ); // light 0
	glLightfv(GL_LIGHT0, GL_AMBIENT, amb_color0 );
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_col0 );
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_col0 );

	glLightfv(GL_LIGHT1, GL_POSITION, light_pos1 ); // light 1
	glLightfv(GL_LIGHT1, GL_AMBIENT, amb_color1 );
	glLightfv(GL_LIGHT1, GL_SPECULAR, light_col1 );
	glLightfv(GL_LIGHT1, GL_DIFFUSE, light_col1 );

	glLightfv(GL_LIGHT2, GL_POSITION, light_pos2 ); // light 2
	glLightfv(GL_LIGHT2, GL_AMBIENT, amb_color2 );
	glLightfv(GL_LIGHT2, GL_SPECULAR, light_col2 );
	glLightfv(GL_LIGHT2, GL_DIFFUSE, light_col2 );

	glLightModeliv(GL_LIGHT_MODEL_TWO_SIDE, &i ); // two-sided lighting

/* attributes */

	glEnable(GL_LIGHTING);   // so lighting models are used
	glEnable(GL_LIGHT0);     // we'll use LIGHT0
	glEnable(GL_LIGHT1);     // ... and LIGHT1
	glEnable(GL_LIGHT2);     // ... and LIGHT2
	glEnable(GL_DEPTH_TEST); // allow z-buffer display
	glEnable(GL_NORMALIZE);  // normalize vectors after transform
}

/*
	Basic function to draw a unit cube centered at the origin with
	faces perpendicular to the standard x, y, and z axes, in a color
	specified through the parameters to the function.  This cube is
	extremely simple but is enough to allow us to build examples on
	it.
*/
void cube(float r, float g, float b)
{
// define point and color data types
	color cubecolor;
	point3 vertices[8]={{-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} };

	point3 normals[6]={{ 0.0, 0.0, 1.0},
				 {-1.0, 0.0, 0.0},
				 { 0.0, 0.0,-1.0},
				 { 1.0, 0.0, 0.0},
				 { 0.0,-1.0, 0.0},
				 { 0.0, 1.0, 0.0} };

	GLfloat mat_shininess[]={ 50.0 };

	cubecolor[0] = r; cubecolor[1] = g; cubecolor[2] = b;
	cubecolor[3] = 1.0; 
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, cubecolor );
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, cubecolor );
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );
	glBegin(GL_QUADS);
		glNormal3fv(normals[0]);   // first quad: positive Z face
		glVertex3fv(vertices[1]);
		glNormal3fv(normals[0]);
		glVertex3fv(vertices[5]);
		glNormal3fv(normals[0]);
		glVertex3fv(vertices[7]);
		glNormal3fv(normals[0]);
		glVertex3fv(vertices[3]);
		glNormal3fv(normals[5]);   // second quad: positive Y face
		glVertex3fv(vertices[7]);
		glNormal3fv(normals[5]);
		glVertex3fv(vertices[6]);
		glNormal3fv(normals[5]);
		glVertex3fv(vertices[2]);
		glNormal3fv(normals[5]);
		glVertex3fv(vertices[3]);
		glNormal3fv(normals[2]);   // third quad: negative Z face
		glVertex3fv(vertices[2]);
		glNormal3fv(normals[2]);
		glVertex3fv(vertices[6]);
		glNormal3fv(normals[2]);
		glVertex3fv(vertices[4]);
		glNormal3fv(normals[2]);
		glVertex3fv(vertices[0]);
		glNormal3fv(normals[3]);  // fourth quad: positive X face
		glVertex3fv(vertices[5]);
		glNormal3fv(normals[3]);
		glVertex3fv(vertices[4]);
		glNormal3fv(normals[3]);
		glVertex3fv(vertices[6]);
		glNormal3fv(normals[3]);
		glVertex3fv(vertices[7]);
		glNormal3fv(normals[4]);  // fifth quad: negative Y face
		glVertex3fv(vertices[4]);
		glNormal3fv(normals[4]);
		glVertex3fv(vertices[5]);
		glNormal3fv(normals[4]);
		glVertex3fv(vertices[1]);
		glNormal3fv(normals[4]);
		glVertex3fv(vertices[0]);
		glNormal3fv(normals[1]);  // sixth quad: negative X face
		glVertex3fv(vertices[0]);
		glNormal3fv(normals[1]);
		glVertex3fv(vertices[1]);
		glNormal3fv(normals[1]);
		glVertex3fv(vertices[3]);
		glNormal3fv(normals[1]);
		glVertex3fv(vertices[2]);
	glEnd();
}

void display( void )
{
#define Angle 5.0

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//  The GL_MODELVIEW_MATRIX starts out including the viewing transformation,
//  so we'll first take that off, then construct the modeling transformation,
//  then take *THAT* off, then put them back together.
	//  We save the original viewing projection in the  viewProj  array
	glPushMatrix();
	glGetFloatv( GL_MODELVIEW_MATRIX, viewProj );
	//  Put the identity onto the modelview stack to start viewing transform
	glLoadIdentity();

	switch(ch)
	{
		case 'q':
			glRotatef( Angle, 1.0, 0.0, 0.0); break;
		case 'w':
			glRotatef(-Angle, 1.0, 0.0, 0.0); break;
		case 'a':
			glRotatef( Angle, 0.0, 1.0, 0.0); break;
		case 's':
			glRotatef(-Angle, 0.0, 1.0, 0.0); break;
		case 'z':
			glRotatef( Angle, 0.0, 0.0, 1.0); break;
		case 'x':
			glRotatef(-Angle, 0.0, 0.0, 1.0); break;
	}
	ch = ' ';
	
	//  NOW apply the remaining modeling transformation by POST-multiplying
	//  by the saved matrix, then save that and put the identity back on the
	//  stack.  *whew*
	glMultMatrixf( saveState );
	glGetFloatv( GL_MODELVIEW_MATRIX, saveState );
	glLoadIdentity();
	//  Finally rebuild the overall modelview matrix by multiplying by the
	//  viewing transformation and then by the modeling transformation
	glMultMatrixf( viewProj );
	glMultMatrixf( saveState );

	cube(1.0,1.0,1.0);

	//  Put ourselves back into the original GL_MODELVIEW_MATRIX by popping
	//  off the new work.  The stack again has one thing on it, and that is
	//  the viewing transformation.
	glPopMatrix();
	glutSwapBuffers();
 }

void reshape(int w,int h)
{
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0,1.0,1.0,30.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	//           eye point     center of view      up
	gluLookAt( 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void keyboard(unsigned char key, int x, int y)
{
	ch = ' ';
	switch (key)
	{
		case 'q' :     // rotate around X; q = positive, w = negative
			ch = key; break;
		case 'w' :
			ch = key; break;
		case 'a' :    // rotate around Y; a = positive, s = negative
			ch = key; break;
		case 's' :
			ch = key; break;
		case 'z' :    // rotate around Z; z = positive, x = negative
			ch = key; break;
		case 'x' :
			ch = key; break;
	}
	glutPostRedisplay();
}

void main(int argc, char** argv)
{
// Standard GLUT initialization
	glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(500,500);
	glutInitWindowPosition(70,70);
	glutCreateWindow("A white cube with three colored lights");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard); // enable keyboard callback

	myinit();
	glutMainLoop();
}
