
/* 23_bump_map_floor.c - OpenGL-based specular bump mapping example
   using Cg programs from Chapter 8 of "The Cg Tutorial" (Addison-Wesley,
   ISBN 0321194969). */

/* Requires the OpenGL Utility Toolkit (GLUT) and Cg runtime (version
   1.5 or higher). */

#include <stdio.h>    /* for printf and NULL */
#include <stdlib.h>   /* for exit */
#include <math.h>     /* for sqrt, sin, and cos */
#include <assert.h>   /* for assert */

#include <GL/glew.h>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#ifdef _WIN32
#include <GL/wglew.h>
#else
#ifdef __APPLE__
#include <OpenGL/OpenGL.h>
#else
#include <GL/glxew.h>
#endif
#endif

#include <Cg/cg.h>    /* Can't include this?  Is Cg Toolkit installed! */
#include <Cg/cgGL.h>

static CGcontext   myCgContext;
static CGprofile   myCgVertexProfile,
                   myCgFragmentProfile;
static CGprogram   myCgVertexProgram,
                   myCgFragmentProgram;
static CGparameter myCgVertexParam_lightPosition,
                   myCgVertexParam_eyePosition,
                   myCgVertexParam_modelViewProj,
                   myCgFragmentParam_normalMap,
                   myCgFragmentParam_normalizeCube;

static const char *myProgramName = "23_bump_map_floor",
                  *myVertexProgramFileName = "C8E5v_bumpAny.cg",
/* page 216 */    *myVertexProgramName = "C8E5v_bumpAny",
                  *myFragmentProgramFileName = "C8E2f_bumpSurf.cg",
/* page 209 */    *myFragmentProgramName = "C8E2f_bumpSurf";

static float lightAngle = 4.0;   /* Angle light rotates around scene. */
static float lightZ = 4.0;
static float eyeHeight = 0;    /* Vertical height of light. */
static float eyeAngle  = 0;   /* Angle in radians eye rotates around scene. */

/* OpenGL texture object (TO) handles. */
enum {
  TO_NORMALIZE_VECTOR_CUBE_MAP = 0,
  TO_NORMAL_MAP = 1,
};
GLuint texObj[2];

static const GLubyte
myBrickNormalMapImage[3*(128*128+64*64+32*32+16*16+8*8+4*4+2*2+1*1)] = {
/* RGB8 image data for a mipmapped 128x128 normal map for a brick pattern */
#include "brick_image.h"
};

static const GLubyte
myNormalizeVectorCubeMapImage[6*3*32*32] = {
/* RGB8 image data for a normalization vector cube map with 32x32 faces */
#include "normcm_image.h"
};

static void checkForCgError(const char *situation)
{
  CGerror error;
  const char *string = cgGetLastErrorString(&error);

  if (error != CG_NO_ERROR) {
    printf("%s: %s: %s\n",
      myProgramName, situation, string);
    if (error == CG_COMPILER_ERROR) {
      printf("%s\n", cgGetLastListing(myCgContext));
    }
    exit(1);
  }
}

/* Forward declared GLUT callbacks registered by main. */
static void display(void);
static void keyboard(unsigned char c, int x, int y);
static void menu(int item);
static void mouse(int button, int state, int x, int y);
static void motion(int x, int y);
static void reshape(int width, int height);

/* Other forward declared functions. */
static void requestSynchronizedSwapBuffers(void);

int main(int argc, char **argv)
{
  const GLubyte *image;
  unsigned int size, level;
  int face;

  glutInitWindowSize(400, 400);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutInit(&argc, argv);

  glutCreateWindow(myProgramName);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutReshapeFunc(reshape);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);

  /* Initialize OpenGL entry points. */
  if (glewInit()!=GLEW_OK || !GLEW_VERSION_1_3) {
    fprintf(stderr, "%s: failed to initialize GLEW, OpenGL 1.3 required.\n", myProgramName);    
    exit(1);
  }

  requestSynchronizedSwapBuffers();
  glClearColor(0.1, 0.3, 0.6, 0.0);  /* Blue background */
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* Tightly packed texture data. */

  glGenTextures( 2, texObj );

  glBindTexture(GL_TEXTURE_2D, texObj[TO_NORMAL_MAP]);
  /* Load each mipmap level of range-compressed 128x128 brick normal
     map texture. */
  for (size = 128, level = 0, image = myBrickNormalMapImage;
       size > 0;
       image += 3*size*size, size /= 2, level++) {
    glTexImage2D(GL_TEXTURE_2D, level,
      GL_RGB8, size, size, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
  }
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR);

  glBindTexture(GL_TEXTURE_CUBE_MAP, texObj[TO_NORMALIZE_VECTOR_CUBE_MAP]);
  /* Load each 32x32 face (without mipmaps) of range-compressed "normalize
     vector" cube map. */
  for (face = 0, image = myNormalizeVectorCubeMapImage;
       face < 6;
       face++, image += 3*32*32) {
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0,
      GL_RGB8, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
  }
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  myCgContext = cgCreateContext();
  checkForCgError("creating context");
  cgGLSetDebugMode(CG_FALSE);
  cgGLSetManageTextureParameters(myCgContext, CG_TRUE);
  cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);

  myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
  cgGLSetOptimalOptions(myCgVertexProfile);
  checkForCgError("selecting vertex profile");

  myCgVertexProgram =
    cgCreateProgramFromFile(
      myCgContext,              /* Cg runtime context */
      CG_SOURCE,                /* Program in human-readable form */
      myVertexProgramFileName,  /* Name of file containing program */
      myCgVertexProfile,        /* Profile: OpenGL ARB vertex program */
      myVertexProgramName,      /* Entry function name */
      NULL);                    /* No extra compiler options */
  checkForCgError("creating vertex program from file");
  cgGLLoadProgram(myCgVertexProgram);
  checkForCgError("loading vertex program");

  myCgVertexParam_lightPosition =
    cgGetNamedParameter(myCgVertexProgram, "lightPosition");
  checkForCgError("could not get lightPosition parameter");

  myCgVertexParam_eyePosition =
    cgGetNamedParameter(myCgVertexProgram, "eyePosition");
  checkForCgError("could not get eyePosition parameter");

  myCgVertexParam_modelViewProj =
    cgGetNamedParameter(myCgVertexProgram, "modelViewProj");
  checkForCgError("could not get modelViewProj parameter");

  myCgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
  cgGLSetOptimalOptions(myCgFragmentProfile);
  checkForCgError("selecting fragment profile");

  myCgFragmentProgram =
    cgCreateProgramFromFile(
      myCgContext,                /* Cg runtime context */
      CG_SOURCE,                  /* Program in human-readable form */
      myFragmentProgramFileName,  /* Name of file containing program */
      myCgFragmentProfile,        /* Profile: OpenGL ARB vertex program */
      myFragmentProgramName,      /* Entry function name */
      NULL);                      /* No extra compiler options */
  checkForCgError("creating fragment program from file");
  cgGLLoadProgram(myCgFragmentProgram);
  checkForCgError("loading fragment program");

  myCgFragmentParam_normalMap =
    cgGetNamedParameter(myCgFragmentProgram, "normalMap");
  checkForCgError("getting normalMap parameter");

  myCgFragmentParam_normalizeCube =
    cgGetNamedParameter(myCgFragmentProgram, "normalizeCube");
  checkForCgError("getting normalizeCube parameter");

  cgGLSetTextureParameter(myCgFragmentParam_normalMap,
    texObj[TO_NORMAL_MAP]);
  checkForCgError("setting normal map 2D texture");

  cgGLSetTextureParameter(myCgFragmentParam_normalizeCube,
    texObj[TO_NORMALIZE_VECTOR_CUBE_MAP]);
  checkForCgError("setting normalize vector cube map");

  glutCreateMenu(menu);
  glutAddMenuEntry("[ ] Animate", ' ');
  glutAttachMenu(GLUT_RIGHT_BUTTON);

  glutMainLoop();
  return 0;
}

static void reshape(int width, int height)
{
  double aspectRatio = (float) width / (float) height;
  double fieldOfView = 75.0; /* Degrees */

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(fieldOfView, aspectRatio,
    0.1,    /* Z near */
    100.0); /* Z far */
  glMatrixMode(GL_MODELVIEW);

  glViewport(0, 0, width, height);
}

static void drawRoom(void)
{
  glBegin(GL_QUADS);
    /* back wall */
    glNormal3f(0, 0, 1);
    /* As S increases, x component of position increases, so tangent is (+1,0,0). */
    glMultiTexCoord3f(GL_TEXTURE1, 1, 0, 0);
    
    glTexCoord2f(0,0);
    glVertex2f(-7,-7);

    glTexCoord2f(1,0);
    glVertex2f( 7,-7);

    glTexCoord2f(1,1);
    glVertex2f( 7, 7);

    glTexCoord2f(0,1);
    glVertex2f(-7, 7);

    /* floor */
    glNormal3f(0, 1, 0);
    /* As S increases, x component of position increases, so tangent is (+1,0,0). */
    glMultiTexCoord3f(GL_TEXTURE1, 1, 0, 0);

    glTexCoord2f(0,0);
    glVertex3f(-7, -7, 14);

    glTexCoord2f(1,0);
    glVertex3f( 7, -7, 14);

    glTexCoord2f(1,1);
    glVertex3f( 7,-7, 0);

    glTexCoord2f(0,1);
    glVertex3f(-7,-7, 0);

    /* ceiling */
    glNormal3f(0, -1, 0);
    /* As S increases, x component of position increases, so tangent is (+1,0,0). */
    glMultiTexCoord3f(GL_TEXTURE1, 1, 0, 0);

    glTexCoord2f(0,0);
    glVertex3f(-7,7, 0);

    glTexCoord2f(1,0);
    glVertex3f( 7,7, 0);

    glTexCoord2f(1,1);
    glVertex3f( 7,7, 14);

    glTexCoord2f(0,1);
    glVertex3f(-7,7, 14);

    /* left wall */
    glNormal3f(1, 0, 0);
    /* As S increases, z component of position decreases, so tangent is (0,0,-1). */
    glMultiTexCoord3f(GL_TEXTURE1, 0, 0, -1);

    glTexCoord2f(1,0);
    glVertex3f(-7,-7, 0);

    glTexCoord2f(1,1);
    glVertex3f(-7,7, 0);

    glTexCoord2f(0,1);
    glVertex3f(-7,7, 14);

    glTexCoord2f(0,0);
    glVertex3f(-7,-7, 14);

    /* right wall */
    glNormal3f(-1, 0, 0);
    /* As S increases, z component of position increases, so tangent is (0,0,+1). */
    glMultiTexCoord3f(GL_TEXTURE1, 0, 0, 1);

    glTexCoord2f(0,0);
    glVertex3f(7,-7, 0);

    glTexCoord2f(1,0);
    glVertex3f(7,-7, 14);

    glTexCoord2f(1,1);
    glVertex3f(7,7, 14);

    glTexCoord2f(0,1);
    glVertex3f(7,7, 0);

    /* front wall */
    glNormal3f(0, 0, -1);
    /* As S increases, x component of position increases, so tangent is (+1,0,0). */
    glMultiTexCoord3f(GL_TEXTURE1, 1, 0, 0);

    glTexCoord2f(0,1);
    glVertex3f(-7,-7, 14);

    glTexCoord2f(0,0);
    glVertex3f(-7, 7, 14);

    glTexCoord2f(1,0);
    glVertex3f( 7, 7, 14);

    glTexCoord2f(1,1);
    glVertex3f( 7,-7, 14);
  glEnd();
}

static void display(void)
{
  const float lightRadisu = 5.1;
  const float lightPosition[3] = { lightRadisu*sin(lightAngle),
                                   lightRadisu*cos(lightAngle),
                                   lightZ };
  const float eyePosition[3] = { 20*sin(eyeAngle), 
                                 eyeHeight,
                                 20*cos(eyeAngle) };

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glLoadIdentity();
  gluLookAt(
    eyePosition[0], eyePosition[1], eyePosition[2],
    0.0, 0.0,  0.0,   /* XYZ view center */
    0.0, 1.0,  0.0);  /* Up is in positive Y direction */

  cgGLBindProgram(myCgVertexProgram);
  checkForCgError("binding vertex program");

  cgGLSetStateMatrixParameter(myCgVertexParam_modelViewProj,
                              CG_GL_MODELVIEW_PROJECTION_MATRIX,
                              CG_GL_MATRIX_IDENTITY);
  checkForCgError("setting modelview-projection matrix");

  cgSetParameter3fv(myCgVertexParam_lightPosition, lightPosition);
  checkForCgError("setting light position");

  cgSetParameter3fv(myCgVertexParam_eyePosition, eyePosition);
  checkForCgError("setting eye position");

  cgGLEnableProfile(myCgVertexProfile);
  checkForCgError("enabling vertex profile");

  cgGLBindProgram(myCgFragmentProgram);
  checkForCgError("binding fragment program");

  cgGLEnableTextureParameter(myCgFragmentParam_normalMap);
  checkForCgError("enable texture normal map");
  cgGLEnableTextureParameter(myCgFragmentParam_normalizeCube);
  checkForCgError("enable normalize vector cube map");

  cgGLEnableProfile(myCgFragmentProfile);
  checkForCgError("enabling fragment profile");

  cgUpdateProgramParameters(myCgVertexProgram);
  cgUpdateProgramParameters(myCgFragmentProgram);

  drawRoom();

  cgGLDisableProfile(myCgVertexProfile);
  checkForCgError("disabling vertex profile");

  cgGLDisableProfile(myCgFragmentProfile);
  checkForCgError("disabling fragment profile");

  /*** Render light as white ball using fixed function pipe ***/

  glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]);
  glColor3f(0.8, 0.8, 0.1); /* yellow */
  glutSolidSphere(0.4, 12, 12);

  glutSwapBuffers();
}

static const double my2Pi = 2.0 * 3.14159265358979323846;
static unsigned int animating = 0;

static void idle(void)
{
#define lightZdirectionMagnitude 0.125f
  static float lightZdirection = lightZdirectionMagnitude;

  /* Rotate light around Z axis if animating==1 or animating==3 */
  if (animating & 1) {
    lightAngle += 0.0125;  /* Add a small angle (in radians). */
    if (lightAngle > my2Pi) {
      lightAngle -= my2Pi;
    }
  }
  /* Sweep light back and forth along Z axis if animating==1 or animating==2 */
  if ((animating+1) & 2) {
    lightZ += lightZdirection;
    if (lightZ >= 13) {
      lightZdirection = -lightZdirectionMagnitude;
    }
    if (lightZ <= 1) {
      lightZdirection = lightZdirectionMagnitude;
    }
  }
  glutPostRedisplay();
}

static void keyboard(unsigned char c, int x, int y)
{
  switch (c) {
  case ' ':
    animating = (animating+1)%4; /* Toggle */
    if (animating) {
      glutIdleFunc(idle);
    } else {
      glutIdleFunc(NULL);
    }    
    break;
  case 'f':
    lightZ += 0.2;
    glutPostRedisplay();
    break;
  case 'b':
    lightZ -= 0.2;
    glutPostRedisplay();
    break;
  case 27:  /* Esc key */
    /* Demonstrate proper deallocation of Cg runtime data structures.
       Not strictly necessary if we are simply going to exit. */
    cgDestroyProgram(myCgVertexProgram);
    cgDestroyContext(myCgContext);
    exit(0);
    break;
  }
}

static void menu(int item)
{
  /* Pass menu item character code to keyboard callback. */
  keyboard((unsigned char)item, 0, 0);
}

/* Use a motion and mouse GLUT callback to allow the viewer and light to
   rotate around the monkey head and move the viewer up and down. */

static int beginx, beginy;
static int moving = 0;

void
motion(int x, int y)
{
  const float heightMax = 20,
              heightMin = -20;

  if (moving) {
    eyeAngle += 0.005*(beginx - x);
    eyeHeight += 0.03*(y - beginy);
    if (eyeHeight > heightMax) {
      eyeHeight = heightMax;
    }
    if (eyeHeight < heightMin) {
      eyeHeight = heightMin;
    }
    beginx = x;
    beginy = y;
    glutPostRedisplay();
  }
}

void
mouse(int button, int state, int x, int y)
{
  const int spinButton = GLUT_LEFT_BUTTON;

  if (button == spinButton && state == GLUT_DOWN) {
    moving = 1;
    beginx = x;
    beginy = y;
  }
  if (button == spinButton && state == GLUT_UP) {
    moving = 0;
  }
}

/* Platform-specific code to request synchronized buffer swaps. */

static void requestSynchronizedSwapBuffers(void)
{
#if defined(__APPLE__)
#ifdef CGL_VERSION_1_2
  const GLint sync = 1;
#else
  const long sync = 1;
#endif
  CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &sync);

#elif defined(_WIN32)
  if (wglSwapIntervalEXT) {
    wglSwapIntervalEXT(1);
  }
#else
  if (glXSwapIntervalSGI) {
    glXSwapIntervalSGI(1);
  }
#endif
}
