/*
 * Hacked on by Qef.
 *
 * Also Copyright (c) Mark J. Kilgard, 1997.
 *
 * 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.
 *
 *
 * This program was requested by Patrick Earl; hopefully someone else
 * will write the equivalent Direct3D immediate mode program.
 */


#include <GL/glut.h>
#include <tiffio.h>
#include <iostream>
#include <cctype>

static void key (unsigned char c, int x, int y);
void recurse_cube (int depth, int d, float size, float x, float y, float z);
int writetiff (char *filename, char *description);


GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 0};
GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};
GLfloat mat_specular[] = {1.0, 1.0, 1.0, 0};

float trans[20][3] =
{
   { -1, -1, -1 },   // Near slice, bottom row.
   { 0, -1, -1 },
   { 1, -1, -1 },
   { -1, 0, -1 },    // Near slice, middle row.
   { 1, 0, -1 },
   { -1, 1, -1 },    // Near slice, top row.
   { 0, 1, -1 },
   { 1, 1, -1 },
   { -1, -1, 0 },    // Middle slice, bottom row.
   { 1, -1, 0 },
   { -1, 1, 0 },     // Middle slice, top row.
   { 1, 1, 0 },
   { -1, -1, 1 },    // Far slice, bottom row.
   { 0, -1, 1 },
   { 1, -1, 1 },
   { -1, 0, 1 },
   { 1, 0, 1 },
   { -1, 1, 1 },
   { 0, 1, 1 },
   { 1, 1, 1 }
};

int *track = 0;

float rot_x = 110, rot_z = 30;
float pos_z = 17.5;
int detail = 2;
int displist = 0;
bool wireframe = false, colorshade = true;
bool changed = true;
int win_xs = 1, win_ys = 1;


void
qef_cube (float size, float x, float y, float z)
{
   if (wireframe)
      glutWireCube (size);
   else
   {
      size /= 2;
      float c = 0;

      float colors[8][3];
      for (int i = 0; i < 8; ++i)
      {
         if (colorshade)
         {
            colors[i][0] = 0.3 + 0.7 * ((x + (i & 1 ? size : -size)) / 3);
            colors[i][1] = 0.3 + 0.7 * ((y + (i & 2 ? size : -size)) / 3);
            colors[i][2] = 0.3 + 0.7 * ((z + (i & 4 ? size : -size)) / 3);
         }
         else
         {
            colors[i][0] = 1.0;
            colors[i][1] = 0.7;
            colors[i][2] = 1.0;
         }
      }

      // Low X: Left face.
      glNormal3f (-1, 0, 0);
      glColor3f (colors[2][0], colors[2][1], colors[2][2]);
      glVertex3f (x - size, y + size, z - size);
      glColor3f (colors[0][0], colors[0][1], colors[0][2]);
      glVertex3f (x - size, y - size, z - size);
      glColor3f (colors[4][0], colors[4][1], colors[4][2]);
      glVertex3f (x - size, y - size, z + size);
      glVertex3f (x - size, y - size, z + size);
      glColor3f (colors[6][0], colors[6][1], colors[6][2]);
      glVertex3f (x - size, y + size, z + size);
      glColor3f (colors[2][0], colors[2][1], colors[2][2]);
      glVertex3f (x - size, y + size, z - size);

      // High X: Right face.
      glNormal3f (1, 0, 0);
      glColor3f (colors[5][0], colors[5][1], colors[5][2]);
      glVertex3f (x + size, y - size, z + size);
      glColor3f (colors[1][0], colors[1][1], colors[1][2]);
      glVertex3f (x + size, y - size, z - size);
      glColor3f (colors[3][0], colors[3][1], colors[3][2]);
      glVertex3f (x + size, y + size, z - size);
      glVertex3f (x + size, y + size, z - size);
      glColor3f (colors[7][0], colors[7][1], colors[7][2]);
      glVertex3f (x + size, y + size, z + size);
      glColor3f (colors[5][0], colors[5][1], colors[5][2]);
      glVertex3f (x + size, y - size, z + size);

      // Low Y: Bottom face.
      glNormal3f (0, -1, 0);
      glColor3f (colors[4][0], colors[4][1], colors[4][2]);
      glVertex3f (x - size - c, y - size, z + size + c);
      glColor3f (colors[0][0], colors[0][1], colors[0][2]);
      glVertex3f (x - size - c, y - size, z - size - c);
      glColor3f (colors[1][0], colors[1][1], colors[1][2]);
      glVertex3f (x + size + c, y - size, z - size - c);
      glVertex3f (x + size + c, y - size, z - size - c);
      glColor3f (colors[5][0], colors[5][1], colors[5][2]);
      glVertex3f (x + size + c, y - size, z + size + c);
      glColor3f (colors[4][0], colors[4][1], colors[4][2]);
      glVertex3f (x - size - c, y - size, z + size + c);

      // High Y: Top face.
      glNormal3f (0, 1, 0);
      glColor3f (colors[3][0], colors[3][1], colors[3][2]);
      glVertex3f (x + size + c, y + size, z - size - c);
      glColor3f (colors[2][0], colors[2][1], colors[2][2]);
      glVertex3f (x - size - c, y + size, z - size - c);
      glColor3f (colors[6][0], colors[6][1], colors[6][2]);
      glVertex3f (x - size - c, y + size, z + size + c);
      glVertex3f (x - size - c, y + size, z + size + c);
      glColor3f (colors[7][0], colors[7][1], colors[7][2]);
      glVertex3f (x + size + c, y + size, z + size + c);
      glColor3f (colors[3][0], colors[3][1], colors[3][2]);
      glVertex3f (x + size + c, y + size, z - size - c);

      // High Z: Near face.
      glNormal3f (0, 0, 1);
      glColor3f (colors[6][0], colors[6][1], colors[6][2]);
      glVertex3f (x - size, y + size, z + size);
      glColor3f (colors[4][0], colors[4][1], colors[4][2]);
      glVertex3f (x - size, y - size, z + size);
      glColor3f (colors[5][0], colors[5][1], colors[5][2]);
      glVertex3f (x + size, y - size, z + size);
      glVertex3f (x + size, y - size, z + size);
      glColor3f (colors[7][0], colors[7][1], colors[7][2]);
      glVertex3f (x + size, y + size, z + size);
      glColor3f (colors[6][0], colors[6][1], colors[6][2]);
      glVertex3f (x - size, y + size, z + size);

      // Low Z: Far face.
      glNormal3f (0, 0, -1);
      glColor3f (colors[1][0], colors[1][1], colors[1][2]);
      glVertex3f (x + size, y - size, z - size);
      glColor3f (colors[0][0], colors[0][1], colors[0][2]);
      glVertex3f (x - size, y - size, z - size);
      glColor3f (colors[2][0], colors[2][1], colors[2][2]);
      glVertex3f (x - size, y + size, z - size);
      glVertex3f (x - size, y + size, z - size);
      glColor3f (colors[3][0], colors[3][1], colors[3][2]);
      glVertex3f (x + size, y + size, z - size);
      glColor3f (colors[1][0], colors[1][1], colors[1][2]);
      glVertex3f (x + size, y - size, z - size);
   }
}


void
drawBox(void)
{
   if (changed || displist == 0)
   {
      delete [] track;
      track = new int[detail * 3];

      glPushMatrix();

      if (displist != 0)
         glDeleteLists (displist, 1);
      displist = glGenLists (1);
      glNewList (displist, GL_COMPILE);


      glBegin (GL_TRIANGLES);
      recurse_cube (detail, 0, 3.0, 0, 0, 0);

      glEnd();

      glEndList();
      changed = false;
      glPopMatrix();
   }

   glPushMatrix();

   glTranslatef (0.0, 0.0, 10);
   glRotatef (rot_x, 1.0, 0.0, 0.0);
   glRotatef (rot_z, 0.0, 0.0, 1.0);
   glCallList (displist);

   glPopMatrix();
}


void
recurse_cube (int depth, int d, float size, float x, float y, float z)
{
   glPushMatrix();

   if (depth == 0)
      qef_cube (size, x, y, z);
   else
   {
      float new_size = size / 3.0;

      for (int i = 0; i < 20; ++i)
      {
         float new_x = x + trans[i][0] * new_size;
         float new_y = y + trans[i][1] * new_size;
         float new_z = z + trans[i][2] * new_size;

//         track[d + 0] = ;

         recurse_cube (depth - 1, d + 1, new_size, new_x, new_y, new_z);
      }
   }

   glPopMatrix();
}


void
display ()
{
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   drawBox();
   glutSwapBuffers();
}


void
set_pos (float z)
{
   if (z < 0)
      z = 0;
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity();
   gluPerspective (40.0,      // field of view in degree
                   float (win_xs) / win_ys, // aspect ratio
                   1,         // Z near
                   100);      // Z far
   gluLookAt (0.0, 0.0, z,    // eye position
              0.0, 0.0, 0.0,  // center position
              0.0, 1.0, 0.0); // up is in positive Y direction
   glMatrixMode (GL_MODELVIEW);
   pos_z = z;
}


void
init ()
{
   // Enable a single OpenGL light.
   glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);
   glLightfv (GL_LIGHT0, GL_POSITION, light_position);
   glEnable (GL_LIGHT0);
   glEnable (GL_LIGHTING);
   glEnable (GL_COLOR_MATERIAL);
   glEnable (GL_CULL_FACE);
//   glEnable (GL_NORMALIZE);
   glEnable(GL_BLEND);
   glShadeModel (GL_SMOOTH);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST);
   glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

   // Use depth buffering for hidden surface elimination.
   glEnable (GL_DEPTH_TEST);

   // Setup the view of the cube.
   set_pos (pos_z);

   glMaterialfv (GL_FRONT, GL_SPECULAR, mat_specular);
   glLightModelfv (GL_LIGHT_MODEL_AMBIENT, mat_specular);
}


void
handle_reshape (int xs, int ys)
{
   win_xs = xs;
   win_ys = ys;
   set_pos (pos_z);
   glViewport (0, 0, xs, ys);
}


int
main (int argc, char **argv)
{
   track = new int[detail * 3];

   glutInit (&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutCreateWindow ("Menger Sponge");
   glutReshapeFunc (handle_reshape);
//   glutFullScreen();
   glutDisplayFunc(display);
   glutKeyboardFunc (key);
   init();
   glutMainLoop();

   return 0;
}


static void
key(unsigned char c, int x, int y)
{
   c = tolower (c);

   switch (c)
   {
    case 27: case 'q':
      exit (0);

    case 'a':
      ++rot_x;
      break;

    case 's':
      --rot_x;
      break;

    case 'e':
      ++rot_z;
      break;

    case 'd':
      --rot_z;
      break;

    case 'i':
      set_pos (pos_z - 0.05);
      break;

    case 'k':
      set_pos (pos_z + 0.05);
      break;

    case 'w':
      wireframe = !wireframe;
      changed = true;
      break;

    case 'c':
      colorshade = !colorshade;
      changed = true;
      break;

    case '+': case '=':
      ++detail;
      changed = true;
      break;

    case '-': case '_':
      if (detail > 0)
         --detail, changed = true;
      break;

    case '#':
      writetiff ("menger.png", "Menger Sponge");
      break;

    case '\'':
      glutReshapeWindow (2048, 2048);
   }

   glutPostRedisplay();
}


int
writetiff (char *filename, char *description)
{
   TIFF *file;
   GLubyte *image, *p;
   int i;

   file = TIFFOpen (filename, "w");
   if (file == NULL)
      return 1;
   image = new GLubyte [win_xs * win_ys * 3];

   glPixelStorei(GL_PACK_ALIGNMENT, 1);

   glReadPixels (0, 0, win_xs, win_ys, GL_RGB, GL_UNSIGNED_BYTE, image);
   TIFFSetField (file, TIFFTAG_IMAGEWIDTH, (uint32) win_xs);
   TIFFSetField (file, TIFFTAG_IMAGELENGTH, (uint32) win_ys);
   TIFFSetField (file, TIFFTAG_BITSPERSAMPLE, 8);
   TIFFSetField (file, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
   TIFFSetField (file, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
   TIFFSetField (file, TIFFTAG_SAMPLESPERPIXEL, 3);
   TIFFSetField (file, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
   TIFFSetField (file, TIFFTAG_ROWSPERSTRIP, 1);
   TIFFSetField (file, TIFFTAG_IMAGEDESCRIPTION, description);
   p = image;

   for (i = win_ys - 1; i >= 0; --i)
   {
      if (TIFFWriteScanline (file, p, i, 0) < 0)
      {
         delete [] image;
         TIFFClose (file);
         return 1;
      }
      p += win_xs * sizeof (GLubyte) * 3;
   }
   TIFFClose (file);
   return 0;
}
