// shadowvol.cpp
// by Glenn G. Chappell
// February 2004
//
// For CS 481/681
// Demo of Shadow Volumes & TRANSF Package

#include <iostream>
using std::cerr;
using std::endl;
#include <string>
using std::string;
#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"   // OpenGL interface for TRANSF
#include "transf.h"  // TRANSF package
#include "vecpos.h"
using namespace tf;


// Global variables
// Window/viewport
const int startwinsize = 400; // Starting window width & height, in pixels
const double neardist = 1.;   // Near & far clipping distances
const double fardist = 10.;

// Keyboard
const int ESCKEY = 27;        // ASCII value of escape character

// Globals affecting display
pos lightpos(0., 1.0, -2.);   // Position of light source
transf receivertf = transf(vec(0., -1., -5.));
                              // Transformation for shadow receiver
transf castertf = transf(vec(0., 0.5, -3.));
                              // Transformation for shadow caster
bool rotate = false;          // True if rotating
const double anglestep = 1.;  // Step for rotation (degrees)


// 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);
   }
}


// setmaterial_1
// Set material paramters for material #1
void setmaterial_1()
{
   glMaterialAmbientAndDiffuse(GL_FRONT_AND_BACK, vec(0.9, 0.2, 0.5));
   glMaterialSpecular(GL_FRONT_AND_BACK, vec(1., 1., 1.));
   glMaterialShininess(GL_FRONT_AND_BACK, 100.);
}


// setmaterial_2
// Set material paramters for material #2
void setmaterial_2()
{
   glMaterialAmbientAndDiffuse(GL_FRONT_AND_BACK, vec(0.2, 0.9, 0.5));
   glMaterialSpecular(GL_FRONT_AND_BACK, vec(1., 1., 1.));
   glMaterialShininess(GL_FRONT_AND_BACK, 100.);
}


// draw_triangle
// Draw triangle with given vertices, if drawobject is true.
// If false, draw its shadow-volume boundaries, using lightpos.
// Uses given transformation.
void draw_triangle(const pos & p1, const pos & p2, const pos & p3,
                   bool drawobject, const transf & thetf)
{
   pos tp1 = thetf.applyto(p1);  // Transformed points
   pos tp2 = thetf.applyto(p2);
   pos tp3 = thetf.applyto(p3);

   if (drawobject)
   {
      glBegin(GL_TRIANGLES);
         glNormal(cross(tp2-tp1,tp3-tp1).normalized());
         glVertex(tp1);
         glVertex(tp2);
         glVertex(tp3);
      glEnd();
   }
   else
   {
      pos far_tp1 = interpolate(lightpos, tp1, 100.);
      pos far_tp2 = interpolate(lightpos, tp2, 100.);
      pos far_tp3 = interpolate(lightpos, tp3, 100.);
      glBegin(GL_QUAD_STRIP);
         glVertex(tp1);
         glVertex(far_tp1);
         glVertex(tp2);
         glVertex(far_tp2);
         glVertex(tp3);
         glVertex(far_tp3);
         glVertex(tp1);
         glVertex(far_tp1);
      glEnd();
   }
}


// draw_shadow_caster
// Draw object casting shadow, if drawobject is true.
// If false, draw its shadow-volume boundaries, using lightpos.
void draw_shadow_caster(bool drawobject)
{
   setmaterial_1();
   draw_triangle(pos(-0.5, 0., -0.5), pos(0.5, 0., -0.5), pos(0., 0., 0.5),
                 drawobject, castertf);
}

// draw_shadow_receiver
// Draw object receiving shadow
void draw_shadow_receiver()
{
   setmaterial_2();
   glPushMatrix();
      glTransform(receivertf);
      glutSolidTeapot(1.);
   glPopMatrix();
}


// display
// The GLUT display function
void display()
{
   /*
   // Code to draw scene with no shadowing
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   draw_shadow_caster(true);
   draw_shadow_receiver();
   glutSwapBuffers();
   return; /**/

   // Phase 1:
   // Draw the scene into the depth buffer
   glColorMask(false, false, false, false);
   glClear(GL_DEPTH_BUFFER_BIT);
   glStencilFunc(GL_ALWAYS, 0, 0);

   draw_shadow_caster(true);  // Draw object, not shadow-volume boundaries
   draw_shadow_receiver();

   // Phase 2:
   // Draw the shadow-volume boundaries, with the result that non-zero
   // pixels in the stencil buffer mark places where we see a shadowed object.
   glClearStencil(0);
   glClear(GL_STENCIL_BUFFER_BIT);
   glDepthMask(false);
   glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);

   draw_shadow_caster(false);  // Draw shadow-volume boundaries

   // Phase 3:
   // Draw scene, using the stencil buffer to determine what is shadowed
   glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
   glDepthMask(true);
   glColorMask(true, true, true, true);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   // Phase 3a:
   // Draw shadowed areas
   glStencilFunc(GL_EQUAL, 1, 1);
   glDisable(GL_LIGHT0);

   draw_shadow_caster(true);  // Draw object, not shadow-volume boundaries
   draw_shadow_receiver();

   // Phase 3b:
   // Draw lit areas
   glStencilFunc(GL_NOTEQUAL, 1, 1);
   glEnable(GL_LIGHT0);

   draw_shadow_caster(true);  // Draw object, not shadow-volume boundaries
   draw_shadow_receiver();

   // Draw instructions
   glStencilFunc(GL_ALWAYS, 0, 0);
   glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_TRANSFORM_BIT);
   glDisable(GL_LIGHTING);
   glDisable(GL_DEPTH_TEST);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);
      glColor3d(1., 1., 1.);
      printbitmap("Shadow Volumes Demo", -0.9, 0.9);
      printbitmap("R      Toggle rotation", -0.9, 0.8);
      printbitmap("Esc    Quit", -0.9, 0.7);
   glPopMatrix();
   glPopAttrib();

   glutSwapBuffers();
}


// 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;
   }

   if (rotate)
   {
      rot newrot(anglestep, vec(1., 0.5, 0.2));
      castertf.compose_assign(transf(newrot));
      glutPostRedisplay();
   }
}


// reshape
// The GLUT reshape function
void reshape(int w, int h)
{
   glViewport(0, 0, w, h);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60, double(w)/h, neardist, fardist);

   glMatrixMode(GL_MODELVIEW);  // Always go back to modelview mode
}


// keyboard
// The GLUT keyboard function
void keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case ESCKEY:  // ESC: Quit
      exit(0);
      break;
   case 'r':     // R: toggle rotation
   case 'R':
      rotate = !rotate;
      glutPostRedisplay();
      break;
   }
}


// init
// Initialization
// Called by main after window creation
void init()
{
   glEnable(GL_DEPTH_TEST);  // We're doing 3-D
   glEnable(GL_STENCIL_TEST);

   glClearColor(0.2, 0.2, 0.2, 1.);  // Background color

   // Set up a light
   glLightPosition(GL_LIGHT0, lightpos);
   glEnable(GL_LIGHT0);

   // General lighting stuff
   glEnable(GL_LIGHTING);
}


int main(int argc, char ** argv)
{
   // Initialize OpenGL/GLUT
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - Shadowing via Shadow Volumes");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display); 
   glutIdleFunc(idle);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);

   // Do something
   glutMainLoop();

   return 0;
}
