// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

#include <GL/freeglut.h> // we use glut here as window manager

#include <math.h>
#ifndef M_PI 
#define M_PI 3.1415926535897932385f 
#endif

#include <sstream>
using namespace std;

class Renderer {

public:
  Renderer() : t(0.0) {}

public:
  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // view scene from the side
    glTranslatef(0.0f, 0.0f, -3.0f);
    glRotatef( -45.0f, 0.0f, 0.0f, 1.0f);
    glRotatef( -45.0f, 0.0f, 1.0f, 0.0f);
    glRotatef( 135.0f, 1.0f, 0.0f, 0.0f);

    if(true) {
      // interpolate rotation matrices, which is wrong
      float r0[16];
      float r1[16];
      float rt[16];

      glPushMatrix(); // save for later use

      glLoadIdentity();

      glGetFloatv(GL_MODELVIEW_MATRIX, r0);
      // rotate around z
      glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
      glGetFloatv(GL_MODELVIEW_MATRIX, r1);

      glPopMatrix(); // restore

      //interpolate
      for(unsigned i=0; i < 16; i++) {
        rt[i] = (1-t) * r0[i] + t * r1[i];
      }

      glMultMatrixf(rt);
    }else{
      // interpolate rotation angle, which is correct

      // rotate around z
      glRotatef(t*180.0f, 0.0f, 0.0f, 1.0f);

    }
    glColor3f(0.0f, 0.0f, 1.0f);
    drawCoordinateAxisZ();
    glColor3f(0.0f, 1.0f, 0.0f);
    drawCoordinateAxisY();
    glColor3f(1.0f, 0.0f, 0.0f);
    drawCoordinateAxisX();
  }

  void init() {
    glEnable(GL_DEPTH_TEST);
  }
  void resize(int w, int h) {
    // ignore this for now
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (45.0, (float)w/(float)h, 0.1, 10.0);
  }
  void dispose() {
  }

public:
  float t; //time

private:
  void drawCoordinateAxisZ() {
    glBegin(GL_LINE_LOOP); // circle in x-y plane
    for(int a=0; a<360; a+=10) {
      float angle = M_PI / 180.0f * a;
      glVertex3f(cos(angle), sin(angle), 0.0);
    }
    glEnd();

    glBegin(GL_LINES);
    glVertex3f(0.9f, 0.0f, 0.0f); // x-axis
    glVertex3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.9f, 0.0f); // y-axis
    glVertex3f(0.0f, 1.0f, 0.0f);
    glVertex3f(0.0f, 0.0f,-1.0f); // z-axis
    glVertex3f(0.0f, 0.0f, 1.0f);
    glEnd();

    glBegin(GL_TRIANGLES); // z-axis tip
    glVertex3f(0.0f,-0.1f, 0.9f);
    glVertex3f(0.0f, 0.0f, 1.0f);
    glVertex3f(0.0f, 0.1f, 0.9f);
    glEnd();
  }

  void drawCoordinateAxisX() {
    glPushMatrix();
    glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    drawCoordinateAxisZ();
    glPopMatrix();
  }

  void drawCoordinateAxisY() {
    glPushMatrix();
    glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
    drawCoordinateAxisZ();
    glPopMatrix();
  }

};

//this is a static pointer to a Renderer used in the glut callback functions
static Renderer *renderer;

//glut static callbacks start
static void glutResize(int w, int h) 
{
  renderer->resize(w,h);
}

static void glutDisplay() 
{
  renderer->display();
  glutSwapBuffers();
}

static void glutKeyboard(unsigned char key, int x, int y) {
  bool redraw = false;
  float offset = 0.02f;

  if (glutGetModifiers() & GLUT_ACTIVE_ALT) {
    offset = -offset;
  }

  switch(key) {
    case '1':
      renderer->t += offset;
      if(renderer->t < 0.0f) renderer->t = 1.0;
      if(renderer->t > 1.0f) renderer->t = 0.0;
      redraw = true;
      break;
  }

  if(redraw) {
    std::stringstream ss;
    ss << "Time " <<  renderer->t;
    glutSetWindowTitle(ss.str().c_str());
    glutDisplay();
  }
}


int main(int argc, char **argv) 
{

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100);
  glutInitWindowSize(320, 320);

  glutCreateWindow("Use the 1 key to interpolate rotation");

  glutDisplayFunc(glutDisplay);
  //glutIdleFunc(glutDisplay);
  glutReshapeFunc(glutResize);
  glutKeyboardFunc(glutKeyboard);

  renderer = new Renderer;
  renderer->init();

  glutMainLoop();
}