/* 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. */

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

/* Uses EXT_polygon_offset extension if available to better
   render the fold outlines. */

#if GL_EXT_polygon_offset
int polygon_offset;
#endif

enum {
  FLAT,                 /* completely flat sheet of paper */
  FLAP1,                /* left flap being folded in */
  FLAP2,                /* right flap being folded int */
  CENTER2,              /* right side folded up at center */
  WING2,                /* right wing folded down */
  CENTER1,              /* left side folded up at center */
  WING1,                /* left wing folded down */
  FOLDED                /* fully folded paper airplane */
} States;

int motion = 1;
int spinning = 1;
int state = FLAT;
int click = 0;
int delay = 0;
int direction;
float flap1_angle = 0;
float flap2_angle = 0;
float center1_angle = 0;
float center2_angle = 0;
float wing1_angle = 0;
float wing2_angle = 0;

/**

These correspond to the polygons for the paper sections:

  +----------+----------+
  |         /|\         |
  |  2     / | \    3   |
  |       /  |  \       |
  +------/   |   \------+
  |     /|   |   |\     |
  | 1  / |   |   | \ 4  |
  |   /  |   |   |  \   |
  |  /   |   |   |   \  |
  | /    | 5 | 6 |    \ |
  |/     |   |   |     \|
  +      |   |   |      +
  |  7   |   |   |  8   |
  |      |   |   |      |
  |      |   |   |      |
  |      |   |   |      |
  |      |   |   |      |
  |      |   |   |      |
  |      |   |   |      |
  +------+---+---+------+

*/

typedef GLfloat Point[2];

Point poly1[] =
{
  {-1, 0},
  {-1 / 3.0, 2 / 3.0},
  {-1, 2 / 3.0}
};

Point poly2[] =
{
  {-1, 1},
  {-1, 2 / 3.0},
  {-1 / 3.0, 2 / 3.0},
  {0, 1}
};

Point poly3[] =
{
  {0, 1},
  {1, 1},
  {1, 2 / 3.0},
  {1 / 3.0, 2 / 3.0}
};

Point poly4[] =
{
  {1 / 3.0, 2 / 3.0},
  {1, 2 / 3.0},
  {1, 0}
};

Point poly5[] =
{
  {-1 / 3.0, 2 / 3.0},
  {0, 1},
  {0, -1.5},
  {-1 / 3.0, -1.5}
};

Point poly6[] =
{
  {0, 1},
  {1 / 3.0, 2 / 3.0},
  {1 / 3.0, -1.5},
  {0, -1.5}
};

Point poly7[] =
{
  {-1, 0},
  {-1 / 3.0, 2 / 3.0},
  {-1 / 3.0, -1.5},
  {-1, -1.5}
};

Point poly8[] =
{
  {1, 0},
  {1 / 3.0, 2 / 3.0},
  {1 / 3.0, -1.5},
  {1, -1.5}
};

void
polydlist(int dlist, int num, Point points[])
{
  int i;

  glNewList(dlist, GL_COMPILE);
  glBegin(GL_POLYGON);
  for (i = 0; i < num; i++) {
    glVertex2fv(&points[i][0]);
  }
  glEnd();
  glEndList();
}

void
idle(void)
{
  if (spinning)
    click++;
  switch (state) {
  case FLAT:
    delay++;
    if (delay >= 80) {
      delay = 0;
      state = FLAP1;
      glutSetWindowTitle("origami (folding)");
      direction = 1;
    }
    break;
  case FLAP1:
    flap1_angle += 2 * direction;
    if (flap1_angle >= 180) {
      state = FLAP2;
    } else if (flap1_angle <= 0) {
      state = FLAT;
    }
    break;
  case FLAP2:
    flap2_angle += 2 * direction;
    if (flap2_angle >= 180) {
      state = CENTER2;
    } else if (flap2_angle <= 0) {
      state = FLAP1;
    }
    break;
  case CENTER2:
    center2_angle += 2 * direction;
    if (center2_angle >= 84) {
      state = WING2;
    } else if (center2_angle <= 0) {
      state = FLAP2;
    }
    break;
  case WING2:
    wing2_angle += 2 * direction;
    if (wing2_angle >= 84) {
      state = CENTER1;
    } else if (wing2_angle <= 0) {
      state = CENTER2;
    }
    break;
  case CENTER1:
    center1_angle += 2 * direction;
    if (center1_angle >= 84) {
      state = WING1;
    } else if (center1_angle <= 0) {
      state = WING2;
    }
    break;
  case WING1:
    wing1_angle += 2 * direction;
    if (wing1_angle >= 84) {
      state = FOLDED;
    } else if (wing1_angle <= 0) {
      state = CENTER1;
    }
    break;
  case FOLDED:
    delay++;
    if (delay >= 80) {
      delay = 0;
      glutSetWindowTitle("origami (unfolding)");
      direction = -1;
      state = WING1;
    }
    break;
  }
  glutPostRedisplay();
}

void
draw_folded_plane(void)
{
  /* *INDENT-OFF* */
  glPushMatrix();
    glRotatef(click, 0, 0, 1);
    glRotatef(click / 5.0, 0, 1, 0);
    glTranslatef(0, .25, 0);
    glPushMatrix();
      glRotatef(center1_angle, 0, 1, 0);
      glPushMatrix();
        glTranslatef(-.5, .5, 0);
        glRotatef(flap1_angle, 1, 1, 0);
        glTranslatef(.5, -.5, 0);
        glCallList(2);
      glPopMatrix();
      glCallList(5);

      glPushMatrix();
        glTranslatef(-1 / 3.0, 0, 0);
        glRotatef(-wing1_angle, 0, 1, 0);
        glTranslatef(1 / 3.0, 0, 0);

        glCallList(7);
        glPushMatrix();
          glTranslatef(-.5, .5, 0);
          glRotatef(flap1_angle, 1, 1, 0);
          glTranslatef(.5, -.5, 0);
          glCallList(1);
        glPopMatrix();
      glPopMatrix();
    glPopMatrix();

    glPushMatrix();
      glRotatef(-center2_angle, 0, 1, 0);
      glPushMatrix();
        glTranslatef(.5, .5, 0);
        glRotatef(-flap2_angle, -1, 1, 0);
        glTranslatef(-.5, -.5, 0);
        glCallList(3);
      glPopMatrix();
      glCallList(6);

      glPushMatrix();
        glTranslatef(1 / 3.0, 0, 0);
        glRotatef(wing2_angle, 0, 1, 0);
        glTranslatef(-1 / 3.0, 0, 0);

        glCallList(8);
        glPushMatrix();
          glTranslatef(.5, .5, 0);
          glRotatef(-flap2_angle, -1, 1, 0);
          glTranslatef(-.5, -.5, 0);
          glCallList(4);
        glPopMatrix();
      glPopMatrix();
    glPopMatrix();
  glPopMatrix();
  /* *INDENT-ON* */

}

void
display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  glColor3ub(67, 205, 128);
#if GL_EXT_polygon_offset
  if (polygon_offset) {
    glPolygonOffsetEXT(0.5, 0.0);
    glEnable(GL_POLYGON_OFFSET_EXT);
  }
#endif
  draw_folded_plane();
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glColor3ub(255, 255, 255);
#if GL_EXT_polygon_offset
  if (polygon_offset) {
    glPolygonOffsetEXT(0.0, 0.0);
    /* XXX a bug in the unpatched IRIX 5.3 OpenGL posts
       GL_INVALID_ENUM when GL_POLYGON_OFFSET_EXT is disabled;
       please ignore it. */
    glDisable(GL_POLYGON_OFFSET_EXT);
  } else {
    glPushMatrix();
    glTranslatef(0, 0, .05);
  }
#else
  glPushMatrix();
  glTranslatef(0, 0, .05);
#endif
  draw_folded_plane();
#if GL_EXT_polygon_offset
  if (!polygon_offset) {
    glPopMatrix();
  }
#else
  glPopMatrix();
#endif
  glutSwapBuffers();
}

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

void
menu(int value)
{
  switch (value) {
  case 1:
    direction = -direction;
    if (direction > 0) {
      glutSetWindowTitle("origami (folding)");
    } else {
      glutSetWindowTitle("origami (unfolding)");
    }
    break;
  case 2:
    motion = 1 - motion;
    if (motion) {
      glutIdleFunc(idle);
    } else {
      glutIdleFunc(NULL);
    }
    break;
  case 3:
    spinning = 1 - spinning;
    break;
  case 666:
    exit(0);
  }
}

int
main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
  glutCreateWindow("origami");
  glutDisplayFunc(display);
  glutVisibilityFunc(visible);
  glClearColor(.488, .617, .75, 1.0);
  glMatrixMode(GL_PROJECTION);
  gluPerspective(40.0, 1.0, 0.1, 10.0);
  glMatrixMode(GL_MODELVIEW);
  gluLookAt(0, 0, 5.5,
    0, 0, 0,
    0, 1, 0);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glLineWidth(2.0);
  polydlist(1, sizeof(poly1) / sizeof(Point), poly1);
  polydlist(2, sizeof(poly2) / sizeof(Point), poly2);
  polydlist(3, sizeof(poly3) / sizeof(Point), poly3);
  polydlist(4, sizeof(poly4) / sizeof(Point), poly4);
  polydlist(5, sizeof(poly5) / sizeof(Point), poly5);
  polydlist(6, sizeof(poly6) / sizeof(Point), poly6);
  polydlist(7, sizeof(poly7) / sizeof(Point), poly7);
  polydlist(8, sizeof(poly8) / sizeof(Point), poly8);
  glutCreateMenu(menu);
  glutAddMenuEntry("Reverse direction", 1);
  glutAddMenuEntry("Toggle motion", 2);
  glutAddMenuEntry("Toggle spinning", 3);
  glutAddMenuEntry("Quit", 666);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
#if GL_EXT_polygon_offset
  polygon_offset = glutExtensionSupported("GL_EXT_polygon_offset");
#endif
  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}