/*****************************************************************
  filter.c
  by Dave Pape
  9 Feb 2003
  
  This program demonstrates using different blending formulas
  to apply color filters to a rendered scene.
  The basic scene is a sphere, cube, and cone.
  A color filter is applied to the scene by drawing a square
  that covers the entire window, and defining a blend function
  that will combine the square's color with the already rendered
  pixels in various ways.
  
  Hitting the space bar switches between four modes:
    By default, no filter is applied.
    The first mode dims the scene by multiplying the existing
   pixels by an alpha value.  The alpha value changes dynamically.
    The second mode multiplies the existing pixels by the RGB
   color (1, 0, 0.5), to simulate a purple filter over the 'camera'.
    The third mode multiplies GL_ONE_MINUS_DST_COLOR (the inverse
   of the existing pixels' color) by 1, to produce a negative effect.
  
  This program also demonstrates using more than one projection
  when rendering a scene.  It first uses gluPerspective to render
  the 3D objects in perspective.  It then uses a glOrtho projection
  so that the square covering the entire window can be easily drawn
  in 2D, like an overlay.  

*****************************************************************/
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <GL/glut.h>
#include <GL/gl.h>

float viewRotY = 0, viewRotX = 0, cameraDistance = 10;
int filter = 0;

void drawEverything(void);
void initLight(void);
void checkGLError(char *);
void idle(void);
void key(unsigned char k, int x, int y);
void specialkey(int k, int x, int y);
float currentTime(void);


int main(int argc, char *argv[])
    {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(512, 512);
    glutCreateWindow(argv[0]);
    glutDisplayFunc(drawEverything);
    glutKeyboardFunc(key);
    glutSpecialFunc(specialkey);
    glutIdleFunc(idle);
    glutMainLoop();
    return 0;
    }


void drawEverything(void)
    {
    GLfloat red[4] = { 1, 0, 0, 1 };
    GLfloat green[4] = { 0, 1, 0, 1 };
    GLfloat white[4] = { 1, 1, 1, 1 };
    glClearColor(0.5, 0.7, 1.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(30.0, 1.0, 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);

    glLoadIdentity();

    initLight();

    glTranslatef(0.0, 0.0, -cameraDistance);
    glRotatef(viewRotX, 1.0, 0.0, 0.0);
    glRotatef(viewRotY, 0.0, 1.0, 0.0);

    glMaterialfv(GL_FRONT, GL_DIFFUSE, white);
    glPushMatrix();
     glTranslatef(0.0, -2.0, -5.0);
     glutSolidSphere(2.5, 16, 8);
    glPopMatrix();    
    
    glMaterialfv(GL_FRONT, GL_DIFFUSE, red);
    glPushMatrix();
     glTranslatef(-1.0, -1.0, -2.0);
     glutSolidCube(2.0);
    glPopMatrix();    
    
    glMaterialfv(GL_FRONT, GL_DIFFUSE, green);
    glPushMatrix();
     glTranslatef(1.0, -1.0, 0.0);
     glRotatef(-90.0, 1.0, 0.0, 0.0);
     glutSolidCone(1.5, 3.5, 16, 1);
    glPopMatrix();    

    glDisable(GL_LIGHTING);

/*************** CODE OF INTEREST ******************************/
/*    If the variable 'filter' is non-zero, apply a filter.    */
/*                                                             */
    if (filter > 0)                                          /**/
        {                                                    /**/
/*    Switch to an orthographic projection, to draw the 2D     */
/*    overlay.  Also, disable depth testing, because we don't  */
/*    need it & don't want it to mess things up.               */
/*                                                             */
        glMatrixMode(GL_PROJECTION);                         /**/
        glLoadIdentity();                                    /**/
        glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);              /**/
        glMatrixMode(GL_MODELVIEW);                          /**/
        glLoadIdentity();                                    /**/
        glDisable(GL_DEPTH_TEST);                            /**/

        glEnable(GL_BLEND);                                  /**/
        
/*    Filter 1: dim the scene by a time-varying alpha value    */
/*                                                             */
        if (filter == 1)                                     /**/
            {                                                /**/
            glBlendFunc(GL_ZERO, GL_SRC_ALPHA);              /**/
            glColor4f(1.0, 1.0, 1.0, fabs(sin(currentTime()))); /**/
            }                                                /**/
/*                                                             */
/*    Filter 2: multiply the scene by a purple color           */
/*                                                             */
        else if (filter == 2)                                /**/
            {                                                /**/
            glBlendFunc(GL_ZERO, GL_SRC_COLOR);              /**/
            glColor4f(1.0, 0.0, 0.5, 1.0);                   /**/
            }                                                /**/
/*                                                             */
/*    Filter 3: invert the scene (create a negative image)     */
/*                                                             */
        else if (filter == 3)                                /**/
            {                                                /**/
            glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);    /**/
            glColor4f(1.0, 1.0, 1.0, 1.0);                   /**/
            }                                                /**/
        
        glBegin(GL_QUADS);                                   /**/
         glVertex2i(0, 0);                                   /**/
         glVertex2i(1, 0);                                   /**/
         glVertex2i(1, 1);                                   /**/
         glVertex2i(0, 1);                                   /**/
        glEnd();                                             /**/
        glDisable(GL_BLEND);                                 /**/
        }                                                    /**/
    
    glutSwapBuffers();
    
    checkGLError("end-of-frame");
    }


void initLight(void)
    {
    GLfloat white[4] = { 1, 1, 1, 1 };
    GLfloat lightPos[4] = {-1, 1, 1, 0};
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, white);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    }


void checkGLError(char *prefix)
    {
    GLenum err = glGetError();
    if (err != GL_NO_ERROR)
        printf("%s GL error '%s'\n",prefix,gluErrorString(err));
    }


void key(unsigned char k, int x, int y)
    {
    if (k == 27)
        exit(0);
    else if (k == '-')
        cameraDistance += 1;
    else if (k == '=')
        cameraDistance -= 1;
    else if (k == ' ')
        filter = (filter + 1) % 4;
    glutPostRedisplay();
    }


void specialkey(int k, int x, int y)
    {
    if (k == GLUT_KEY_LEFT)
        viewRotY += 3;
    else if (k == GLUT_KEY_RIGHT)
        viewRotY -= 3;
    else if (k == GLUT_KEY_UP)
        viewRotX += 3;
    else if (k == GLUT_KEY_DOWN)
        viewRotX -= 3;
    glutPostRedisplay();
    }


void idle(void)
    {
    glutPostRedisplay();
    }


float currentTime(void)
    {
    static struct timeval startTime;
    static int firstCall=1;
    struct timeval t;
    if (firstCall)
        {
        firstCall = 0;
        gettimeofday(&startTime, NULL);
        }
    gettimeofday(&t,NULL);
    return (t.tv_sec-startTime.tv_sec) +
           (t.tv_usec - startTime.tv_usec) / 1000000.0;
    }

