
/* gs_graph_peaks.c - a CgFX 2.0 demo to render to Matlab "peaks" example
   function as a quad-mesh using mean value coordinates for better
   attribute interpolation. */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>   /* for exit */
#ifdef _WIN32
#include <windows.h>  /* for QueryPerformanceCounter */
#endif
#if __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#include <GL/glext.h>
#endif
#include <Cg/cg.h>    /* core Cg runtime API */
#include <Cg/cgGL.h>  /* 3D API specific Cg runtime API for OpenGL */

const char *programName = "gs_interp_quad"; /* Program name for messages. */

/* Cg global variables */
CGcontext   myCgContext;
CGeffect    myCgEffect;

static void handleFPS(void);
static void display(void);
static void reshape(int width, int height);
static void keyboard(unsigned char c, int x, int y);
static void initCg();
static void initMenu();
static void initOpenGL();

int main(int argc, char **argv)
{
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize(600, 300);
  glutInit(&argc, argv);
  glutCreateWindow(programName);

  initCg();
  initMenu();
  initOpenGL();

  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}

static void checkForCgError(CGerror error, const char *situation)
{
  if (error != CG_NO_ERROR) {
    printf("%s: %s: %s\n",
      programName, situation, cgGetErrorString(error));
#ifdef _WIN32
    MessageBox(0, "Cg compile error, see console window", programName, 0);
#endif
    exit(1);
  }
}

static void initCg(void)
{
  CGtechnique technique;
  int problems = 0;

  myCgContext = cgCreateContext();
  cgGLSetDebugMode( CG_FALSE );
  cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);
  cgGLRegisterStates(myCgContext);
  cgGLSetManageTextureParameters(myCgContext, CG_TRUE);
  checkForCgError(cgGetError(), "establishing Cg context");

  myCgEffect = cgCreateEffectFromFile(myCgContext, "gs_interp_quad.cgfx", NULL);
  if (!myCgEffect) {
      printf("%s\n", cgGetLastListing(myCgContext));
  }
  checkForCgError(cgGetError(), "creating gs_interp_quad.cgfx effect");

  technique = cgGetFirstTechnique(myCgEffect);
  if (technique && cgValidateTechnique(technique) == CG_FALSE) {
    fprintf(stderr, "%s: Technique %s did not validate.\n",
      programName, cgGetTechniqueName(technique));
    problems++;
  }
  technique = cgGetNextTechnique(technique);
  if (technique && cgValidateTechnique(technique) == CG_FALSE) {
    fprintf(stderr, "%s: Technique %s did not validate.\n",
      programName, cgGetTechniqueName(technique));
    problems++;
  }
  if (problems) {
    exit(1);
  }
}

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

void initMenu(void)
{
  glutCreateMenu(menu);
  glutAddMenuEntry("[ ] Toggle animation", ' ');
  glutAddMenuEntry("[f] Toggle frame rate", 'f');
  glutAddMenuEntry("[c] Toggle conventional quad rendering", 'c');
  glutAddMenuEntry("[v] Toggle view rotation", 'v');
  glutAddMenuEntry("[o] Toggle ortho/perspective view", 'o');
  glutAddMenuEntry("[r] Reset", 'r');
  glutAddMenuEntry("Quit", 27);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
}

static void initOpenGL(void)
{
  glClearColor(0.1, 0.3, 0.6, 0.0);  /* Blue background */
  glEnable(GL_DEPTH_TEST);
}

GLsizei viewWidth, viewHeight;
GLsizei windowWidth, windowHeight;

static void reshape(int width, int height)
{
  windowWidth = width;
  windowHeight = height;
  viewWidth = width/2;
  viewHeight = height;
}

/* Animation parameters. */
float spinAngle = 0.0;
float viewAngle = 0;
float viewRotateRate = 0;

static void perespectiveView(void)
{
  float viewWidthf, viewHeightf;
  float aspectRatio;
  const float eyeRadius = 1.8;
  float eyePosition[3];

  viewWidthf = viewWidth/2.0f;
  viewHeightf = viewHeight/2.0f;

  aspectRatio = viewWidthf / viewHeightf;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(
    60.0,        /* Field of view in degree */
    aspectRatio, /* Aspect ratio */ 
    0.1,         /* Z near */
    100.0);      /* Z far */

  eyePosition[0] = eyeRadius * sin(viewAngle);
  eyePosition[1] = 0;
  eyePosition[2] = eyeRadius * cos(viewAngle);

  glMatrixMode(GL_MODELVIEW);
  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 */
}

#define SQR(_x)  ((_x)*(_x))
#define CUBE(_x) ((_x)*(_x)*(_x))
#define PWR5(_x) ((_x)*(_x)*(_x)*(_x)*(_x))

float peaks(float x, float y)
{
  /* Generate a function with lots of local maxima and minima
     based on Matlab "peaks" routine evaluating the function:

  f(x,y) = 3*(1-x)^2*exp(-x^2 - (y+1)^2) ...
           - 10*(x/5 - x^3 - y^5)*exp(-x^2-y^2) ...
           - 1/3*exp(-(x+1)^2 - y^2)

  */
  float f = 3.0*SQR(1.0-x)*exp(-SQR(x) - SQR(y+1.0))
            - 10.0*(x/5.0 - CUBE(x) - PWR5(y))*exp(-SQR(x)-SQR(y))
            - 1.0/3.0*exp(-SQR(x+1.0) - SQR(y));
  return f;
}

GLfloat *generateMesh(float x0, float x1, float y0, float y1, int w, int h)
{
  float *mesh = malloc(sizeof(GLfloat) * (w+1) * (h+1));
  float *f = mesh;
  float x, y;
  float dx = (x1-x0) / w;
  float dy = (y1-y0) / h;
  int i, j;

  y = y0;
  for (j=0; j<h; j++) {
    x = x0;

    for (i=0; i<w; i++) {
      *f++ = peaks(x, y);
      x += dx;
    }
    x = x1;
    *f++ = peaks(x, y);
    y += dy;
  }
  y = y1;
  x = x0;
  for (i=0; i<w; i++) {
    *f++ = peaks(x, y);
    x += dx;
  }
  x = x1;
  *f++ = peaks(x, y);
  return mesh;
}

static const float pi  = 3.14159265358979323846,
                   pi2 = 2 * 3.14159265358979323846;

/* Draw the vertices of a single quadrilateral.  Twist the right 
   two vertices based on spinAngle. */
static void quadVertices(void)
{
  glColor3f(1,0,0);  /* red */
  glVertex3f(-0.8, -0.8, 0);
  glColor3f(0,1,0);  /* green */
  glVertex3f( 0.8, 0.8*cos(spinAngle + pi), 0.8*sin(spinAngle + pi));
  glColor3f(1,0,0);  /* red */
  glVertex3f( 0.8,  0.8*cos(spinAngle), 0.8*sin(spinAngle));
  glColor3f(0,0,1);  /* blue */
  glVertex3f(-0.8,  0.8, 0);
}

/* Draw a flat 2D patch that can be "rolled & bent" into a 3D torus by
   a vertex program. */
static void drawQuad(void)
{
  glBegin(GL_LINES_ADJACENCY_EXT);
  quadVertices();
  glEnd();
}

static void drawShadedQuad(CGtechnique technique)
{
  CGpass pass = cgGetFirstPass(technique);

  /* Iterate through rendering passes for technique (even
     though bumpdemo.cgfx has just one pass). */
  while (pass) {
    cgSetPassState(pass);
    drawQuad();
    cgResetPassState(pass);
    pass = cgGetNextPass(pass);
  }
}

static void drawLabel(float x, float y, const char *label)
{
  GLubyte dummy;
  const char *c;

  glRasterPos2f(0,0);
  glBitmap(0, 0, 0, 0, x, -y, &dummy);
  for (c = label; *c != '\0'; c++) {
    glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *c);
  }
}

static void drawLabels(const char *label1, const char *label2)
{
  glViewport(0,0, windowWidth, windowHeight);
  glPushMatrix();
  {
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    {
      glLoadIdentity();
      glOrtho(0, 1, 1, 0, -1, 1);
      glDisable(GL_DEPTH_TEST);

      glColor3f(1,1,1);
      drawLabel(20, 20, label1);
      drawLabel(20+viewWidth, 20, label2);

      /* Draw vertical yellow line to divide the two views. */
      glColor3f(1,1,0);
      glBegin(GL_LINES);
      glVertex2f(0.5, 0);
      glVertex2f(0.5, 1);
      glEnd();

      glEnable(GL_DEPTH_TEST);
    }
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
  }
  glPopMatrix();
}

/* Fixed-function GL_QUADS rendering if true; otherwise use Cg technique. */
int conventionalFixedFunction = 0;
/* Orthographic view if true, otherwise perspective. */
int orthoMode = 0;

static void display(void)
{
  CGtechnique technique1 = cgGetFirstTechnique(myCgEffect),
              technique2 = cgGetNextTechnique(technique1);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  if (orthoMode) {
    /* Configure orthographic view. */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(viewAngle * 360/pi2, 0, 1, 0);
  } else {
    /* Configure a perspective view. */
    perespectiveView();
  }

  glViewport(0, 0, viewWidth, viewHeight);
  if (conventionalFixedFunction) {
    glBegin(GL_QUADS);
    quadVertices();
    glEnd();
  } else {
    drawShadedQuad(technique1);
  }

  glViewport(viewWidth, 0, windowWidth-viewWidth, viewHeight);
  drawShadedQuad(technique2);

  drawLabels(conventionalFixedFunction ? "Fixed-function GL_QUADS" :
                                         cgGetTechniqueName(technique1),
             cgGetTechniqueName(technique2));

  handleFPS();
  glutSwapBuffers();
}

static int myDrawFPS = 1;

static void handleFPS(void)
{
#ifdef _WIN32
  static void drawFPS(double fpsRate);
  static __int64 freq = 0;
  static __int64 lastCount;  /* Timer count for last fps update */
  static int frameCount;     /* Number of frames for timing */
  static int fpsRate;        /* Current frames per second rate */
  static double lastFpsRate;
  __int64 newCount;
  
  if (!freq)
    QueryPerformanceFrequency((LARGE_INTEGER*) &freq);
  
  /* Update the frames per second count if we have gone past at least
     a second since the last update. */
  QueryPerformanceCounter((LARGE_INTEGER*) &newCount);
  frameCount++;
  if (((newCount - lastCount) > freq) && drawFPS) {
    double fpsRate;
    
    fpsRate = (double) (freq * (__int64) frameCount) /
              (double) (newCount - lastCount);
    lastCount = newCount;
    frameCount = 0;
    lastFpsRate = fpsRate;
  }
  if (myDrawFPS)
    drawFPS(lastFpsRate);
#endif
}

#ifdef _WIN32
static void drawFPS(double fpsRate)
{
  GLubyte dummy;
  char buffer[200], *c;

  glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
      glLoadIdentity();
      glOrtho(0, 1, 1, 0, -1, 1);
      glDisable(GL_DEPTH_TEST);
      glColor3f(1,1,1);
      glRasterPos2f(1,1);
      glBitmap(0, 0, 0, 0, -10*9, 15, &dummy);
      sprintf(buffer, "fps %0.1f", fpsRate);
      for (c = buffer; *c != '\0'; c++)
        glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *c);
      glEnable(GL_DEPTH_TEST);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}
#endif

static void advanceAnimation(void)
{
  spinAngle += 0.1;
  if (spinAngle > pi2) {
    spinAngle -= pi2;
  }
  viewAngle += viewRotateRate;
  if (viewAngle > pi2) {
    viewAngle -= pi2;
  }
  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 'v':
    if (viewRotateRate) {
      viewRotateRate = 0;
    } else {
      viewRotateRate = 0.04;
    }
    break;
  case 'c':
    conventionalFixedFunction = !conventionalFixedFunction;
    glutPostRedisplay();
    break;
  case 'o':
    orthoMode = !orthoMode;
    glutPostRedisplay();
    break;
  case 'r':
    spinAngle = 0;
    viewAngle = 0;
    glutPostRedisplay();
    break;
  case 'f':
    myDrawFPS = !myDrawFPS;
#ifndef _WIN32
    printf("%s: draw fps mode just for Windows\n", programName);
#endif
    glutPostRedisplay();
    break;
  case 27:  /* Esc key */
    /* Demonstrate proper deallocation of Cg runtime data structures.
       Not strictly necessary if we are simply going to exit. */
    cgDestroyEffect(myCgEffect);
    cgDestroyContext(myCgContext);
    exit(0);
    break;
  }
}
