/*
	Interpolating specific points with Catmull-Rom cubic blending functions to achieve
	an eyepoint motion for fly-through animations.
	
	To make the function actually set the eye point, in the display function you would
	take the vertices used for the drawing function and use them for the eyepoint in the
	gluLookAt(...) function.

	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>

#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))

// function prototypes
void myinit( void );
void curve( void );
void cube( float r, float g, float b, float a );
void buildings( void );
void display( void );
void reshape( int w,int h );
void keyboard( unsigned char key, int x, int y );

//	control points for each curve segment
#define CURVE_SIZE 9
#define ANGLE 2.
static GLfloat ctrlpts[CURVE_SIZE][3] = {   { -8.,  0.,  1. },
											{  4.,  0.,  1. },
											{  8.,  4.,  2. },
											{  4.,  8.,  3. },
											{  0.,  0.,  3. },
											{ -4., -8.,  3. },
											{ -8., -4.,  4. },
											{ -4.,  0.,  5. },
											{  8.,  0.,  5. }  };
GLfloat xAngle = 0., yAngle = 0., zAngle = 0.;
static char ch; // the character from the keyboard that controls the rotation

void myinit(void)
{ 
	glClearColor( 0.0, 0.0, 0.0, 1.0 );
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	glEnable(GL_NORMALIZE);
	glLineWidth(2.0);
}

void curve(void)
{
#define NPTS 10

	int i, j;
	float xt, yt, zt, t;
	GLfloat tempPts[4][3];
	
	glDisable(GL_LIGHTING);
	glPointSize(5.0);
	glColor3f( 1.0, 0.0, 0.0);	// red for control points
	glBegin(GL_POINTS);
		for (i=0; i<CURVE_SIZE; i++ )
			glVertex3fv(ctrlpts[i]);
	glEnd();
	
	glColor3f(0.0, 1.0, 1.0); // cyan
	glBegin(GL_LINE_STRIP);	
		// first segment of the spline curve
		for (i = 0; i < 3; i++){
			tempPts[0][i] = ctrlpts[0][i];
			tempPts[1][i] = ctrlpts[0][i];
			tempPts[2][i] = ctrlpts[1][i];
			tempPts[3][i] = ctrlpts[2][i];
		}
		for (i=0; i<=NPTS; i++) {
			t = (float)i/(float)NPTS;
			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];
			glVertex3f(xt,yt,zt);
			}
		// for each additional segment, update the tempPts array; note the case for the last segment
		for (j = 3; j <= CURVE_SIZE+1; j++ ) {
			for (i=0; i<3; i++ ) { // update tempPts to the next set of control points
				tempPts[0][i] = tempPts[1][i];
				tempPts[1][i] = tempPts[2][i];
				tempPts[2][i] = tempPts[3][i];
				if (j < CURVE_SIZE) tempPts[3][i] = ctrlpts[j][i];
				else tempPts[3][i] = ctrlpts[CURVE_SIZE-1][i];
			}
			for (i=0; i<=NPTS; i++) { // draw the segment
				t = (float)i/(float)NPTS;
				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];
				//if (j == CURVE_SIZE) printf("%f  %f  %f\n", xt, yt, zt );
				glVertex3f(xt,yt,zt);
			}
		}
	glEnd();
}

void cube(float r, float g, float b, float a)
{
// define point and color data types
	typedef GLfloat point3[3];

	float cubecolor[4];
	const GLint i = 1;
    point3 vertices[8]={{-1.0, -1.0, -1.0},
                        {-1.0, -1.0,  1.0},
                        {-1.0,  1.0, -1.0},
                        {-1.0,  1.0,  1.0},
                        { 1.0, -1.0, -1.0},
                        { 1.0, -1.0,  1.0},
                        { 1.0,  1.0, -1.0},
                        { 1.0,  1.0,  1.0} };

    point3 normals[6]={{ 0.0, 0.0, 1.0},
                       {-1.0, 0.0, 0.0},
                       { 0.0, 0.0,-1.0},
                       { 1.0, 0.0, 0.0},
                       { 0.0,-1.0, 0.0},
                       { 0.0, 1.0, 0.0} };

	GLfloat mat_shininess[]={ 50.0 };
	GLfloat light_pos0[]={ 12.0, 10.0, 0.0, 1.0 };  // out x-y direction
	GLfloat light_col0[]={ 1.0,  1.0, 1.0, 1.0 };   //light is white
	GLfloat amb_color0[]={ 0.3,  0.3, 0.3, 1.0 };
	GLfloat mat_specular[]={ 0.8, 0.8, 0.8, 1.0 };

    cubecolor[0] = r; cubecolor[1] = g; cubecolor[2] = b; cubecolor[3] = a;
		
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos0 ); // light 0
	glLightfv(GL_LIGHT0, GL_AMBIENT, amb_color0 );
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_col0 );
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_col0 );
	
	glLightModeliv(GL_LIGHT_MODEL_TWO_SIDE, &i );
	glShadeModel(GL_SMOOTH);

    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, cubecolor );
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, cubecolor );
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular );
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );

	glEnable(GL_LIGHT0);
	
    glBegin(GL_QUADS);
      glNormal3fv(normals[0]);   // first quad: positive Z face
      glVertex3fv(vertices[1]);
      glVertex3fv(vertices[5]);
      glVertex3fv(vertices[7]);
      glVertex3fv(vertices[3]);
      glNormal3fv(normals[5]);   // second quad: positive Y face
      glVertex3fv(vertices[7]);
      glVertex3fv(vertices[6]);
      glVertex3fv(vertices[2]);
      glVertex3fv(vertices[3]);
      glNormal3fv(normals[2]);   // third quad: negative Z face
      glVertex3fv(vertices[2]);
      glVertex3fv(vertices[6]);
      glVertex3fv(vertices[4]);
      glVertex3fv(vertices[0]);
      glNormal3fv(normals[3]);  // fourth quad: positive X face
      glVertex3fv(vertices[5]);
      glVertex3fv(vertices[4]);
      glVertex3fv(vertices[6]);
      glVertex3fv(vertices[7]);
      glNormal3fv(normals[4]);  // fifth quad: negative Y face
      glVertex3fv(vertices[4]);
      glVertex3fv(vertices[5]);
      glVertex3fv(vertices[1]);
      glVertex3fv(vertices[0]);
      glNormal3fv(normals[1]);  // sixth quad: negative X face
      glVertex3fv(vertices[0]);
      glVertex3fv(vertices[1]);
      glVertex3fv(vertices[3]);
      glVertex3fv(vertices[2]);
    glEnd();
 }
 
void buildings( void )
{
	glEnable(GL_LIGHTING);
	
	glPushMatrix();
		glTranslatef(4., 4., 0.);
		glScalef(1., 1., 3.);
		glTranslatef(0., 0., 1.);
		cube(1., 1., 0., 1.);
	glPopMatrix();
	glPushMatrix();
		glTranslatef(4., -4., 0.);
		glScalef(1., 1., 3.);
		glTranslatef(0., 0., 1.);
		cube(1., 1., 0., 1.);
	glPopMatrix();
	glPushMatrix();
		glTranslatef(-4., 4., 0.);
		glScalef(1., 1., 3.);
		glTranslatef( 0., 0., 1.);
		cube(1., 1., 0., 1.);
	glPopMatrix();
	glPushMatrix();
		glTranslatef(-4., -4., 0.);
		glScalef(1., 1., 3.);
		glTranslatef(0., 0., 1.);
		cube(1., 1., 0., 1.);
	glPopMatrix();
	
	glDisable(GL_LIGHTING);
}

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

	glPushMatrix();

	glRotatef(xAngle, 1.0, 0.0, 0.0);
	glRotatef(yAngle, 0.0, 1.0, 0.0);
	glRotatef(zAngle, 0.0, 0.0, 1.0);
	if (ch != ' ') {	// keyboard indicated a rotation...
	//	rotation control uses two keypads embedded in standard keyboard
	//    q  w                     o  p  -- rotate around x-axis + -
	//     a  s                   k  l   -- rotate around y-axis + -
	//      z  x                 m  ,    -- rotate around z-axis + -
	switch(ch) {
			case 'q':
			case 'o':
				xAngle += ANGLE;
				glRotatef( ANGLE, 1.0, 0.0, 0.0); break;
			case 'w':
			case 'p':
				xAngle -= ANGLE;
				glRotatef(-ANGLE, 1.0, 0.0, 0.0); break;
        	case 'a':
        	case 'k':
				yAngle += ANGLE;
            	glRotatef( ANGLE, 0.0, 1.0, 0.0); break;
        	case 's':
        	case 'l':
				yAngle -= ANGLE;
            	glRotatef(-ANGLE, 0.0, 1.0, 0.0); break;
        	case 'z':
        	case 'm':
				zAngle += ANGLE;
            	glRotatef( ANGLE, 0.0, 0.0, 1.0); break;
        	case 'x':
        	case ',':
				zAngle -= ANGLE;
            	glRotatef(-ANGLE, 0.0, 0.0, 1.0); break;
	    }
    }
	ch = ' ';	// ensure any residual rotation is killed

	glColor3f(.2, .2, .2); // ground plane for the image
	glBegin(GL_QUADS);
		glVertex3f(-8.,-8., 0.);
		glVertex3f(-8., 8., 0.);
		glVertex3f( 8., 8., 0);
		glVertex3f( 8.,-8., 0);
	glEnd();
	curve();     // draw moving eye curve in the model
	buildings(); // draw buildings in the model

	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,70.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt( 20.0, -2.0, 4.0,   0.0, 2.0, 0.0,   0.0, 0.0, 1.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();
}

int main(int argc, char** argv)
{
	glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800,800);
	glutInitWindowPosition(70,70);
	glutCreateWindow("Moving eye position in model");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);

	myinit();
	glutMainLoop();
}