/* cube.c */ 

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <GL/glut.h>

#define MAX(A,B) ((A) > (B) ? (A) : (B))

/*********************/
/*  DATA STRUCTURES  */
/*********************/
/* Point holds the (X,Y,Z) position of a vertex */
typedef struct
{ 
   double x, y, z; 
} Point3D;

/*********************/
/*  Colors  */
/*********************/
GLfloat       red[3] = {  1.0,  0.0,  0.0 };
GLfloat    orange[3] = {  1.0,  0.5,  0.0 };
GLfloat    yellow[3] = {  1.0,  1.0,  0.0 };
GLfloat     green[3] = {  0.0,  1.0,  0.0 };
GLfloat lightblue[3] = {  0.0,  1.0,  1.0 };
GLfloat      blue[3] = {  0.0,  0.0,  1.0 };
GLfloat   magenta[3] = {  1.0,  0.0,  1.0 };
GLfloat     white[3] = {  1.0,  1.0,  1.0 };
GLfloat     black[3] = {  0.0,  0.0,  0.0 };
GLfloat      gray[3] = {  0.5,  0.5,  0.5 };


/* Initialize temporary array of points */
Point3D loc[1000];
GLint num_loc=0;      /* Number of points */


/*************************/
/*  FUNCTION PROTOTYPES  */
/*************************/
void Initialize(void);
void SetCameraPosition(void);
void UpdateDisplay(void);
void ReadData(void);
void DrawCubes(void);
void DrawLegos(void);
void DrawAxes(void);
void MouseButtonPressed(int,int,int,int);
void MouseMovement(int,int);
void ReshapeWindow(int,int);
void QuitProgram(void);

/**********************/
/*  GLOBAL VARIABLES  */
/**********************/
GLint Azimuth = 30;               /* Azimuth, Elevation, and Twist are used */
GLint Elevation = 60;             /*    to compute the camera position when */
GLint Twist = 0;                  /*    viewing the objects in the scene.   */
GLfloat Focal = -21.0;            /* Distance from origin to camera */
GLint LeftButtonDown = 0;         /* Boolean if the Left Button is down */
GLint RightButtonDown = 0;        /* Boolean if the Right Button is down */
GLint MouseButtonDown = -1;       /* Which button is down? (Left or Right) */
GLdouble cubesize = 0.25;         /* Cube size */

GLdouble xscale = 2.0, 
         yscale = 1.0,
         zscale = 1.0;

GLint  wire_flag = 1,
       solid_flag = 0,
       lego_flag = 0,
       axes_flag = 1;


/***********************************************************************/
/*  Initialize is called at program invocation and sets up the main    */
/*  window.  It also intitializes the lighting for the scene.          */
/***********************************************************************/

void Initialize(void)
{
  /* Variables for the lighting model */
  GLfloat LAmbient[] = {0.2,0.2,0.2,1.0};
  GLfloat LDiffuse[] = {1.0,1.0,1.0,1.0};
  GLfloat LPosition[] = {5.0,-9.0,20.0,1.0};
  GLfloat LModelAmbient[] = {0.2,0.2,0.2,1.0};

  /* Initialize the window and color mode */
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(600,600);
  glutCreateWindow("Cuber");

  /* Initialize lights and lighting properties */
  glLightfv(GL_LIGHT0,GL_AMBIENT,LAmbient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,LDiffuse);
  glLightfv(GL_LIGHT0,GL_POSITION,LPosition);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT,LModelAmbient);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,1);

  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);           /* Turn on the light */
  glEnable(GL_DEPTH_TEST);         /* Use the depth buffer */
  glEnable(GL_COLOR_MATERIAL);     /* Allows us to use glColor for surfaces */
  glShadeModel(GL_FLAT);           /* Use flat shading - faster */

  glClearColor(0.0,0.0,0.0,0.0);   /* Background is black */
 
  SetCameraPosition();
  
  srand(time(NULL));               /* Set the random seed */
  ReadData();
}

/***********************************************************************/
/*  SetCameraPosition is called by several procedures to move the      */
/*  camera to the correct viewing position, and then set GL_MODELVIEW  */
/*  to allow drawing of objects on the screen.                         */
/***********************************************************************/

void SetCameraPosition(void)
{
  /* Set the viewing transformation */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(15.0,1.0,1.0,100.0);

  glTranslated(0.0,0.0,Focal);    /* Move camera down the negative z-axis */
  glRotated(-Twist,0.0,0.0,1.0);  /* Do a polar projection for camera */
  glRotated(-Elevation,1.0,0.0,0.0);
  glRotated(Azimuth,0.0,0.0,1.0);
  glTranslated(0.0,0.0,-0.8);     /* Center camera about mountain */

  glMatrixMode(GL_MODELVIEW);
}

/***********************************************************************/
/*  UpdateDisplay is called by the main event loop to refresh the      */
/*  contents of the display and to show the mountain.                  */
/***********************************************************************/

void UpdateDisplay(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  SetCameraPosition();

  if ( !lego_flag )
    DrawCubes();
  else
    DrawLegos();

  if ( axes_flag )
    DrawAxes();

  glutSwapBuffers();
}


void ReadData(void)
{
  loc[0].x = 0.0;    loc[0].y = 0.0;    loc[0].z = 0.0; 

  loc[1].x = 0.0;    loc[1].y = 0.0;    loc[1].z = 0.5; 
  loc[2].x = 0.0;    loc[2].y = 0.0;    loc[2].z = 1.0; 

  loc[3].x = 0.0;    loc[3].y = 0.5;    loc[3].z = 0.0; 
  loc[4].x = 0.0;    loc[4].y = 1.0;    loc[4].z = 0.0; 

  loc[5].x = 0.5;    loc[5].y = 0.0;    loc[5].z = 0.0; 
  loc[6].x = 1.0;    loc[6].y = 0.0;    loc[6].z = 0.0; 

  loc[7].x = 0.5;    loc[7].y = 0.0;    loc[7].z = 1.0; 
  loc[8].x = 1.7;    loc[8].y = 0.0;    loc[8].z = 2.0; 
  loc[9].x = 3.0;    loc[9].y = 0.0;    loc[9].z = 2.0; 
  loc[10].x = 2.0;   loc[10].y = 1.0;   loc[10].z = 2.0; 
  loc[11].x = 1.8;   loc[11].y = 4.0;   loc[11].z = 3.0; 
  loc[12].x = 3.0;   loc[12].y = 2.0;   loc[12].z = 0.5; 
  loc[13].x = 4.0;   loc[13].y = 3.0;   loc[13].z = 1.5; 


  num_loc = 14;
}


void DrawCubes(void)
{
  int i=0;

  if ( wire_flag )
    for ( i=0 ; i<num_loc ; i++ )
    {
      glPushMatrix(); 
        glTranslatef(loc[i].x,loc[i].y,loc[i].z);
        glutWireCube(cubesize);
      glPopMatrix();
    }
  else if ( solid_flag )
    for ( i=0 ; i<num_loc ; i++ )
    {
      glPushMatrix(); 
        glTranslatef(loc[i].x,loc[i].y,loc[i].z);
        glutSolidCube(cubesize);
      glPopMatrix();
    }  
}

void DrawLegos(void)
{
  int i=0;
  GLUquadricObj *cylinder;

  if ( wire_flag )
    for ( i=0 ; i<num_loc ; i++ )
    {
      glPushMatrix(); 
        glTranslatef(loc[i].x,loc[i].y,loc[i].z);

        glPushMatrix(); 
          glScalef(xscale,yscale,zscale);
	  glutWireCube(cubesize);
	glPopMatrix();
 
        glTranslatef( 0.0, 0.0, 0.5*cubesize );        
        cylinder = gluNewQuadric();
        gluQuadricDrawStyle(cylinder, GLU_FILL);
        gluQuadricNormals(cylinder, GLU_SMOOTH);
        gluCylinder(cylinder, 0.2*cubesize, 0.2*cubesize, 0.2*cubesize, 10, 10);
        gluDeleteQuadric(cylinder);

      glPopMatrix();
    }
  else if ( solid_flag )
    for ( i=0 ; i<num_loc ; i++ )
    {
      glPushMatrix(); 
        glTranslatef(loc[i].x,loc[i].y,loc[i].z);

        glPushMatrix(); 
          glScalef(xscale,yscale,zscale);
	  glutSolidCube(cubesize);
	glPopMatrix();
 
        glTranslatef( 0.0, 0.0, 0.5*cubesize );        
        cylinder = gluNewQuadric();
        gluQuadricDrawStyle(cylinder, GLU_FILL);
        gluQuadricNormals(cylinder, GLU_SMOOTH);
        gluCylinder(cylinder, 0.2*cubesize, 0.2*cubesize, 0.2*cubesize, 10, 10);
        gluDeleteQuadric(cylinder);

      glPopMatrix();
    }  
}


void DrawAxes(void)
{
  /* Set linewidth to be a bit thicker than normal for the axes */
  glLineWidth(3);

  /* X axis - RED */ 
  glColor3fv(red);
  glBegin(GL_LINES);
    glVertex3f( 0.0, 0.0, 0.0  );
    glVertex3f( 1.0, 0.0, 0.0 );
  glEnd();

  /* Y axis - GREEN */ 
  glColor3fv(green);
  glBegin(GL_LINES);
    glVertex3f( 0.0, 0.0, 0.0 );
    glVertex3f( 0.0, 1.0, 0.0 );
  glEnd();

  /* Z axis - BLUE */ 
  glColor3fv(blue);
  glBegin(GL_LINES);
    glVertex3f( 0.0, 0.0, 0.0 );
    glVertex3f( 0.0, 0.0, 1.0 );
  glEnd();
  
  /* Reset color to normal  and linewidth to normal */
  glColor3fv(white);
  glLineWidth(1);
}



/***********************************************************************/
/*  MouseButtonPressed is called when a Mouse Button is pressed or     */
/*  released.  It updates the value of MouseButtonDown, which is used  */
/*  by the procedure MouseMovement to set the camera position.  This   */
/*  procedure is necessary so that at most 1 mouse button (Left or     */
/*  Right) is active at any given time.  Otherwise, the camera would   */
/*  go wacky!                                                          */
/***********************************************************************/

void MouseButtonPressed(int Button, int State, int X, int Y)
{
  if (State == GLUT_DOWN)
    {
      if (Button == GLUT_LEFT_BUTTON)
        {
          LeftButtonDown = 1;
          if (!RightButtonDown)
            MouseButtonDown = GLUT_LEFT_BUTTON;
        }
      else if (Button == GLUT_RIGHT_BUTTON)
        {
          RightButtonDown = 1;
          if (!LeftButtonDown)
            MouseButtonDown = GLUT_RIGHT_BUTTON;
        }
    }
  else if (State == GLUT_UP)
    {
      MouseButtonDown = -1;
      if (Button == GLUT_LEFT_BUTTON)
        {
          LeftButtonDown = 0;
          if (RightButtonDown)
            MouseButtonDown = GLUT_RIGHT_BUTTON;
        }
      else if (Button == GLUT_RIGHT_BUTTON)
        {
          RightButtonDown = 0;
          if (LeftButtonDown)
            MouseButtonDown = GLUT_LEFT_BUTTON;
        }
    }
}

/***********************************************************************/
/*  MouseMovement is called when the user moves the mouse in the       */
/*  window and a mouse button is down.  It updates the position of     */
/*  the camera.  It checks the values LastX and LastY to see if the    */
/*  mouse has moved since the last call.  If so, it modifies the       */
/*  values Azimuth and Elevation (if the Left Mouse Button was down),  */
/*  or the Twist and Focal Point (if the Right Mouse Button was down). */
/***********************************************************************/

void MouseMovement(int X, int Y)
{
  static int LastX = 0;  /* Holds the old X coord of mouse */
  static int LastY = 0;  /* Holds the old Y coord of mouse */

  if (MouseButtonDown == GLUT_LEFT_BUTTON)
    {
      if (X > LastX)
        Azimuth = (Azimuth - 3) % 360;
      else if (X < LastX)
        Azimuth = (Azimuth + 3) % 360;

      if (Y > LastY)
        Elevation = (Elevation + 3) % 360;
      else if (Y < LastY)
        Elevation = (Elevation - 3) % 360;
    }
  else if (MouseButtonDown == GLUT_RIGHT_BUTTON)
    {
      if (X > LastX)
        Twist = (Twist + 3) % 360;
      else if (X < LastX)
        Twist = (Twist - 3) % 360;

      if (Y > LastY)
        {
          if (Focal > -160.0)
            Focal = Focal - 0.5;
        }
      else if (Y < LastY)
        {
          if (Focal < -6.0)
            Focal = Focal + 0.5;
        }
    }

  LastX = X;
  LastY = Y;
  glutPostRedisplay();
}

/***********************************************************************/
/*  ReshapeWindow is called if the main window needs to be resized.    */
/***********************************************************************/

void ReshapeWindow(int Width, int Height)
{
  SetCameraPosition();
  glViewport(0,0,Width,Height);
}

/***********************************************************************/
/*  SpecialKeyPressed is called when the user presses a "special" key  */
/*  on the keyboard, such as an arrow key.                             */
/***********************************************************************/

void SpecialKeyPressed(int Key, int X, int Y)
{
  switch (Key)
    {
      case GLUT_KEY_UP   : break;
      case GLUT_KEY_DOWN : break;
    }
  glutPostRedisplay();
}

/***********************************************************************/
/*  KeyPressed is called when the user presses a key on the keyboard.  */
/*  It calls the appropriate procedure based on the input.             */
/***********************************************************************/

void KeyPressed(unsigned char Key, int X, int Y)
{
  switch (toupper(Key))
    {
      case 'Q' : QuitProgram();                      break;
      case 'W' : wire_flag = 1;            
                 solid_flag = 0;
                 break;
      case 'S' : solid_flag = 1;
                 wire_flag = 0;
                 break;
      case 'L' : lego_flag = !lego_flag;             break;
      case 'A' : axes_flag = !axes_flag;             break;
    }
  glutPostRedisplay();
}

/***********************************************************************/
/*  QuitProgram is called to exit gracefully.                          */
/***********************************************************************/

void QuitProgram(void)
{
  exit(0);
}

/***********************************************************************/
/*  PrintUsage is called at program startup to print out a short       */
/*  help message on how to use the program.                            */
/***********************************************************************/

void PrintUsage(void)
{
  printf("Cuber\n");
  printf("INPUTS:\n");
  printf("Left Mouse Button  - Click/Hold to Rotate Camera Azimuth/Elevation\n");
  printf("Right Mouse Button - Click/Hold to Zoom (Up/Down) or Twist (Left/Right)\n");
  printf("\n'w' or 'W'         - Draw Wire Frame cubes");
  printf("\n's' or 'S'         - Draw Solid cubes");
  printf("\n'l' or 'L'         - Draw Legos instead of cubes");
  printf("\n'a' or 'A'         - Draw Axes");
  printf("\n'q' or 'Q'         - Quit Program\n");
}


/**************************/
/*   BEGIN MAIN PROGRAM   */
/**************************/

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

  PrintUsage();

  Initialize();    /* Set up main window and variables */
  
  /* Set the Mouse / Keyboard / Reshape / Display callbacks */
  glutKeyboardFunc(KeyPressed);
  glutSpecialFunc(SpecialKeyPressed);
  glutMouseFunc(MouseButtonPressed);
  glutMotionFunc(MouseMovement);
  glutReshapeFunc(ReshapeWindow);
  glutDisplayFunc(UpdateDisplay);
  glutMainLoop();
}

/**************************/
/*    END MAIN PROGRAM    */
/**************************/


