/***********************************************************************/
/*                                                                     */
/*  NAME     : Terry Fleury            PROGRAM : mp4                   */
/*  NET ID   : cs318ta1                COURSE  : CS318 - Fall 1999     */
/*  DUE DATE : November 5, 1999                                        */
/*  PURPOSE  : In this MP, we implement Cubic Periodic B-Splines, as   */
/*             described in the textbook on pp. 339-341.  We do the    */
/*             actual drawing of the splines using Forward Differences */
/*             as described on pp. 351-353.  I have also used the      */
/*             matrix notation from "Computer Graphics" by Foley,      */
/*             vanDam to simplify the geometric and delta matrices.    */
/*  INPUT    : Left Mouse Button - designates control points of the    */
/*                 B-spline.                                           */
/*             Right Mouse Button - for 1-unit grad students only.     */
/*                 This button selects a current control point to be   */
/*                 moved.  First, click "near" a control point with    */
/*                 the right mouse button.  The point turns white.     */
/*                 Then, click again with the right mouse button to    */
/*                 set its new location.                               */
/*             Spacebar - clears all control points.                   */
/*             Backspace - deletes the last-entered control point.     */
/*             "q" - quit.  Exits the program.                         */
/*                                                                     */
/***********************************************************************/

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

#define MAXVERT   50              /* Max number of control points for spline */
#define PRECISION 20              /* Precision for drawing the spline */
#define SIXTH     1.0/6.0         /* Used by B-Spline basis matrix */
#define DELTA     1.0/PRECISION   /* Used by Forward Difference matrix */
#define DELTA2    DELTA*DELTA     /* Used by Forward Difference matrix */
#define DELTA3    DELTA2*DELTA    /* Used by Forward Difference matrix */

/***********************/
/* FUNCTION PROTOTYPES */
/***********************/
void Initialize(void);
void SetCameraPosition(void);
void UpdateDisplay(void);
void DrawControlPoints(void);
void DrawSpline(void);
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 NumControlPoints = 0;       /* Number of control points entered */
int MovePoint = -1;             /* Control point to be moved - neg if none */
int Control[MAXVERT][2];        /* Array to hold MAXVERT Control Points */
double DeltaSpline[4][4];       
/* Matrix to hold values of the B-Spline basis matrix and Forward        */
/* Difference delta matrix.  In terms of matrix multiplication, this is: */
/*                  |   0   0  0  1 |             | -1  3 -3  1 |        */
/*    DeltaSpline = |  d3  d2  d  0 |  *  1/6  *  |  3 -6  3  0 |        */
/*                  | 6d3 2d2  0  0 |             | -3  0  3  0 |        */
/*                  | 6d3   0  0  0 |             |  1  4  1  0 |        */
/* where  d = delta, d2 = delta^2, d3 = delta^3                          */

/***********************************************************************/
/*  MultMatrix takes in two 4x4 matrices (In1 and In2), multiplies     */
/*  them together, and returns the resulting matrix in Out.            */
/***********************************************************************/

void MultMatrix(double In1[4][4], double In2[4][4], double Out[4][4])
{
  int row, col, count;

  /* Initialize the matrix to be returned */
  for (row = 0; row < 4; row++)
    for (col = 0; col < 4; col++)
      Out[row][col] = 0.0;

  /* 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] += In1[row][count] * In2[count][col];
}

/***********************************************************************/
/*  SetDeltaSplineMatrix is called by Initialize to set up a matrix    */
/*  that contains values for both the E(delta) matrix (as described    */
/*  in Ch. 11.2.9 of "Computer Graphics" by Foley, vanDam) and the     */
/*  B-Spline Basis matrix.  This matrix is stored in the global        */
/*  matrix DeltaSpline.  It uses the PRECISION constant declared       */
/*  above to calculate the appropriate values for the delta matrix.    */
/***********************************************************************/

void SetDeltaSplineMatrix(void)
{
  double BSplineMatrix[4][4] =   /* B-Spline basis matrix */
    { { -1*SIXTH,  3*SIXTH, -3*SIXTH,   SIXTH },
      {  3*SIXTH, -6*SIXTH,  3*SIXTH, 0       },
      { -3*SIXTH,  0,        3*SIXTH, 0       },
      {    SIXTH,  4*SIXTH,    SIXTH, 0       } };

  double DeltaMatrix[4][4] =     /* E(delta) matrix for forward differences */
    { { 0.0,        0.0,        0.0,       1.0 },
      {     DELTA3,     DELTA2,     DELTA, 0.0 },
      { 6.0*DELTA3, 2.0*DELTA2, 0.0,       0.0 },
      { 6.0*DELTA3, 0.0,        0.0,       0.0} };

  /* Multiply DeltaMatrix*BSplineMatrix and store in DeltaSpline */
  MultMatrix(DeltaMatrix,BSplineMatrix,DeltaSpline);
}

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

void Initialize(void)
{
  /* Calculate the global DeltaSpline matrix which holds both the      */
  /* B-Spline basis matrix and the Forward Difference E(delta) matrix. */
  SetDeltaSplineMatrix();

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

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

/***********************************************************************/
/*  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 squares.                   */
/***********************************************************************/

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

  DrawControlPoints(); /* Draw the control points and connecting lines */
  DrawSpline();        /* Draw the associated B-spline */

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

/***********************************************************************/
/*  DrawControlPoints draws each of the control points with a big      */
/*  square point, and connects them with line segments.  For 1-unit    */
/*  grad students, it also draws the point "to be moved" in white.     */
/***********************************************************************/

void DrawControlPoints(void)
{
  int i;

  /* Check to make sure we have control points to draw */
  if (NumControlPoints > 0)
    {
      glColor3f(0.0,0.0,1.0);    /* Color is blue */

      /* First, draw big points for the control points */
      glPointSize(5.0);         /* Draw big points */
      glBegin(GL_POINTS);
      for (i = 0; i < NumControlPoints; i++)
        glVertex2i(Control[i][0],Control[i][1]);
      glEnd();

      /* Next, draw a bunch of connected line segments */
      glPointSize(1.0);
      glBegin(GL_LINE_STRIP);
      for (i = 0; i < NumControlPoints; i++)
        glVertex2i(Control[i][0],Control[i][1]);
      glEnd();

      /* If we have a control point that is being moved, draw it in white. */
      /* This is only for 1-unit grad students. */
      if (MovePoint >= 0)
        {
          glColor3f(1.0,1.0,1.0);    /* Draw this point in white */
          glPointSize(5.0);          /* Draw a big point */
          glBegin(GL_POINTS);
            glVertex2i(Control[MovePoint][0],Control[MovePoint][1]);
          glEnd();
        }
    }
}

/***********************************************************************/
/*  DrawSpline is called by UpdateDisplay to draw the B-spline defined */
/*  by the control points.  Here, I am drawing Cubic Periodic          */
/*  B-Splines as described in the textbook on pp 339-341.  It assumes  */
/*  that the global matrix DeltaSpline has already been initialized.   */
/***********************************************************************/

void DrawSpline(void)
{
  int pnt, i;               /* pnt is the current starting control point */
  double GeomMatrix[4][4];  /* The Geometry matrix for 4 control points  */
  double PlotMatrix[4][4];  /* Matrix containing values for Forward Diff */
  double LastPlot[2];       /* Last endpoint for a line segment          */
  double CurrPlot[2];       /* Current endpoing for a line segment       */

  /* Check to make sure we have a spline to draw */
  if (NumControlPoints > 0)
    {
      glColor3f(1.0,1.0,0.0);    /* Color is yellow */

      /* Draw the spline only when we have 4 or more control points */
      for (pnt = 3; pnt < NumControlPoints; pnt++)
        {
          /* First, set up the Geometry Matrix for 4 control points. */
          /* Notice that the last two columns are zero since we are  */
          /* dealing with two dimensions only.                       */
          for (i = 0; i < 4; i++)
            {
              GeomMatrix[i][0] = Control[pnt-3+i][0];
              GeomMatrix[i][1] = Control[pnt-3+i][1];
              GeomMatrix[i][2] = 0.0;
              GeomMatrix[i][3] = 0.0;
            }

          /* Next, multiply this matrix by the global DeltaSpline */
          MultMatrix(DeltaSpline,GeomMatrix,PlotMatrix);

          /* Initialize the first endpoint of the line segments */
          LastPlot[0] = PlotMatrix[0][0];
          LastPlot[1] = PlotMatrix[0][1];
          CurrPlot[0] = LastPlot[0];
          CurrPlot[1] = LastPlot[1];

          /* Finally, plot the line segments */
          for (i = 0; i < PRECISION; i++)
            {
              /* Get the forward difference values from the PlotMatrix */
              CurrPlot[0] += PlotMatrix[1][0];
              CurrPlot[1] += PlotMatrix[1][1];
              PlotMatrix[1][0] += PlotMatrix[2][0];
              PlotMatrix[1][1] += PlotMatrix[2][1];
              PlotMatrix[2][0] += PlotMatrix[3][0];
              PlotMatrix[2][1] += PlotMatrix[3][1];
              
              /* Plot this little line segment */
              glBegin(GL_LINE_STRIP);
                glVertex2dv(LastPlot);
                glVertex2dv(CurrPlot);
              glEnd();
     
              /* Update the last endpoint of the line segment */
              LastPlot[0] = CurrPlot[0];
              LastPlot[1] = CurrPlot[1];
            }
        }
    }
}

/***********************************************************************/
/*  MouseButtonPressed is the callback function for any mouse button   */
/*  presses.  If the left button is pressed, it adds another control   */
/*  point.  If the right button is pressed, it clears all control      */
/*  points, effectively clearing the screen.  If the right button is   */
/*  pressed (1-unit grad students only), it checks to see if the       */
/*  is "near" an existing control point.  If so, this control point is */
/*  moved when the right button is pressed again.                      */
/***********************************************************************/

void MouseButtonPressed(int Button, int State, int X, int Y)
{
  int pnt;

  if (State == GLUT_DOWN)
    {
      if (Button == GLUT_LEFT_BUTTON)
        { /* Input another control point */
          /* If we are moving control point, don't enter new control point. */
          /* Also, check to see that we haven't entered too many vertices.  */
          if ((MovePoint < 0) && (NumControlPoints < MAXVERT))
            {
              Control[NumControlPoints][0] = X;
              Control[NumControlPoints][1] = Y;
              NumControlPoints++;
            }
        }
      else if (Button == GLUT_RIGHT_BUTTON)
        { /* Check to see if we are already moving a control point */
          if (MovePoint >= 0)
            { /* We already selected a point to be moved. */
              /* Now, let's set its new position.         */
              Control[MovePoint][0] = X;
              Control[MovePoint][1] = Y;
              MovePoint = -1;
            }
          else
            { /* Look for a control point to be moved */
              pnt = 0;
              while ((pnt < NumControlPoints) && (MovePoint < 0))
                {
                  if ((abs(X - Control[pnt][0]) <= 3) &&
                      (abs(Y - Control[pnt][1]) <= 3))
                    MovePoint = pnt;
                  pnt++;
                }
            }
        }
      glutPostRedisplay();
    }
}

/***********************************************************************/
/*  KeyPressed is called whenever a key is pressed by the user.  We    */
/*  check to see if the user hit 'q' or 'Q'.  If so, we quit.          */
/***********************************************************************/

void KeyPressed(unsigned char Key, int X, int Y)
{
  switch(toupper(Key))
    {
      case 'Q' : QuitProgram();
                 break;
      case ' ' : if (MovePoint < 0)
                   {
                     NumControlPoints = 0;
                     glutPostRedisplay();
                   }
                 break;
      case '\b': if ((MovePoint < 0) && (NumControlPoints > 0))
                   {
                     NumControlPoints--;
                     glutPostRedisplay();
                   }
                 break;
    }
}

/***********************************************************************/
/*  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();
  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 - MP4 - Cubic Periodic B-Splines using Forward Differences\n");
  printf("INPUTS:\n");
  printf("Left Mouse Button  - Enter Control Points for the Spline\n");
  printf("Right Mouse Button - Select a Control Point to Move (1-unit only)\n");
  printf("<Backspace>        - Delete most recent Control Point\n");
  printf("<Spacebar>         - Clear all Control Points\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 / Reshape / Display callbacks */
  glutKeyboardFunc(KeyPressed);
  glutMouseFunc(MouseButtonPressed);
  glutReshapeFunc(ReshapeWindow);
  glutDisplayFunc(UpdateDisplay);
  glutMainLoop();

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

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


