// evaluator.cpp
// by Glenn G. Chappell
// March 2004
//
// For CS 481/681
// OpenGL Evaluator Demo

// This program draws a Bezier curve using an OpenGL evaluator.
// Includes examples both of using an evaluator grid
//  and using "manual" loops.

#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#include <sstream>
#include <stdlib.h>
//using std::exit;
#include <GL/glut.h> // GLUT stuff - includes OpenGL headers

// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// For Bezier curve
const int numcontrolpts = 4;  // Number of control points
                              // Below are coord's of control points
                              // (Must use 3 coord's each, even in 2-D.)
GLfloat controlpts[numcontrolpts][3] = {
   {-0.9, -0.9,  0.0},
   {-0.5,  0.2,  0.0}, 
   { 0.9, -0.9,  0.0},
   { 0.9,  0.9,  0.0}
};

int numdrawsegs = 10;         // Number of segments drawn in the Bezier curve
const int minnumdrawsegs = 1;
const int maxnumdrawsegs = 50;

bool drawcurve = true;        // true: draw Bezier curve
bool drawcurvepts = false;    // true: draw points used to approx curve
bool drawcontrolpts = true;   // true: draw control points for curve
bool drawcontrolpoly = false; // true: draw control polygon for curve


// tostring
// Convert argument to string class using operator<<
// Must include <sstream>
template<typename T>
std::string tostring(const T & input)
{
   std::ostringstream os;
   os << input;
   return os.str();
}


// printbitmap
// Prints the given string at the given raster position
//  using GLUT bitmap fonts.
// You probably don't want any rotations in the model/view
//  transformation when calling this function.
void printbitmap(const string msg, double x, double y)
{
   glRasterPos2d(x, y);
   for (string::const_iterator ii = msg.begin();
        ii != msg.end();
        ++ii)
   {
      glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *ii);
   }
}


// display
// The GLUT display function
void display()
{
   int i;  // Loop counter

   // Set up grid for evaluator
   // THIS IS ONLY NEEDED IF YOU USE "glEvalMesh1"!!!
   glMapGrid1d(numdrawsegs, 0.0, 1.0);
   // I do the above here, instead of in init,
   //  since numdrawsegs can change.

   // Clear screen
   glClear(GL_COLOR_BUFFER_BIT);

   // Draw control points for Bezier curve
   if (drawcontrolpts)
   {
      glColor3d(0.1, 0.1, 0.9);
      glPointSize(20.0);
      glBegin(GL_POINTS);
         for (i=0; i<numcontrolpts; ++i)
            glVertex3fv(controlpts[i]);
      glEnd();
   }

   // Draw control polygon for Bezier curve
   if (drawcontrolpoly)
   {
      glColor3d(0.9, 0.1, 0.9);
      glLineWidth(1.0);
      glBegin(GL_LINE_STRIP);
         for (i=0; i<numcontrolpts; ++i)
            glVertex3fv(controlpts[i]);
      glEnd();
   }

   // Draw points on Bezier curve
   if (drawcurvepts)
   {
      glColor3d(0.1, 0.7, 0.1);
      glPointSize(15.0);

      glBegin(GL_POINTS);
         for (i=0; i<=numdrawsegs; ++i)
            glEvalCoord1d(GLdouble(i)/numdrawsegs);
      glEnd();

      // I did the above "manually".
      // Code to do the same thing, using an evaluator grid, is as follows:

      //glEvalMesh1(GL_POINT, 0, numdrawsegs);
   }

   // Draw Bezier curve
   if (drawcurve)
   {
      glColor3d(0.9, 0.1, 0.1);
      glLineWidth(3.0);

      glEvalMesh1(GL_LINE, 0, numdrawsegs);

      // Here, I used an evaluator grid.
      // Code to do the same thing, "manually", is as follows:

      //glBegin(GL_LINE_STRIP);
      //   for (i=0; i<=numdrawsegs; ++i)
      //      glEvalCoord1d(GLdouble(i)/numdrawsegs);
      //glEnd();
   }

   // Draw instructions
   glColor3d(0., 0., 0.);
   printbitmap("OpenGL Evaluator Demo", -0.9, 0.9);
   printbitmap("<- ->  change number of segments [" + tostring(numdrawsegs) + "]", -0.9, 0.8);
   glColor3d(0.9, 0.1, 0.1);
   printbitmap(string("Q     Toggle curve [") + (drawcurve ? "X" : " ") + "]", -0.9, 0.7);
   glColor3d(0.1, 0.7, 0.1);
   printbitmap(string("W     Toggle pts on curve [") + (drawcurvepts ? "X" : " ") + "]", -0.9, 0.6);
   glColor3d(0.9, 0.1, 0.9);
   printbitmap(string("A     Toggle control polygon [") + (drawcontrolpoly ? "X" : " ") + "]", -0.9, 0.5);
   glColor3d(0.1, 0.1, 0.9);
   printbitmap(string("S     Toggle control pts [") + (drawcontrolpts ? "X" : " ") + "]", -0.9, 0.4);

   glutSwapBuffers();
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   // Set up coordinate system so that aspect ratios are always correct,
   //  and the region from -1..1 in x & y always just fits in the viewport.
   if (w > h)
      gluOrtho2D(-double(w)/h, double(w)/h, -1., 1.);
   else
      gluOrtho2D( -1., 1., -double(h)/w, double(h)/w);

   glMatrixMode(GL_MODELVIEW);  // Always go back to modelview mode
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   case 'q':     // Q: toggle curve
   case 'Q':
      drawcurve = !drawcurve;
      glutPostRedisplay();
      break;
   case 'w':     // W: toggle points on curve
   case 'W':
      drawcurvepts = !drawcurvepts;
      glutPostRedisplay();
      break;
   case 'a':     // A: toggle control polygon
   case 'A':
      drawcontrolpoly = !drawcontrolpoly;
      glutPostRedisplay();
      break;
   case 's':     // S: toggle control points
   case 'S':
      drawcontrolpts = !drawcontrolpts;
      glutPostRedisplay();
      break;
   }
}


// special
// The GLUT special function
void special(int key, int x, int y)
{
   switch (key)
   {
   case GLUT_KEY_LEFT:
      --numdrawsegs;
      if (numdrawsegs < minnumdrawsegs) numdrawsegs = minnumdrawsegs;
      glutPostRedisplay();
      break;
   case GLUT_KEY_RIGHT:
      ++numdrawsegs;
      if (numdrawsegs > maxnumdrawsegs) numdrawsegs = maxnumdrawsegs;
      glutPostRedisplay();
      break;
   }
}


// idle
// The GLUT idle function
void idle()
{
   // Print OpenGL errors, if there are any (for debugging)
   if (GLenum err = glGetError())
   {
      cerr << "OpenGL ERROR: " << gluErrorString(err) << endl;
   }
}


// init
// Initializes GL states
// Called by main
void init()
{
   // Set background color
   glClearColor(1.0, 1.0, 1.0, 0.0);

   // Set up Bezier curve evaluator
   glMap1f(GL_MAP1_VERTEX_3,   // target: 1-d [curve], 3 coord's per pt
           0.0, 1.0,           // start & end param value
           3,                  // "stride": pts stored 3 GLfloat's apart
           numcontrolpts,      // no. of control points
           &controlpts[0][0]); // control pt data
   glEnable(GL_MAP1_VERTEX_3); // Enable this evaluator
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - OpenGL Evaluator Demo");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutSpecialFunc(special);
   glutIdleFunc(idle);

   // Do something
   glutMainLoop();

   return 0;
}
