/* pony.c */

/* 
 * By Brian Paul,  written July 31, 1997  for Mark.
 */

#include <GL/glut.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "readtex.h"

/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

/************************ Pony-specific code *********************************/

static float BodyDepth = 0.216;

static GLfloat BodyVerts[][2] =
{
  {-0.993334, 0.344444},
  {-0.72, 0.462964},
  {-0.58, -0.411113},
  {-0.406667, -0.692593},
  {0.733334, -0.633334},
  {0.846666, -0.225926},
  {0.873335, -0.55926},
  {0.879998, -0.988888},
  {0.933332, -0.974074},
  {0.953334, -0.537037},
  {0.906667, -0.0777776},
  {0.806666, 0.0333334},
  {-0.26, 0.0111111},
  {-0.406667, 0.27037},
  {-0.54, 0.781481},
  {-0.673333, 1.00371},
  {-0.653332, 0.803704},
  {-1.05333, 0.44815}
};

static float LegDepth = 0.144;

static float FrontLegPos[3] =
{-0.36, -0.324, 0.108};
static GLfloat FrontLegVerts[][2] =
{
  {-0.23, -0.113481},
  {-0.123333, -0.528296},
  {-0.0926752, -0.728103},
  {-0.0766667, -1.232},
  {0.0233333, -1.232},
  {0.0433332, -0.743111},
  {0.0366667, -0.424593},
  {0.0699998, -0.157926},
  {0.116667, 0.049482},
  {-0.0166667, 0.197629},
  {-0.196667, 0.13837}
};

static float BackLegPos[3] =
{0.684, -0.324, 0.108};
static GLfloat BackLegVerts[][2] =
{
  {-0.24, -0.195556},
  {-0.0933332, -0.41037},
  {-0.04, -0.684445},
  {-0.113333, -1.26222},
  {0, -1.26222},
  {0.1, -0.677037},
  {0.213333, -0.121482},
  {0.153333, 0.108148},
  {-0.0533333, 0.211853},
  {-0.26, 0.063702}
};

static float ManeDepth = 0.288;
static GLfloat ManeVerts[][2] =
{
  {-0.512667, 0.578519},
  {-0.419333, 0.267407},
  {-0.299333, -0.00666719},
  {-0.239333, -0.0140724},
  {-0.226, 0.0896296},
  {-0.319333, 0.422963},
  {-0.532667, 0.741481}
};

static float EyePos[3] =
{-0.702, 0.648, 0.1116};
static float EyeSize = 0.025;

/* Display lists */
static GLuint Body = 0, FrontLeg = 0, BackLeg = 0, Mane = 0;

/* Generate an extruded, capped part from a 2-D polyline. */
static void 
ExtrudePart(int n, GLfloat v[][2], float depth)
{
  static GLUtriangulatorObj *tobj = NULL;
  int i;
  float z0 = 0.5 * depth;
  float z1 = -0.5 * depth;
  GLdouble vertex[3];

  if (tobj == NULL) {
    tobj = gluNewTess();  /* create and initialize a GLU polygon * *
                             tesselation object */
    gluTessCallback(tobj, GLU_BEGIN, glBegin);
    gluTessCallback(tobj, GLU_VERTEX, glVertex2fv);  /* semi-tricky */
    gluTessCallback(tobj, GLU_END, glEnd);
  }
  /* +Z face */
  glPushMatrix();
  glTranslatef(0.0, 0.0, z0);
  glNormal3f(0.0, 0.0, 1.0);
  gluBeginPolygon(tobj);
  for (i = 0; i < n; i++) {
    vertex[0] = v[i][0];
    vertex[1] = v[i][1];
    vertex[2] = 0.0;
    gluTessVertex(tobj, vertex, v[i]);
  }
  gluEndPolygon(tobj);
  glPopMatrix();

  /* -Z face */
  glFrontFace(GL_CW);
  glPushMatrix();
  glTranslatef(0.0, 0.0, z1);
  glNormal3f(0.0, 0.0, -1.0);
  gluBeginPolygon(tobj);
  for (i = 0; i < n; i++) {
    vertex[0] = v[i][0];
    vertex[1] = v[i][1];
    vertex[2] = z1;
    gluTessVertex(tobj, vertex, v[i]);
  }
  gluEndPolygon(tobj);
  glPopMatrix();

  glFrontFace(GL_CCW);
  /* edge polygons */
  glBegin(GL_TRIANGLE_STRIP);
  for (i = 0; i <= n; i++) {
    float x = v[i % n][0];
    float y = v[i % n][1];
    float dx = v[(i + 1) % n][0] - x;
    float dy = v[(i + 1) % n][1] - y;
    glVertex3f(x, y, z0);
    glVertex3f(x, y, z1);
    glNormal3f(dy, -dx, 0.0);
  }
  glEnd();

}

/* 
 * Build the four display lists which make up the pony.
 */
static void 
MakePony(void)
{
  static GLfloat blue[4] =
  {0.1, 0.1, 1.0, 1.0};
  static GLfloat black[4] =
  {0.0, 0.0, 0.0, 1.0};
  static GLfloat pink[4] =
  {1.0, 0.5, 0.5, 1.0};

  Body = glGenLists(1);
  glNewList(Body, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
  ExtrudePart(sizeof(BodyVerts) / sizeof(GLfloat) / 2, BodyVerts, BodyDepth);

  /* eyes */
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, black);
  glNormal3f(0.0, 0.0, 1.0);
  glBegin(GL_POLYGON);
  glVertex3f(EyePos[0] - EyeSize, EyePos[1] - EyeSize, EyePos[2]);
  glVertex3f(EyePos[0] + EyeSize, EyePos[1] - EyeSize, EyePos[2]);
  glVertex3f(EyePos[0] + EyeSize, EyePos[1] + EyeSize, EyePos[2]);
  glVertex3f(EyePos[0] - EyeSize, EyePos[1] + EyeSize, EyePos[2]);
  glEnd();
  glNormal3f(0.0, 0.0, -1.0);
  glBegin(GL_POLYGON);
  glVertex3f(EyePos[0] - EyeSize, EyePos[1] + EyeSize, -EyePos[2]);
  glVertex3f(EyePos[0] + EyeSize, EyePos[1] + EyeSize, -EyePos[2]);
  glVertex3f(EyePos[0] + EyeSize, EyePos[1] - EyeSize, -EyePos[2]);
  glVertex3f(EyePos[0] - EyeSize, EyePos[1] - EyeSize, -EyePos[2]);
  glEnd();
  glEndList();

  Mane = glGenLists(1);
  glNewList(Mane, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, pink);
  ExtrudePart(sizeof(ManeVerts) / sizeof(GLfloat) / 2, ManeVerts, ManeDepth);
  glEndList();

  FrontLeg = glGenLists(1);
  glNewList(FrontLeg, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
  ExtrudePart(sizeof(FrontLegVerts) / sizeof(GLfloat) / 2,
    FrontLegVerts, LegDepth);
  glEndList();

  BackLeg = glGenLists(1);
  glNewList(BackLeg, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
  ExtrudePart(sizeof(BackLegVerts) / sizeof(GLfloat) / 2,
    BackLegVerts, LegDepth);
  glEndList();
}

/* 
 * Draw the pony.  legAngle should be in [-15,15] or so.
 * The pony display lists will be constructed the first time this is called.
 */
static void 
DrawPony(float legAngle)
{
  if (!Body) {
    MakePony();
  }
  assert(Body);

  /* BODY */
  glCallList(Body);

  /* MANE */
  glCallList(Mane);

  /* FRONT +Z LEG */
  glPushMatrix();
  glTranslatef(FrontLegPos[0], FrontLegPos[1], FrontLegPos[2]);
  glRotatef(legAngle, 0.0, 0.0, 1.0);
  glCallList(FrontLeg);
  glPopMatrix();

  /* FRONT -Z LEG */
  glPushMatrix();
  glTranslatef(FrontLegPos[0], FrontLegPos[1], -FrontLegPos[2]);
  glRotatef(-legAngle, 0.0, 0.0, 1.0);
  glCallList(FrontLeg);
  glPopMatrix();

  /* BACK +Z LEG */
  glPushMatrix();
  glTranslatef(BackLegPos[0], BackLegPos[1], BackLegPos[2]);
  glRotatef(-legAngle, 0.0, 0.0, 1.0);
  glCallList(BackLeg);
  glPopMatrix();

  /* BACK -Z LEG */
  glPushMatrix();
  glTranslatef(BackLegPos[0], BackLegPos[1], -BackLegPos[2]);
  glRotatef(legAngle, 0.0, 0.0, 1.0);
  glCallList(BackLeg);
  glPopMatrix();
}

/************************* end of pony code **********************************/

static float Speed = 2.0;

static float LegAngleStep = 0.75;
static float LegMaxAngle = 15.0;
static float LegAngle = 0.0, LegDeltaAngle = 0.5;

static float WalkAngle = -90.0, DeltaWalkAngle = 0.225;
static float WalkRadius = 4.0;

static float Xrot = 0, Yrot = 30.0;

static GLboolean AnimFlag = GL_TRUE;

static void 
Idle(void)
{
    /* update animation vars */
    LegAngle += LegDeltaAngle * Speed;
    if (LegAngle > LegMaxAngle) {
      LegDeltaAngle = -LegAngleStep;
    } else if (LegAngle < -LegMaxAngle) {
      LegDeltaAngle = LegAngleStep;
    }
    WalkAngle += DeltaWalkAngle * Speed;

    glutPostRedisplay();
}

static void 
DrawGround(void)
{
  static GLuint ground = 0;

  if (ground == 0) {
    const int rows = 20, columns = 20;
    float sizeA = 1.25, sizeB = 0.2;
    float x, z;
    int i, j;
    GLfloat mat[2][4] =
    {
      {0.0, 0.6, 0.0, 1.0},
      {0.1, 0.8, 0.1, 1.0}
    };

    ground = glGenLists(1);
    glNewList(ground, GL_COMPILE);

    glNormal3f(0.0, 1.0, 0.0);

    x = -(columns * (sizeA + sizeB)) / 4;
    for (i = 0; i <= rows; i++) {
      float size = (i & 1) ? sizeA : sizeB;
      z = -(rows * (sizeA + sizeB)) / 4;
      glBegin(GL_QUAD_STRIP);
      for (j = 0; j <= columns; j++) {
        /* glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat[(i+j)%2]); */
        glColor4fv(mat[(i + j) % 2]);
        glVertex3f(x + size, 0.0, z);
        glVertex3f(x, 0.0, z);
        if (j & 1)
          z += sizeA;
        else
          z += sizeB;
      }
      glEnd();
      x += size;
    }

    glEndList();
  }
  glCallList(ground);
}

static void 
DrawLogo(void)
{
  glEnable(GL_TEXTURE_2D);
  glShadeModel(GL_SMOOTH);
  glBegin(GL_POLYGON);
  glColor3f(1, 0, 0);
  glTexCoord2f(0, 0);
  glVertex2f(-1.0, -0.5);
  glColor3f(0, 1, 0);
  glTexCoord2f(1, 0);
  glVertex2f(1.0, -0.5);
  glColor3f(0, 0, 1);
  glTexCoord2f(1, 1);
  glVertex2f(1.0, 0.5);
  glColor3f(1, 1, 0);
  glTexCoord2f(0, 1);
  glVertex2f(-1.0, 0.5);
  glEnd();
  glDisable(GL_TEXTURE_2D);
  glShadeModel(GL_FLAT);
}

static void 
Display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /* viewing */
  glPushMatrix();
  glRotatef(Xrot, 1.0, 0.0, 0.0);
  glRotatef(Yrot, 0.0, 1.0, 0.0);

  /* ground */
  glDisable(GL_LIGHTING);
  glPushMatrix();
  glTranslatef(0.0, -1.6, 0.0);
  DrawGround();
  glPopMatrix();

  /* logo */
  glPushMatrix();
  glScalef(2.5, 2.5, 2.5);
  DrawLogo();
  glPopMatrix();

  /* pony */
  {
    float xPos, zPos;
    xPos = WalkRadius * cos(WalkAngle * M_PI / 180.0);
    zPos = WalkRadius * sin(WalkAngle * M_PI / 180.0);
    glEnable(GL_LIGHTING);
    glPushMatrix();
    glTranslatef(xPos, 0.0, zPos);
    glRotatef(-WalkAngle + 90.0, 0.0, 1.0, 0.0);
    DrawPony(LegAngle);
    glPopMatrix();
  }

  glPopMatrix();
  glutSwapBuffers();
}

static void 
Reshape(int width, int height)
{
  float ar;
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  ar = (float) width / (float) height;
  glFrustum(-ar, ar, -1.0, 1.0, 2.0, 100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -23.0 / 2.5);
}

/* ARGSUSED1 */
static void 
Key(unsigned char key, int x, int y)
{
  switch (key) {
  case 27:
    exit(0);
    break;
  case ' ':
    AnimFlag = !AnimFlag;
    if (AnimFlag) {
      glutIdleFunc(Idle);
    } else {
      glutIdleFunc(NULL);
    }
    break;
  }
  glutPostRedisplay();
}

/* ARGSUSED1 */
static void 
SpecialKey(int key, int x, int y)
{
  float step = 2.0;
  switch (key) {
  case GLUT_KEY_UP:
    Xrot += step;
    break;
  case GLUT_KEY_DOWN:
    Xrot -= step;
    break;
  case GLUT_KEY_LEFT:
    Yrot -= step;
    break;
  case GLUT_KEY_RIGHT:
    Yrot += step;
    break;
  }
  glutPostRedisplay();
}

static void 
Init(void)
{
  GLfloat lightPos[4] =
  {1.0, 10.0, 10.0, 0.0};
  glClearColor(0.5, 0.8, 0.99, 1.0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
  glShadeModel(GL_FLAT);

  LoadRGBMipmaps("logo.bw", 1);
}

static void
vis(int visible)
{
  if (visible == GLUT_VISIBLE) {
    if (AnimFlag)
      glutIdleFunc(Idle);
  } else {
    if (AnimFlag)
      glutIdleFunc(NULL);
  }
}

int 
main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitWindowSize(640, 480);

  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);

  glutCreateWindow("Blue Pony");

  Init();

  glutReshapeFunc(Reshape);
  glutKeyboardFunc(Key);
  glutSpecialFunc(SpecialKey);
  glutDisplayFunc(Display);
  glutVisibilityFunc(vis);

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