/*********************************************************************NVMH1****
File:
s3tc.c

Copyright (C) 1999, 2000 NVIDIA Corporation
This file is provided without support, instruction, or implied warranty of any
kind.  NVIDIA makes no guarantee of its fitness for a particular purpose and is
not liable under any circumstances for any damages or loss whatsoever arising
from the use or inability to use this file or items derived from it.

Comments: This tutorial code example is showing the use of 
ARB_texture_compression in conjunction with EXT_texture_compression_s3tc. 

Comments/Ideas can be sent to sebastien.domine@nvidia.com

******************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <windows.h>  /* for wglGetProcAddress */

#include <GL/glut.h>
#include <GL/glext.h>
#include "tga.h"
#include "dds.h"
#include "trackball.h"

GLfloat t[4][2];
GLfloat v[4][3];

float curquat[4];
float lastquat[4];
int   spinning = 0;
int   moving = 0;
int   beginx = 0;
int   beginy = 0;
int   W,H;
GLfloat aspectRatio;

GLuint  current_map = 0;
GLuint  decal_map = 0;
GLuint  compressed_decal_map = 1;
GLuint  already_compressed_decal_map = 2;
GLuint  dds_compressed_decal_map = 3;

GLuint filterarray[] = {GL_LINEAR,GL_NEAREST,GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_NEAREST};
char * filterarraytext[] = {"GL_LINEAR","GL_NEAREST","GL_LINEAR_MIPMAP_LINEAR","GL_LINEAR_MIPMAP_NEAREST"};
GLuint filterarraysize = 4;
GLuint filteridx = 0;
GLuint minfilter = GL_LINEAR;
GLuint magfilter = GL_LINEAR;
const char * appTitle = "Texture Compression Sample - ";

/* ARB_multitexture command function pointers */
PFNGLMULTITEXCOORD2IARBPROC         glMultiTexCoord2iARB;
PFNGLMULTITEXCOORD3FARBPROC         glMultiTexCoord3fARB;
PFNGLMULTITEXCOORD3FVARBPROC        glMultiTexCoord3fvARB;
PFNGLMULTITEXCOORD2FARBPROC         glMultiTexCoord2fARB;
PFNGLACTIVETEXTUREARBPROC           glActiveTextureARB;

/* ARB_texture_compression */
PFNGLCOMPRESSEDTEXIMAGE3DARBPROC    glCompressedTexImage3DARB;
PFNGLCOMPRESSEDTEXIMAGE2DARBPROC    glCompressedTexImage2DARB;
PFNGLCOMPRESSEDTEXIMAGE1DARBPROC    glCompressedTexImage1DARB;
PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC glCompressedTexSubImage3DARB;
PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC glCompressedTexSubImage2DARB;
PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC glCompressedTexSubImage1DARB;
PFNGLGETCOMPRESSEDTEXIMAGEARBPROC   glGetCompressedTexImageARB;

/*** OPENGL INITIALIZATION AND CHECKS ***/

/* Check if required extensions exist. */
void
initExtensions(void)
{
  int missingRequiredExtension = 0;  /* Start optimistic. */

  if (!glutExtensionSupported("GL_EXT_bgra")) {
    printf("I require the GL_EXT_bgra OpenGL extension to work.\n");
    missingRequiredExtension = 1;
  }
  if (!glutExtensionSupported("GL_ARB_multitexture")) {
    printf("I require the GL_ARB_multitexture OpenGL extension to work.\n");
    missingRequiredExtension = 1;
  }
  if (!glutExtensionSupported("GL_ARB_texture_compression")) {
    printf("I require the GL_ARB_texture_compression OpenGL extension to work.\n");
    missingRequiredExtension = 1;
  }
  if (!glutExtensionSupported("GL_EXT_texture_compression_s3tc")) {
    printf("I require the GL_EXT_texture_compression_s3tc OpenGL extension to work.\n");
    missingRequiredExtension = 1;
  }
  if (!glutExtensionSupported("WGL_EXT_swap_control")) {
    printf("I require the WGL_EXT_swap_control OpenGL extension to work.\n");
    missingRequiredExtension = 1;
  }
  if (missingRequiredExtension) {
    printf("\nExiting because required OpenGL extensions are not supported.\n");
    exit(1);
  }

  /* Retrieve some ARB_multitexture routines. */
  glMultiTexCoord2iARB =
    (PFNGLMULTITEXCOORD2IARBPROC)
    wglGetProcAddress("glMultiTexCoord2iARB");
  glMultiTexCoord3fARB =
    (PFNGLMULTITEXCOORD3FARBPROC)
    wglGetProcAddress("glMultiTexCoord3fARB");
  glMultiTexCoord3fvARB =
    (PFNGLMULTITEXCOORD3FVARBPROC)
    wglGetProcAddress("glMultiTexCoord3fvARB");
  glMultiTexCoord2fARB = 
    (PFNGLMULTITEXCOORD2FARBPROC)
    wglGetProcAddress("glMultiTexCoord2fARB");
  glActiveTextureARB =
    (PFNGLACTIVETEXTUREARBPROC)
    wglGetProcAddress("glActiveTextureARB");

  /* ARB_texture_compression */
  glCompressedTexImage3DARB    = 
    (PFNGLCOMPRESSEDTEXIMAGE3DARBPROC)    
    wglGetProcAddress("glCompressedTexImage3DARB");
  glCompressedTexImage2DARB    = 
    (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)    
    wglGetProcAddress("glCompressedTexImage2DARB");
  glCompressedTexImage1DARB    = 
    (PFNGLCOMPRESSEDTEXIMAGE1DARBPROC)
    wglGetProcAddress("glCompressedTexImage1DARB");
  glCompressedTexSubImage3DARB = 
    (PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) 
    wglGetProcAddress("glCompressedTexSubImage3DARB");
  glCompressedTexSubImage2DARB = 
    (PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) 
    wglGetProcAddress("glCompressedTexSubImage2DARB");
  glCompressedTexSubImage1DARB = 
    (PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) 
    wglGetProcAddress("glCompressedTexSubImage1DARB");
  glGetCompressedTexImageARB   = 
    (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)   
    wglGetProcAddress("glGetCompressedTexImageARB");
}

/* Draw a simple quad to render the texture */
void
drawObject()
{
  glBindTexture(GL_TEXTURE_2D, current_map);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minfilter);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magfilter);

  glBegin(GL_QUADS);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB,t[0][0],t[0][1]);
    glVertex3fv(v[0]);

    glMultiTexCoord2fARB(GL_TEXTURE0_ARB,t[1][0],t[1][1]);
    glVertex3fv(v[1]);

    glMultiTexCoord2fARB(GL_TEXTURE0_ARB,t[2][0],t[2][1]);
    glVertex3fv(v[2]);

    glMultiTexCoord2fARB(GL_TEXTURE0_ARB,t[3][0],t[3][1]);
    glVertex3fv(v[3]);
  glEnd();
}

void
display(void)
{
  GLfloat m[4][4];
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  build_rotmatrix(m, curquat);

  /* Push the modelview matrix so the object rotation can be done. */
  glPushMatrix();
    glMultMatrixf(&m[0][0]);
    drawObject();
  glPopMatrix();

  glFinish();
  glutSwapBuffers();
}

gliGenericImage *
readImage(char *filename)
{
  FILE *file;
  gliGenericImage *image;

  file = fopen(filename, "rb");
  if (file == NULL) {
    printf("npeturb: could not open \"%s\"\n", filename);
    exit(1);
  }
  image = gliReadTGA(file, filename);
  fclose(file);
  return image;
}

void GLErrorReport()
{
  GLuint errnum;
  const char *errstr;
  while (errnum = glGetError()) 
  {
    errstr = gluErrorString(errnum);
    printf(errstr);
  }
  return;
}

void
loadTextureAndCompress(void)
{
  gliGenericImage * image = NULL;
  int ddsbufsize;
  gliGenericImage * ddsimage = NULL;
  int compressed;
  int internalformat;
  int compressed_size;
  int num_compressed_format;
  int * compressed_format = NULL;
  unsigned char * img = NULL;
  int i;
  int width;
  int height;
  int size;
  int offset;
  int numMipmaps;
  int blockSize;

  /* Assume tightly packed textures. */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  /* Our original texture */
  glBindTexture(GL_TEXTURE_2D, decal_map);
  image = readImage("flowers128.tga"); /* "flowers128.tga" */

  if (image->format == GL_COLOR_INDEX) {
    /* Rambo 8-bit color index into luminance. */
    image->format = GL_LUMINANCE;
  }
  gluBuild2DMipmaps(GL_TEXTURE_2D, image->components,
    image->width, image->height,
    image->format, GL_UNSIGNED_BYTE, image->pixels);

  /* Lets have GL do the compression for us and choose the best matching 
     compression format */
  glBindTexture(GL_TEXTURE_2D, compressed_decal_map);
  gluBuild2DMipmaps(GL_TEXTURE_2D, 
               (image->components == 4) ? GL_COMPRESSED_RGBA_ARB : GL_COMPRESSED_RGB_ARB, 
               image->width, image->height,
               image->format, GL_UNSIGNED_BYTE, image->pixels);

  glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB, &num_compressed_format);
  compressed_format = (int*)malloc(num_compressed_format * sizeof(int));
  glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS_ARB, compressed_format);

  /* Check if the image has been compressed by GL*/
  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB, &compressed);

  /* Query for the compressed internal format */
  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalformat);

  /* Query for the size of the compressed data texture buffer */
  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_IMAGE_SIZE_ARB, &compressed_size);
  
  /* Allocate a buffer to host a copy of the compressed image data */
  img = (unsigned char *)malloc(compressed_size * sizeof(unsigned char));

  /* get the compressed data buffer */
  glGetCompressedTexImageARB(GL_TEXTURE_2D, 0, img);

  /* pass directly the compressed data to GL */
  /* No mipmap though! */
  glBindTexture(GL_TEXTURE_2D, already_compressed_decal_map);
  glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, internalformat, image->width, image->height, 0, compressed_size, img);


  /* load the .dds file */
  /* flowers128.dds flowerdxt5.dds*/

  ddsimage = ReadDDSFile("flowers128.dds",&ddsbufsize,&numMipmaps);
  height = ddsimage->height;
  width = ddsimage->width;
  offset = 0;
  blockSize = (ddsimage->format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
  glBindTexture(GL_TEXTURE_2D, dds_compressed_decal_map);
  /* load the mipmaps */
  for (i = 0; i < numMipmaps && (width || height); ++i)
  {
    if (width == 0)
      width = 1;
    if (height == 0)
      height = 1;

    size = ((width+3)/4)*((height+3)/4)*blockSize;

    glCompressedTexImage2DARB(GL_TEXTURE_2D, i, ddsimage->format, width, height, 
      0, size, ddsimage->pixels + offset);
    
    GLErrorReport();
    offset += size;
    width >>= 1;
    height >>= 1;
  }

  GLErrorReport();

  free(image->pixels);
  free(image);
  free(ddsimage->pixels);
  free(ddsimage);
  free(compressed_format);
  free(img);
}

void
init(void)
{
  initExtensions();
  loadTextureAndCompress();

  /* Lets create a textured quad */
  v[0][0] = -1.0f;
  v[0][1] = -1.0f;
  v[0][2] = 0.0f;

  t[0][0] = 0.0f;
  t[0][1] = 0.0f;

  v[1][0] = 1.0f;
  v[1][1] = -1.0f;
  v[1][2] = 0.0f;

  t[1][0] = 1.0f;
  t[1][1] = 0.0f;

  v[2][0] = 1.0f;
  v[2][1] = 1.0f;
  v[2][2] = 0.0f;

  t[2][0] = 1.0f;
  t[2][1] = 1.0f;

  v[3][0] = -1.0f;
  v[3][1] = 1.0f;
  v[3][2] = 0.0f;

  t[3][0] = 0.0f;
  t[3][1] = 1.0f;
  
  glActiveTextureARB(GL_TEXTURE0_ARB);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  /* Use depth buffering for hidden surface elimination. */
  glEnable(GL_DEPTH_TEST);

  /* Setup the view of the cube. */
  glMatrixMode(GL_PROJECTION);
  gluPerspective( /* field of view in degree */ 40.0,
    /* aspect ratio */ 1.0,
    /* Z near */ 1.0, /* Z far */ 10.0);
  glMatrixMode(GL_MODELVIEW);
  gluLookAt(0.0, 0.0, 3.0,  /* eye is at (0,0,3) */
    0.0, 0.0, 0.0,          /* center is at (0,0,0) */
    0.0, 1.0, 0.0);        /* up is in positive +Y direction */
}

void
SetTitle()
{
  char buf[1024];
  sprintf(buf,"%s%s%s,%s",appTitle,
    (current_map == dds_compressed_decal_map) ? "DDS " : "",
    (current_map == decal_map) ? "Uncompressed" : "Compressed",
    filterarraytext[filteridx]
    );
  glutSetWindowTitle(buf);
}

void
keyboard(unsigned char c, int x, int y)
{
  switch(c)
  {
    case 'u':
    case 'U':
      current_map = decal_map;
      break;
    case 'c':
    case 'C':
      current_map = compressed_decal_map;
      break;
    case 'a':
    case 'A':
      current_map = already_compressed_decal_map;
      break;
    case 'd':
    case 'D':
      current_map = dds_compressed_decal_map;
      break;
    case 'f':
    case 'F':
      filteridx++;
      filteridx = filteridx % filterarraysize;
      minfilter = filterarray[filteridx];
      if (minfilter == GL_NEAREST)
        magfilter = GL_NEAREST;
      else
        magfilter = GL_LINEAR;
      break;
  }
  SetTitle();
  glutPostRedisplay();
}

void
animate(void)
{
  if (spinning) {
    add_quats(lastquat, curquat, curquat);
  }
  glutPostRedisplay();
}

void
motion(int x, int y)
{
  if (moving) {
    trackball(lastquat,
      (2.0 * beginx - W) / W,
      (H - 2.0 * beginy) / H,
      (2.0 * x - W) / W,
      (H - 2.0 * y) / H
      );
    beginx = x;
    beginy = y;
    spinning = 1;
    glutIdleFunc(animate);
  }
}

void
mouse(int button, int state, int x, int y)
{
  if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
    spinning = 0;
    trackball(lastquat, 0, 0, 0, 0);
    glutIdleFunc(NULL);
    moving = 1;
    beginx = x;
    beginy = y;
  }
  if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
    moving = 0;
  }
}

void
reshape(int w, int h)
{
  glViewport(0, 0, w, h);
  W = w;
  H = h;
  aspectRatio = (GLfloat) W/(GLfloat) H;
}

int
main(int argc, char **argv)
{
  glutInitWindowSize(800, 600);
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutCreateWindow("Texture Compression Sample");
  glutDisplayFunc(display);
  glutMotionFunc(motion);
  glutMouseFunc(mouse);
  glutKeyboardFunc(keyboard);
  glutReshapeFunc(reshape);

  trackball(curquat, 0.0, 0.0, 0.0, 0.0);
  init();
  SetTitle();
  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}

