// Copyright (C) 2005 Dave Griffiths
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

// Needs OpenGL, GLU and glut installed
// To compile:
// g++ nurbshowto.cpp -o nurbshowto -lGL -lGLU -lglut

#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

// NURBS stands for "non uniform rational b spline"
// I'll attempt an explanation below
//
// In a polygon representation of a surface a shape is directly described by the vertex 
// positions of the polygons it is made from. NURBS patches are an example of a "higher 
// order surface". What that means is that the surface is described by parameters which 
// form a shape indirectly, by a mathematical function, which is then converted into 
// polygons in a seperate step. 
//
// It's easier to explain NURBS if we begin with curves (or splines to be precise) rather
// than surfaces or patches.
//
// Any NURBS curve can be defined by it's degree, some control vertices, it's order
// and a knot vector.
//
// The degree refers to the polynomial degree of the curve, degree 1 is a straight 
// line (linear) degree 2 is a quadratic curve and 3 is a cubic curve. Degrees 
// correspond to the highest continuity possible with the resulting curve:
//
// Degree 1 - continuous is position only, i.e straight connected lines
// Degree 2 - continuous in curve direction (or tangent) - actually curvy
// Degree 3 - continuous in curve tangent direction and magnitude 
//
// Degree 2 curves look smooth, but highlights do not move correctly over them, so
// degree 3 are most often used (highlights are very important in the car industry
// which is where these curves come from).
//
// The control vertices largely describe the shape of the curve, and are the main things 
// which are edited for general purpose use. Each cv also has a weight associated with it
// which biases the curve in the direction of that cv. This ability is where the rational
// part of NURBS comes from. Weights do not seem to be supported by the GLU tesselator?
//
// The order of a curve is simply the degree + 1 and is the number of control vertices 
// that influence the curve at any given position, i.e. changing cv 1 on this curve won't 
// change the shape of the second half of the curve.
//
//     2-------3
//    /  .-----.\
//   /  /       \\
//  1             4             7
//                 \\       /  /
//                  \'-----'  /
//                   5-------6 
//
// Knots give you the ability to change or break the continuity of the curve. They need to 
// be in ascending order, and the ratios between them are important, rather than the values.
// If they increase in equal fashion then the curve is uniform. Duplicating knots (and cvs) 
// in the middle of the curve means that we can break the continuity to give sharp corners 
// or creases in the surface - this would comprise a non-uniform curve, the duplicates 
// can't be more than the degree of the curve.
//
// Notice that the curve does not meet up with the first or last control vertex above. 
// Another use for knot and cv duplication is to fix this by duplicating degree times the 
// first or last cv/knot to snap the curve to the corresponding cv position.
//
// The number of knots in a curve is the number of cvs + degree + 1.
//
// NURBS patches are made up from two sets of curves which share control vertices and knots.
// In this example one direction is called u and the other is v. The rules regarding order,
// degree and knots all apply in the same manner - but they do not have to be the same for 
// u and v curves comprising the patch. 
// 
// Tesselators are algorithms which take the description of a curve or patch and break it
// down into line segments or polygons in order to render it. We are using the standard
// GLU tesselator here to do the heavy work. One interesting ability of this tesselator is
// that it is dynamic - in that it changes the tesselation as the patch deforms, to use the
// minimum polygons to describe the shape as best it can.

// our globals
const int uorder=4;        // we don't need to directly refer to the degree in this code
const int vorder=4;        // keep u and v order the same for simplicity
const int ucvcount=10;     // number of cvs in u
const int vcvcount=10;     // number of cvs in v
const int stride=3;        // just refers to the number of float values in each cv 
const int numknots=ucvcount+uorder;
const int numcvs=ucvcount*vcvcount*stride;
float knots[numknots];     // somewhere to keep the knots
float cvs[numcvs];         // somewhere to keep the cvs
int frame=0;               // for cheezy animation purposes

GLUnurbsObj *mynurbs=NULL; // the nurbs tesselator (or rather a pointer to what will be it)

// setup the GL state - just gets run once at the start
void Setup()
{
	// setup a simple light
	float col[3] = {1,1,1};
	float pos[3] = {100,10,1};
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, col);
	glLightfv(GL_LIGHT0, GL_POSITION, pos);
	
	// turn the z buffer on
	glEnable(GL_DEPTH_TEST);
	
	// setup a perspective projection
    glMatrixMode (GL_PROJECTION);
  	glLoadIdentity();
	glFrustum(-1,1,-0.75,0.75,1,100);
	
	// set the modelview matrix
	glMatrixMode (GL_MODELVIEW);
  	glLoadIdentity();
	
	// set point size and colour for the cv rendering
	glPointSize(4);
	glColor3f(0,0,1);
	
	// set the clear colour to something other than black
	glClearColor(0.5,0.5,0.5,1);
	
	// position the camera in the world
	glTranslatef(0,0.5,-3);
	glRotatef(-55,1,0,0);
	glRotatef(30,0,0,1);
	
	// now, make the nurbs tesselator we are going to use
	mynurbs = gluNewNurbsRenderer();
	
	// these lines sets the tesselation error metrics, or how to 
	// know when to split the surface into smaller triagles. these settings 
	// are specific to the GLU tesselator, and not to nurbs in general.
	gluNurbsProperty(mynurbs, GLU_SAMPLING_METHOD, GLU_PARAMETRIC_ERROR);
	gluNurbsProperty(mynurbs, GLU_PARAMETRIC_TOLERANCE, 5.0);
	
	// set up the knot vector - make it continuous
	for (int knot=0; knot<numknots; knot++) 
	{
		knots[knot]=knot;
	}	
	
	// setup automatic normal calculation for the surface
	glEnable(GL_MAP2_VERTEX_3);
	glEnable(GL_AUTO_NORMAL);
	
}

// called once per frame before render, just change our cvs with a silly function
void Update()
{
	int count=0;
	int halfvcvcount = vcvcount/2;
	int halfucvcount = ucvcount/2;
	
	for (int v=-halfucvcount; v<halfucvcount; v++)
	{
		for (int u=-halfvcvcount; u<halfvcvcount; u++)
		{
			cvs[count++]=(v/(float)vcvcount)*3;
			cvs[count++]=(u/(float)ucvcount)*3;
			cvs[count++]=sin(2*sqrt(u*u+v*v)+frame*0.01)*0.5;
		}
	}
	frame++;
}

// called once per frame to show the surface
void Render()
{
	// the interesting part
	
	// sets the rendering style - wireframe, but swap to the line below
	// to get a solid surface (wireframe shows off the dynamic tesselation)
	gluNurbsProperty(mynurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON);
	//gluNurbsProperty(mynurbs, GLU_DISPLAY_MODE, GLU_FILL);
	
	// tells GLU we are going to describe a nurbs patch
	gluBeginSurface(mynurbs);
	
	// send it the definition and pointers to cv and knot data
	gluNurbsSurface(mynurbs,numknots,knots,numknots,knots,
		 			vcvcount*stride,stride,
					cvs,uorder,vorder,GL_MAP2_VERTEX_3);

	// thats all...
	gluEndSurface(mynurbs);
	
	// render the control vertex positions
	glDisable(GL_LIGHTING);
	glBegin(GL_POINTS);
	for (int n=0; n<vcvcount*ucvcount; n++)
	{
		glVertex3fv(&cvs[n*3]);
	}
	glEnd();
	glEnable(GL_LIGHTING);
}

void DisplayCallback()
{
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	Update();
	Render();
	glutSwapBuffers();
}

void IdleCallback()
{
	glutPostRedisplay();
}

int main(int argc, char *argv[])
{		
	glutInitWindowSize(720,576) ;
  	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
  	glutCreateWindow("nurbshowto");
	glutDisplayFunc(DisplayCallback);
	glutIdleFunc(IdleCallback);
	
	Setup();
	
	glutMainLoop();
  	
	// cleans up the tesselator
	gluDeleteNurbsRenderer(mynurbs);

	return 0;
}



