/***********************************************************************/
/*                                                                     */
/*  NAME     : Terry Fleury            PROGRAM : mp2                   */
/*  NET ID   : cs318ta1                COURSE  : CS318 - Fall 1999     */
/*  DUE DATE : October 1, 1999                                         */
/*  PURPOSE  : In this MP, we implement several of the 2D geometric    */
/*             transformations: translate, rotate, and scale.  The     */
/*             1-unit grad students also implement reflect.  All of    */
/*             these operations are done about an arbitrary point.     */
/*             In this program, I use my own matrix multiplication     */
/*             routines since we are allowed to use only glLoadMatrix  */
/*             for the transformations.                                */
/*  INPUT    : Left Mouse Button - designates vertices of the polygon  */
/*                   drawn on the screen.  The last point must be      */
/*                   sufficiently close to the first point for         */
/*                   termination of points entry.                      */
/*             Right Mouse Button - sets the rotation point.           */
/*             <Spacebar> - sets the reflection point.  The rotation   */
/*                   point and reflection point define a line about    */
/*                   which the polygon is reflected.                   */
/*             "i" - identity.  Resets the screen to the original      */
/*                    state by loading the identity matrix.            */
/*             "t" - translate.  Prompts the user for an x,y pair for  */
/*                   the number of pixels to translate.                */
/*             "r" - rotate.  Prompts the user for the number of       */
/*                   degrees to rotate counterclockwise.               */
/*             "s" - scale.  Prompts the user for an x,y pair for the  */
/*                   scaling factors in the x and y directions.        */
/*             "f" - reflection/flip.  Reflects the polygon about the  */
/*                   line defined by the two points above.             */
/*             "q" - quit.  Exits the program.                         */
/*  COMMENTS:  1) In OpenGL, the matrix is a 3D matrix, and is stored  */
/*                as a one-dimensional array as follows:               */
/*                         | m0  m4  m8  m12 |                         */
/*                     M = | m1  m5  m9  m13 |                         */
/*                         | m2  m6  m10 m14 |                         */
/*                         | m3  m7  m11 m15 |                         */
/*             2) The y coordinates increase from the top of the       */
/*                window down, so we need to reverse translations in   */
/*                the y direction and rotations.                       */
/*                                                                     */
/***********************************************************************/

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

#define MAXVERT 20              /* Max number of vertices for the polygon */
#define RADIANS 0.0174532925    /* PI/180 */

/***********************/
/* FUNCTION PROTOTYPES */
/***********************/
void Initialize(void);
void SetCameraPosition(void);
void UpdateDisplay(void);
void DrawPolygon(void);
void DrawPoints(void);
void IdentityMatrix(double[]);
void MouseButtonPressed(int,int,int,int);
void ReshapeWindow(int,int);
void QuitProgram(void);

/***********************/
/*   GLOBAL VARIABLES  */
/***********************/
int WinWidth = 600;           /* Width of the main window */
int WinHeight = 600;          /* Height of the main window */
int VerticesEntered = 0;      /* Number of endpoints entered so far */
int DoneEnteringVertices = 0; /* Finished entering points for the polygon */
int Vertices[MAXVERT][2];     /* Array to hold MAXVERT vertex points */
int RotationPoint[2];         /* Rotate around this point */
int ReflectionPoint[2];       /* Define reflection line with RotationPoint */
GLdouble GlobalMatrix[16];    /* Matrix to hold transformations */
char Line[80];                /* String to hold user input */

/***********************************************************************/
/*  Initialize is called at program invocation and sets up the main    */
/*  window.  It also initializes the Rotation and Reflection points.   */
/***********************************************************************/

void Initialize(void)
{
  /* Initialize the window and color mode */
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
  glutInitWindowSize(WinWidth,WinHeight);
  glutCreateWindow("CS318 MP2 - Solution");

  glClearColor(0.0,0.0,0.0,0.0);   /* Background is black */
 
  SetCameraPosition();

  /* Initialize the Rotation and Reflection points */
  RotationPoint[0] = WinWidth / 2;
  RotationPoint[1] = WinHeight / 2;
  ReflectionPoint[0] = WinWidth / 2;
  ReflectionPoint[1] = 2;

  IdentityMatrix(GlobalMatrix);  /* Initialize global transformation matrix */
}

/***********************************************************************/
/*  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();
  gluOrtho2D(0.0,(GLdouble)WinWidth+1.0,(GLdouble)WinHeight+1.0,0.0);
  glMatrixMode(GL_MODELVIEW);
}

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

void UpdateDisplay(void)
{
  glClear(GL_COLOR_BUFFER_BIT);   /* Clear the main window */

  DrawPolygon();       /* Draw the transformed polygon */
  DrawPoints();        /* Draw the Rotation and Reflection points */

  glFlush();           /* Flush all output to the main window */
}

/***********************************************************************/
/*  DrawPolygon is called by UpdateDisplay to draw the polygon.  If    */
/*  we are not yet finished entering all of the points for the         */
/*  then the points are connected by lines.  Otherwise, the polygon    */
/*  is drawn filled in.                                                */
/***********************************************************************/

void DrawPolygon(void)
{
  int i;

  /* Check to make sure we have vertices to draw */
  if (VerticesEntered > 0)
    {
      glColor3f(1.0,1.0,0.0);      /* Color is yellow */
      glLoadMatrixd(GlobalMatrix); /* Load the transformation matrix */

      /* Draw first endpoint as a large square if not complete */
      if (!DoneEnteringVertices)
        {
          glPointSize(3.0);
          glBegin(GL_POINTS);
            glVertex2iv(Vertices[0]);
          glEnd();
        }

      glPointSize(1.0);
      if (DoneEnteringVertices)
        glBegin(GL_POLYGON);     /* Draw a filled polygon */
      else
        glBegin(GL_LINE_STRIP);  /* Draw a bunch of line segments */

      for (i = 0; i < VerticesEntered; i++)
        glVertex2iv(Vertices[i]);
      glEnd();
    }
}

/***********************************************************************/
/*  DrawPoints is called by UpdateDisplay to draw the Rotation point   */
/*  and the Reflection point on the screen.  Since we do not want      */
/*  these points to move (when the polygon does), we load the identity */
/*  matrix before drawing the points.                                  */
/***********************************************************************/

void DrawPoints(void)
{
  glLoadIdentity();    /* Load the identity matrix - fresh start */

  glPointSize(3.0);
  glBegin(GL_POINTS);
    glColor3f(1.0,0.0,1.0);        /* Color is magenta */
    glVertex2iv(RotationPoint);    /* Draw Rotation Point */
    glColor3f(0.0,1.0,1.0);        /* Color is cyan */
    glVertex2iv(ReflectionPoint);  /* Draw the Reflection Point */
  glEnd();
}

/***********************************************************************/
/*  ClearMatrix sets all entries in the passed-in Matrix to zero.      */
/***********************************************************************/

void ClearMatrix(GLdouble Matrix[])
{
  int i;

  for (i = 0; i < 16; i++)
    Matrix[i] = 0.0;
}

/***********************************************************************/
/*  IdentityMatrix sets the passed in Matrix array to the identity     */
/*  matrix by setting the values along the diagonal to 1.0.            */
/***********************************************************************/

void IdentityMatrix(GLdouble Matrix[])
{
  int i;

  ClearMatrix(Matrix);      /* First, clear all matrix entries */
  for (i = 0; i < 16; i+=5) /* Then, set the diagonal entries to 1.0 */
    Matrix[i] = 1.0;
}

/***********************************************************************/
/*  MultMatrix takes two matrices (In1 and In2) and multiplies them    */
/*  together, returning the result in In2.  The matrices are assumed   */
/*  to be declared as OpenGL matrices (1-dimensional, column-major).   */
/***********************************************************************/

void MultMatrix(GLdouble In1[], GLdouble In2[])
{
  GLdouble Out[16];      /* Temporary storage for matrix multiplication */
  int row, col, count;   /* Loop counters for multiplication */

  ClearMatrix(Out);

  /* Do the matrix multiplication */
  for (row = 0; row < 4; row++)
    for (col = 0; col < 4; col++)
      for (count = 0; count < 4; count++)
        Out[row+col*4] += In1[row+count*4] * In2[count+col*4];

  /* Now, store the final result in matrix In2 */
  for (count = 0; count < 16; count++)
    In2[count] = Out[count];
}

/***********************************************************************/
/*  TranslateMatrix takes an x and y value for the number of pixels to */
/*  translate the polygon.  It sets up a translation matrix and        */
/*  multiplies this with the incoming Matrix.  The result is returned  */
/*  in Matrix.  This function assumes that the matrices are declared   */
/*  the way OpenGL likes (1-dimensional, column-major).                */
/***********************************************************************/

void TranslateMatrix(GLdouble x, GLdouble y, GLdouble Matrix[])
{
  GLdouble TransMatrix[16];

  /* Initialize and set up the Translation Matrix */
  IdentityMatrix(TransMatrix);
  TransMatrix[12] = x;
  TransMatrix[13] = y;

  /* Multiply the matrices and return the result in Matrix */
  MultMatrix(TransMatrix,Matrix);
}

/***********************************************************************/
/*  RotateMatrix takes the number of degrees that you want to rotate   */
/*  the current polygon  counterclockwise.  Recall that since the y-    */
/*  values increase from the top to the bottom of the screen, we       */
/*  need to call RotateMatrix with the negative number of degrees.     */
/*  It sets up the rotation matrix, multiplies it with the incoming    */
/*  matrix, and returns the result in Matrix.  It assumes that the     */
/*  matrices are declared the way OpenGL likes (1-dimensional,         */
/*  column-major).                                                     */
/***********************************************************************/

void RotateMatrix(double degrees, GLdouble Matrix[])
{
  GLdouble RotMatrix[16];
  double theta;

  /* Initialize and set up the Translation Matrix */
  theta = degrees * RADIANS;
  IdentityMatrix(RotMatrix);
  RotMatrix[0] = (GLdouble)cos(theta);
  RotMatrix[4] = -(GLdouble)sin(theta);
  RotMatrix[1] = (GLdouble)sin(theta);
  RotMatrix[5] = (GLdouble)cos(theta);

  /* Multiply the matrices and return the result in Matrix */
  MultMatrix(RotMatrix,Matrix);
}

/***********************************************************************/
/*  ScaleMatrix takes two scaling factors: sx and sy.  These are the   */
/*  amounts that you want to scale the square, so a value of 1.0       */
/*  leaves the polygon unaffected in that direction.  Just like the    */
/*  above procedures, it sets up the matrices and returns the          */
/*  resulting matrix in Matrix.                                        */
/***********************************************************************/

void ScaleMatrix(GLdouble sx, GLdouble sy, GLdouble Matrix[])
{
  double ScaleMatrix[16];

  /* Initialize and set up the Scaling Matrix */
  IdentityMatrix(ScaleMatrix);
  ScaleMatrix[0] = sx;
  ScaleMatrix[5] = sy;

  /* Multiply the matrices and return the result in Matrix */
  MultMatrix(ScaleMatrix,Matrix);
}

/***********************************************************************/
/*  MouseButtonPressed is the callback function for any mouse button   */
/*  presses.  If the left button is pressed, it either adds vertices   */
/*  to the polygon, or finishes the polygon.  If the right button is   */
/*  pressed, the Rotation point is set.                                */
/***********************************************************************/

void MouseButtonPressed(int Button, int State, int X, int Y)
{
  if (State == GLUT_DOWN)
    {
      if (Button == GLUT_LEFT_BUTTON)
        {
          /* If we already have a filled polygon on the screen */
          /* then start with a new set of vertices.            */
          if (DoneEnteringVertices)  
            {
              VerticesEntered = 0;
              DoneEnteringVertices = 0;
              IdentityMatrix(GlobalMatrix);
            }

          /* Check to see that we haven't entered too many vertices. */
          if (VerticesEntered < MAXVERT)
            {
              Vertices[VerticesEntered][0] = X;
              Vertices[VerticesEntered][1] = Y;

              /* Check to see if the point entered is sufficiently */
              /* close to the first point to close up the polygon. */
              /* Also make sure to enter at least 3 vertices.      */
              if ((VerticesEntered >= 3) &&
                  (abs(X - Vertices[0][0]) <= 3) &&
                  (abs(Y - Vertices[0][1]) <= 3))
                DoneEnteringVertices = 1;
              else  /* Keep on entering more vertices */
                VerticesEntered++;

              if (VerticesEntered == MAXVERT)
                DoneEnteringVertices = 1;
            }
          else  /* MAXVERT reached - close up the polygon */
            {
              DoneEnteringVertices = 1;
            }
        }
      else if (Button == GLUT_RIGHT_BUTTON)
        {
          /* Set the Rotation point */
          RotationPoint[0] = X;
          RotationPoint[1] = Y;

          /* Make sure the Reflection point and Rotation point are different. */
          if ((RotationPoint[0] == ReflectionPoint[0]) &&
              (RotationPoint[1] == ReflectionPoint[1]))
            RotationPoint[1]++;
        }
      glutPostRedisplay();
    }
}

/***********************************************************************/
/*  PressedI is called when the user types "i".  If we have a polygon  */
/*  on the screen, then we reset the polygon by resetting the global   */
/*  transformation matrix to the identity matrix.                      */
/***********************************************************************/

void PressedI(void)
{
  if (DoneEnteringVertices)  /* Make sure we have a filled polygon */
    IdentityMatrix(GlobalMatrix);
}

/***********************************************************************/
/*  PromptForInput prints out the Message to standard out and then     */
/*  waits for the user to type some input.  This string is stored in   */
/*  the global variable "Line" as is also returned.                    */
/***********************************************************************/

char *PromptForInput(char *Message)
{
  fprintf(stdout,Message);
  fflush(NULL);  /* Make sure output shows up */
  fgets(Line,80,stdin);

  return Line;
}

/***********************************************************************/
/*  PromptForOneNum prints out the Message to the user and then        */
/*  returns a single value in Number.                                  */
/***********************************************************************/

void PromptForOneNum(char *Message, float *Number)
{
  *Number = (float)atof(PromptForInput(Message));
}

/***********************************************************************/
/*  PromptForTwoNums prints out the Message to the user and then       */
/*  scans for two numbers separated by a comma.  It returns these      */
/*  two numbers as floats in Number1 and Number2.                      */
/***********************************************************************/

void PromptForTwoNums(char *Message, float *Number1, float *Number2)
{
  char *tmpstr;

  PromptForInput(Message);
  if ((tmpstr = strtok(Line,",")) != NULL)
    {
      *Number1 = (float)atof(tmpstr);
      if ((tmpstr = strtok(NULL,",")) != NULL)
        *Number2 = (float)atof(tmpstr);
    }
}

/***********************************************************************/
/*  PressedR is called when the user types "r".  It prompts the user   */
/*  for the number of degrees to rotate counterclockwise, and then     */
/*  rotates the polygon that number of degrees.                        */
/***********************************************************************/

void PressedR(void)
{
  float degrees;

  if (DoneEnteringVertices)  /* Make sure we have a filled polygon */
    {
      PromptForOneNum("Enter the degrees to rotate counterclockwise: ",
                      &degrees);

      /* See if we really don't need to rotate after all */
      if (((int)degrees % 360) != 0)
        {
          TranslateMatrix(-RotationPoint[0],-RotationPoint[1],GlobalMatrix);
          /* Degrees here are negative since y increases downward */
          RotateMatrix(-degrees,GlobalMatrix);  
          TranslateMatrix(RotationPoint[0],RotationPoint[1],GlobalMatrix);
        }
    }
}

/***********************************************************************/
/*  PressedT is called when the user types "t".  It prompts the user   */
/*  for two values - the number of pixels to move the polygon in the   */
/*  x and y directions.                                                */
/***********************************************************************/

void PressedT(void)
{
  float tx, ty;      /* Amount to translate in X and Y directions */

  if (DoneEnteringVertices)  /* Make sure we have a filled polygon */
    {
      PromptForTwoNums("Enter the amounts to translate as x,y: ",&tx,&ty);

      /* See if both values are zero - don't bother to move */
      if ((fabs(tx) >  0.00001) || (fabs(ty) > 0.00001))
        TranslateMatrix(tx,-ty,GlobalMatrix);   
        /* ty is negative since y increases downward */
    }
}

/***********************************************************************/
/*  PressedS is called when the user types "s".  It prompts the user   */
/*  for two number - the scaling factors in the x and y directions.    */
/*  It then scales the polygon by these amounts by its first vertex.   */
/***********************************************************************/

void PressedS(void)
{
  float sx, sy;

  if (DoneEnteringVertices)  /* Make sure we have a filled polygon */
    {
      PromptForTwoNums("Enter the amounts to scale as x,y: ",&sx,&sy);

      /* Check to make sure both factors are non-zero */
      /* Also check if factors are 1 - don't bother to scale */
      if (((fabs(sx) > 0.00001) && (fabs(sy) > 0.00001)) && 
          ((fabs(sx-1) > 0.00001) || (fabs(sy-1) > 0.00001)))
        {
          TranslateMatrix(-RotationPoint[0],-RotationPoint[1],GlobalMatrix);
          ScaleMatrix(sx,sy,GlobalMatrix);
          TranslateMatrix(RotationPoint[0],RotationPoint[1],GlobalMatrix);
        }
    }
}

/***********************************************************************/
/*  PressedF is called when the user types "f".  It "flips" the        */
/*  polygon about the line defined by the Reflection point and the     */
/*  Rotation point.                                                    */
/***********************************************************************/

void PressedF(void)
{
  double angle;

  if (DoneEnteringVertices)  /* Make sure we have a filled polygon */
    {
      angle = atan2(ReflectionPoint[1]-RotationPoint[1],
                    ReflectionPoint[0]-RotationPoint[0]) / RADIANS;

      TranslateMatrix(-RotationPoint[0],-RotationPoint[1],GlobalMatrix);
      RotateMatrix(-angle,GlobalMatrix);  /* Negative since y increases down */
      ScaleMatrix(1.0,-1.0,GlobalMatrix);
      RotateMatrix(angle,GlobalMatrix);
      TranslateMatrix(RotationPoint[0],RotationPoint[1],GlobalMatrix);
    }
}

/***********************************************************************/
/*  PressedSpace is called when the user types the Spacebar.  We use   */
/*  the position of the mouse when the spacebar was pressed (X,Y) to   */
/*  designate the new position for the reflection point.               */
/***********************************************************************/

void PressedSpace(int X, int Y)
{
  ReflectionPoint[0] = X;
  ReflectionPoint[1] = Y;

  /* Make sure the Reflection point and Rotation point are different. */
  if ((RotationPoint[0] == ReflectionPoint[0]) &&
      (RotationPoint[1] == ReflectionPoint[1]))
    ReflectionPoint[1]++;
}

/***********************************************************************/
/*  KeyPressed is the callback function for when a key on the keyboard */
/*  gets pressed.  It takes the passed in Key, puts it in uppercase,   */
/*  and takes the appropriate action.                                  */
/***********************************************************************/

void KeyPressed(unsigned char Key, int X, int Y)
{
  switch (toupper(Key))
    {
      case 'Q' : QuitProgram(); break;
      case 'I' : PressedI(); break;
      case 'R' : PressedR(); break;
      case 'T' : PressedT(); break;
      case 'S' : PressedS(); break;
      case 'F' : PressedF(); break;
      case ' ' : PressedSpace(X,Y); break;
    }
  glutPostRedisplay();
}

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

void ReshapeWindow(int Width, int Height)
{
  WinWidth = Width;     /* Update the global WinWidth/WinHeight variables */
  WinHeight = Height;
  SetCameraPosition();  /* Update the camera view to be full screen */
  glViewport(0,0,WinWidth,WinHeight);
}

/***********************************************************************/
/*  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("CS318 - MP2 - Two-Dimensional Transformations\n");
  printf("INPUTS:\n");
  printf("Left Mouse Button   - Enter up to 20 Vertices for Polygon\n");
  printf("Right Mouse Button  - Set the Rotation Point\n");
  printf("<Spacebar>          - Set the Reflection Point\n");
  printf("'i' or 'I'          - Reset all Transformations\n");
  printf("'r' or 'R'          - Prompt for Degrees to Rotate (ccw)\n");
  printf("'t' or 'T'          - Prompt for Pixels to Translate tx,ty\n");
  printf("'s' or 'S'          - Prompt for Scaling Factors sx,sy\n");
  printf("'f' or 'F'          - Reflect the Polygon about a Line\n");
  printf("'q' or 'Q'          - Quit Program\n");
  fflush(NULL);   /* Make sure all output shows up */
}



/**************************/
/*   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);
  glutMouseFunc(MouseButtonPressed);
  glutReshapeFunc(ReshapeWindow);
  glutDisplayFunc(UpdateDisplay);
  glutMainLoop();

  return 0;   /* Required by ANSI C */
}

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


