
/* cgfx_bumpdemo.c - OpenGL-based bump mapping example using a CgFX
   effect with Cg programs from Chapter 8 of "The Cg Tutorial"
   (Addison-Wesley, ISBN 0321194969). */

/* Explained in the whitepaper "Re-implementing the Follow-up Cg Runtime
   Tutorial with CgFX" by Mark Kilgard.  */

/* Requires the OpenGL Utility Toolkit (GLUT) and Cg runtime (version
   1.4 or higher). */

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>
#include <Cg/cg.h>
#include <Cg/cgGL.h>

CGcontext   myCgContext;
CGeffect    myCgEffect;
CGtechnique myCgTechnique;
CGparameter myCgEyePositionParam,
            myCgModelViewProjParam;

static void initCgFX();
static void initOpenGL();
static void display(void);
static void keyboard(unsigned char c, int x, int y);	

int main(int argc, char **argv)
{
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize(400, 400);
  glutInit(&argc, argv);
  glutCreateWindow("cgfx_bumpdemo");

  initCgFX();
  initOpenGL();

  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}

static void checkForCgError(const char *situation)
{
  CGerror error;
  const char *string = cgGetLastErrorString(&error);

  if (error != CG_NO_ERROR) {
    printf("cgfx_bumpdemo: %s: %s\n", situation, string);
    if (error == CG_COMPILER_ERROR) {
      printf("%s\n", cgGetLastListing(myCgContext));
    }
    exit(1);
  }
}

static void initCgFX(void)
{	
  myCgContext = cgCreateContext();
  cgGLRegisterStates(myCgContext);
  cgGLSetManageTextureParameters(myCgContext, CG_TRUE);
  checkForCgError("establishing Cg context");

  myCgEffect = cgCreateEffectFromFile(myCgContext, "bumpdemo.cgfx", NULL);
  checkForCgError("creating bumpdemo.cgfx effect");

  myCgTechnique = cgGetFirstTechnique(myCgEffect);
  while (myCgTechnique && cgValidateTechnique(myCgTechnique) == CG_FALSE) {
    fprintf(stderr, 
      "cgfx_bumpdemo: Technique %s did not validate.  Skipping.\n",
      cgGetTechniqueName(myCgTechnique));
    myCgTechnique = cgGetNextTechnique(myCgTechnique);
  }
  if (myCgTechnique) {
    fprintf(stderr, "cgfx_bumpdemo: Use technique %s.\n",
      cgGetTechniqueName(myCgTechnique));
  } else {
    fprintf(stderr, "cgfx_bumpdemo: No valid technique\n");
    exit(1);
  }

  myCgModelViewProjParam =
    cgGetEffectParameterBySemantic(myCgEffect, "ModelViewProjection");
  if (!myCgModelViewProjParam) {
    fprintf(stderr,
      "cgfx_bumpdemo: no parameter with ModelViewProjection semantic\n");
    exit(1);
  }
  myCgEyePositionParam =
    cgGetNamedEffectParameter(myCgEffect, "EyePosition");
  if (!myCgEyePositionParam) {
    fprintf(stderr, "cgfx_bumpdemo: no parameter named EyePosition\n");
    exit(1);
  }
}

/* OpenGL texture object (TO) handles. */
enum {
  TO_NORMALIZE_VECTOR_CUBE_MAP = 1,
  TO_NORMAL_MAP = 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 mipmapped 128x128 normal map for a brick pattern */
#include "brick_image.h"
};

static const GLubyte
myNormalizeVectorCubeMapImage[6*3*32*32] = {
/* RGB8 image data for normalization vector cube map with 32x32 faces */
#include "normcm_image.h"
};

static void useSamplerParameter(CGeffect effect,
                                 const char *paramName,
                                 GLuint texobj)
{
  CGparameter param;

  param = cgGetNamedEffectParameter(effect, paramName);
  if (!param) {
    fprintf(stderr, "cgfx_bumpdemo: expected effect parameter named %s\n",
      paramName);
    exit(1);
  }
  cgGLSetTextureParameter(param, texobj);
  cgSetSamplerState(param);
}

static void initOpenGL(void)
{	
  const GLubyte *image;
  unsigned int face;

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* Tightly packed texture data. */

/* OpenGL tokens for cube maps missing from Windows version of <GL/gl.h> */
#define GL_TEXTURE_CUBE_MAP                 0x8513
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X      0x8515
  glBindTexture(GL_TEXTURE_CUBE_MAP, TO_NORMALIZE_VECTOR_CUBE_MAP);
  useSamplerParameter(myCgEffect, "normalizeCube",
                      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);
  }

  glBindTexture(GL_TEXTURE_2D, TO_NORMAL_MAP);
  useSamplerParameter(myCgEffect, "normalMap",
                      TO_NORMAL_MAP);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 128, 128, 0,
    GL_RGB, GL_UNSIGNED_BYTE, myBrickNormalMapImage);

  glEnable(GL_DEPTH_TEST);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(
    60.0,   /* Field of view in degree */
    1.0,    /* Aspect ratio */ 
    0.1,    /* Z near */
    100.0); /* Z far */
  glMatrixMode(GL_MODELVIEW);
  glClearColor(0.1, 0.3, 0.6, 0.0);  /* Blue background */
} 

/* Draw a flat 2D patch that can be "rolled & bent" into a 3D torus by
   a vertex program. */	
void
drawFlatPatch(float rows, float columns)
{
  const float m = 1.0f/columns;
  const float n = 1.0f/rows;
  int i, j;

  for (i=0; i<columns; i++) {
    glBegin(GL_QUAD_STRIP);
    for (j=0; j<=rows; j++) {
      glVertex2f(i*m, j*n);
      glVertex2f((i+1)*m, j*n);
    }
    glVertex2f(i*m, 0);
    glVertex2f((i+1)*m, 0);
    glEnd();
  }
}

/* Initial scene state */
static float myEyeAngle = 0;

static void display(void)
{	
  const int sides = 20, rings = 40;
  const float eyeRadius = 18.0,
              eyeElevationRange = 8.0;
  float eyePosition[3];
  CGpass pass;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  eyePosition[0] = eyeRadius * sin(myEyeAngle);
  eyePosition[1] = eyeElevationRange * sin(myEyeAngle);
  eyePosition[2] = eyeRadius * cos(myEyeAngle);

  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 */

  cgGLSetStateMatrixParameter(myCgModelViewProjParam,
    CG_GL_MODELVIEW_PROJECTION_MATRIX,
    CG_GL_MATRIX_IDENTITY);
  cgSetParameter3fv(myCgEyePositionParam, eyePosition);

  pass = cgGetFirstPass(myCgTechnique);
  while (pass) {
    cgSetPassState(pass);
    drawFlatPatch(sides, rings);
    cgResetPassState(pass);
    pass = cgGetNextPass(pass);
  }

  glutSwapBuffers();
}

static void advanceAnimation(void)
{	
  myEyeAngle += 0.05f;
  if (myEyeAngle > 2*3.14159)
    myEyeAngle -= 2*3.14159;
  glutPostRedisplay();
}

static void keyboard(unsigned char c, int x, int y)
{
  static int animating = 0;

  switch (c) {
  case ' ':
    animating = !animating; /* Toggle */
    glutIdleFunc(animating ? advanceAnimation : NULL);
    break;
  case 27:  /* Esc key */
    cgDestroyEffect(myCgEffect);
    cgDestroyContext(myCgContext);
    exit(0);
    break;
  }
}
