////////////////////////////////////////////////////
// anim.cpp
// Template code for drawing an interesting animation.
////////////////////////////////////////////////////

#ifdef WIN32
#include <windows.h>
#endif

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

void nextFrame();
void save_image(int frame);
void instructions();

const int STRLEN = 100;
typedef char STR[STRLEN];
int Win[2];     // window (x,y) size

#define PI 3.1415926535897
#define X 0
#define Y 1
#define Z 2

double t      = 0.0;
double tStep  = 0.05;
double dt     = tStep;
double tMax   = 1.0;
int animating = 0;
int recording = 0;

double eye[3] = {1.5, 2.0, 10.0};

//////////////////////////////////////////////////////
// ***************************************************
// * You should change these lines to reflect the number
// * of degrees of freedom in your animation
// ***************************************************
//////////////////////////////////////////////////////
#define MAXDOF 3
int cur_dof = 0;
double dof[MAXDOF] = {
  0.0,
  0.0,
  0.0
};
char *dofName[MAXDOF] = {
  "base azimuth",
  "arm elevation",
  "eye y"
};
enum {
  BASE_ANGLE,
  ARM_ANGLE,
  EYE_Y
};

//////////////////////////////////////////////////////
// ***************************************************
// * You should change these lines to reflect the number
// * of cubic curves used to control your degrees of freedom.
// * These are initialized in init_curves().
// * NOTE: Here, simple linear interpolation is used.
// *       You should define cubic curves instead.
// ***************************************************
//////////////////////////////////////////////////////

#define MAXCURVES 4
typedef struct {
  double a[MAXDOF][4];
  double len;
} curve;

curve dofHistory[MAXCURVES];


//////////////////////////////////////////////////////
//    PROC: choose_dof()
//    DOES: prepares another degree of freedom for editing.
//////////////////////////////////////////////////////
void choose_dof(int step) {
  cur_dof += step;
  if (cur_dof < 0)
    cur_dof = 0;
  if (cur_dof >= MAXDOF)
    cur_dof = MAXDOF-1;
  printf("Active: %s\n",dofName[cur_dof]);
}


//////////////////////////////////////////////////////
//    PROC: change_dof()
//    DOES: increases or decreases current degree of freedom variable.
//////////////////////////////////////////////////////
void change_dof(double increment) {
  dof[cur_dof] += increment;
  printf("  %s: %6.2lf\n",dofName[cur_dof],dof[cur_dof]);
  glutPostRedisplay();
}


//////////////////////////////////////////////////////
//    PROC: glut_key_action()
//    DOES: this function gets called for any keypresses
//////////////////////////////////////////////////////

void glut_key_action(unsigned char key, int x, int y)
{
  switch (key) {
  case 'q':
  case 27:
    exit(0); 
  case 's':
    save_image(-1);
    break;
  case 'r':
    recording ^= 1;
    if (recording)
      printf("Recording ON\n");
    else
      printf("Recording OFF\n");
    break;
  case ',':
  case '+':
    animating = 0;
    t -= tStep;
    nextFrame();
    break;
  case '.':
  case '-':
    animating = 0;
    t += tStep;
    nextFrame();
    break;
  case ' ':
    animating = !animating;
    break;
  case '<':
    animating = 1;
    dt = -tStep;
    break;
  case '>':
    animating = 1;
    dt = tStep;
    break;
  case '8':
    choose_dof(+1);
    break;
  case '2':
    choose_dof(-1);
    break;
  case '4':
    change_dof(-0.1);
    break;
  case '6':
    change_dof(+0.1);
    break;
  case '1':
    change_dof(-0.01);
    break;
  case '3':
    change_dof(+0.01);
    break;
  case '7':
    change_dof(-1.0);
    break;
  case '9':
    change_dof(+1.0);
    break;
  case 'h':
  case '?':
    instructions();
    break;
  }

}

//////////////////////////////////////////////////////
// ***************************************************
// * You should change this function to use the
// * curve coefficients for your animation.
// ***************************************************
//////////////////////////////////////////////////////

void init_curves() {
  int i,j;

  /*
   * No quadratic or cubic terms in this example.
   */
  for (i=0; i<MAXCURVES; i++)
    for (j=0; j<MAXDOF; j++) {
      dofHistory[i].a[j][2] = 0.0;
      dofHistory[i].a[j][3] = 0.0;
    }

  /*
   * Base turns
   */
  dofHistory[0].len = 1.0;
  dofHistory[0].a[BASE_ANGLE][0] = 0.0;
  dofHistory[0].a[BASE_ANGLE][1] = 0.5;
  dofHistory[0].a[ARM_ANGLE][0]  = 0.0;
  dofHistory[0].a[ARM_ANGLE][1]  = 0.0;
  dofHistory[0].a[EYE_Y][0]      = 2.0;
  dofHistory[0].a[EYE_Y][1]      = 0.0;
  


  /*
   * Arm goes up, camera goes up
   */
  dofHistory[1].len = 0.5;
  dofHistory[1].a[BASE_ANGLE][0] = 0.5;
  dofHistory[1].a[BASE_ANGLE][1] = 0.0;
  dofHistory[1].a[ARM_ANGLE][0]  = 0.0;
  dofHistory[1].a[ARM_ANGLE][1]  = 1.0;
  dofHistory[1].a[EYE_Y][0]      = 2.0;
  dofHistory[1].a[EYE_Y][1]      = 8.0;

  /*
   * Arm goes down
   */
  dofHistory[2].len = 0.5;
  dofHistory[2].a[BASE_ANGLE][0] =  0.5;
  dofHistory[2].a[BASE_ANGLE][1] =  0.0;
  dofHistory[2].a[ARM_ANGLE][0]  =  1.0;
  dofHistory[2].a[ARM_ANGLE][1]  = -1.0;
  dofHistory[2].a[EYE_Y][0]      = 10.0;
  dofHistory[2].a[EYE_Y][1]      =  0.0;

  /*
   * Base turns back, camera goes down
   */
  dofHistory[3].len = 1.0;
  dofHistory[3].a[BASE_ANGLE][0] =  0.5;
  dofHistory[3].a[BASE_ANGLE][1] = -0.5;
  dofHistory[3].a[ARM_ANGLE][0]  =  0.0;
  dofHistory[3].a[ARM_ANGLE][1]  =  0.0;
  dofHistory[3].a[EYE_Y][0]      = 10.0;
  dofHistory[3].a[EYE_Y][1]      = -8.0;

  tMax = 0.0;
  for (i=0; i<MAXCURVES; i++)
    tMax += dofHistory[i].len;  
}

/////////////////////////////////////////
//	PROC:	save_image
//	DOES:	saves the current image to a ppm file
/////////////////////////////////////////

void save_image(int frame)
{
  FILE *fp;
  STR fname;
  const maxVal=255;
  register int y;
  unsigned char *pixels;

  if (frame < 0) {
    strcpy(fname,"scene.ppm");
  }
  else {
    sprintf(fname,"anim%03d.ppm",frame);
  }
  printf("Saving image %s: %d x %d\n", fname,Win[0],Win[1]);
  fp = fopen(fname,"wb");
  if (!fp) {
	printf("Unable to open file '%s'\n",fname);
	return;
  }
  fprintf(fp, "P6\n");
  fprintf(fp, "%d %d\n", Win[0], Win[1]);
  fprintf(fp, "%d\n", maxVal);

  pixels = new unsigned char [3*Win[0]];
  for ( y = Win[1]-1; y>=0; y-- ) {
	glReadPixels(0,y,Win[0],1,GL_RGB,GL_UNSIGNED_BYTE, (GLvoid *) pixels);
	fwrite(pixels, 3, Win[0], fp);
  }
  fclose(fp);
}



/*********************************************************
    PROC: myinit()
    DOES: performs most of the OpenGL intialization
     -- change these with care, if you must.
**********************************************************/

void myinit(void)
{
    GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };
    
    GLfloat lmodel_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
    GLfloat local_view[] = { 0.0 };

       /**** set lighting parameters ****/
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

/*    glFrontFace (GL_CW); */
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
}

/*********************************************************
    PROC: set_colour();
    DOES: sets all material properties to the given colour -- don't change
**********************************************************/

void set_colour(float r, float g, float b)
{
  float ambient = 0.2f;
  float diffuse = 0.7f;
  float specular = 0.4f;
  GLfloat mat[4];
      /**** set ambient lighting parameters ****/
    mat[0] = ambient*r;
    mat[1] = ambient*g;
    mat[2] = ambient*b;
    mat[3] = 1.0;
    glMaterialfv (GL_FRONT, GL_AMBIENT, mat);

      /**** set diffuse lighting parameters ******/
    mat[0] = diffuse*r;
    mat[1] = diffuse*g;
    mat[2] = diffuse*b;
    mat[3] = 1.0;
    glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);

      /**** set specular lighting parameters *****/
    mat[0] = specular*r;
    mat[1] = specular*g;
    mat[2] = specular*b;
    mat[3] = 1.0;
    glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
    glMaterialf (GL_FRONT, GL_SHININESS, 0.5);
}

/*********************************************************
    PROC: display()
    DOES: this gets called by the event handler to draw
          the scene, so this is where you need to build
	  your scene -- make your changes and additions here
	  Add other procedures if you like.
**********************************************************/

void display(void)
{
  eye[Y] = dof[EYE_Y];
  glLoadIdentity();

  gluLookAt (eye[X], eye[Y], eye[Z],  0.0,0.0,0.0, 0.0,1.0,0.0);

  /* glClearColor (red, green, blue, alpha           */
  /* Ignore the meaning of the 'alpha' value for now */
  glClearColor(0.7f,0.7f,0.9f,1.0f);   /* set the background colour */
  /* OK, now clear the screen with the background colour */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      /* draw the base */
  glRotated(360.0*dof[BASE_ANGLE], 0.0, 1.0, 0.0);
  set_colour(1, 1, 0.6f);  /* yellow */
  glPushMatrix();
    glScaled(1.0, 2.0, 1.0);
    glutSolidCube(1.0);
  glPopMatrix();

    /* draw the arm */
  set_colour(1, 0.5f, 0.5f);  /* pink */
  glTranslated(0.5, 1.0, 0.0);
  glRotated(180.0*dof[ARM_ANGLE], 0.0, 0.0, 1.0);
  glTranslated(0.5, -1.0, 0.0);
  glPushMatrix();
    glScalef(1.0f, 2.0f, 1.0f);
    glutSolidCube(1.0);
  glPopMatrix();

  glFlush();

  glutSwapBuffers();
}

/*********************************************************
    PROC: myReshape()
    DOES: handles the window being resized -- don't change
**********************************************************/

void myReshape(int w, int h)
{
  Win[0] = w;
  Win[1] = h;

  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
     /*** this defines the field of view of the camera   ***/
     /*** Making the first 4 parameters larger will give ***/
     /*** a larger field of view, therefore making the   ***/
     /*** objects in the scene appear smaller            ***/
  glFrustum(-1,1,-1,1,4,100);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
     /*** this sets the virtual camera          ***/
     /*** gluLookAt( x,y,z,   x,y,z   x,y,z );  ***/
     /***            camera  look-at camera-up  ***/
     /***            pos'n    point   vector    ***/
    
     /*** place camera at (x=4, y=6, z=20), looking at origin,
          y-axis being up         ***/
  gluLookAt(eye[X],eye[Y],eye[Z],   0,0,0,   0,1,0);
}

void idle() {
  if (animating) {
    t += dt;
    if (t > tMax) {
      t = tMax;
      return;
    }
    if (t < 0.0) {
      t = 0.0;
      return;
    }

    nextFrame();

    if (recording) {
      int frame = (int)(t / tStep + 0.5);
      save_image(frame);
    }      
  }
}

//////////////////////////////////////////////////////
// This is called when it's time to draw the next frame.
//////////////////////////////////////////////////////

void nextFrame() {
  int i,iCurve,j,k;
  double tStart,tp,d;

  //
  // Make sure time is in bounds
  //
  if (t > tMax) t = tMax;
  if (t < 0.0)  t = 0.0;

  //
  // Find the cubic curve that contains the current time point.
  //
  iCurve = 0;
  tStart = 0.0;
  for (i=0; i<MAXCURVES; i++) {
    if (tStart < t && t <= tStart +dofHistory[i].len) {
      iCurve = i;
      break;
    }
    tStart += dofHistory[i].len;
  }

  //
  // Get the time, as a fraction of the current
  // curve's time interval.
  //
  tp = (t-tStart) / dofHistory[iCurve].len;
  for (j=0; j<MAXDOF; j++) {
    //
    // Evaluate a degree of freedom, using Horner's
    // algorithm, from the cubic curve coefficients.
    //
    d = 0.0;
    for (k=3; k>=0; k--)
      d = (d*tp) + dofHistory[iCurve].a[j][k];
    dof[j] = d;
  }

  printf("t=%6.2lf\n",t);
  for (i=0; i<MAXDOF; i++)
    printf("  %15s : %6.2lf\n",dofName[i],dof[i]);

  glutPostRedisplay();
}

void instructions() {
  printf("An OpenGL Animation\n");
  printf("Animation controls:\n");
  printf("Key      Result\n");
  printf(" >        play forwards\n");
  printf(" <        play backwards\n");
  printf("space     stop/start animation\n");
  printf(" r        stop/start recording frames\n");
  printf(" . or +   step forwards one frame\n");
  printf(" , or -   step backwards one frame\n");
  printf("\n");
  printf("Hierarchy controls: use numeric keypad, with Num-Lock ON\n");
  printf("7: dof -= 1     8: next dof    9: dof += 1\n");
  printf("4: dof -= 0.1                  6: dof += 0.1\n");
  printf("1: dof -= 0.01  2: prev dof    3: dof += 0.01\n");
}

/*********************************************************
     PROC: main()
     DOES: calls initialization, then hands over control
	   to the event handle, which calls 
	   display() whenever the screen needs to be redrawn
**********************************************************/

int main(int argc, char** argv)
{
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition (0, 0);
    glutInitWindowSize(196,196);
    glutCreateWindow(argv[0]);

    myinit();
    init_curves();

    glutReshapeFunc (myReshape);
    glutKeyboardFunc( glut_key_action );

    instructions();
	
    glutIdleFunc(idle);

    glutDisplayFunc(display);
    glutMainLoop();
    return 0;         // never reached
}
