// masking.cpp
// by Glenn G. Chappell
// February 2004
//
// For CS 481/681
// A simple OpenGL/GLUT program that demonstrates OpenGL masking
// Displays a torus that can rotate

#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
#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 = 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
double angle = 0.0;           // Rotating angle of torus
const double anglestep = 1.0; // Amount to rotate by
bool rotate = false;          // True if rotating
bool redmask = true,          // Mask flags for R, G, B, depth
     greenmask = true,
     bluemask = true,
     depthmask = true;
bool clearall = true;         // If true, do unmasked clear


// display
// The GLUT display function
void display()
{
   // Fancy clearing code
   if (clearall)
   {
      glColorMask(true, true, true, true);
      glDepthMask(true);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // Clear the viewport
   }
   glColorMask(redmask, greenmask, bluemask, true);
   glDepthMask(depthmask);
   if (!clearall) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // Clear the viewport

   // Draw (rotating?) torus
   glPushMatrix();
      glLoadIdentity();
      glTranslated(0., 0., -4.);
      glRotated(angle, 0.5, 1.0, 0.0);

      glutSolidTorus(0.5, 1.0, 80, 160);
   glPopMatrix();

   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;
   }

   // Do rotation, if turned on
   if (rotate)
   {
      angle += anglestep;
      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 ' ':  // Space - toggle rotation
      rotate = !rotate;
      break;
   case 'r':  // r - toggle red mask
   case 'R':
      redmask = !redmask;
      glutPostRedisplay();
      break;
   case 'g':  // g - toggle green mask
   case 'G':
      greenmask = !greenmask;
      glutPostRedisplay();
      break;
   case 'b':  // b - toggle blue mask
   case 'B':
      bluemask = !bluemask;
      glutPostRedisplay();
      break;
   case 'd':  // d - toggle depth mask
   case 'D':
      depthmask = !depthmask;
      glutPostRedisplay();
      break;
   case 'c':  // c - toggle clearall flag
   case 'C':
      clearall = !clearall;
      glutPostRedisplay();
   }
}


// 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 material    
   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);
   glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess);

   // 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);

   // Print instructions
   // (Text instructions because window gets messed up intentionally)
   cout << "CS 481/681 - Masking Demo" << endl;
   cout << endl;
   cout << "SPACE  Toggle rotation" << endl;
   cout << "R      Toggle red mask" << endl;
   cout << "G      Toggle green mask" << endl;
   cout << "B      Toggle blue mask" << endl;
   cout << "D      Toggle depth mask" << endl;
   cout << "C      Toggle unmasked buffer clearing" << endl;
}


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 - Masking Demo");

   // Initialize GL states & register callbacks
   init();
   glutDisplayFunc(display); 
   glutIdleFunc(idle);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);

   // Do something
   glutMainLoop();

   return 0;
}
