
/* dissolve.c - by Tom McReynolds, SGI */

/* An Example of dissolve, using stencil */

/* Drag with left mouse button to dissolve to the 3D background,
   Drag with middle mouse button to dissolve to checkerboard, and
   use right button for menu to clear stencil. */

#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdio.h>

#ifdef _WIN32
#define random rand
#endif

int winWidth = 512;
int winHeight = 512;

/* Create a single component texture map */
GLfloat *
make_texture(int maxs, int maxt)
{
  int s, t;
  static GLfloat *texture;

  texture = (GLfloat *) malloc(maxs * maxt * sizeof(GLfloat));
  for (t = 0; t < maxt; t++) {
    for (s = 0; s < maxs; s++) {
      texture[s + maxs * t] = ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1);
    }
  }
  return texture;
}

enum {
  SPHERE = 1, CONE
};

GLfloat angle = 0.f;    /* angle of rotating object in  layer 0 */

enum {
  X, Y
};
GLboolean eraser = GL_FALSE;
GLint layer = 1;
GLint eraserpos[2] =
{512 / 8, 512 / 12};

/* draw eraser and erase what's underneath */

GLubyte *eraserpix = 0;
int erasersize = 0;
void
makeEraser(void)
{
  int i, skip;

  erasersize = 4 * winWidth / 4 * winHeight / 6;
  eraserpix = (GLubyte *) realloc(eraserpix, erasersize * sizeof(GLubyte));

  /* make it white */
  (void) memset(eraserpix, 255, erasersize * sizeof(GLubyte));

  skip = (int) (random() % 8);
  for (i = 0; i < erasersize; i++) {
    if (!skip) {
      eraserpix[i] = 0;
      eraserpix[i + 1] = 0;
      eraserpix[i + 2] = 0;
      eraserpix[i + 3] = 0;
      skip = (int) (random() % 8);
    } else
      skip--;
  }

}

/* ARGSUSED2 */
/* left button, first layer, middle button, second layer */
void
mouse(int button, int state, int x, int y)
{
  if (state == GLUT_DOWN) {
    eraser = GL_TRUE;
    if (button == GLUT_LEFT_BUTTON)
      layer = 1;
    else                /* GLUT_MIDDLE: GLUT_RIGHT is for menu */
      layer = 0;
  } else {              /* GLUT_UP */
    eraser = GL_FALSE;
  }
  glutPostRedisplay();
}

enum {
  CLEAR
};                      /* menu choices */
GLboolean clearstencil = GL_TRUE;

void
menu(int choice)
{
  switch (choice) {
  case CLEAR:
    clearstencil = GL_TRUE;
    break;
  }
  glutPostRedisplay();
}

/* used to get current width and height of viewport */
void
reshape(int wid, int ht)
{
  glViewport(0, 0, wid, ht);
  winWidth = wid;
  winHeight = ht;
  clearstencil = GL_TRUE;
  makeEraser();
  glutPostRedisplay();
}

void
draweraser(void)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0, winWidth, 0, winHeight);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* replace with this layer */
  glStencilFunc(GL_ALWAYS, layer, 0);
  glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);

  glEnable(GL_ALPHA_TEST);
  glAlphaFunc(GL_NOTEQUAL, 0);
  glRasterPos2i(eraserpos[X], eraserpos[Y]);
  glBitmap(0, 0, 0.f, 0.f, -winWidth / 8.f, -winHeight / 12.f, 0);
  glDrawPixels(winWidth / 4, winHeight / 6, GL_RGBA, GL_UNSIGNED_BYTE, eraserpix);
  glDisable(GL_ALPHA_TEST);
}

void
drawlayer2(void)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0, winWidth, 0, winHeight);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_TEXTURE_2D);

  glBegin(GL_QUADS);
  glTexCoord2i(0, 0);
  glVertex2i(0, 0);
  glTexCoord2i(1, 0);
  glVertex2i(winWidth, 0);
  glTexCoord2i(1, 1);
  glVertex2i(winWidth, winHeight);
  glTexCoord2i(0, 1);
  glVertex2i(0, winHeight);
  glEnd();

  glDisable(GL_TEXTURE_2D);

  if (glGetError())     /* to catch programming errors; should never happen */
    printf("Oops! I screwed up my OpenGL calls somewhere\n");
}

void
drawlayer1(void)
{
  /* material properties for objects in scene */
  static GLfloat wall_mat[] =
  {1.f, 1.f, 1.f, 1.f};
  static GLfloat lightpos[] =
  {50.f, 50.f, -320.f, 1.f};

  /* draw a perspective scene */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-100., 100., -100., 100., 320., 640.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* turn on features */
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  /* place light 0 in the right place */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);

  /* remove back faces to speed things up */
  glCullFace(GL_BACK);

  /* Note: wall verticies are ordered so they are all front facing this lets
     me do back face culling to speed things up.  */

  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, wall_mat);

  /* floor */
  /* make the floor textured */
  glEnable(GL_TEXTURE_2D);

  /* Since we want to turn texturing on for floor only, we have to make floor 
     a separate glBegin()/glEnd() sequence. You can't turn texturing on and
     off between begin and end calls */
  glBegin(GL_QUADS);
  glNormal3f(0.f, 1.f, 0.f);
  glTexCoord2i(0, 0);
  glVertex3f(-100.f, -100.f, -320.f);
  glTexCoord2i(1, 0);
  glVertex3f(100.f, -100.f, -320.f);
  glTexCoord2i(1, 1);
  glVertex3f(100.f, -100.f, -520.f);
  glTexCoord2i(0, 1);
  glVertex3f(-100.f, -100.f, -520.f);
  glEnd();

  glDisable(GL_TEXTURE_2D);

  /* walls */

  glBegin(GL_QUADS);
  /* left wall */
  glNormal3f(1.f, 0.f, 0.f);
  glVertex3f(-100.f, -100.f, -320.f);
  glVertex3f(-100.f, -100.f, -520.f);
  glVertex3f(-100.f, 100.f, -520.f);
  glVertex3f(-100.f, 100.f, -320.f);

  /* right wall */
  glNormal3f(-1.f, 0.f, 0.f);
  glVertex3f(100.f, -100.f, -320.f);
  glVertex3f(100.f, 100.f, -320.f);
  glVertex3f(100.f, 100.f, -520.f);
  glVertex3f(100.f, -100.f, -520.f);

  /* ceiling */
  glNormal3f(0.f, -1.f, 0.f);
  glVertex3f(-100.f, 100.f, -320.f);
  glVertex3f(-100.f, 100.f, -520.f);
  glVertex3f(100.f, 100.f, -520.f);
  glVertex3f(100.f, 100.f, -320.f);

  /* back wall */
  glNormal3f(0.f, 0.f, 1.f);
  glVertex3f(-100.f, -100.f, -520.f);
  glVertex3f(100.f, -100.f, -520.f);
  glVertex3f(100.f, 100.f, -520.f);
  glVertex3f(-100.f, 100.f, -520.f);
  glEnd();

  glPushMatrix();
  glTranslatef(-80.f, -80.f, -420.f);
  glCallList(SPHERE);
  glPopMatrix();

  glPushMatrix();
  glTranslatef(-20.f, -100.f, -500.f);
  glCallList(CONE);
  glPopMatrix();

  if (glGetError())     /* to catch programming errors; should never happen */
    printf("Oops! I screwed up my OpenGL calls somewhere\n");
}

void
drawlayer0(void)
{
  static GLfloat lightpos[] =
  {50.f, 50.f, 0.f, 1.f};
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-50.f, 50.f, -50.f, 50.f, 0.f, 100.f);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.f, 0.f, -50.f);
  glRotatef(angle, 0.f, 1.f, 0.f);
  glRotatef(90.f, 0.f, 0.f, 1.f);
  glTranslatef(0.f, -25.f, 0.f);

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
  glCullFace(GL_BACK);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  glCallList(CONE);

}

void
redraw(void)
{
  if (glutLayerGet(GLUT_NORMAL_DAMAGED) ||
    clearstencil == GL_TRUE) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    clearstencil = GL_FALSE;
  } else
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

  glStencilFunc(GL_EQUAL, 2, ~0);
  drawlayer2();

  glStencilFunc(GL_EQUAL, 1, ~0);
  drawlayer1();

  glStencilFunc(GL_EQUAL, 0, ~0);
  drawlayer0();

  if (eraser)
    draweraser();

  glutSwapBuffers();
}

void
idle(void)
{
  angle += 1.f;
  glutPostRedisplay();
}

void
passive(int x, int y)
{

  eraserpos[X] = x;
  eraserpos[Y] = winHeight - y;
}

void
motion(int x, int y)
{

  eraserpos[X] = x;
  eraserpos[Y] = winHeight - y;

  glutPostRedisplay();
}

/* ARGSUSED1 */
void 
key(unsigned char key, int x, int y)
{
  switch (key) {
  case '\033':
    exit(0);
  }
}

void 
visible(int vis)
{
  if (vis == GLUT_VISIBLE)
    glutIdleFunc(idle);
  else
    glutIdleFunc(NULL);
}

const int TEXDIM = 256;
GLfloat *tex = 0;

int
main(int argc, char *argv[])
{
  static GLfloat sphere_mat[] =
  {1.f, .5f, 0.f, 1.f};
  static GLfloat cone_mat[] =
  {0.f, .5f, 1.f, 1.f};
  GLUquadricObj *sphere, *cone, *base;

  glutInit(&argc, argv);
  glutInitWindowSize(winWidth, winHeight);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_STENCIL | GLUT_DEPTH);
  (void) glutCreateWindow("dissolve");
  glutDisplayFunc(redraw);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutPassiveMotionFunc(passive);
  glutKeyboardFunc(key);
  glutVisibilityFunc(visible);
  glutReshapeFunc(reshape);

  glutCreateMenu(menu);
  glutAddMenuEntry("Clear Stencil", CLEAR);
  glutAttachMenu(GLUT_RIGHT_BUTTON);

  glNewList(SPHERE, GL_COMPILE);
  /* make display lists for sphere and cone; for efficiency */
  sphere = gluNewQuadric();
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, sphere_mat);
  gluSphere(sphere, 20.f, 20, 20);
  gluDeleteQuadric(sphere);
  glEndList();

  glNewList(CONE, GL_COMPILE);
  cone = gluNewQuadric();
  base = gluNewQuadric();
  glRotatef(-90.f, 1.f, 0.f, 0.f);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cone_mat);
  gluQuadricOrientation(base, GLU_INSIDE);
  gluDisk(base, 0., 20., 20, 1);
  gluCylinder(cone, 20., 0., 60., 20, 20);
  gluDeleteQuadric(cone);
  gluDeleteQuadric(base);
  glEndList();

  makeEraser();

  /* load pattern for current 2d texture */
  tex = make_texture(TEXDIM, TEXDIM);
  glTexImage2D(GL_TEXTURE_2D, 0, 1, TEXDIM, TEXDIM, 0, GL_RED, GL_FLOAT, tex);
  free(tex);

  glClearStencil(2);
  glEnable(GL_STENCIL_TEST);  /* used all the time */

  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);

  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}
