/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>       /* for cos(), sin(), and sqrt() */
#include <GL/glut.h>

extern unsigned char mjk_image[];
extern int mjk_depth;
extern int mjk_height;
extern int mjk_width;

float tick1 = 0;
float tick2 = 0;
float angle;
float size;
int set_timeout = 0;
int visible = 0;
int spinning = 1;
int scaling = 1;
int interval = 100;
#define CUBE 1
#define SQUARES 2
#define DRUM 3
int mode = SQUARES;

void
animate(int value)
{
  if (visible) {
    if (spinning || scaling) {
      if (value) {
        if (spinning) {
          tick1 += 4 * (interval / 100.0);
          angle = ((int) tick1) % 360;
        }
        if (scaling) {
          tick2 += 2 * (interval / 100.0);
          size = .7 - .5 * sin(tick2 / 20.0);
        }
      }
      glutPostRedisplay();
      set_timeout = 1;
    }
  }
}

#define TIMEDELTA(dest, src1, src2) { \
        if(((dest).tv_usec = (src1).tv_usec - (src2).tv_usec) < 0) {\
              (dest).tv_usec += 1000000;\
              (dest).tv_sec = (src1).tv_sec - (src2).tv_sec - 1;\
        } else  (dest).tv_sec = (src1).tv_sec - (src2).tv_sec;  }

void
redraw(void)
{
  int begin, end, elapsed;
  int i, j;
  float amplitude;

  if (set_timeout) {
    begin = glutGet(GLUT_ELAPSED_TIME);
  }
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();

  if (mode != DRUM) {
    glScalef(size, size, size);
  }
  switch (mode) {
  case SQUARES:

#define COLS 6
#define TILE_TEX_W (1.0/COLS)
#define ROWS 6
#define TILE_TEX_H (1.0/ROWS)

    glTranslatef(-COLS / 2.0 + .5, -ROWS / 2.0 + .5, 0);
    for (i = 0; i < COLS; i++) {
      for (j = 0; j < ROWS; j++) {

        glPushMatrix();
        glTranslatef(i, j, 0);
        glRotatef(angle, 0, 1, 1);
        glBegin(GL_QUADS);
        glTexCoord2f(i * TILE_TEX_W, j * TILE_TEX_H);
        glVertex2f(-.5, -.5);
        glTexCoord2f((i + 1) * TILE_TEX_W, j * TILE_TEX_H);
        glVertex2f(.5, -.5);
        glTexCoord2f((i + 1) * TILE_TEX_W, (j + 1) * TILE_TEX_H);
        glVertex2f(.5, .5);
        glTexCoord2f(i * TILE_TEX_W, (j + 1) * TILE_TEX_H);
        glVertex2f(-.5, .5);
        glEnd();
        glPopMatrix();

      }
    }
    break;
  case DRUM:

#undef COLS
#undef TILE_TEX_W
#undef ROWS
#undef TILE_TEX_H
#define COLS 12
#define TILE_TEX_W (1.0/COLS)
#define ROWS 12
#define TILE_TEX_H (1.0/ROWS)

    glRotatef(angle, 0, 0, 1);
    glTranslatef(-COLS / 2.0 + .5, -ROWS / 2.0 + .5, 0);
    amplitude = 0.4 * sin(tick2 / 6.0);
    for (i = 0; i < COLS; i++) {
      for (j = 0; j < ROWS; j++) {

#define Z(x,y)	(((COLS-(x))*(x) + (ROWS-(y))*(y)) * amplitude) - 28.0

        glPushMatrix();
        glTranslatef(i, j, 0);
        glBegin(GL_QUADS);
        glTexCoord2f(i * TILE_TEX_W, j * TILE_TEX_H);
        glVertex3f(-.5, -.5, Z(i, j));
        glTexCoord2f((i + 1) * TILE_TEX_W, j * TILE_TEX_H);
        glVertex3f(.5, -.5, Z(i + 1, j));
        glTexCoord2f((i + 1) * TILE_TEX_W, (j + 1) * TILE_TEX_H);
        glVertex3f(.5, .5, Z(i + 1, j + 1));
        glTexCoord2f(i * TILE_TEX_W, (j + 1) * TILE_TEX_H);
        glVertex3f(-.5, .5, Z(i, j + 1));
        glEnd();
        glPopMatrix();

      }
    }
    break;
  case CUBE:
    glRotatef(angle, 0, 1, 0);
    glBegin(GL_QUADS);

    /* front */
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0, -1.0, 1.0);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0, -1.0, 1.0);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0, 1.0, 1.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0, 1.0, 1.0);

    /* back */
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0, 1.0, -1.0);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0, 1.0, -1.0);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0, -1.0, -1.0);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0, -1.0, -1.0);

    /* left */
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0, -1.0, -1.0);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(-1.0, -1.0, 1.0);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(-1.0, 1.0, 1.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0, 1.0, -1.0);

    /* right */
    glTexCoord2f(0.0, 1.0);
    glVertex3f(1.0, 1.0, -1.0);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0, 1.0, 1.0);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0, -1.0, 1.0);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(1.0, -1.0, -1.0);

    glEnd();
  }

  glPopMatrix();
  glutSwapBuffers();
  if (set_timeout) {
    set_timeout = 0;
    end = glutGet(GLUT_ELAPSED_TIME);
    elapsed = end - begin;
    if (elapsed > interval) {
      glutTimerFunc(0, animate, 1);
    } else {
      glutTimerFunc(interval - elapsed, animate, 1);
    }
  }
}

int width;
int height;
int depth;
unsigned char *bits;

void
visibility(int state)
{
  if (state == GLUT_VISIBLE) {
    visible = 1;
    animate(0);
  } else {
    visible = 0;
  }
}

void
minify_select(int value)
{
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, value);
  gluBuild2DMipmaps(GL_TEXTURE_2D, depth, width, height,
    GL_RGB, GL_UNSIGNED_BYTE, bits);
  glutPostRedisplay();
}

void
rate_select(int value)
{
  interval = value;
}

void
menu_select(int value)
{
  switch (value) {
  case 1:
    spinning = !spinning;
    if (spinning)
      animate(0);
    break;
  case 2:
    scaling = !scaling;
    if (scaling)
      animate(0);
    break;
  case 3:
    mode++;
    if (mode > DRUM)
      mode = CUBE;
    switch (mode) {
    case CUBE:
      glEnable(GL_CULL_FACE);
      glDisable(GL_DEPTH_TEST);
      break;
    case SQUARES:
      glDisable(GL_CULL_FACE);
      glDisable(GL_DEPTH_TEST);
      break;
    case DRUM:
      glEnable(GL_DEPTH_TEST);
      glDisable(GL_CULL_FACE);
      break;
    }
    glutPostRedisplay();
    break;
  case 666:
    exit(0);
  }
}

int
main(int argc, char **argv)
{
  int minify_menu, rate_menu;

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutCreateWindow("mjkwarp");
  glutDisplayFunc(redraw);
  glMatrixMode(GL_PROJECTION);
  gluPerspective( /* field of view in degree */ 40.0,
  /* aspect ratio */ 1.0,
    /* Z near */ 1.0, /* Z far */ 70.0);
  glMatrixMode(GL_MODELVIEW);
  gluLookAt(0.0, 0.0, 5.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 positive Y direction */
  depth = mjk_depth;
  width = mjk_width;
  height = mjk_height;
  bits = mjk_image;
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  gluBuild2DMipmaps(GL_TEXTURE_2D, depth, width, height,
    GL_RGB, GL_UNSIGNED_BYTE, bits);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
  glEnable(GL_TEXTURE_2D);
  glutVisibilityFunc(visibility);
  minify_menu = glutCreateMenu(minify_select);
  glutAddMenuEntry("Nearest", GL_NEAREST);
  glutAddMenuEntry("Linear", GL_LINEAR);
  glutAddMenuEntry("Nearest mipmap nearest", GL_NEAREST_MIPMAP_NEAREST);
  glutAddMenuEntry("Linear mipmap nearest", GL_LINEAR_MIPMAP_NEAREST);
  glutAddMenuEntry("Nearest mipmap linear", GL_NEAREST_MIPMAP_LINEAR);
  glutAddMenuEntry("Linear mipmap linear", GL_LINEAR_MIPMAP_LINEAR);
  rate_menu = glutCreateMenu(rate_select);
  glutAddMenuEntry(" 2/sec", 500);
  glutAddMenuEntry(" 6/sec", 166);
  glutAddMenuEntry("10/sec", 100);
  glutAddMenuEntry("20/sec", 50);
  glutAddMenuEntry("30/sec", 33);
  glutAddMenuEntry("60/sec", 16);
  glutCreateMenu(menu_select);
  glutAddMenuEntry("Toggle spinning", 1);
  glutAddMenuEntry("Toggle scaling", 2);
  glutAddMenuEntry("Switch mode", 3);
  glutAddSubMenu("Minimum frame rate", rate_menu);
  glutAddSubMenu("Minify modes", minify_menu);
  glutAddMenuEntry("Quit", 666);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
  menu_select(3);
  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}