// fade.cpp
// by Glenn G. Chappell
// February 2004
//
// For CS 481/681
// Demonstrates use of the accumulation buffer for scene-to-scene fade

#include <iostream>
using std::cout;
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


// Global variables
// Window/viewport
const int startwinsize = 300; // 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
double angle = 0.;            // Rotation angle of torus (degrees)
const double anglestep = 4.;  // Amount to rotate by
double scene1weight = 1.;     // Weight for scene 1 in mix
double s1weightstep = 0.05;   // Amount to change weight by
bool s1weightgoingup = false; // True if scene 1 weight is going up
                              // False if down


// 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()
{
   GLfloat diffuse[] = { 0.9, 0.2, 0.5, 1.0 };
   GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat shininess = 100.;
   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, diffuse);
   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}


// setmaterial_2
// Set material paramters for material #2
void setmaterial_2()
{
   GLfloat diffuse[] = { 0.2, 0.9, 0.5, 1.0 };
   GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat shininess = 100.;
   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, diffuse);
   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}


// display
// The GLUT display function
void display()
{
   // Draw scene 1
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   setmaterial_1();
   glPushMatrix();
      glLoadIdentity();
      glTranslated(0., 0., -4.);
      glRotated(angle, 1., 0., 0.);
      glutSolidTorus(0.5, 1., 20, 40);
   glPopMatrix();

   // Draw instructions for scene 1
   glDisable(GL_LIGHTING);
   glDisable(GL_DEPTH_TEST);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);
      glColor3d(1., 1., 1.);
      printbitmap("Accumulation/Fade Demo", -0.9, 0.9);
      printbitmap("ESC  Quit", -0.9, 0.8);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_LIGHTING);

   // Send scene 1 to the accumulation buffer
   glAccum(GL_LOAD, scene1weight);

   // Draw scene 2
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   setmaterial_2();
   glPushMatrix();
      glLoadIdentity();
      glTranslated(0., 0., -4.);
      glRotated(angle, 0., 1., 0.);
      glPushMatrix();
         glTranslated(1.2, 0., 0.);
         glutSolidSphere(0.5, 40, 20);
      glPopMatrix();
      glPushMatrix();
         glTranslated(-1.2, 0., 0.);
         glutSolidSphere(0.5, 40, 20);
      glPopMatrix();
   glPopMatrix();

   // Draw instructions for scene 2
   glDisable(GL_LIGHTING);
   glDisable(GL_DEPTH_TEST);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
      glLoadIdentity();
      gluOrtho2D(-1., 1., -1., 1.);
      glColor3d(1., 1., 1.);
      printbitmap("Accumulation/Fade Demo", -0.9, -0.8);
      printbitmap("ESC  Quit", -0.9, -0.9);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_LIGHTING);

   // Send scene 2 to the accumulation buffer
   glAccum(GL_ACCUM, 1.-scene1weight);

   // Display combined image
   glAccum(GL_RETURN, 1.);
   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;
   }

   // Handle rotation
   angle += anglestep;

   // Handle scene weight changes
   if (s1weightgoingup)  // weight is going up
   {
      scene1weight += s1weightstep;
      if (scene1weight >= 1.)
      {
         scene1weight = 1.;
         s1weightgoingup = false;
      }
   }
   else  // weight is going down
   {
      scene1weight -= s1weightstep;
      if (scene1weight <= 0.)
      {
         scene1weight = 0.;
         s1weightgoingup = true;
      }
   }

   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;
   }
}


// init
// Initialization
// Called by main after window creation
void init()
{
   glEnable(GL_DEPTH_TEST);  // We're doing 3-D

   glClearColor(0., 0., 0., 0.);  // Background color

   // Set up a light
   GLfloat light_position[] = { -1.0, 1.3, 1.0, 0.0 };
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   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_ACCUM);

   // Make a window
   glutInitWindowSize(startwinsize, startwinsize);
   glutInitWindowPosition(50, 50);
   glutCreateWindow("CS 481/681 - Accumulation/Fade Demo");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display); 
   glutIdleFunc(idle);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);

   // Do something
   glutMainLoop();

   return 0;
}
