/*
	Rotating white cube in an environment with three colored lights, to illustrate how
	light color works.
	
	Modified to add a moving eye point that slides along a line from (2.0, 2.0, 10.0) to
	(2.0, 2.0, -10.0), always looking at the origin.  As the eye moves, the cube can also
	be rotated in standard fashion.
	
	Example for the introductory computer graphics class; copyright ©2000, Steve Cunningham
*/
#include "glut.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

typedef GLfloat point3[3];
typedef GLfloat color [4];
typedef struct eyepoint {
			GLfloat x, y, z;
		} eyepoint;

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
               whichway = 1.0;
eyepoint ep;

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

void myinit(void)
{
//	Don't define set light positions here because they must be set 
//	in the context where the view is set
	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_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_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
        
	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_AMBIENT,  amb_color0 );
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_col0 );
	glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_col0 );

	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_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);  // make normal vectors 1-unit long after transform

//	initial eyepoint
	ep.x = ep.y = 2.0;
	ep.z = 10.0;
}

#include "cube.c"

void display( void )
{
//	Light positions...
	GLfloat light_pos0[]={  0.0, 10.0,  2.0, 1.0 }; // first light up y-axis
	GLfloat light_pos1[]={  5.0, -5.0,  2.0, 1.0 }; // second light lower right
	GLfloat light_pos2[]={ -5.0,  5.0,  2.0, 1.0 }; // third light lower left

//	Begin with a *PARAMETRIZED* viewpoint...
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	//             eye point     center of view       up
	gluLookAt( ep.x, ep.y, ep.z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

//	Light positions are set after the viewing transformation is defined
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos0 ); // light 0
	glLightfv(GL_LIGHT1, GL_POSITION, light_pos1 ); // light 1
	glLightfv(GL_LIGHT2, GL_POSITION, light_pos2 ); // light 2

//  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.  Sounds a little like
//  Humpty-Dumpty, but...
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 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 the viewing transform
    glLoadIdentity();
    
#define Angle 5.0

	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 we apply the rest of the modeling transformation by POST-multiplying
    //  by the saved matrix, and then save that and put the identity back on the
    //  stack.  *whew*
    glMultMatrixf( saveState );
    glGetFloatv( GL_MODELVIEW_MATRIX, saveState );
    glLoadIdentity();
    
    //  And 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);

    //  And 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);
}

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

void animate(void)
{
	GLfloat numsteps = 200.0, direction[3] = {0.0, 0.0, -20.0};

	if (ep.z < -10.0)
		whichway = -1.0;
	if (ep.z > 10.0)
		whichway = 1.0;
	ep.z += whichway*direction[2]/numsteps;	
	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
        glutIdleFunc(animate);

        myinit();
        glutMainLoop();
}