/*
	Defining and working with a Bˇzier curve in 3-space; an extended curve with
	many control points.  Use constant colors instead of lighting to focus on
	the geometry of the spline curve.

	Example for intro graphics course -- copyright (c) 2000, Steve Cunningham
*/
#include "glut.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

// function prototypes
void myinit(void);
void drawAxis(void);
void drawAxes(void);
void curve(void);
void makeCurve(void);
void display( void );
void reshape(int w,int h);
void keyboard(unsigned char key, int x, int y);

static char ch; // the character from the keyboard that controls the rotation
static GLfloat saveState[16] = {1.0,0.0,0.0,0.0,  // manage the MODELVIEW matrix
                                0.0,1.0,0.0,0.0,
                                0.0,0.0,1.0,0.0,
                                0.0,0.0,0.0,1.0},
               viewProj[16]; // hold transforms
static GLfloat fogColor[4]={0.5,0.5,0.5,1.0};

//	control points for each Bˇzier curve segment and the entire Bˇzier curve
#define CURVE_SIZE 32
static GLfloat segpts[4][3];
static GLfloat ctrlpts[CURVE_SIZE][3];

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

void drawAxis(void)
{
//	draw one axis, the Z-axis, as the template for all axes
//  Axes are to be colored 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.03, 0.03,  3.0 );
      glVertex3f( 0.03, 0.03, -3.0 );
      glVertex3f(-0.03, 0.03,  3.0 );
      glVertex3f(-0.03, 0.03, -3.0 );
      glVertex3f(-0.03,-0.03,  3.0 );
      glVertex3f(-0.03,-0.03, -3.0 );
      glVertex3f( 0.03,-0.03,  3.0 );
      glVertex3f( 0.03,-0.03, -3.0 );
      glVertex3f( 0.03, 0.03,  3.0 );
      glVertex3f( 0.03, 0.03, -3.0 );
    glEnd();
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 3.0 );
    glutSolidCone( 0.2, 0.3, 20, 20 );
    glPopMatrix();
}

void drawAxes(void)
{
//  Draw the Z-axis
    drawAxis();
//  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 LAST_STEP (CURVE_SIZE/2)-1
#define NPTS 30

	int step, i, j;

	makeCurve(); // calculate the control points for the entire curve
	glPointSize(5.0);
	// copy/compute the points from  ctrlpts  to  segpts to define each segment
	// of the curve.  First and last cases are different from middle cases...
	for ( step = 0; step < LAST_STEP; step++ ) {
		if (step==0) { // first case
			for (j=0; j<3; j++) {
				segpts[0][j]=ctrlpts[0][j];
				segpts[1][j]=ctrlpts[1][j];
				segpts[2][j]=ctrlpts[2][j];
				segpts[3][j]=(ctrlpts[2][j]+ctrlpts[3][j])/2.0;
			}
			for (j=0; j<4;j++) {
				glBegin(GL_POINTS);
//					for (i=0; i<4; i++) {
						if (j!=3) glColor3f(0.0, 1.0, 1.0); // cyan
						else glColor3f(0.0,1.0,0.0);	// green
						glVertex3fv(&segpts[j][0]);	//was i
//					}
				glEnd();
			}
		}
		else if (step==LAST_STEP-1) { // last case
			for (j=0; j<3; j++) {
				segpts[0][j]=(ctrlpts[CURVE_SIZE-4][j]+ctrlpts[CURVE_SIZE-3][j])/2.0;
				segpts[1][j]=ctrlpts[CURVE_SIZE-3][j];
				segpts[2][j]=ctrlpts[CURVE_SIZE-2][j];
				segpts[3][j]=ctrlpts[CURVE_SIZE-1][j];
			}
			for (j=0; j<4;j++) {
				glBegin(GL_POINTS);
//					for (i=0; i<4; i++) {
						if (j!=0) glColor3f(0.0, 1.0, 1.0); // cyan
						else glColor3f(0.0,1.0,0.0);	// green
						glVertex3fv(&segpts[j][0]);	// was i
//					}
				glEnd();
			}
		}
		else {
			for (j=0; j<3; j++) {	// general case
				segpts[0][j]=(ctrlpts[2*step][j]+ctrlpts[2*step+1][j])/2.0;
				segpts[1][j]=ctrlpts[2*step+1][j];
				segpts[2][j]=ctrlpts[2*step+2][j];
				segpts[3][j]=(ctrlpts[2*step+2][j]+ctrlpts[2*step+3][j])/2.0;
			}
			for (j=0; j<4;j++) {
				glBegin(GL_POINTS);
//					for (i=0; i<4; i++) {
						if ((j==1)||(j==2)) glColor3f(0.0, 1.0, 1.0); // cyan
						else glColor3f(0.0,1.0,0.0);	// green
						glVertex3fv(&segpts[j][0]);	// was i
//					}
				glEnd();
			}
		}
		// define the evaluator
		glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &segpts[0][0]);

		glColor3f(1.0,0.0,0.0);  // red
		glBegin(GL_LINE_STRIP);
		for (i=0; i<=NPTS; i++)
			glEvalCoord1f( (GLfloat)i/(GLfloat)NPTS );
		glEnd();
	}
}

void makeCurve( void )
{
#define PI 3.14159
#define RAD 2.0
#define INITANGLE 0.0
#define STEPANGLE PI/3.0

	int i;

	for (i=0; i<CURVE_SIZE; i++) {
		ctrlpts[i][0]= RAD*cos(INITANGLE + i*STEPANGLE);
		ctrlpts[i][1]= RAD*sin(INITANGLE + i*STEPANGLE);
		ctrlpts[i][2]= -4.0 + i * 0.25;
	}
}

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

	// We save the original viewing projection in the  viewProj  array
	glPushMatrix();
	glGetFloatv( GL_MODELVIEW_MATRIX, viewProj );

	//  Put the identity onto the modelview stack to start the viewing transform
	glLoadIdentity();
    
#define Angle 2.0

	switch(ch) {
		case 'q':
			glRotatef( Angle, 1.0, 0.0, 0.0); break;
		case 'w':
			glRotatef(-Angle, 1.0, 0.0, 0.0); break;
		case 'a':
			glRotatef( Angle, 0.0, 1.0, 0.0); break;
		case 's':
			glRotatef(-Angle, 0.0, 1.0, 0.0); break;
		case 'z':
			glRotatef( Angle, 0.0, 0.0, 1.0); break;
		case 'x':
			glRotatef(-Angle, 0.0, 0.0, 1.0); break;
	}
    
	glMultMatrixf( saveState );
	glGetFloatv( GL_MODELVIEW_MATRIX, saveState );
	glLoadIdentity();
	glMultMatrixf( viewProj );

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

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

	glPopMatrix();
	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( 6.0,  6.0, 6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void keyboard(unsigned char key, int x, int y)
{
	ch = ' ';
	switch (key) {
		case 'q' :
		case 'w' :
		case 'a' :
		case 's' :
		case 'z' :
		case 'x' :
			ch = key; break;
	}
	glutPostRedisplay();
}

void main(int argc, char** argv)
{
	glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(500,500);
	glutInitWindowPosition(70,70);
	glutCreateWindow("Splines in Space!!");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);

	myinit();
	glutMainLoop();
}