// face.cpp
// by Glenn G. Chappell
// January 2004
// Portions based on arm.c by Chris Hartman
//
// For CS 481/681
// Demo of matrix stacks & hierarchical objects


#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#include <stdlib.h>  // Some versions of MS-Vis C++ have broken <cstdlib>
//using std::exit;
#include <math.h>    // Some versions of MS-Vis C++ have broken <cmath>
//using std::cos;
//using std::sin;
#ifndef M_PI
#define M_PI (3.1415926535897932384626433832795)
#endif
#include <GL/glut.h> // GLUT stuff, includes OpenGL headers as well


// Global variables
// Window/viewport
const int startwinsize = 400;   // Starting window width & height, in pixels

// Keyboard
const int ESCKEY = 27;          // ASCII value of escape character

// Parameters for disk
const int NUM_DISK_POINTS = 50; // No. of points in polygon making up "disk"
GLuint disk_list;               // Display list name for disk list

// Globals affecting display
double face_scale = 0.7;        // Size of face
const double min_face_scale = 0.05;
const double max_face_scale = 10.0;
double face_angle = 0.0;        // Angle face is rotated
double face_move = 0.0;         // Horizontal face movement
const double min_face_move = -1.0;
const double max_face_move = 1.0;
double eye_move = 0.0;          // Horizontal iris/pupil movement
const double min_eye_move = -0.5;
const double max_eye_move = 0.5;


// 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);
   }
}


// draw_ear
// Draws an ear, centered at origin, fits in square: -1..1 in x & y.
// Uses, and does not modify, current model/view transformation.
void draw_ear()
{
   glPushMatrix();
      glScaled(0.5, 1.0, 1.0);
      glColor3d(0.6, 0.4, 0.2);
      glCallList(disk_list);
   glPopMatrix();
}


// draw_pupil
// Draws pupil, centered at origin, fits in square: -1..1 in x & y.
// Uses, and does not modify, current model/view transformation.
void draw_pupil()
{
   glColor3d(0.0, 0.0, 0.0);
   glCallList(disk_list);

   // Draw specular highlight in pupil
   glPushMatrix();
      glTranslated(-0.5, 0.5, 0.0);
      glScaled(0.2, 0.2, 1.0);
      glColor3d(1.0, 1.0, 1.0);
      glCallList(disk_list);
   glPopMatrix();
}


// draw_iris_and_pupil
// Draws iris/pupil, centered at origin, fits in square: -1..1 in x & y.
// Uses, and does not modify, current model/view transformation.
// Uses function draw_pupil.
void draw_iris_and_pupil()
{
   glColor3d(0.6, 0.4, 0.2);
   glCallList(disk_list);

   // Draw pupil in iris
   glPushMatrix();
      glScaled(0.5, 0.5, 1.0);
      draw_pupil();
   glPopMatrix();
}


// draw_eye
// Draws an eye, centered at origin, fits in square: -1..1 in x & y.
// Uses, and does not modify, current model/view transformation.
// Uses function draw_iris_and_pupil.
void draw_eye()
{
   // Draw white of eye
   glPushMatrix();
      glScaled(1.0, 0.4, 1.0);
      glColor3d(1.0, 1.0, 1.0);
      glCallList(disk_list);
   glPopMatrix();

   // Draw iris & pupil
   glPushMatrix();
      glTranslated(eye_move, 0.0, 0.0);
      glScaled(0.37, 0.37, 1.0);
      draw_iris_and_pupil();
   glPopMatrix();
}


// draw_nose
// Draws a nose, centered at origin, fits in square: -1..1 in x & y.
// Uses, and does not modify, current model/view transformation.
void draw_nose()
{
   glColor3d(0.6, 0.4, 0.2);
   glPushMatrix();
      glScaled(0.7, 1.0, 1.0);
      glCallList(disk_list);
   glPopMatrix();
   glPushMatrix();
      glTranslated( 0.6, -0.6, 0.0);
      glScaled(0.4, 0.4, 1.0);
      glCallList(disk_list);
   glPopMatrix();
   glPushMatrix();
      glTranslated(-0.6, -0.6, 0.0);
      glScaled(0.4, 0.4, 1.0);
      glCallList(disk_list);
   glPopMatrix();
}


// draw_face
// Draws a face, centered at origin, head (w/o hair) has radius 1.
// Uses, and does not modify, current model/view transformation.
// Uses functions draw_ear, draw_eye, draw_nose.
void draw_face()
{
   // Draw hair
   glPushMatrix();
      glTranslated(0.0, 0.2, 0.0);
      glScaled(1.1, 1.1, 1.0);
      glColor3d(0.0, 0.0, 0.0);
      glCallList(disk_list);
   glPopMatrix();

   // Draw left ear (on viewer's right)
   glPushMatrix();
      glTranslated(1.0, 0.0, 0.0);
      glScaled(0.3, 0.3, 1.0);
      draw_ear();
   glPopMatrix();

   // Draw right ear (on viewer's left)
   glPushMatrix();
      glTranslated(-1.0, 0.0, 0.0);
      glScaled(0.3, 0.3, 1.0);
      draw_ear();
   glPopMatrix();

   // Draw head
   glColor3d(0.8, 0.6, 0.4);
   glCallList(disk_list);

   // Draw left eye (on viewer's right)
   glPushMatrix();
      glTranslated( 0.4, 0.3, 0.0);
      glScaled(0.2, 0.2, 1.0);
      draw_eye();
   glPopMatrix();

   // Draw right eye (on viewer's left)
   glPushMatrix();
      glTranslated(-0.4, 0.3, 0.0);
      glScaled(0.2, 0.2, 1.0);
      draw_eye();
   glPopMatrix();

   // Draw nose
   glPushMatrix();
      glTranslated(0.0, -0.05, 0.0);
      glScaled(0.18, 0.18, 1.0);
      draw_nose();
   glPopMatrix();

   // Draw mouth
   glPushMatrix();
      glTranslated(0.0, -0.5, 0.0);
      glScaled(0.5, 0.1, 1.0);
      glColor3d(0.9, 0.1, 0.3);
      glCallList(disk_list);
   glPopMatrix();
}


// display
// The GLUT display function
// Uses function draw_face.
void display()
{
   glClear(GL_COLOR_BUFFER_BIT);

   // Draw face
   glPushMatrix();
      glTranslated(face_move, 0.0, 0.0);
      glRotated(face_angle, 0,0,1); 
      glScaled(face_scale, face_scale, face_scale);
      draw_face();
   glPopMatrix();

   // Draw instructions
   glColor3d(0.1, 0.1, 0.9);
   printbitmap("FACE - Hierarchical Object Demo", -0.9, 0.9);
   printbitmap("A    Rotate face", -0.9, 0.7);
   printbitmap("S    Scale face", -0.9, 0.6);
   printbitmap("D    Move face", -0.9, 0.5);
   printbitmap("E    Move eyes", -0.9, 0.4);
   printbitmap("Shift reverses all the above actions", -0.9, 0.3);
   printbitmap("Esc  Quit", -0.9, 0.1);

   glutSwapBuffers();
}


// 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;
   }
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   case 'a':     // a: Rotate face
      face_angle -= 5;
      glutPostRedisplay();
      break;
   case 'A':     // (Shift-a)
      face_angle += 5;
      glutPostRedisplay();
      break;
   case 's':     // s: Scale face
      face_scale *= 1.1;
      if (face_scale > max_face_scale) face_scale = max_face_scale;
      glutPostRedisplay();
      break;
   case 'S':     // (Shift-s)
      face_scale /= 1.1;
      if (face_scale < min_face_scale) face_scale = min_face_scale;
      glutPostRedisplay();
      break;
   case 'd':     // d: Move face
      face_move += 0.1;
      if (face_move > max_face_move) face_move = max_face_move;
      glutPostRedisplay();
      break;
   case 'D':     // (Shift-d)
      face_move -= 0.1;
      if (face_move < min_face_move) face_move = min_face_move;
      glutPostRedisplay();
      break;
   case 'e':     // e: Move eyes
      eye_move += 0.1;
      if (eye_move > max_eye_move) eye_move = max_eye_move;
      glutPostRedisplay();
      break;
   case 'E':     // (Shift-e)
      eye_move -= 0.1;
      if (eye_move < min_eye_move) eye_move = min_eye_move;
      glutPostRedisplay();
      break;
   }
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   // We 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
}


// make_disk_list
// Creates a display list, putting the integer "name" in disk_list.
// The display list holds commands to draw a disk (filled circle)
//  with radius 1 and centered at the origin.
// The disk is approximated using a polygon with NUM_DISK_POINTS points.
void make_disk_list()
{
   disk_list = glGenLists(1);
   if (!disk_list)
   {
      cerr << "Could not generate display list." << endl;
      exit(1);
   }

   glNewList(disk_list, GL_COMPILE);
      glBegin(GL_POLYGON);
         for(int ii=0; ii<NUM_DISK_POINTS; ++ii)
         {
            double ang = ii*2*M_PI/NUM_DISK_POINTS;
            glVertex2d(cos(ang), sin(ang));
         }
      glEnd();
   glEndList();
}


// init
// Initialization
// Called by main after window creation
void init() 
{
   make_disk_list();                  // Make the disk display list

   glClearColor(0.6, 0.6, 0.6, 0.0);  // Gray background
}


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 - FACE - Hierarchical Object Demo");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display); 
   glutIdleFunc(idle);
   glutKeyboardFunc(keyboard);
   glutReshapeFunc(reshape);

   // Do something
   glutMainLoop();

   return 0;
}
