/*
   Controlling the rotation of an object with respect to the world axes
   Control is provided by the keyboard; the object is our canonical cube
   
   X-rotations  :  q  w
   Y-rotations  :  a  s
   Z-rotations  :  z  x
   
   Cube contains one face that is textured with the output of another sample
   program prepared earlier.
   
   Sample for introductory graphics class.  Copyright © 2000, Steve Cunningham
*/
#include "glut.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

static char ch; // the character from the keyboard that controls the rotation
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
               
#define TEX_WIDTH 512
#define TEX_HEIGHT 512
static  GLubyte texImage[TEX_WIDTH][TEX_HEIGHT][3];
static  GLuint texName[1]; // parameter is the number of textures in program

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

void setTexture(void)
{
        FILE * fd;
        GLubyte ch;
        int i,j,k;

        fd = fopen("function.rgb", "r");
        for (i=0; i<TEX_WIDTH; i++)     // for each row
        {
           for (j=0; j<TEX_HEIGHT; j++) // for each column
           {
              for (k=0; k<3; k++)       // read RGB components of the pixel
              {
                 fread(&ch, 1, 1, fd);
                 texImage[i][j][k] = (GLubyte) ch;
              }
           }
        }
        fclose(fd);
}

void myinit(void)
{
//      set up overall light data, including specular=ambient=light colors
        GLfloat light_position[]={ 10.0, 10.0, -10.0, 1.0 };
        GLfloat light_color[]   ={ 1.0, 1.0, 1.0, 1.0 };
        GLfloat ambient_color[] ={ 0.3, 0.3, 0.3, 1.0 };
        GLfloat mat_specular[]  ={ 1.0, 1.0, 1.0, 1.0 };
        GLfloat fogColor[4]={0.5,0.5,0.5,1.0};
        glClearColor( 0.0, 0.0, 1.0, 0.0 );
        glShadeModel(GL_SMOOTH);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular );
        glLightfv(GL_LIGHT0, GL_POSITION, light_position );
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color );
        glLightfv(GL_LIGHT0, GL_SPECULAR, light_color );
        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_color );

// define the texture available to the project
        setTexture();
        
// define the fog parameters
        glFogi(GL_FOG_MODE,GL_EXP);
        glFogfv(GL_FOG_COLOR,fogColor);
        glFogf(GL_FOG_START, 0.0 );
        glFogf(GL_FOG_END, 2.0 );
        glFogf(GL_FOG_DENSITY,0.15);

/* attributes */
        glEnable(GL_LIGHTING);   // so lighting models are used
        glEnable(GL_LIGHT0);     // we'll use LIGHT0
        glEnable(GL_DEPTH_TEST); // allow z-buffer display
        glEnable(GL_TEXTURE_2D); // allow 2D texture maps
        glEnable(GL_FOG);
}

void cube(void)
{	/* define a point data type */
    typedef GLfloat point3[3];

    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 color1[]={1.0, 0.0, 0.0, 1.0};
    GLfloat color2[]={1.0, 1.0, 0.0, 1.0};
    GLfloat mat_shininess[]={ 50.0 };

//  actually draw the cube
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color1 );
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color1 );
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );
    glBegin(GL_QUADS);
      glNormal3fv(normals[0]);   // first quad: positive Z face
      glVertex3fv(vertices[1]);
      glVertex3fv(vertices[5]);
      glVertex3fv(vertices[7]);
      glVertex3fv(vertices[3]);
      glNormal3fv(normals[5]);   // second quad: positive Y face
      glVertex3fv(vertices[7]);
      glVertex3fv(vertices[6]);
      glVertex3fv(vertices[2]);
      glVertex3fv(vertices[3]);
      glNormal3fv(normals[2]);   // third quad: negative Z face
      glVertex3fv(vertices[2]);
      glVertex3fv(vertices[6]);
      glVertex3fv(vertices[4]);
      glVertex3fv(vertices[0]);
    glEnd();

    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color2 );
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color2 );
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );
    glBegin(GL_QUADS);
      glNormal3fv(normals[3]);  // fourth quad: positive X face
      glVertex3fv(vertices[5]);
      glVertex3fv(vertices[4]);
      glVertex3fv(vertices[6]);
      glVertex3fv(vertices[7]);
      glNormal3fv(normals[4]);  // fifth quad: negative Y face
      glVertex3fv(vertices[4]);
      glVertex3fv(vertices[5]);
      glVertex3fv(vertices[1]);
      glVertex3fv(vertices[0]);
    glEnd();
    
    glGenTextures(1, texName);                 // define texture for sixth face
    glBindTexture(GL_TEXTURE_2D,texName[0]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGB8,TEX_WIDTH,TEX_HEIGHT,
                     0,GL_RGB,GL_UNSIGNED_BYTE,texImage);
    glBindTexture(GL_TEXTURE_2D, texName[0]);
    
    glBegin(GL_QUADS);          // sixth quad: negative X face
      glNormal3fv(normals[1]);
      glTexCoord2f(0.0, 0.0); glVertex3fv(vertices[0]);
      glTexCoord2f(0.0, 1.0); glVertex3fv(vertices[1]);
      glTexCoord2f(1.0, 1.0); glVertex3fv(vertices[3]);
      glTexCoord2f(1.0, 0.0); glVertex3fv(vertices[2]);
    glEnd();
    glDeleteTextures(1, texName);
}

void display( void )
{
#define Angle 2.0

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

    //  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);
        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; 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(); /* perform display again */
}

void animate(void)
{
}

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("spinning cube");
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutIdleFunc(animate);      // an empty function this time
        glutKeyboardFunc(keyboard); // enable keyboard callback

        myinit();
        glutMainLoop();
}