/* Copyright (c) Mark J. Kilgard, 1994.  */

/* This program is freely distributable without licensing fees 
   and is provided without guarantee or warrantee expressed or 
   implied. This program is -not- in the public domain. */

/* zoomdino demonstrates GLUT 3.0's new overlay support.  Both
   rubber-banding the display of a help message use the overlays. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>       /* for cos(), sin(), and sqrt() */
#include <GL/glut.h>

typedef enum {
  RESERVED, BODY_SIDE, BODY_EDGE, BODY_WHOLE, ARM_SIDE, ARM_EDGE, ARM_WHOLE,
  LEG_SIDE, LEG_EDGE, LEG_WHOLE, EYE_SIDE, EYE_EDGE, EYE_WHOLE, DINOSAUR
} displayLists;

GLfloat angle = -150;   /* in degrees */
int moving, begin;
int W = 300, H = 300;
GLdouble bodyWidth = 3.0;
int newModel = 1;
/* *INDENT-OFF* */
GLfloat body[][2] = { {0, 3}, {1, 1}, {5, 1}, {8, 4}, {10, 4}, {11, 5},
  {11, 11.5}, {13, 12}, {13, 13}, {10, 13.5}, {13, 14}, {13, 15}, {11, 16},
  {8, 16}, {7, 15}, {7, 13}, {8, 12}, {7, 11}, {6, 6}, {4, 3}, {3, 2},
  {1, 2} };
GLfloat arm[][2] = { {8, 10}, {9, 9}, {10, 9}, {13, 8}, {14, 9}, {16, 9},
  {15, 9.5}, {16, 10}, {15, 10}, {15.5, 11}, {14.5, 10}, {14, 11}, {14, 10},
  {13, 9}, {11, 11}, {9, 11} };
GLfloat leg[][2] = { {8, 6}, {8, 4}, {9, 3}, {9, 2}, {8, 1}, {8, 0.5}, {9, 0},
  {12, 0}, {10, 1}, {10, 2}, {12, 4}, {11, 6}, {10, 7}, {9, 7} };
GLfloat eye[][2] = { {8.75, 15}, {9, 14.7}, {9.6, 14.7}, {10.1, 15},
  {9.6, 15.25}, {9, 15.25} };
GLfloat lightZeroPosition[] = {10.0, 4.0, 10.0, 1.0};
GLfloat lightZeroColor[] = {0.8, 1.0, 0.8, 1.0}; /* green-tinted */
GLfloat lightOnePosition[] = {-1.0, -2.0, 1.0, 0.0};
GLfloat lightOneColor[] = {0.6, 0.3, 0.2, 1.0}; /* red-tinted */
GLfloat skinColor[] = {0.1, 1.0, 0.1, 1.0}, eyeColor[] = {1.0, 0.2, 0.2, 1.0};
int overlaySupport, red, white, transparent, rubberbanding;
int anchorx, anchory, stretchx, stretchy, pstretchx, pstretchy;
float vx, vy, vx2, vy2, vw, vh;
float wx, wy, wx2, wy2, ww, wh;
int fancy, wasFancy, help, clearHelp;
/* *INDENT-ON* */

void
extrudeSolidFromPolygon(GLfloat data[][2], unsigned int dataSize,
  GLdouble thickness, GLuint side, GLuint edge, GLuint whole)
{
  static GLUtriangulatorObj *tobj = NULL;
  GLdouble vertex[3], dx, dy, len;
  int i;
  int count = dataSize / (int) (2 * sizeof(GLfloat));

  if (tobj == NULL) {
    tobj = gluNewTess();  /* create and initialize a GLU
                             polygontesselation object */
    gluTessCallback(tobj, GLU_BEGIN, glBegin);
    gluTessCallback(tobj, GLU_VERTEX, glVertex2fv);  /* semi-tricky 

                                                      */
    gluTessCallback(tobj, GLU_END, glEnd);
  }
  glNewList(side, GL_COMPILE);
  glShadeModel(GL_SMOOTH);  /* smooth minimizes seeing
                               tessellation */
  gluBeginPolygon(tobj);
  for (i = 0; i < count; i++) {
    vertex[0] = data[i][0];
    vertex[1] = data[i][1];
    vertex[2] = 0;
    gluTessVertex(tobj, vertex, data[i]);
  }
  gluEndPolygon(tobj);
  glEndList();
  glNewList(edge, GL_COMPILE);
  glShadeModel(GL_FLAT);  /* flat shade keeps angular hands
                             from being "smoothed" */
  glBegin(GL_QUAD_STRIP);
  for (i = 0; i <= count; i++) {
    /* mod function handles closing the edge */
    glVertex3f(data[i % count][0], data[i % count][1], 0.0);
    glVertex3f(data[i % count][0], data[i % count][1], thickness);

    /* Calculate a unit normal by dividing by Euclidean
       distance. We could be lazy and use
       glEnable(GL_NORMALIZE) so we could pass in arbitrary
       normals for a very slight performance hit. */

    dx = data[(i + 1) % count][1] - data[i % count][1];
    dy = data[i % count][0] - data[(i + 1) % count][0];
    len = sqrt(dx * dx + dy * dy);
    glNormal3f(dx / len, dy / len, 0.0);
  }
  glEnd();
  glEndList();
  glNewList(whole, GL_COMPILE);
  glFrontFace(GL_CW);
  glCallList(edge);
  glNormal3f(0.0, 0.0, -1.0);  /* constant normal for side */
  glCallList(side);
  glPushMatrix();
  glTranslatef(0.0, 0.0, thickness);
  glFrontFace(GL_CCW);
  glNormal3f(0.0, 0.0, 1.0);  /* opposite normal for other side */
  glCallList(side);
  glPopMatrix();
  glEndList();
}

void
makeDinosaur(void)
{
  extrudeSolidFromPolygon(body, sizeof(body), bodyWidth,
    BODY_SIDE, BODY_EDGE, BODY_WHOLE);
  extrudeSolidFromPolygon(arm, sizeof(arm), bodyWidth / 4,
    ARM_SIDE, ARM_EDGE, ARM_WHOLE);
  extrudeSolidFromPolygon(leg, sizeof(leg), bodyWidth / 2,
    LEG_SIDE, LEG_EDGE, LEG_WHOLE);
  extrudeSolidFromPolygon(eye, sizeof(eye), bodyWidth + 0.2,
    EYE_SIDE, EYE_EDGE, EYE_WHOLE);
  glNewList(DINOSAUR, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, skinColor);
  glCallList(BODY_WHOLE);
  glPushMatrix();
  glTranslatef(0.0, 0.0, bodyWidth);
  glCallList(ARM_WHOLE);
  glCallList(LEG_WHOLE);
  glTranslatef(0.0, 0.0, -bodyWidth - bodyWidth / 4);
  glCallList(ARM_WHOLE);
  glTranslatef(0.0, 0.0, -bodyWidth / 4);
  glCallList(LEG_WHOLE);
  glTranslatef(0.0, 0.0, bodyWidth / 2 - 0.1);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, eyeColor);
  glCallList(EYE_WHOLE);
  glPopMatrix();
  glEndList();
}

void
recalcModelView(void)
{
  glPopMatrix();
  glPushMatrix();
  glRotatef(angle, 0.0, 1.0, 0.0);
  glTranslatef(-8, -8, -bodyWidth / 2);
  newModel = 0;
}

void
redraw(void)
{
  if (newModel)
    recalcModelView();
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glCallList(DINOSAUR);
  glutSwapBuffers();
}

void
output(int x, int y, char *string)
{
  int len, i;

  glRasterPos2f(x, y);
  len = (int) strlen(string);
  for (i = 0; i < len; i++) {
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, string[i]);
  }
}

char *helpMsg[] =
{
  "Welcome to zoomdino!",
  "   Left mouse button rotates",
  "     the dinosaur.",
  "   Middle mouse button zooms",
  "     via overlay rubber-banding.",
  "   Right mouse button shows",
  "     pop-up menu.",
  "   To reset view, use \"Reset",
  "     Projection\".",
  "(This message is in the overlays.)",
  NULL
};

void
redrawOverlay(void)
{
  if (help) {
    int i;

    glClear(GL_COLOR_BUFFER_BIT);
    glIndexi(white);
    for (i = 0; helpMsg[i]; i++) {
      output(15, 24 + i * 18, helpMsg[i]);
    }
    return;
  }
  if (glutLayerGet(GLUT_OVERLAY_DAMAGED) || clearHelp) {
    /* Opps, damage means we need a full clear. */
    glClear(GL_COLOR_BUFFER_BIT);
    clearHelp = 0;
    wasFancy = 0;
  } else {
    /* Goody!  No damage.  Just erase last rubber-band. */
    if (fancy || wasFancy) {
      glLineWidth(3.0);
    }
    glIndexi(transparent);
    glBegin(GL_LINE_LOOP);
    glVertex2i(anchorx, anchory);
    glVertex2i(anchorx, pstretchy);
    glVertex2i(pstretchx, pstretchy);
    glVertex2i(pstretchx, anchory);
    glEnd();
  }
  if (wasFancy) {
    glLineWidth(1.0);
    wasFancy = 0;
  }
  if (fancy)
    glLineWidth(3.0);
  glIndexi(red);
  glBegin(GL_LINE_LOOP);
  glVertex2i(anchorx, anchory);
  glVertex2i(anchorx, stretchy);
  glVertex2i(stretchx, stretchy);
  glVertex2i(stretchx, anchory);
  glEnd();
  if (fancy) {
    glLineWidth(1.0);
    glIndexi(white);
    glBegin(GL_LINE_LOOP);
    glVertex2i(anchorx, anchory);
    glVertex2i(anchorx, stretchy);
    glVertex2i(stretchx, stretchy);
    glVertex2i(stretchx, anchory);
    glEnd();
  }
  glFlush();

  /* Remember last place rubber-banded so the rubber-band can
     be erased next redisplay. */
  pstretchx = stretchx;
  pstretchy = stretchy;
}

void
defaultProjection(void)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  vx = -1.0;
  vw = 2.0;
  vy = -1.0;
  vh = 2.0;
  glFrustum(vx, vx + vw, vy, vy + vh, 1.0, 40);
  glMatrixMode(GL_MODELVIEW);
}

void
mouse(int button, int state, int x, int y)
{
  if (button == GLUT_LEFT_BUTTON) {
    if (state == GLUT_DOWN) {
      glutSetCursor(GLUT_CURSOR_LEFT_RIGHT);
      moving = 1;
      begin = x;
    } else if (state == GLUT_UP) {
      glutSetCursor(GLUT_CURSOR_INHERIT);
      moving = 0;
    }
  }
  if (overlaySupport && button == GLUT_MIDDLE_BUTTON) {
    if (state == GLUT_DOWN) {
      help = 0;
      clearHelp = 1;
      rubberbanding = 1;
      anchorx = x;
      anchory = y;
      stretchx = x;
      stretchy = y;
      glutShowOverlay();
    } else if (state == GLUT_UP) {
      rubberbanding = 0;
      glutHideOverlay();
      glutUseLayer(GLUT_NORMAL);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();

#undef max
#undef min
#define max(a,b)  ((a) > (b) ? (a) : (b))
#define min(a,b)  ((a) < (b) ? (a) : (b))

      wx = min(anchorx, stretchx);
      wy = min(H - anchory, H - stretchy);
      wx2 = max(anchorx, stretchx);
      wy2 = max(H - anchory, H - stretchy);
      ww = wx2 - wx;
      wh = wy2 - wy;
      if (ww == 0 || wh == 0) {
        glutUseLayer(GLUT_NORMAL);
        defaultProjection();
      } else {

        vx2 = wx2 / W * vw + vx;
        vx = wx / W * vw + vx;
        vy2 = wy2 / H * vh + vy;
        vy = wy / H * vh + vy;
        vw = vx2 - vx;
        vh = vy2 - vy;

        glFrustum(vx, vx + vw, vy, vy + vh, 1.0, 40);
      }
      glutPostRedisplay();
      glMatrixMode(GL_MODELVIEW);
    }
  }
}

void
motion(int x, int y)
{
  if (moving) {
    angle = angle + (x - begin);
    begin = x;
    newModel = 1;
    glutPostRedisplay();
  }
  if (rubberbanding) {
    stretchx = x;
    stretchy = y;
    glutPostOverlayRedisplay();
  }
}

void
reshape(int w, int h)
{
  if (overlaySupport) {
    glutUseLayer(GLUT_OVERLAY);
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, w, 0, h);
    glScalef(1, -1, 1);
    glTranslatef(0, -h, 0);
    glMatrixMode(GL_MODELVIEW);
    glutUseLayer(GLUT_NORMAL);
  }
  glViewport(0, 0, w, h);
  W = w;
  H = h;
}

GLboolean lightZeroSwitch = GL_TRUE, lightOneSwitch = GL_TRUE;

void
controlLights(int value)
{
  glutUseLayer(GLUT_NORMAL);
  switch (value) {
  case 1:
    lightZeroSwitch = !lightZeroSwitch;
    if (lightZeroSwitch) {
      glEnable(GL_LIGHT0);
    } else {
      glDisable(GL_LIGHT0);
    }
    break;
  case 2:
    lightOneSwitch = !lightOneSwitch;
    if (lightOneSwitch) {
      glEnable(GL_LIGHT1);
    } else {
      glDisable(GL_LIGHT1);
    }
    break;
  case 3:
    defaultProjection();
    break;
  case 4:
    fancy = 1;
    break;
  case 5:
    fancy = 0;
    wasFancy = 1;
    break;
  case 6:
    if (!rubberbanding)
      help = 1;
    glutShowOverlay();
    glutPostOverlayRedisplay();
    break;
  }
  glutPostRedisplay();
}

int
main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutCreateWindow("zoomdino");
  glutDisplayFunc(redraw);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutCreateMenu(controlLights);
  glutAddMenuEntry("Toggle right light", 1);
  glutAddMenuEntry("Toggle left light", 2);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
  makeDinosaur();
  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  defaultProjection();
  gluLookAt(0.0, 0.0, 30.0,  /* eye is at (0,0,30) */
    0.0, 0.0, 0.0,      /* center is at (0,0,0) */
    0.0, 1.0, 0.);      /* up is in postivie Y direction */
  glPushMatrix();       /* dummy push so we can pop on model
                           recalc */
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
  glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
  glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1);
  glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05);
  glLightfv(GL_LIGHT1, GL_POSITION, lightOnePosition);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, lightOneColor);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_INDEX);
  overlaySupport = glutLayerGet(GLUT_OVERLAY_POSSIBLE);
  if (overlaySupport) {
    glutEstablishOverlay();
    glutHideOverlay();
    transparent = glutLayerGet(GLUT_TRANSPARENT_INDEX);
    glClearIndex(transparent);
    red = (transparent + 1) % glutGet(GLUT_WINDOW_COLORMAP_SIZE);
    white = (transparent + 2) % glutGet(GLUT_WINDOW_COLORMAP_SIZE);
    glutSetColor(red, 1.0, 0.0, 0.0);  /* Red. */
    glutSetColor(white, 1.0, 1.0, 1.0);  /* White. */
    glutOverlayDisplayFunc(redrawOverlay);
    glutReshapeFunc(reshape);
    glutSetWindowTitle("zoomdino with rubber-banding");
    glutAddMenuEntry("------------------", 0);
    glutAddMenuEntry("Reset projection", 3);
    glutAddMenuEntry("------------------", 0);
    glutAddMenuEntry("Fancy rubber-banding", 4);
    glutAddMenuEntry("Simple rubber-banding", 5);
    glutAddMenuEntry("------------------", 0);
    glutAddMenuEntry("Show help", 6);
  } else {
    printf("Sorry, no whizzy zoomdino overlay usage!\n");
  }
  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}