// grtmain.cpp
// by Glenn G. Chappell
// April 2004
// Part of GRT package

// Glenn's Ray Tracer: Main Program (bare-bones version)


#include "grtobject.h"
#include "grtray.h"
#include "grttypes.h"

#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#include <stdlib.h>
//using std::exit;
#include <GL/glut.h> // GLUT stuff - includes OpenGL headers


// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
int winw, winh;               // Current window width & height, in pixels
                              // Set by reshape

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// For image
const int img_width = 400;
const int img_height = img_width;
GLubyte the_image[img_height][img_width][3];
                              // The image
                              // 3rd subscript 0 = R, 1 = G, 2 = B

const GrtColor emptycolor(0.5, 0.5, 0.5);
                              // "Nothing here yet" color for image
                              // Entire image is set to this color, in initialization.

const numpixelstocompute = 5000;
                              // Number of pixels to compute each idle cycle

int nexti, nextj;             // subscripts of next pixel to compute. 
bool imagedone;               // true if nothing left to compute

   // Screen is centered on, and perpendicular to, the -z axis
double screenz;               // z-coord of screen in world
double screenhalfx,           // HALF of width & height of image in world
       screenhalfy;

// For ray-traced scene
GrtObjList sceneobjects;      // List of objects in ray-traced scene 
GrtLightList scenelights;     // List of light-sources in ray-traced scene

// Misc
bool texton = true;  // True if instructions drawn


// backgroundfunction
// Given ray, return background color
// Passed to sceneobjects
GrtColor backgroundfunction(const GrtRay & theray)
{
   //return GrtColor(0.9, 0.9, 0.9);

   const double up = theray.dir()[1];
   if (up < 0) // floor
   {
      if (up > -0.2)
      {
         const double t = up/-0.2;
         return (1-t)*GrtColor(0.9, 0.6, 0.5) + t*GrtColor(0.5, 0.9, 0.9);
      }
      else
      {
         const double t = (up-(-0.2))/-0.8;
         return (1-t)*GrtColor(0.5, 0.9, 0.9) + t*GrtColor(0.3, 0.3, 0.7);
      }
   }
   else
   {
      if (up < 0.2)
      {
         const double t = up/0.2;
         return (1-t)*GrtColor(0.9, 0.6, 0.5) + t*GrtColor(0.5, 0.9, 0.9);
      }
      else
      {
         const double t = (up-0.2)/0.8;
         return (1-t)*GrtColor(0.5, 0.9, 0.9) + t*GrtColor(0.3, 0.3, 0.7);
      }
   }
}


// initscene
// Create ray-traced scene
void initscene()
{
   sceneobjects.setbackgroundfunction(backgroundfunction);
   sceneobjects.setrecursiondepthlimit(20);
   
   GrtObjectSphere * sphere;

   // 2 silver @ middle left & right
   sphere = new GrtObjectSphere;
   sphere->setcenter(pos(-1,0.0,-7));
   sphere->setradius(0.95);
   sphere->setcolor(GrtColor(0.9, 0.9, 0.9));
   sceneobjects.insert(sphere);

   sphere = new GrtObjectSphere;
   sphere->setcenter(pos(1,0.0,-7));
   sphere->setradius(0.95);
   sphere->setcolor(GrtColor(0.9, 0.9, 0.9));
   sceneobjects.insert(sphere);

   // 1 silver @ top center
   sphere = new GrtObjectSphere;
   sphere->setcenter(pos(0,1.8,-7));
   sphere->setradius(0.95);
   sphere->setcolor(GrtColor(0.9, 0.9, 0.9));
   sceneobjects.insert(sphere);

   // 2 silver @ top left & right
   sphere = new GrtObjectSphere;
   sphere->setcenter(pos(-2,1.8,-7));
   sphere->setradius(0.95);
   sphere->setcolor(GrtColor(0.9, 0.9, 0.9));
   sceneobjects.insert(sphere);

   sphere = new GrtObjectSphere;
   sphere->setcenter(pos(2,1.8,-7));
   sphere->setradius(0.95);
   sphere->setcolor(GrtColor(0.9, 0.9, 0.9));
   sceneobjects.insert(sphere);

   // 1 BIG shiny gray @ bottom
   sphere = new GrtObjectSphere;
   sphere->setcenter(pos(0,-100,-7));
   sphere->setradius(98.8);
   sphere->setcolor(GrtColor(0.5, 0.5, 0.5));
   sceneobjects.insert(sphere);
}


// resetpixelcomputations
// Resets pixels-computed state
// Posts redisplay event
void resetpixelcomputations()
{
   nexti = 0;  // Next pixel to compute is first in image
   nextj = 0;
   imagedone = false;
   glutPostRedisplay();
}


// initimage
// Initialize image in the_image to solid color (from emptycolor)
// Initialize image center, size
// Reset pixels-computed state
void initimage()
{
   for (int i=0; i<img_width; ++i)
   {
      for (int j=0; j<img_height; ++j)
      {
         the_image[j][i][0] = emptycolor[0]*255;
         the_image[j][i][1] = emptycolor[1]*255;
         the_image[j][i][2] = emptycolor[2]*255;
      }
   }

   screenz = -2.0;
   screenhalfx = 1.0;
   screenhalfy = screenhalfx;
   resetpixelcomputations();
}


// computepixel
// Computes color of image pixel with given subscripts
// Stores this color in the_image
void computepixel(int i, int j)
{
   const pos eyepos(0,0,0);  // Position of eye in world

   // Calculate position (in world) of pixel (i, j)
   pos pixelpos;  // Position of pixel
   pixelpos[0] = ((2.*i+1.)/img_width-1.)*screenhalfx;
   pixelpos[1] = ((2.*j+1.)/img_height-1.)*screenhalfy;
   pixelpos[2] = screenz;

   const GrtRay pixelray(eyepos, pixelpos-eyepos);  // Ray from eye to pixel

   const GrtColor pixelcolor = sceneobjects.doraytrace(pixelray, scenelights);

   // Store pixel color in the_image
   the_image[j][i][0] = pixelcolor[0]*255;
   the_image[j][i][1] = pixelcolor[1]*255;
   the_image[j][i][2] = pixelcolor[2]*255;
}


// computesomepixels
// Computes colors of given number of pixels in image
void computesomepixels(int howmany)
{
   // If nothing to compute, leave
   if (imagedone) return;

   // Need to redraw window
   glutPostRedisplay();  // Do this before computations,
                         //  since we might return
                         //  in the middle of the following loop

   // Compute some pixel colors, using computepixel()
   for (int n=0; n<howmany; ++n)
   {
      computepixel(nexti, nextj);
      ++nexti;
      if (nexti >= img_width)
      {
         nexti = 0;
         ++nextj;
         if (nextj >= img_height)
         {
            imagedone = true;
            return;
         }
      }
   }
}

// 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);
   }
}


// display
// The GLUT display function
void display()
{
   glClear(GL_COLOR_BUFFER_BIT);

   // Draw image
   // Image is always square, centered in window, as large as will fit
   double zoomx = double(winw)/img_width;
   double zoomy = double(winh)/img_height;
   double zoom = (zoomx < zoomy) ? zoomx : zoomy;
   glPixelZoom(zoom, zoom);
   glRasterPos2d(-1., -1);
   glDrawPixels(img_width, img_height,
                GL_RGB, GL_UNSIGNED_BYTE,
                the_image);

   // Draw instructions, if flag is set
   if (texton)
   {
      glColor3d(0.0, 0.0, 0.0);
      printbitmap("H    toggle help", -0.9, 0.9);
      printbitmap("Esc  quit", -0.9, 0.8);
   }

   // Make it all appear
   glutSwapBuffers();
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   winw = w;  // Save globals for other functions
   winh = 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
   glLoadIdentity();
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   case 'h':     // H: Toggle help
   case 'H':
      texton = !texton;
      glutPostRedisplay();
      break;
   }
}


// 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;
   }

   // Compute a few pixels in the image, if any need to be computed
   computesomepixels(numpixelstocompute);
}


// init
// Initialization
// Called by main after window creation
void init()
{
   glClearColor(1.0, 1.0, 1.0, 0.0);

   // Tell OpenGL to treat image as normal array
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   // Create the image in the array
   initimage();

   // Create the scene to ray trace
   initscene();
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(20, 20);
   glutCreateWindow("CS 481/681 - Glenn's Ray Tracer");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutIdleFunc(idle);

   // Do something
   glutMainLoop();

   return 0;
}
