/*****************************************************************
  drawOrder2.c
  by Dave Pape
  11 Feb 2003
  
  This program is a followup to drawOrder1 from the previous class.
  It demonstrates how one can turn off depth buffer updates, as a 
  way to avoid the drawing-order problem with multiple transparent
  objects. When the transparent objects are rendered, depth testing
  is still used, so that the opaque sphere will occlude objects
  behind it, but writing new values to the depth buffer is disabled
  (via glDepthMask()), so the transparent objects won't occlude
  anything.
   
  The space bar again steps between three different possible drawing
  orders.  This shows how the solution is not perfect - the results
  of blending will be different, depending on the order that the
  objects are drawn.
  
  New functions shown here:
        glDepthMask()
  
*****************************************************************/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <GL/glut.h>
#include <GL/gl.h>

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

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


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);
    glutMainLoop();
    return 0;
    }


void drawEverything(void)
    {
    GLfloat red[4] = { 1, 0, 0, 0.5 };
    GLfloat green[4] = { 0, 1, 0, 0.25 };
    GLfloat blue[4] = { 0, 0, 0.8, 0.33 };
    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();

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    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, 0.0, -5.0);
     glutSolidSphere(2.5, 16, 8);
    glPopMatrix();    

/*************** CODE OF INTEREST ******************************/
/*    This disables writing into the depth buffer.  It does    */
/*    not disable testing against whatever is already in the   */
/*    depth buffer.                                            */
/*                                                             */
    glDepthMask(GL_FALSE);                                   /**/

    if (drawOrder == 0)
        {
        glMaterialfv(GL_FRONT, GL_DIFFUSE, red);
        glutSolidCube(2.0);
        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();
        glMaterialfv(GL_FRONT, GL_DIFFUSE, blue);
        glPushMatrix();
         glTranslatef(0.0, 0.0, 3.0);
         glutSolidTeapot(0.5);
        glPopMatrix();
        }
    else if (drawOrder == 1)
        {
        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();
        glMaterialfv(GL_FRONT, GL_DIFFUSE, red);
        glutSolidCube(2.0);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, blue);
        glPushMatrix();
         glTranslatef(0.0, 0.0, 3.0);
         glutSolidTeapot(0.5);
        glPopMatrix();
        }
    else if (drawOrder == 2)
        {
        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();
        glMaterialfv(GL_FRONT, GL_DIFFUSE, blue);
        glPushMatrix();
         glTranslatef(0.0, 0.0, 3.0);
         glutSolidTeapot(0.5);
        glPopMatrix();
        glMaterialfv(GL_FRONT, GL_DIFFUSE, red);
        glutSolidCube(2.0);
        }

/*************** CODE OF INTEREST ******************************/
/*    This re-enables writing into the depth buffer, so that   */
/*    things are back to normal for the beginning of the next  */
/*    frame.                                                   */
/*                                                             */
    glDepthMask(GL_TRUE);                                    /**/

    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 == ' ')
        drawOrder = (drawOrder+1) % 3;
    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();
    }
