//
// 3-D gear wheels.  This program is in the public domain.
//
//
// Original: Brian Paul
// Conversion to GLUT by Mark J. Kilgard 
//

// Modified for use in cs4731 by Mark R. Stevens (February 12, 2000)
// contact: stevensm@cs.wpi.edu with comments / concerns
// 
// Additional Modifications for windows port
//     Paul Tessier<phernost@wpi.edu>
//     Stephen Laverty <steve@WPI.EDU>
//
// C includes
#include <math.h>

// C++ includes
#include <iostream>

// Paul Tessier <phernost@wpi.edu>
using namespace std;

#ifdef WIN32                     
// Stephen Laverty <steve@WPI.EDU>
#ifndef M_PI
#define M_PI 3.14159
#endif

#endif

// The local includes
#include "minigl.h"
#include "miniglut.h"
#include "gears.h"

//-------------------------------------------------------------------------------------
void gears::drawSingleGear(minigl &mgl,
			   float inner_radius, 
			   float outer_radius, 
			   float width, 
			   int   teeth, 
			   float tooth_depth) {
  
  //  Draw a gear wheel. 
  //
  //  Input:  inner_radius - radius of hole at center
  //          outer_radius - radius at center of teeth
  //          width - width of gear
  //          teeth - number of teeth
  //          tooth_depth - depth of tooth
  int i;
  float u, v, len;
  float angle = 0;

  // the various radius used by the gear
  float r0 = inner_radius;
  float r1 = outer_radius - tooth_depth / 2.0;
  float r2 = outer_radius + tooth_depth / 2.0;
  
  float da = 2.0 * M_PI / teeth / 4.0;

  // turn on flat shading
  mgl.mglShadeModel(minigl::MGL_FLAT);

  // draw using the current  normal
  mgl.mglNormal3f(0.0, 0.0, 1.0);

  // draw front face using gl quad strip
  mgl.mglBegin(minigl::MGL_QUAD_STRIP);
  for (i = 0; i <= teeth; i++) {
    angle = i * 2.0 * M_PI / teeth;
    mgl.mglVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
    mgl.mglVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
    if (i < teeth) {
      mgl.mglVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
      mgl.mglVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
    }
  }
  mgl.mglEnd();

  // draw front sides of teeth
  mgl.mglBegin(minigl::MGL_QUADS);
  da = 2.0 * M_PI / teeth / 4.0;
  for (i = 0; i < teeth; i++) {
    angle = i * 2.0 * M_PI / teeth;
    mgl.mglVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
    mgl.mglVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
    mgl.mglVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), width * 0.5);
    mgl.mglVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
  }
  mgl.mglEnd();
  
  // reset the normal
  mgl.mglNormal3f(0.0, 0.0, -1.0);

  // draw back face 
  mgl.mglBegin(minigl::MGL_QUAD_STRIP);
  for (i = 0; i <= teeth; i++) {
    angle = i * 2.0 * M_PI / teeth;
    mgl.mglVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
    mgl.mglVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
    if (i < teeth) {
      mgl.mglVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
      mgl.mglVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
    }
  }
  mgl.mglEnd();

  // draw back sides of teeth 
  mgl.mglBegin(minigl::MGL_QUADS);
  da = 2.0 * M_PI / teeth / 4.0;
  for (i = 0; i < teeth; i++) {
    angle = i * 2.0 * M_PI / teeth;
    mgl.mglVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
    mgl.mglVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -width * 0.5);
    mgl.mglVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
    mgl.mglVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
  }
  mgl.mglEnd();

  // draw outward faces of teeth 
  mgl.mglBegin(minigl::MGL_QUAD_STRIP);
  for (i = 0; i < teeth; i++) {
    angle = i * 2.0 * M_PI / teeth;

    mgl.mglVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
    mgl.mglVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
    u = r2 * cos(angle + da) - r1 * cos(angle);
    v = r2 * sin(angle + da) - r1 * sin(angle);
    len = sqrt(u * u + v * v);
    u /= len;
    v /= len;
    mgl.mglNormal3f(v, -u, 0.0);
    mgl.mglVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
    mgl.mglVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
    mgl.mglNormal3f(cos(angle), sin(angle), 0.0);
    mgl.mglVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), width * 0.5);
    mgl.mglVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -width * 0.5);
    u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
    v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
    mgl.mglNormal3f(v, -u, 0.0);
    mgl.mglVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5);
    mgl.mglVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5);
    mgl.mglNormal3f(cos(angle), sin(angle), 0.0);
  }
  mgl.mglVertex3f(r1 * cos(0.0), r1 * sin(0.0), width * 0.5);
  mgl.mglVertex3f(r1 * cos(0.0), r1 * sin(0.0), -width * 0.5);
  mgl.mglEnd();

  // Change the shading type again
  mgl.mglShadeModel(minigl::MGL_SMOOTH);

  // draw inside radius cylinder
  mgl.mglBegin(minigl::MGL_QUAD_STRIP);
  for (i = 0; i <= teeth; i++) {
    angle = i * 2.0 * M_PI / teeth;
    mgl.mglNormal3f(-cos(angle), -sin(angle), 0.0);
    mgl.mglVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
    mgl.mglVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
  }
  mgl.mglEnd();

}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
void gears::draw(minigl &mgl, miniglut &mglut)
{

  // clear the screen
  mgl.mglClear(minigl::MGL_COLOR_BUFFER_BIT | minigl::MGL_DEPTH_BUFFER_BIT);

  // set up the Modelview Matrix for rendering
  mgl.mglPushMatrix();
  mgl.mglRotatef(view_rotx, 1.0, 0.0, 0.0);
  mgl.mglRotatef(view_roty, 0.0, 1.0, 0.0);
  mgl.mglRotatef(view_rotz, 0.0, 0.0, 1.0);

  // draw the first gear
  mgl.mglPushMatrix();
  mgl.mglTranslatef(-3.0, -2.0, 0.0);
  mgl.mglRotatef(angle, 0.0, 0.0, 1.0);
  mgl.mglMaterialf(minigl::MGL_FRONT,
		   minigl::MGL_AMBIENT_AND_DIFFUSE, 
		   0.8,
		   0.1,
		   0.0,
		   1.0);
  drawSingleGear(mgl, 1.0, 4.0, 1.0, 20, 0.7);
  mgl.mglPopMatrix();

  // draw the second gear
  mgl.mglPushMatrix();
  mgl.mglTranslatef(3.1, -2.0, 0.0);
  mgl.mglRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
  mgl.mglMaterialf(minigl::MGL_FRONT, minigl::MGL_AMBIENT_AND_DIFFUSE, 0.0, 0.8, 0.2, 1.0);
  drawSingleGear(mgl, 0.5, 2.0, 2.0, 10, 0.7);
  mgl.mglPopMatrix();

  // draw the third gear
  mgl.mglPushMatrix();
  mgl.mglTranslatef(-3.1, 4.2, 0.0);
  mgl.mglRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
  mgl.mglMaterialf(minigl::MGL_FRONT, 
		   minigl::MGL_AMBIENT_AND_DIFFUSE,
		   0.2, 
		   0.2, 
		   1.0, 
		   1.0);
  drawSingleGear(mgl, 1.3, 2.0, 0.5, 10, 0.7);
  mgl.mglPopMatrix();

  // restore the matrix to the top state
  mgl.mglPopMatrix();

  // finish rendering
  mgl.mglFinish();

  // swap the buffers and draw
  mglut.mglutSwapBuffers();

  // count frames and output the frame rate
  Frames++;
  int t = mglut.mglutGet(miniglut::MGLUT_ELAPSED_TIME);
  if (t - T0 >= 5000) {
    float seconds = (t - T0) / 1000.0;
    float fps = Frames / seconds;
    cout << "\t" << Frames << " frames in ";
    cout << seconds << " seconds = " << fps << " fps\n";
    T0 = t;
    Frames = 0;
  }
}
//-----------------------------------------------------------------------------

//-------------------------------------------------------------------------------------
void gears::reshape(minigl &mgl, miniglut &mglut, int width, int height)
{

  // compute the aspect
  float h = static_cast<float>(height) / static_cast<float>(width);

  // set up the viewport
  mgl.mglViewport(0, 0, width, height);

  // set up the projection matrix stack
  mgl.mglMatrixMode(minigl::MGL_PROJECTION);
  mgl.mglLoadIdentity();
  mgl.mglFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);

  // set up the modelview matrix stack
  mgl.mglMatrixMode(minigl::MGL_MODELVIEW);
  mgl.mglLoadIdentity();
  mgl.mglTranslatef(0.0, 0.0, -40.0);

}
//-------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------
void gears::init(minigl &mgl, miniglut &mglut)
{

  // default settings for the gears
  view_rotx = 20.0;
  view_roty = 30.0;
  view_rotz = 0.0;
  angle = 0.0;

  // The timings 
  T0 = 0;
  Frames = 0;

  // set up the lighting
  mgl.mglLightf(minigl::MGL_LIGHT0, minigl::MGL_POSITION, 5.0, 5.0, 10.0, 0.0);
  mgl.mglEnable(minigl::MGL_LIGHTING);
  mgl.mglEnable(minigl::MGL_LIGHT0);

  // initiate backface removal
  mgl.mglEnable(minigl::MGL_CULL_FACE);
  
  // turn on z-buffering
  mgl.mglEnable(minigl::MGL_DEPTH_TEST);

  // turn on vector normalization
  mgl.mglEnable(minigl::MGL_NORMALIZE);

}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
void gears::key    (minigl &mgl, miniglut &mglut, unsigned char k, int x, int y) {
  
  // based on the key, update the rotation
  switch (k) {
  case 'r':
    angle += 2.0;
    break;
  case 'R':
    angle -= 2.0;
    break;
  case 'z':
    view_rotz += 5.0;
    break;
  case 'Z':
    view_rotz -= 5.0;
    break;
  case 27:  /* Escape */
    std::exit(0);
    break;
  default:
    return;
  }

  // update the screen
  mglut.mglutPostRedisplay();                                                                        
}
//--------------------------------------------------------------------------------

void gears::mouse (minigl &mgl, miniglut &mglut, int button, int state, int x, int y){

	mglut.mglutPostRedisplay( );
}

//--------------------------------------------------------------------------------
void gears::special(minigl &mgl, miniglut &mglut, int k, int x, int y) {

  // change view angle 
  switch (k) {
  case 'r':
    angle += 2.0;
    break;
  case 'R':
    angle -= 2.0;
    break;
  case miniglut::MGLUT_KEY_UP:
    view_rotx += 5.0;
    break;
  case miniglut::MGLUT_KEY_DOWN:
    view_rotx -= 5.0;
    break;
  case miniglut::MGLUT_KEY_LEFT:
    view_roty += 5.0;
    break;
  case miniglut::MGLUT_KEY_RIGHT:
    view_roty -= 5.0;
    break;
  default:
    return;
  }

  // update the screen
  mglut.mglutPostRedisplay();

}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
void gears::usage() {

  // tell the user what the keys do to the gears
  cout << "\nGears Usage:\n";
  cout << "\tKeys:\n";
  cout << "\t\tr\t\trotate individual gears about their z axis 2 degrees\n";
  cout << "\t\tR\t\trotate individual gears about their z axis -2 degrees\n";
  cout << "\t\tz\t\trotate all gears about the z axis 5 degrees\n";
  cout << "\t\tZ\t\trotate all gears about the z axis -5 degrees\n";
  cout << "\t\tleftarrow\trotate all gears about the x axis 5 degrees\n";
  cout << "\t\trightarrow\trotate all gears about the x axis -5 degrees\n";
  cout << "\t\tuparrow\t\trotate all gears about the y axis 5 degrees\n";
  cout << "\t\tdownarrow\trotate all gears about the y axis -5 degrees\n";
  cout << "\t\tescape\t\texit\n";
  cout << "\tMouse:\n";
  cout << "\t\tNone\n";
}
//--------------------------------------------------------------------------------








