/*
	Interpolating specific points with cubic blending functions.  2D operation only.
	This code produces examples of three different kinds of spline curves, Catmull-Rom, Bezier, or
	quadratic, depending on the #define settings.

	Source file to be used with
	Cunningham, Computer Graphics: Programming in OpenGL for Visual Communication, Prentice-Hall, 2007

	Intended for class use only
*/
#include <GLUT/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#undef   CATMULLROM
#undef   BEZIER
#define  QUADRATIC

#ifdef CATMULLROM
#define f0(t) 0.5*(-(t)*(t)*(t)+2.*(t)*(t)-(t))
#define f1(t) 0.5*(3.*(t)*(t)*(t)-5.*(t)*(t)+2.)
#define f2(t) 0.5*(-3.*(t)*(t)*(t)+4.*(t)*(t)+(t))
#define f3(t) 0.5*((t)*(t)*(t)-(t)*(t))
#endif

#ifdef BEZIER
#define f0(t) (1.-(t))*(1.-(t))*(1.-(t))
#define f1(t) 3.*(t)*(1.-(t))*(1.-(t))
#define f2(t) 3.*(t)*(t)*(1.-(t))
#define f3(t) (t)*(t)*(t)
#endif

#ifdef QUADRATIC
#define f0(t) (1.-(t))*(1.-(t))
#define f1(t) 2.*(t)*(1.-(t))
#define f2(t) (t)*(t)
#endif

// function prototypes
void myinit(void);
void drawAxis(void);
void drawAxes(void);
void curve(void);
void display( void );
void reshape(int w,int h);

//	control points for each curve segment
#ifdef BEZIER
#define CURVE_SIZE 4
static GLfloat ctrlpts[CURVE_SIZE][3] = {	{ -1., -1.,  0.},
											{  1.,  4.,  0.},
											{  3.,  1.,  0.},
											{  2., -2.,  0.}};
#endif

#ifdef CATMULLROM
#define CURVE_SIZE 4
static GLfloat ctrlpts[CURVE_SIZE][3] = {	{ -1., -1.,  0.},
											{  1.,  4.,  0.},
											{  3.,  1.,  0.},
											{  2., -2.,  0.}};
#endif

#ifdef QUADRATIC
#define CURVE_SIZE 3
static GLfloat ctrlpts[CURVE_SIZE][3] = {	{ -1., -1.,  0.},
											{  1.,  4.,  0.},
											{  3.,  1.,  0.}};
#endif

void myinit(void)
{ 
	glClearColor( 0.0, 0.0, 0.0, 0.0 );
	glEnable(GL_DEPTH_TEST);
}

void drawAxis(void)
{
//	draw one axis, the Z-axis, as the template for all axes; axes all white
    GLfloat white[]={1.0, 1.0, 1.0, 1.0};
	
//  Draw the standard axis in Z-orientation
	glColor3fv(white);
    glBegin(GL_QUAD_STRIP);
      glVertex3f( 0.02, 0.02,  3.0 );
      glVertex3f( 0.02, 0.02, -3.0 );
      glVertex3f(-0.02, 0.02,  3.0 );
      glVertex3f(-0.02, 0.02, -3.0 );
      glVertex3f(-0.02,-0.02,  3.0 );
      glVertex3f(-0.02,-0.02, -3.0 );
      glVertex3f( 0.02,-0.02,  3.0 );
      glVertex3f( 0.02,-0.02, -3.0 );
      glVertex3f( 0.02, 0.02,  3.0 );
      glVertex3f( 0.02, 0.02, -3.0 );
    glEnd();
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 3.0 );
    glutSolidCone( 0.1, 0.2, 20, 20 );
    glPopMatrix();
}

void drawAxes(void)
{
//  Draw the standard axis in X-orientation
    glPushMatrix();
    glRotatef( 90.0, 0.0, 1.0, 0.0 );
    drawAxis();
    glPopMatrix();
//  Draw the standard axis in Y-orientation
    glPushMatrix();
    glRotatef( -90.0, 1.0, 0.0, 0.0 );
    drawAxis();
    glPopMatrix();
}

void curve(void)
{
#define NPTS 30

	int i;
	float xt, yt, zt, t;
	GLfloat tempPts[4][3];
	
	for (i = 0; i < 3; i++){
		tempPts[0][i] = ctrlpts[0][i];
		tempPts[1][i] = ctrlpts[1][i];
		tempPts[2][i] = ctrlpts[2][i];
		tempPts[3][i] = ctrlpts[3][i];
	}
	
	glPointSize(5.0);
	glBegin(GL_POINTS);
		for (i=0; i<CURVE_SIZE; i++ ) {
			glColor3f(1.0,0.0,0.0);
			glVertex3fv(ctrlpts[i]);
		}
	glEnd();
	
	glColor3f(0.0, 1.0, 1.0); // cyan
	glBegin(GL_LINE_STRIP);
			for (i=0; i<=NPTS; i++) {
				t = (float)i/(float)NPTS;
			#ifdef BEZIER
			xt = f0(t)*tempPts[0][0]+f1(t)*tempPts[1][0]+
				 f2(t)*tempPts[2][0]+f3(t)*tempPts[3][0];
			yt = f0(t)*tempPts[0][1]+f1(t)*tempPts[1][1]+
				 f2(t)*tempPts[2][1]+f3(t)*tempPts[3][1];
			zt = f0(t)*tempPts[0][2]+f1(t)*tempPts[1][2]+
				 f2(t)*tempPts[2][2]+f3(t)*tempPts[3][2];
			#endif
			#ifdef CATMULLROM
			xt = f0(t)*tempPts[0][0]+f1(t)*tempPts[1][0]+
				 f2(t)*tempPts[2][0]+f3(t)*tempPts[3][0];
			yt = f0(t)*tempPts[0][1]+f1(t)*tempPts[1][1]+
				 f2(t)*tempPts[2][1]+f3(t)*tempPts[3][1];
			zt = f0(t)*tempPts[0][2]+f1(t)*tempPts[1][2]+
				 f2(t)*tempPts[2][2]+f3(t)*tempPts[3][2];
			#endif
			#ifdef QUADRATIC
			xt = f0(t)*ctrlpts[0][0]+f1(t)*ctrlpts[1][0]+
				 f2(t)*ctrlpts[2][0];
			yt = f0(t)*ctrlpts[0][1]+f1(t)*ctrlpts[1][1]+
				 f2(t)*ctrlpts[2][1];
			zt = f0(t)*ctrlpts[0][2]+f1(t)*ctrlpts[1][2]+
				 f2(t)*ctrlpts[2][2];
			#endif
			glVertex3f(xt,yt,zt);
		}
	glEnd();
}

void display( void )
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	drawAxes();	// axes stay fixed; curve will rotate

	glLineWidth(2.0);
	curve();
	glLineWidth(1.0);

	glutSwapBuffers();
}

void reshape(int w,int h)
{
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(70.0,1.0,1.0,30.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt( 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

int main(int argc, char** argv)
{
	glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800,800);
	glutInitWindowPosition(70,70);
#ifdef BEZIER
	glutCreateWindow("Bezier spline curve");
#endif
#ifdef CATMULLROM
	glutCreateWindow("Catmull-Rom spline curve");
#endif
#ifdef QUADRATIC
	glutCreateWindow("Quadratic spline curve");
#endif
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);

	myinit();
	glutMainLoop();
}