/***************************************************************************/
/* This is a simple demo program written for COMS 4160 by Ravi Ramamoorthi */
/* This program corresponds to the second OpenGL lecture.                  */
/* It builds on the program written in the first OpenGL lecture.           */
/* This version adds 4 "pillars" at the corners, depth buffering, texturing*/
/* of the ground plane, and a moving teapot with double buffering          */  
/* The intent is to show how to draw a simple scene.                       */
/***************************************************************************/

/**************************************************************************/
/* INCREMENTAL STEPS RELATIVE TO THE FIRST DEMO                           */
/* 0. Add global for pillar display list, change floor to white           */
/* 1. Init includes a Display list for the pillars with GLu               */
/* 2. Draw 4 pillars by using matrix stacks                               */
/*    - See if order matters by moving floor to the end.                  */
/* 3. Add depth test with Z-buffering                                     */
/* 4. Add animation routines for teapot (add double buffering)            */
/* 5. Texture map the floor                                               */
/**************************************************************************/


#include <GL/glut.h>
#include <stdio.h> // ** NEW ** for loading the texture 
#include <stdlib.h>
#include <assert.h> // ** NEW ** for errors

int mouseoldx, mouseoldy ; // For mouse motion
GLdouble eyeloc = 2.0 ; // Where to look from; initially 0 -2, 2
GLuint pillar ; // ** NEW ** For the display list for the pillars 
GLint animate = 0 ; // ** NEW ** whether to animate or not
GLdouble teapotloc = -0.5 ; // ** NEW ** where the teapot is located
GLubyte woodtexture[256][256][3] ; // ** NEW ** texture (from grsites.com)
GLuint texName ;                   // ** NEW ** for texture 
GLenum err ;                       // ** NEW ** for errors

void display(void)
{
/* clear all pixels  */
  /* ** New for demo 2, to handle depth ** */
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   // draw white polygon (square) of unit length centered at the origin
   // Note that vertices must generally go counterclockwise
   /* ** Change from demo 1 **, in that I just made it white */

   /* Does the order of drawing matter?  What happens if I draw the ground 
      after the pillars?  I will show this in class */

   /*
   glColor3f(1.0,1.0,1.0) ;
   glBegin(GL_POLYGON);
      glVertex3f (0.5, 0.5, 0.0);
      glVertex3f (-0.5, 0.5, 0.0);
      glVertex3f (-0.5, -0.5, 0.0);
      glVertex3f (0.5, -0.5, 0.0);
   glEnd();
   */

   /* ** New for OpenGL2 **, Add some more geometry */
   /* Note the use of matrix stacks and push and pop */
   glMatrixMode(GL_MODELVIEW) ;

   /* Draw first pillar by Translating */   
   glPushMatrix() ;
      glTranslatef(0.4, 0.4, 0.0) ;
      glColor3f(1.0, 1.0, 0.0) ;
      glCallList(pillar) ;
   glPopMatrix() ;

   /* Draw second pillar by Translating */
   glPushMatrix() ;
      glTranslatef(-0.4, 0.4, 0.0) ;
      glColor3f(1.0, 0.0, 0.0) ;
      glCallList(pillar) ;
   glPopMatrix() ;

   /* Draw third pillar by Translating */
   glPushMatrix() ;
      glTranslatef(-0.4, -0.4, 0.0) ;
      glColor3f(0.0, 1.0, 0.0) ;
      glCallList(pillar) ;
   glPopMatrix() ;

   /* Draw fourth pillar by Translating */
   glPushMatrix() ;
      glTranslatef(0.4, -0.4, 0.0) ;
      glColor3f(0.0, 0.0, 1.0) ;
      glCallList(pillar) ;
   glPopMatrix() ;

   // draw white polygon (square) of unit length centered at the origin
   // Note that vertices must generally go counterclockwise
   /* ** Change from opengl1 **, in that I just made it white */

   /* Does the order of drawing matter?  What happens if I draw the ground 
      after the pillars?  I will show this in class */

   /*
   
   glColor3f(1.0,1.0,1.0) ;
   glBegin(GL_POLYGON);
      glVertex3f (0.5, 0.5, 0.0);
      glVertex3f (-0.5, 0.5, 0.0);
      glVertex3f (-0.5, -0.5, 0.0);
      glVertex3f (0.5, -0.5, 0.0);
   glEnd();
 
   */

   /* As a final step, I modify this for texture mapping * NEW * */
   /* Consult chapter 9 for the explanation of the various options */
   /* Note addition of texture coordinates, and the glue to add texturing */
   /* Also note some effort to find the error if any */
   
   glEnable(GL_TEXTURE_2D) ;
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ;
   glBindTexture(GL_TEXTURE_2D, texName) ;
   glColor3f(1.0,1.0,1.0) ;
   err = glGetError() ; assert(err == GL_NO_ERROR) ;
   glBegin(GL_POLYGON) ;
      glTexCoord2f(1.0, 1.0) ; glVertex3f (0.5, 0.5, 0.0);
      glTexCoord2f(0.0,1.0) ; glVertex3f (-0.5, 0.5, 0.0);
      glTexCoord2f(0.0,0.0); glVertex3f (-0.5, -0.5, 0.0);
      glTexCoord2f(1.0,0.0) ; glVertex3f (0.5, -0.5, 0.0);
   glEnd() ;
   err = glGetError() ; assert(err == GL_NO_ERROR) ;
   glDisable(GL_TEXTURE_2D) ;
  
   /*  ** NEW ** Put a teapot in the middle that animates */
   glColor3f(0.0,1.0,1.0) ;
   glPushMatrix() ;
   /* I now transform by the teapot translation for animation */
   glTranslatef(teapotloc, 0.0, 0.0) ;

   /* The following two transforms set up and center the teapot */
   /* Remember that transforms right-multiply the stack */

   glTranslatef(0.0,0.0,0.1) ;
   glRotatef(90.0,1.0,0.0,0.0) ;
   glutSolidTeapot(0.15) ; 
   glPopMatrix() ;

/* don't wait!  
 * start processing buffered OpenGL routines 
 */
   glutSwapBuffers() ;
   glFlush ();
}

/* ** NEW ** in this assignment, is an animation of a teapot */
/* Hitting p will pause this animation; see keyboard callback */

void animation(void) {
  teapotloc = teapotloc + 0.005 ;
  if (teapotloc > 0.5) teapotloc = -0.5 ;
  glutPostRedisplay() ;  
}

/* Defines a Mouse callback to zoom in and out */
/* This is done by modifying gluLookAt         */
/* The actual motion is in mousedrag           */
/* mouse simply sets state for mousedrag       */
void mouse(int button, int state, int x, int y) 
{
  if (button == GLUT_LEFT_BUTTON) {
    if (state == GLUT_UP) {
      // Do Nothing ;
    }
    else if (state == GLUT_DOWN) {
      mouseoldx = x ; mouseoldy = y ; // so we can move wrt x , y 
    }
  }
  else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) 
    { // Reset gluLookAt
      eyeloc = 2.0 ; 
      glMatrixMode(GL_MODELVIEW) ;
      glLoadIdentity() ;
      gluLookAt(0,-eyeloc,eyeloc,0,0,0,0,1,1) ;
      glutPostRedisplay() ;
    }
}

void mousedrag(int x, int y) {
  int yloc = y - mouseoldy  ;    // We will use the y coord to zoom in/out
  eyeloc  += 0.005*yloc ;         // Where do we look from
  if (eyeloc < 0) eyeloc = 0.0 ;
  mouseoldy = y ;

  /* Set the eye location */
  glMatrixMode(GL_MODELVIEW) ;
  glLoadIdentity() ;
  gluLookAt(0,-eyeloc,eyeloc,0,0,0,0,1,1) ;
  
  glutPostRedisplay() ;
}

/* Defines what to do when various keys are pressed */
void keyboard (unsigned char key, int x, int y) 
{
  switch (key) {
  case 27:  // Escape to quit
    exit(0) ;
    break ;
  case 'p': // ** NEW ** to pause/restart animation
    animate = !animate ;
      if (animate) glutIdleFunc(animation) ;
      else glutIdleFunc(NULL) ;
    break ;
  default:
    break ;
  }
}

/* Reshapes the window appropriately */
void reshape(int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();

   // Think about the rationale for this choice for gluPerspective 
   // What would happen if you changed near and far planes? 
   gluPerspective(30.0, (GLdouble)w/(GLdouble)h, 1.0, 10.0) ;

}


void init (void) 
{
   FILE *fp ; int i,j,k ; // For the texture ** NEW **
   GLUquadricObj * cyl ; // For the cylinder new quadric ** NEW **

/* select clearing color 	*/
   glClearColor (0.0, 0.0, 0.0, 0.0);

/* initialize viewing values  */
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();

   // Think about this.  Why is the up vector not normalized?
   glMatrixMode(GL_MODELVIEW) ;
   glLoadIdentity() ;
   gluLookAt(0,-eyeloc,eyeloc,0,0,0,0,1,1) ;

   /* ** NEW for demo 2 ** Some new primitives that I added */
   /* This uses gluCylinder.  The glu primitives are sometimes useful */
   /* The GLU library is described in chapter 11.  We need only a small */
   /* part of it. */

   cyl = gluNewQuadric() ;

   /* This part sets up a display list for the pillars.  
      Refer to chapter 7 for more details */

   pillar = glGenLists(1) ; 
   glNewList(pillar, GL_COMPILE) ;
   gluCylinder (cyl, 0.1, 0.1, .5, 10, 10) ;
   glEndList() ;

   /* ** New for demo 2 ** enable depth test */
   glEnable(GL_DEPTH_TEST) ;
   glDepthFunc(GL_LESS) ; // The default option

   /* ** New for demo 2 ** setup for textures */
   /* First, read this simple ppm file in */
   assert(fp = fopen("wood.ppm","rb")) ;
   fscanf(fp,"%*s %*d %*d %*d%*c") ;
   for (i = 0 ; i < 256 ; i++)
     for (j = 0 ; j < 256 ; j++)
       for (k = 0 ; k < 3 ; k++)
	 fscanf(fp,"%c",&(woodtexture[i][j][k])) ;
   fclose(fp) ;
   
   /* Now, set up all the stuff for texturing, per red book */
   glGenTextures(1, &texName) ;
   glBindTexture(GL_TEXTURE_2D, texName) ;
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) ;
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) ;
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) ;
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) ;
   glTexImage2D(GL_TEXTURE_2D,0,GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE,
                woodtexture) ;
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);

   // Requests the type of buffers (Single, RGB).
   // Think about what buffers you would need...

   /* ** NEW for demo 2, request depth, later switch to double buffer ** */
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   glutInitWindowSize (500, 500); 
   glutInitWindowPosition (100, 100);
   glutCreateWindow ("Simple Demo");
   init (); // Always initialize first

   // Now, we define callbacks and functions for various tasks.
   glutDisplayFunc(display); 
   glutReshapeFunc(reshape) ;
   glutKeyboardFunc(keyboard);
   glutMouseFunc(mouse) ;
   glutMotionFunc(mousedrag) ;

   glutMainLoop(); // Start the main code
   return 0;   /* ANSI C requires main to return int. */
}
