/*****************************************************************************
 * file:      v12.c
 * status:    09-may-2001, pfk.
 *            04-nov-2020, pfk, update with more comment.
 * function:  Example of texture mapping and mipmapping.
 * description:
 * - A square ppm formatted image is read into "texImage" and displayed as
 *   texture (Original dog, texture, from 512x512 image).
 * - The same image is used to generate mipmaps to display a rectangle in the
 *   middle of the output window from left to right.  The right part is very 
 *   far away (in Z) and vanishes into a dot. 
 *   The dog is hardly visible, just a few pixels near the vanishing point.
 * - A sequence of mipmaps of different colours is created to show the effect 
 *   of mipmapping at the bottom of the output window, from right to the 
 *   vanishing point on the left.
 ****************************************************************************/

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

/* Size and position of output window. */
#define WINSIZE_X   1000   /* width  in pixels         */
#define WINSIZE_Y    800   /* height in pixels         */
#define WINPOS_X     100   /* position of left         */
#define WINPOS_Y     100   /*             upper corner */

#define FRAME_TITLE  "Mipmapping example"

/* Array of unsigned bytes to hold texture image:
 *   minimum image size is 64x64,
 *   both sizes must be power of 2, not necessarily the same power. */
static int texImageWidth, texImageHeight;
static GLubyte *texImage;  /* ptr to 3-dimensional array:
                              texImage[texImageWidth][texImageHeight][4]
                              with four bytes per pixel */

static GLuint texName[3];

GLubyte mipmapImage512[512][512][4];
GLubyte mipmapImage256[256][256][4];
GLubyte mipmapImage128[128][128][4];
GLubyte mipmapImage64[64][64][4];
GLubyte mipmapImage32[32][32][4];
GLubyte mipmapImage16[16][16][4];
GLubyte mipmapImage8[8][8][4];
GLubyte mipmapImage4[4][4][4];
GLubyte mipmapImage2[2][2][4];
GLubyte mipmapImage1[1][1][4];


/****************************************************************************
 * Print text in window.
 ****************************************************************************/
void printText (float x, float y, char s[])
{
  int i, length;
  char c;

  glRasterPos2f (x, y);

  length = strlen (s);
  for (i=0; i<length; i++)
  { c = s[i];
    glutBitmapCharacter (GLUT_BITMAP_8_BY_13, c);
  }
}


/****************************************************************************
 * Create single colour texture images.
 ***************************************************************************/
void makeImages (void)
{
  int i, j;
    
  for (i = 0; i < 512; i++)          // red
  { for (j = 0; j < 512; j++)
    { mipmapImage512[i][j][0] = 255;
      mipmapImage512[i][j][1] = 0;
      mipmapImage512[i][j][2] = 0;
      mipmapImage512[i][j][3] = 255;
    }
  }
    
  for (i = 0; i < 256; i++)          // blue
  { for (j = 0; j < 256; j++)
    { mipmapImage256[i][j][0] = 0;
      mipmapImage256[i][j][1] = 0;
      mipmapImage256[i][j][2] = 255;
      mipmapImage256[i][j][3] = 255;
    }
  }
    
  for (i = 0; i < 128; i++)          // green
  { for (j = 0; j < 128; j++)
    { mipmapImage128[i][j][0] = 0;
      mipmapImage128[i][j][1] = 255;
      mipmapImage128[i][j][2] = 0;
      mipmapImage128[i][j][3] = 255;
    }
  }
    
  for (i = 0; i < 64; i++)          // white
  { for (j = 0; j < 64; j++)
    { mipmapImage64[i][j][0] = 255;
      mipmapImage64[i][j][1] = 255;
      mipmapImage64[i][j][2] = 255;
      mipmapImage64[i][j][3] = 255;
    }
  }
    
  for (i = 0; i < 32; i++)          // yellow
  { for (j = 0; j < 32; j++)
    { mipmapImage32[i][j][0] = 255;
      mipmapImage32[i][j][1] = 255;
      mipmapImage32[i][j][2] = 0;
      mipmapImage32[i][j][3] = 255;
    }
  }

  for (i = 0; i < 16; i++) {        // magenta
    for (j = 0; j < 16; j++) {
      mipmapImage16[i][j][0] = 255;
      mipmapImage16[i][j][1] = 0;
      mipmapImage16[i][j][2] = 255;
      mipmapImage16[i][j][3] = 255;
    }
  }

  for (i = 0; i < 8; i++)           // red
  { for (j = 0; j < 8; j++)
    { mipmapImage8[i][j][0] = 255;
      mipmapImage8[i][j][1] = 0;
      mipmapImage8[i][j][2] = 0;
      mipmapImage8[i][j][3] = 255;
    }
  }

  for (i = 0; i < 4; i++)           // green
  { for (j = 0; j < 4; j++)
    { mipmapImage4[i][j][0] = 0;
      mipmapImage4[i][j][1] = 255;
      mipmapImage4[i][j][2] = 0;
      mipmapImage4[i][j][3] = 255;
    }
  }

  for (i = 0; i < 2; i++)           // blue
  { for (j = 0; j < 2; j++)
    { mipmapImage2[i][j][0] = 0;
      mipmapImage2[i][j][1] = 0;
      mipmapImage2[i][j][2] = 255;
      mipmapImage2[i][j][3] = 255;
    }
  }

   mipmapImage1[0][0][0] = 255;     // white
   mipmapImage1[0][0][1] = 255;
   mipmapImage1[0][0][2] = 255;
   mipmapImage1[0][0][3] = 255;
}


/****************************************************************************
 * Get texture image from pgm- or ppm-file and copy image into "texImage".
 * Format of input file:
 * - line containing ascii file type, e.g. strings 'P5' or 'P6'
 * - optional ascii comment lines starting with '#'
 * - ascii line containing horizontal and vertical size separated by space
 * - ascii line containing number '255'
 * - binary pixel values: single byte per pixel with grayscale if type P5 (pgm)
 * -                      triples of bytes with RGB values if type P6 (ppm)
 ***************************************************************************/
void getImageFromFile (void)
{
  #define inputLineLength 81

  int i, j;
  int byteValue, intensityLevels;
  char inputLine[inputLineLength], typeOfFile[3];
  FILE *f = fopen ("mickey.ppm", "r");

  if (f == NULL)        /* check if file exists */
  { fprintf (stderr, "\nERROR: Failed to open file 'mickey.ppm'.\n");
    exit (1);
  }
  else
  {
    printf ("\nUsing file 'mickey.ppm'\n");
  }

  /* Get file type. */
  do  fgets (inputLine, inputLineLength-1, f);  while (inputLine[0] == '#');
  typeOfFile[0] = inputLine[0];
  typeOfFile[1] = inputLine[1];
  typeOfFile[2] = '\0';
  printf ("  file type:  %s\n", typeOfFile);

  /* Get horizontal en vertical size. */
  do  fgets (inputLine, inputLineLength-1, f);  while (inputLine[0] == '#');
  sscanf (inputLine, "%d %d", &texImageWidth, &texImageHeight);
  printf ("  horizontal: %d\n", texImageWidth);
  printf ("  vertical:   %d\n", texImageHeight);
  /* Allocate array that will contain the texture image. */
  texImage =
    (GLubyte *) malloc (texImageWidth*texImageHeight*4*sizeof(GLubyte));

  /* Get number with maximum number of shades. */
  do  fgets (inputLine, inputLineLength-1, f);  while (inputLine[0] == '#');
  sscanf (inputLine, "%d", &intensityLevels);
  printf ("  int levels: %d\n", intensityLevels);

  /* Copy binary pixel values into texture image array. */
  for (i = texImageHeight-1; i >= 0; i--)
  { for (j = 0; j < texImageWidth; j++)
    { int position = (i*texImageWidth+j)*4;
      /* read and store RGB or grayscale values*/
      byteValue = getc (f);     /* R-value or grayscale value */
      texImage[position] = (GLubyte) byteValue;
      if (typeOfFile[1] == '6')
        byteValue = getc (f);   /* G-value if file type = R6 */
      texImage[position+1] = (GLubyte) byteValue;
      if (typeOfFile[1] == '6')
        byteValue = getc (f);   /* B-value if file type = R6 */
      texImage[position+2] = (GLubyte) byteValue;
                                /*  alpha-value */
      texImage[position+3] = (GLubyte) 255;
    }
  }
}


/****************************************************************************
 * Create texture image and do set-up for texture mapping.
 ***************************************************************************/
void progInit (void)
{   
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glEnable     (GL_DEPTH_TEST);       // use depth buffer
  glEnable     (GL_TEXTURE_2D);       // use 2D texture

  getImageFromFile (); // copy image from file into "texImage"
  makeImages ();       // create series of mono-coloured images for mipmap

  // set pixel storage mode to byte alignment
  glPixelStorei (GL_UNPACK_ALIGNMENT, 1);

  // generate texture names for 3 textures
  glGenTextures (2, texName);

  //-- dog, original picture, simple texture mapping ------------------------

  // bind to named texture
  glBindTexture (GL_TEXTURE_2D, texName[0]);

  // parameter settings for texture
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  glTexImage2D (GL_TEXTURE_2D, 0, 4, texImageWidth, texImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texImage);

  //-- dog, mipmapped, from left to right -----------------------------------

  glBindTexture (GL_TEXTURE_2D, texName[1]);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);

  // create and specify a full series of texture images from original
  gluBuild2DMipmaps (GL_TEXTURE_2D, 4, texImageWidth, texImageHeight, GL_RGBA, GL_UNSIGNED_BYTE, texImage);

  //-- coloured polygons, mipmapped, from right to left ---------------------

  glBindTexture (GL_TEXTURE_2D, texName[2]);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);

  // specify texture images (created in function "makeImages")
  glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage512);
  glTexImage2D (GL_TEXTURE_2D, 1, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage256);
  glTexImage2D (GL_TEXTURE_2D, 2, GL_RGBA, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage128);
  glTexImage2D (GL_TEXTURE_2D, 3, GL_RGBA,  64,  64, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage64);
  glTexImage2D (GL_TEXTURE_2D, 4, GL_RGBA,  32,  32, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage32);
  glTexImage2D (GL_TEXTURE_2D, 5, GL_RGBA,  16,  16, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage16);
  glTexImage2D (GL_TEXTURE_2D, 6, GL_RGBA,   8,   8, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage8);
  glTexImage2D (GL_TEXTURE_2D, 7, GL_RGBA,   4,   4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage4);
  glTexImage2D (GL_TEXTURE_2D, 8, GL_RGBA,   2,   2, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage2);
  glTexImage2D (GL_TEXTURE_2D, 9, GL_RGBA,   1,   1, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipmapImage1);
}


/****************************************************************************
 * Display textured objects:
 *   three four-sided polygons with the same 2D texture applied (cube),
 *   one   four-sided polygon  with part of the same 2D texture applied.
 ***************************************************************************/
void progDisplay (void)
{
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // set texture environment variables
  glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

  //-- dog, original picture, simple texture mapping ------------------------

  glBindTexture (GL_TEXTURE_2D, texName[0]);

  glBegin (GL_QUADS);
  glTexCoord2f (0.0, 0.0);  glVertex3f (-0.5,  0.5,  0.0);
  glTexCoord2f (0.0, 1.0);  glVertex3f (-0.5,  1.1,  0.0);
  glTexCoord2f (1.0, 1.0);  glVertex3f ( 0.5,  1.1,  0.0);
  glTexCoord2f (1.0, 0.0);  glVertex3f ( 0.5,  0.5,  0.0);
  glEnd ();

  //-- dog, mipmapped, from left to right -----------------------------------

  glBindTexture (GL_TEXTURE_2D, texName[1]);

  glBegin (GL_QUADS);
  glTexCoord2f (0.0, 0.0);  glVertex3f (   -1.4,  0.0,     0.0);
  glTexCoord2f (0.0, 1.0);  glVertex3f (   -1.4,  1.0,     0.0);
  glTexCoord2f (1.0, 1.0);  glVertex3f (  250.0,  1.0,  -500.0);
  glTexCoord2f (1.0, 0.0);  glVertex3f (  250.0,  0.0,  -500.0);
  glEnd ();

  //-- coloured polygons, mipmapped, from right to left ---------------------

  // bind to named texture
  glBindTexture (GL_TEXTURE_2D, texName[2]);

  // define quadrilateral to glue texture to
  glBegin (GL_QUADS);
  glTexCoord2f (0.0, 0.0);  glVertex3f (    1.4,  -1.0,     0.0);
  glTexCoord2f (0.0, 1.0);  glVertex3f (    1.4,   0.0,     0.0);
  glTexCoord2f (1.0, 1.0);  glVertex3f (  -250.0,  0.0,  -500.0);
  glTexCoord2f (1.0, 0.0);  glVertex3f (  -250.0, -1.0,  -500.0);
  glEnd ();

  //-- finish ---------------------------------------------------------------

  // disable 2D texture mapping
  glDisable (GL_TEXTURE_2D);

  printText (-0.475, 1.11, "Original dog, texture, from 512x512 image");
  printText (0.6, 0.25, "Dog, mipmapped");
  printText (-0.9, -0.4, "Coloured polygons, mipmapped");

  glFlush ();
}


/****************************************************************************
 * Reshape handling.
 ***************************************************************************/
void progReshape (int w, int h)
{
  glViewport (0, 0, (GLsizei) w, (GLsizei) h);

  printf ("viewport w=%d, h=%d.\n", w, h);

  glMatrixMode   (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (60.0, (GLfloat) w/(GLfloat) h, 1.0, 30000.0);

  glMatrixMode   (GL_MODELVIEW);
  glLoadIdentity ();
  glTranslatef   (0.0,  0.0, -2.0);
}


/****************************************************************************
 * Keyboard handling.
 ***************************************************************************/
void progKeyboard (unsigned char key, int x, int y)
{
  switch (key)
  { case 'q': 
    case  27: exit(0);
    default:  break;
  }
}


/****************************************************************************
 * Main program.
 ***************************************************************************/
int main (int argc, char** argv)
{
  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize (WINSIZE_X, WINSIZE_Y);
  glutInitWindowPosition (WINPOS_X, WINPOS_Y);
  glutCreateWindow (FRAME_TITLE);

  progInit ();

  glutDisplayFunc  (progDisplay);
  glutReshapeFunc  (progReshape);
  glutKeyboardFunc (progKeyboard);

  glutMainLoop ();

  return 0; 
}
