/**
 * surfgrid.c - simple test of polygon offset
 *
 * GLUT distribution version  $Revision: 1.1 $
 *
 * usage:
 *	surfgrid [-f]
 *
 * options:
 *	-f	run on full screen
 *
 * keys:
 *	p	toggle polygon offset
 *      F       increase polygon offset factor
 *      f       decrease polygon offset factor
 *      B       increase polygon offset bias
 *      b       decrease polygon offset bias
 *	g	toggle grid drawing
 *	s	toggle smooth/flat shading
 *	n	toggle whether to use GL evaluators or GLU nurbs
 *	u	decr number of segments in U direction
 *	U	incr number of segments in U direction
 *	v	decr number of segments in V direction
 *	V	incr number of segments in V direction
 *	escape	quit
 */

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

#define W 600
#define H 600

float z_axis[] =
{0.0, 0.0, 1.0};

void
norm(float v[3])
{
  float r;

  r = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);

  v[0] /= r;
  v[1] /= r;
  v[2] /= r;
}

void
cross(float v1[3], float v2[3], float result[3])
{
  result[0] = v1[1] * v2[2] - v1[2] * v2[1];
  result[1] = v1[2] * v2[0] - v1[0] * v2[2];
  result[2] = v1[0] * v2[1] - v1[1] * v2[0];
}

float
length(float v[3])
{
  float r = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  return r;
}

static int winwidth = W, winheight = H;
GLUnurbsObj *nobj;
GLuint surflist, gridlist;

int useglunurbs = 0;
int smooth = 1;
GLboolean tracking = GL_FALSE;
int showgrid = 1;
int showsurf = 1;
int fullscreen = 0;
float modelmatrix[16];
float factor = 0.5;
float bias = 0.002;
int usegments = 4;
int vsegments = 4;

int spindx, spindy;
int startx, starty;
int curx, cury;
int prevx, prevy;       /* to get good deltas using glut */

void redraw(void);
void createlists(void);

/* Control points of the torus in Bezier form.  Can be rendered
   using OpenGL evaluators. */
static GLfloat torusbezierpts[] =
{
/* *INDENT-OFF* */
   4.0, 0.0, 0.0, 4.0, 2.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0,
   3.0, 0.0, 1.0, 2.0, 4.0, 0.0, 1.0, 2.0, 8.0, 0.0, 0.0, 4.0,
   8.0, 0.0, 0.0, 4.0, 4.0, 0.0,-1.0, 2.0, 3.0, 0.0,-1.0, 2.0,
   3.0, 0.0,-1.0, 2.0, 2.0, 0.0,-1.0, 2.0, 4.0, 0.0, 0.0, 4.0,
   2.0,-2.0, 0.0, 2.0, 1.0,-1.0, 0.5, 1.0, 1.5,-1.5, 0.5, 1.0,
   1.5,-1.5, 0.5, 1.0, 2.0,-2.0, 0.5, 1.0, 4.0,-4.0, 0.0, 2.0,
   4.0,-4.0, 0.0, 2.0, 2.0,-2.0,-0.5, 1.0, 1.5,-1.5,-0.5, 1.0,
   1.5,-1.5,-0.5, 1.0, 1.0,-1.0,-0.5, 1.0, 2.0,-2.0, 0.0, 2.0,
   0.0,-2.0, 0.0, 2.0, 0.0,-1.0, 0.5, 1.0, 0.0,-1.5, 0.5, 1.0,
   0.0,-1.5, 0.5, 1.0, 0.0,-2.0, 0.5, 1.0, 0.0,-4.0, 0.0, 2.0,
   0.0,-4.0, 0.0, 2.0, 0.0,-2.0,-0.5, 1.0, 0.0,-1.5,-0.5, 1.0,
   0.0,-1.5,-0.5, 1.0, 0.0,-1.0,-0.5, 1.0, 0.0,-2.0, 0.0, 2.0,
   0.0,-2.0, 0.0, 2.0, 0.0,-1.0, 0.5, 1.0, 0.0,-1.5, 0.5, 1.0,
   0.0,-1.5, 0.5, 1.0, 0.0,-2.0, 0.5, 1.0, 0.0,-4.0, 0.0, 2.0,
   0.0,-4.0, 0.0, 2.0, 0.0,-2.0,-0.5, 1.0, 0.0,-1.5,-0.5, 1.0,
   0.0,-1.5,-0.5, 1.0, 0.0,-1.0,-0.5, 1.0, 0.0,-2.0, 0.0, 2.0,
  -2.0,-2.0, 0.0, 2.0,-1.0,-1.0, 0.5, 1.0,-1.5,-1.5, 0.5, 1.0,
  -1.5,-1.5, 0.5, 1.0,-2.0,-2.0, 0.5, 1.0,-4.0,-4.0, 0.0, 2.0,
  -4.0,-4.0, 0.0, 2.0,-2.0,-2.0,-0.5, 1.0,-1.5,-1.5,-0.5, 1.0,
  -1.5,-1.5,-0.5, 1.0,-1.0,-1.0,-0.5, 1.0,-2.0,-2.0, 0.0, 2.0,
  -4.0, 0.0, 0.0, 4.0,-2.0, 0.0, 1.0, 2.0,-3.0, 0.0, 1.0, 2.0,
  -3.0, 0.0, 1.0, 2.0,-4.0, 0.0, 1.0, 2.0,-8.0, 0.0, 0.0, 4.0,
  -8.0, 0.0, 0.0, 4.0,-4.0, 0.0,-1.0, 2.0,-3.0, 0.0,-1.0, 2.0,
  -3.0, 0.0,-1.0, 2.0,-2.0, 0.0,-1.0, 2.0,-4.0, 0.0, 0.0, 4.0,
  -4.0, 0.0, 0.0, 4.0,-2.0, 0.0, 1.0, 2.0,-3.0, 0.0, 1.0, 2.0,
  -3.0, 0.0, 1.0, 2.0,-4.0, 0.0, 1.0, 2.0,-8.0, 0.0, 0.0, 4.0,
  -8.0, 0.0, 0.0, 4.0,-4.0, 0.0,-1.0, 2.0,-3.0, 0.0,-1.0, 2.0,
  -3.0, 0.0,-1.0, 2.0,-2.0, 0.0,-1.0, 2.0,-4.0, 0.0, 0.0, 4.0,
  -2.0, 2.0, 0.0, 2.0,-1.0, 1.0, 0.5, 1.0,-1.5, 1.5, 0.5, 1.0,
  -1.5, 1.5, 0.5, 1.0,-2.0, 2.0, 0.5, 1.0,-4.0, 4.0, 0.0, 2.0,
  -4.0, 4.0, 0.0, 2.0,-2.0, 2.0,-0.5, 1.0,-1.5, 1.5,-0.5, 1.0,
  -1.5, 1.5,-0.5, 1.0,-1.0, 1.0,-0.5, 1.0,-2.0, 2.0, 0.0, 2.0,
   0.0, 2.0, 0.0, 2.0, 0.0, 1.0, 0.5, 1.0, 0.0, 1.5, 0.5, 1.0,
   0.0, 1.5, 0.5, 1.0, 0.0, 2.0, 0.5, 1.0, 0.0, 4.0, 0.0, 2.0,
   0.0, 4.0, 0.0, 2.0, 0.0, 2.0,-0.5, 1.0, 0.0, 1.5,-0.5, 1.0,
   0.0, 1.5,-0.5, 1.0, 0.0, 1.0,-0.5, 1.0, 0.0, 2.0, 0.0, 2.0,
   0.0, 2.0, 0.0, 2.0, 0.0, 1.0, 0.5, 1.0, 0.0, 1.5, 0.5, 1.0,
   0.0, 1.5, 0.5, 1.0, 0.0, 2.0, 0.5, 1.0, 0.0, 4.0, 0.0, 2.0,
   0.0, 4.0, 0.0, 2.0, 0.0, 2.0,-0.5, 1.0, 0.0, 1.5,-0.5, 1.0,
   0.0, 1.5,-0.5, 1.0, 0.0, 1.0,-0.5, 1.0, 0.0, 2.0, 0.0, 2.0,
   2.0, 2.0, 0.0, 2.0, 1.0, 1.0, 0.5, 1.0, 1.5, 1.5, 0.5, 1.0,
   1.5, 1.5, 0.5, 1.0, 2.0, 2.0, 0.5, 1.0, 4.0, 4.0, 0.0, 2.0,
   4.0, 4.0, 0.0, 2.0, 2.0, 2.0,-0.5, 1.0, 1.5, 1.5,-0.5, 1.0,
   1.5, 1.5,-0.5, 1.0, 1.0, 1.0,-0.5, 1.0, 2.0, 2.0, 0.0, 2.0,
   4.0, 0.0, 0.0, 4.0, 2.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0,
   3.0, 0.0, 1.0, 2.0, 4.0, 0.0, 1.0, 2.0, 8.0, 0.0, 0.0, 4.0,
   8.0, 0.0, 0.0, 4.0, 4.0, 0.0,-1.0, 2.0, 3.0, 0.0,-1.0, 2.0,
   3.0, 0.0,-1.0, 2.0, 2.0, 0.0,-1.0, 2.0, 4.0, 0.0, 0.0, 4.0,
/* *INDENT-ON* */

};

/* Control points of a torus in NURBS form.  Can be rendered using
   the GLU NURBS routines. */
static GLfloat torusnurbpts[] =
{
/* *INDENT-OFF* */
   4.0, 0.0, 0.0, 4.0, 2.0, 0.0, 1.0, 2.0, 4.0, 0.0, 1.0, 2.0,
   8.0, 0.0, 0.0, 4.0, 4.0, 0.0,-1.0, 2.0, 2.0, 0.0,-1.0, 2.0,
   4.0, 0.0, 0.0, 4.0, 2.0,-2.0, 0.0, 2.0, 1.0,-1.0, 0.5, 1.0,
   2.0,-2.0, 0.5, 1.0, 4.0,-4.0, 0.0, 2.0, 2.0,-2.0,-0.5, 1.0,
   1.0,-1.0,-0.5, 1.0, 2.0,-2.0, 0.0, 2.0,-2.0,-2.0, 0.0, 2.0,
  -1.0,-1.0, 0.5, 1.0,-2.0,-2.0, 0.5, 1.0,-4.0,-4.0, 0.0, 2.0,
  -2.0,-2.0,-0.5, 1.0,-1.0,-1.0,-0.5, 1.0,-2.0,-2.0, 0.0, 2.0,
  -4.0, 0.0, 0.0, 4.0,-2.0, 0.0, 1.0, 2.0,-4.0, 0.0, 1.0, 2.0,
  -8.0, 0.0, 0.0, 4.0,-4.0, 0.0,-1.0, 2.0,-2.0, 0.0,-1.0, 2.0,
  -4.0, 0.0, 0.0, 4.0,-2.0, 2.0, 0.0, 2.0,-1.0, 1.0, 0.5, 1.0,
  -2.0, 2.0, 0.5, 1.0,-4.0, 4.0, 0.0, 2.0,-2.0, 2.0,-0.5, 1.0,
  -1.0, 1.0,-0.5, 1.0,-2.0, 2.0, 0.0, 2.0, 2.0, 2.0, 0.0, 2.0,
   1.0, 1.0, 0.5, 1.0, 2.0, 2.0, 0.5, 1.0, 4.0, 4.0, 0.0, 2.0,
   2.0, 2.0,-0.5, 1.0, 1.0, 1.0,-0.5, 1.0, 2.0, 2.0, 0.0, 2.0,
   4.0, 0.0, 0.0, 4.0, 2.0, 0.0, 1.0, 2.0, 4.0, 0.0, 1.0, 2.0,
   8.0, 0.0, 0.0, 4.0, 4.0, 0.0,-1.0, 2.0, 2.0, 0.0,-1.0, 2.0,
   4.0, 0.0, 0.0, 4.0,
/* *INDENT-ON* */

};

void
move(int x, int y)
{
  prevx = curx;
  prevy = cury;
  curx = x;
  cury = y;
  if (curx != startx || cury != starty) {
    glutPostRedisplay();
    startx = curx;
    starty = cury;
  }
}

void
button(int button, int state, int x, int y)
{
  if (button != GLUT_LEFT_BUTTON)
    return;
  switch (state) {
  case GLUT_DOWN:
    prevx = curx = startx = x;
    prevy = cury = starty = y;
    spindx = 0;
    spindy = 0;
    tracking = GL_TRUE;
    break;
  case GLUT_UP:
    /* 
     * If user released the button while moving the mouse, keep
     * spinning.
     */
    if (x != prevx || y != prevy) {
      spindx = x - prevx;
      spindy = y - prevy;
    }
    tracking = GL_FALSE;
    break;
  }
}

/* Maintain a square window when resizing */
void
reshape(int width, int height)
{
  int size;
  size = (width < height ? width : height);
  glViewport((width - size) / 2, (height - size) / 2, size, size);
  glutReshapeWindow(size, size);
  glutPostRedisplay();
}

void
gridmaterials(void)
{
  static float front_mat_diffuse[] =
  {1.0, 1.0, 0.4, 1.0};
  static float front_mat_ambient[] =
  {0.1, 0.1, 0.1, 1.0};
  static float back_mat_diffuse[] =
  {1.0, 0.0, 0.0, 1.0};
  static float back_mat_ambient[] =
  {0.1, 0.1, 0.1, 1.0};

  glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
  glMaterialfv(GL_FRONT, GL_AMBIENT, front_mat_ambient);
  glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);
  glMaterialfv(GL_BACK, GL_AMBIENT, back_mat_ambient);
}

void
surfacematerials(void)
{
  static float front_mat_diffuse[] =
  {0.2, 0.7, 0.4, 1.0};
  static float front_mat_ambient[] =
  {0.1, 0.1, 0.1, 1.0};
  static float back_mat_diffuse[] =
  {1.0, 1.0, 0.2, 1.0};
  static float back_mat_ambient[] =
  {0.1, 0.1, 0.1, 1.0};

  glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
  glMaterialfv(GL_FRONT, GL_AMBIENT, front_mat_ambient);
  glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);
  glMaterialfv(GL_BACK, GL_AMBIENT, back_mat_ambient);
}

void
init(void)
{
  static float ambient[] =
  {0.0, 0.0, 0.0, 1.0};
  static float diffuse[] =
  {1.0, 1.0, 1.0, 1.0};
  static float position[] =
  {90.0, 90.0, -150.0, 0.0};
  static float lmodel_ambient[] =
  {1.0, 1.0, 1.0, 1.0};

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(40.0, 1.0, 2.0, 200.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glGetFloatv(GL_MODELVIEW_MATRIX, modelmatrix);

  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_AUTO_NORMAL);
  glFrontFace(GL_CCW);

  glEnable(GL_MAP2_VERTEX_4);
  glClearColor(0.25, 0.25, 0.5, 0.0);

#if GL_EXT_polygon_offset
  glPolygonOffsetEXT(factor, bias);
  glEnable(GL_POLYGON_OFFSET_EXT);
#endif

  nobj = gluNewNurbsRenderer();
#ifdef GLU_VERSION_1_1  /* New GLU 1.1 interface. */
  gluNurbsProperty(nobj, GLU_SAMPLING_METHOD, GLU_DOMAIN_DISTANCE);
#endif

  surflist = glGenLists(1);
  gridlist = glGenLists(1);
  createlists();
}

void
drawmesh(void)
{
  int i, j;
  float *p;

  int up2p = 4;
  int uorder = 3, vorder = 3;
  int nu = 4, nv = 4;
  int vp2p = up2p * uorder * nu;

  for (j = 0; j < nv; j++) {
    for (i = 0; i < nu; i++) {
      p = torusbezierpts + (j * vp2p * vorder) + (i * up2p * uorder);
#if GL_EXT_polygon_offset
      glPolygonOffsetEXT(factor, bias);
#endif
      glMap2f(GL_MAP2_VERTEX_4, 0.0, 1.0, up2p, 3, 0.0, 1.0, vp2p, 3,
        (void *) p);
      if (showsurf) {
        surfacematerials();
        glEvalMesh2(GL_FILL, 0, usegments, 0, vsegments);
      }
      if (showgrid) {
        gridmaterials();
        glEvalMesh2(GL_LINE, 0, usegments, 0, vsegments);
      }
    }
  }
}

void
redraw(void)
{
  int dx, dy;
  float v[3], rot[3];
  float len, ang;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glColor3f(1, 0, 0);

  if (tracking) {
    dx = curx - startx;
    dy = cury - starty;
  } else {
    dx = spindx;
    dy = spindy;
  }
  if (dx || dy) {
    dy = -dy;
    v[0] = dx;
    v[1] = dy;
    v[2] = 0;

    len = length(v);
    ang = -len / 600 * 360;
    norm(v);
    cross(v, z_axis, rot);

    /* This is certainly not recommended for programs that care
       about performance or numerical stability: we concatenate
       the rotation onto the current modelview matrix and read
       the matrix back, thus saving ourselves from writing our
       own matrix manipulation routines.  */
    glLoadIdentity();
    glRotatef(ang, rot[0], rot[1], rot[2]);
    glMultMatrixf(modelmatrix);
    glGetFloatv(GL_MODELVIEW_MATRIX, modelmatrix);
  }
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -10.0);
  glMultMatrixf(modelmatrix);

  if (useglunurbs) {
    if (showsurf)
      glCallList(surflist);
    if (showgrid)
      glCallList(gridlist);
  } else {
    glMapGrid2f(usegments, 0.0, 1.0, vsegments, 0.0, 1.0);
    drawmesh();
  }

  glutSwapBuffers();
}

static void
usage(void)
{
  printf("usage: surfgrid [-f]\n");
  exit(-1);
}

/* what to do when a menu item is selected. This function also handles
   keystroke events.  */
void
menu(int item)
{
  switch (item) {
  case 'p':
#if GL_EXT_polygon_offset
    if (glIsEnabled(GL_POLYGON_OFFSET_EXT)) {
      glDisable(GL_POLYGON_OFFSET_EXT);
      printf("disabling polygon offset\n");
    } else {
      glEnable(GL_POLYGON_OFFSET_EXT);
      printf("enabling polygon offset\n");
    }
#endif
    break;
  case 'F':
    factor += 0.1;
    printf("factor: %8.4f\n", factor);
    break;
  case 'f':
    factor -= 0.1;
    printf("factor: %8.4f\n", factor);
    break;
  case 'B':
    bias += 0.0001;
    printf("bias:  %8.4f\n", bias);
    break;
  case 'b':
    bias -= 0.0001;
    printf("bias:  %8.4f\n", bias);
    break;
  case 'g':
    showgrid = !showgrid;
    break;
  case 'n':
    useglunurbs = !useglunurbs;
    break;
  case 's':
    smooth = !smooth;
    if (smooth) {
      glShadeModel(GL_SMOOTH);
    } else {
      glShadeModel(GL_FLAT);
    }
    break;
  case 't':
    showsurf = !showsurf;
    break;
  case 'u':
    usegments = (usegments < 2 ? 1 : usegments - 1);
    createlists();
    break;
  case 'U':
    usegments++;
    createlists();
    break;
  case 'v':
    vsegments = (vsegments < 2 ? 1 : vsegments - 1);
    createlists();
    break;
  case 'V':
    vsegments++;
    createlists();
    break;
  case '\033':         /* ESC key: quit */
    exit(0);
    break;
  }
  glutPostRedisplay();
}

/* ARGSUSED1 */
void
key(unsigned char key, int x, int y)
{
  menu((int) key);
}

void
animate(void)
{
  if (!tracking && (spindx != 0 || spindy != 0))
    glutPostRedisplay();
}

int
main(int argc, char **argv)
{
  int i;

  glutInit(&argc, argv);  /* initialize glut, processing
                             arguments */

  for (i = 1; i < argc; i++) {
    if (argv[i][0] == '-') {
      switch (argv[i][1]) {
      case 'f':
        fullscreen = 1;
        break;
      default:
        usage();
        break;
      }
    } else {
      usage();
    }
  }

  glutInitWindowSize(winwidth, winheight);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
  glutCreateWindow("surfgrid");

  /* create a menu for the right mouse button */
  glutCreateMenu(menu);
#if GL_EXT_polygon_offset
  glutAddMenuEntry("p: toggle polygon offset", 'p');
#endif
  glutAddMenuEntry("F: increase factor", 'F');
  glutAddMenuEntry("f: decrease factor", 'f');
  glutAddMenuEntry("B: increase bias", 'B');
  glutAddMenuEntry("b: decrease bias", 'b');
  glutAddMenuEntry("g: toggle grid", 'g');
  glutAddMenuEntry("s: toggle smooth shading", 's');
  glutAddMenuEntry("t: toggle surface", 't');
  glutAddMenuEntry("n: toggle GL evalutators/GLU nurbs", 'n');
  glutAddMenuEntry("u: decrement u segments", 'u');
  glutAddMenuEntry("U: increment u segments", 'U');
  glutAddMenuEntry("v: decrement v segments", 'v');
  glutAddMenuEntry("V: increment v segments", 'V');
  glutAddMenuEntry("<esc>: exit program", '\033');
  glutAttachMenu(GLUT_RIGHT_BUTTON);

  /* set callbacks */
  glutKeyboardFunc(key);
  glutDisplayFunc(redraw);
  glutReshapeFunc(reshape);
  glutMouseFunc(button);
  glutMotionFunc(move);
  glutIdleFunc(animate);

#if GL_EXT_polygon_offset
  if (!glutExtensionSupported("GL_EXT_polygon_offset")) {
    printf("Warning: "
      "GL_EXT_polygon_offset not supported on this machine... "
      "trying anyway\n");
  }
#else
  printf("Warning: not compiled with GL_EXT_polygon_offset support.\n");
#endif

  init();
  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}

float circleknots[] =
{0.0, 0.0, 0.0, 0.25, 0.50, 0.50, 0.75, 1.0, 1.0, 1.0};

void
createlists(void)
{
#ifdef GLU_VERSION_1_1  /* New GLU 1.1 interface. */
  gluNurbsProperty(nobj, GLU_U_STEP, (usegments - 1) * 4);
  gluNurbsProperty(nobj, GLU_V_STEP, (vsegments - 1) * 4);

  gluNurbsProperty(nobj, GLU_DISPLAY_MODE, GLU_FILL);
#endif
  glNewList(surflist, GL_COMPILE);
  surfacematerials();
  gluBeginSurface(nobj);
  gluNurbsSurface(nobj, 10, circleknots, 10, circleknots,
    4, 28, torusnurbpts, 3, 3, GL_MAP2_VERTEX_4);
  gluEndSurface(nobj);
  glEndList();

  gluNurbsProperty(nobj, GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON);
  glNewList(gridlist, GL_COMPILE);
  gridmaterials();
  gluBeginSurface(nobj);
  gluNurbsSurface(nobj, 10, circleknots, 10, circleknots,
    4, 28, torusnurbpts, 3, 3, GL_MAP2_VERTEX_4);
  gluEndSurface(nobj);
  glEndList();
}
