// tffly.cpp
// by Glenn G. Chappell
// February 2004
//
// For CS 481/681
// Flying through "city" scene
// Uses TRANSF & tfogl.h

#include <iostream>
using std::cerr;
using std::endl;
#include <sstream>
#include <string>
using std::string;
#include <math.h>    // Some versions of MS-Vis C++ have broken <cmath>
//using std::sqrt;
#include <stdlib.h>  // Some versions of MS-Vis C++ have broken <cstdlib>
//using std::exit;
#include <GL/glut.h> // GLUT stuff - includes OpenGL headers as well

#include "tfogl.h"   // includes TRANSF headers
using namespace tf;


// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
const double neardist = 0.01; // Near & far clipping distances
const double fardist = 50.;

double winw, winh;            // Window width & height, set by reshape

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// Viewing/flying
transf viewtf;                // Viewing transformation
const double speed = .003;    // Flying speed (units/frame)
const double turnmult = .001; // Multiplier for up/down/left/right turning
const double rollamt = 1.;    // Roll amount (deg/frame)

// Mouse state
// Set in mouse, motion, passivemotion. Used in idle.
bool leftmousedown;           // Left mouse state
bool rightmousedown;          // Right mouse state
int mx, my;                   // Mouse pos, in pixels. (0,0) is center of window.


// perprot
// Rotates about an axis in the x,y-plane, perpendicular to the given
// vector (vx, vy). Rotation amount is proportional to the length of
// the vector and to rotmultiplier.
void perprot(double rotmultiplier, double vx, double vy)
{
   double len = sqrt(vx*vx + vy*vy);

   if (len != 0.)
   {
      rot newrot(rotmultiplier*len, vec(-vy,vx,0.));
      viewtf.lcompose_assign(transf(newrot));
      glutPostRedisplay();
   }
}


// 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);
   }
}


// drawbox
// Draws a multi-colored cube.
// Center is (0,0,0), side length is 2.
// No back face is drawn.
void drawbox()
{
   glBegin(GL_QUADS);
      // Front face
      glColor3d(0.9, 0.1, 0.1);
      glVertex3d(-1., -1.,  1.);
      glVertex3d( 1., -1.,  1.);
      glVertex3d( 1.,  1.,  1.);
      glVertex3d(-1.,  1.,  1.);

      //// Back face
      //glColor3d(0.1, 0.9, 0.9);
      //glVertex3d(-1.,  1., -1.);
      //glVertex3d( 1.,  1., -1.);
      //glVertex3d( 1., -1., -1.);
      //glVertex3d(-1., -1., -1.);

      // Top face
      glColor3d(0.9, 0.9, 0.1);
      glVertex3d(-1.,  1.,  1.);
      glVertex3d( 1.,  1.,  1.);
      glVertex3d( 1.,  1., -1.);
      glVertex3d(-1.,  1., -1.);

      // Bottom face
      glColor3d(0.1, 0.1, 0.9);
      glVertex3d(-1., -1., -1.);
      glVertex3d( 1., -1., -1.);
      glVertex3d( 1., -1.,  1.);
      glVertex3d(-1., -1.,  1.);

      // Right face
      glColor3d(0.1, 0.9, 0.1);
      glVertex3d( 1., -1.,  1.);
      glVertex3d( 1., -1., -1.);
      glVertex3d( 1.,  1., -1.);
      glVertex3d( 1.,  1.,  1.);

      // Left face
      glColor3d(0.9, 0.1, 0.9);
      glVertex3d(-1.,  1.,  1.);
      glVertex3d(-1.,  1., -1.);
      glVertex3d(-1., -1., -1.);
      glVertex3d(-1., -1.,  1.);
   glEnd();
}


// drawcity
// Draws a 2x2 square, in the x,y plane,
//  convered with cubical blocks on the +z side.
void drawcity()
{
   const int halfside = 8;
      // Half of the side of the square in the coord system we draw it in.
      // "Buildings" are drawn at unit intervals from -halfside+1/2 to halfside-1/2.
      // We scale the square down so that it goes from -1 to 1 in both x & y.
   glPushMatrix();
      glScaled(1./halfside, 1./halfside, 1./halfside);

      // Draw gray square
      glColor3d(0.7, 0.7, 0.7);
      glBegin(GL_QUADS);
         glVertex3d(-halfside, -halfside, 0.);
         glVertex3d( halfside, -halfside, 0.);
         glVertex3d( halfside,  halfside, 0.);
         glVertex3d(-halfside,  halfside, 0.);
      glEnd();

      // Draw "buildings": boxes with side 1/2, spaced at intervals of 1 unit.
      int i, j;  // loop counters. Actual (x, y) of building is (i+0.5, j+0.5).
      for (i=-halfside; i<halfside; ++i)
      {
         for (j=-halfside; j<halfside; ++j)
         {
            // Draw a multi-colored box with side 1/2. 
            glPushMatrix();
               glTranslated(0., 0., 0.002);     // Raise it a hair above the ground
               glTranslated(i+0.5, j+0.5, 0.);  // Position it in the city
               glTranslated(0., 0., 0.25);      // Put back side right on the ground
               glScaled(0.25, 0.25, 0.25);      // Give the building side 1/2
               drawbox();
            glPopMatrix();
         }
      }
   glPopMatrix();
}


// display
// The GLUT display function
void display()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix();
      glTransform(viewtf);
      drawcity();
   glPopMatrix();

   // Text documentation
   glDisable(GL_DEPTH_TEST);  // No depth test for text
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();  // Save old projection matrix
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);  // old faithful
      glColor3d(0., 0., 0.);
      printbitmap("City Fly-Through", -0.9, 0.9);
      printbitmap("Esc     Quit", -0.9, 0.8);
      printbitmap("MOUSE   Fly & turn", -0.9, 0.7);
      printbitmap("Q W     Roll", -0.9, 0.6);
      printbitmap("Z       Reset position", -0.9, 0.5);
      pos p = viewtf.inverse().applyto(pos::zero());
      printbitmap("Position: (" + tostring(p[0]) + ", " + tostring(p[1]) + ", " + tostring(p[2]) + ")", -0.9, 0.4);
   glPopMatrix();  // Restore old projection matrix
   glMatrixMode(GL_MODELVIEW);  // Always go back to model/view mode
   glEnable(GL_DEPTH_TEST);  // Back to "normal" for 3-D drawing

   glutSwapBuffers();
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);
   winw = w;
   winh = h;

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(45, double(w)/h, neardist, fardist);
   // When drawing text, we set up a temporary projection with
   //  gluOrtho2D, so we don't need to worry about exactly where
   //  left, right, bottom, and top are.

   glMatrixMode(GL_MODELVIEW);  // Always go back to modelview mode
}


// 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;
   }

   // Fly
   perprot(turnmult, mx, my);
   if (leftmousedown)
   {
      vec newtranslate(0., 0., speed);
      viewtf.lcompose_assign(transf(newtranslate));
      glutPostRedisplay();
   }
   else if (rightmousedown)
   {
      vec newtranslate(0., 0., -speed);
      viewtf.lcompose_assign(transf(newtranslate));
      glutPostRedisplay();
   }
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   rot rollrot;

   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   case 'q':     // Q: Left roll
   case 'Q':
      rollrot = rot(-rollamt, vec(0., 0., 1.));
      viewtf.lcompose_assign(transf(rollrot));
      glutPostRedisplay();
      break;
   case 'w':     // W: Right roll
   case 'W':
      rollrot = rot(rollamt, vec(0., 0., 1.));
      viewtf.lcompose_assign(transf(rollrot));
      glutPostRedisplay();
      break;
   case 'z':     // Z: Reset position
   case 'Z':
      viewtf = transf(vec(0., 0., -3.));
      glutPostRedisplay();
      break;
   }
}


// mouse
// The GLUT mouse function
void mouse(int button, int state, int x, int y)
{
   mx = x - winw/2.;
   my = winh/2. - y;
   if (button == GLUT_LEFT_BUTTON)
      leftmousedown = (state == GLUT_DOWN);
   if (button == GLUT_RIGHT_BUTTON)
      rightmousedown = (state == GLUT_DOWN);
}


// motion
// The GLUT motion function
void motion(int x, int y)
{
   mx = x - winw/2.;
   my = winh/2. - y;
}


// passivemotion
// The GLUT passive motion function
void passivemotion(int x, int y)
{
   mx = x - winw/2.;
   my = winh/2. - y;
}


// init
// Initialization
// Called by main after window creation
void init()
{
   // General
   glClearColor(0.7, 0.8, 0.8, 0.0);  // Light blue-gray background
   glEnable(GL_DEPTH_TEST);           // 3-D so we need HSR

   // Mouse state
   leftmousedown = false;
   rightmousedown = false;
   mx = 0;
   my = 0;

   // Viewing
   viewtf = transf(vec(0., 0., -3.));
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - City Fly-Through");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutIdleFunc(idle);
   glutKeyboardFunc(keyboard);
   glutMouseFunc(mouse);
   glutMotionFunc(motion);
   glutPassiveMotionFunc(passivemotion);

   // Do something
   glutMainLoop();

   return 0;
}
