/*
   Program to present conic sections by creating a cone and using
   a clipping plane to create planar figures
   
   Menu will allow the user to modify the parameters for the clipping plane

   Proof of concept for mathematics project for intro graphics course
   
   (c) February 2001, Steve Cunningham & Ken Brown
*/

// due to cross-platform incompatabilities with viewport clearing
// when there are multiple viewports, please specify either
// CLEAR_ONCE (PC) or CLEAR_TWICE (Mac)
#define CLEAR_ONCE
#define OSX

#ifdef MAC
#include "glut.h"
#endif
#ifdef OSX
#include <GLUT/glut.h>
#endif
#ifdef PC
#include <GL/glut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

//	function prototypes
void myinit(void);
void cone(void);
void display(void);
void reshape(int w,int h);
void drawAxis(float, char);
void drawAxes(float);
void rotate(void);
void keyboard(unsigned char key, int x, int y);
void options_menu(int input);

void giveHelp(void);
void giveAbout(void);

//	definitions of parameters & globals for program
#define ANGLE 2.0	// step angle for rotations
#define HEIGHT 4.0	// height of cone
#define GRAIN 100	// number of steps around the cone
#define RADIUS 3.0	// radius of top/bottom of cone

enum tags { TAG1 = 1, TAG2, TAG3, TAG4, TAG5, TAG6, TAG7, TAG8,	TAG9 };

float	ep = 10.0; // eye point variable
static char ch; // character from keyboard that controls the rotation
static GLfloat saveState[16] = {1.0,0.0,0.0,0.0,
                                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

int width = 300, height = 300; // for keeping track of reshapes

typedef GLfloat point3[3];
GLdouble myClipPlane[] = { 1.0, 0.0, 0.0, HEIGHT+1.0 };	// plane x=-HEIGHT-1

// initialize OpenGL and all necessary global variables
void myinit(void)
{
//		set up overall light data, including specular=ambient=light colors
		GLfloat light_position[]={ 10.0, 10.0, -10.0, 1.0 };
		GLfloat light_color[]={ 1.0, 1.0, 1.0, 1.0 };
		GLfloat ambient_color[]={ 0.2, 0.2, 0.2, 1.0 };
		GLfloat mat_specular[]={ 1.0, 1.0, 1.0, 1.0 };

		glClearColor( 0.0, 0.0, 1.0, 0.0 );
		glShadeModel(GL_SMOOTH); // use gourand shading
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular );
		glLightfv(GL_LIGHT0, GL_POSITION, light_position );
		glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color );
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_color );
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_color );

        glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1 ); // two-sided lighting

//		attributes
		glEnable(GL_LIGHTING);		// so lighting models are used
		glEnable(GL_LIGHT0);		// we'll use LIGHT0
		glEnable(GL_DEPTH_TEST);	// allow z-buffer display
		glEnable(GL_CLIP_PLANE1);	// enable clipping on plane 1
       
		glEnable(GL_BLEND);      // enable alpha-channel blending        
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

//		define display lists for the axes and cone
		glNewList(1, GL_COMPILE);
			cone();
		glEndList();
		glNewList(2, GL_COMPILE);
			drawAxes(3.0);
		glEndList();
}

// create the cone call list
void cone(void)
{
	//	create double cone with center at origin and with modestly steep
	//	sides that will be presented with portions clipped to show the
	//	conic sections

	int i;
	float aStep, myAngle;	// step in angle

    GLfloat color1[]={1.0, 0.0, 0.0, 1.0};
    GLfloat color2[]={1.0, 1.0, 0.0, 1.0};
    GLfloat mat_shininess[]={ 50.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, color1 );
    glMaterialfv(GL_FRONT, GL_DIFFUSE, color1 );
    glMaterialfv(GL_BACK, GL_AMBIENT, color2 );
    glMaterialfv(GL_BACK, GL_DIFFUSE, color2 );
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );

	aStep = 3.14159/(float)GRAIN;
	glBegin(GL_TRIANGLE_FAN);	// top half of double cone
		glVertex3f(0.0, 0.0, 0.0);
		for (i=0; i<=GRAIN; i++) {
			myAngle = (float)i*aStep*2.0;
			glVertex3f(RADIUS*cos(myAngle), RADIUS*sin(myAngle), HEIGHT);
		}
	glEnd();
	glBegin(GL_TRIANGLE_FAN);	// bottom half of double cone
		glVertex3f(0.0, 0.0, 0.0);
		for (i=0; i<=GRAIN; i++) {
			myAngle = -(float)i*aStep*2.0;
			glVertex3f(RADIUS*cos(myAngle), RADIUS*sin(myAngle), -HEIGHT);
		}
	glEnd();
}

void display( void )
{
	//  standard display with the rotations specified from the keyboard

	//	eye offset from center
	float offset = 1.0;
	
	//	left-hand viewport
	glViewport(0,0,width/2,height);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //        eye point  center of view       up
    gluLookAt(ep-offset, ep, ep, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
	glGetFloatv( GL_MODELVIEW_MATRIX, viewProj );

	rotate();
	// rotation finished, if any; now call display lists
	// note that the axes are drawn with the clip plane *OFF*
	glDisable(GL_CLIP_PLANE1);
	glCallList(2);
	glEnable(GL_CLIP_PLANE1);
	glClipPlane(GL_CLIP_PLANE1, myClipPlane);
	glCallList(1);
	glutSwapBuffers();
//	right-hand viewport
	glViewport(width/2,0,width/2,height);

#ifdef CLEAR_TWICE
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
#endif
#ifdef CLEAR_ONCE
	glClear(GL_DEPTH_BUFFER_BIT);
#endif

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //        eye point  center of view       up
    gluLookAt(ep+offset, ep, ep, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
	glGetFloatv( GL_MODELVIEW_MATRIX, viewProj );

	rotate();
	// rotation finished, if any; now call display lists
	// note that the axes are drawn with the clip plane *OFF*
	glDisable(GL_CLIP_PLANE1);
	glCallList(2);
	glEnable(GL_CLIP_PLANE1);
	glClipPlane(GL_CLIP_PLANE1, myClipPlane);
	glCallList(1);
	glutSwapBuffers();
}

void drawAxis(float scale, char whichAxis)
{
// 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}, red[]={1.0, 0.0, 0.0, 1.0},
    		green[]={0.0, 1.0, 0.0, 1.0}, blue[]={0.0, 0.0, 1.0, 1.0};
    GLfloat coneHeight, coneRad, axisLength, axisRad;

//	granularity of cone
#define GRAN 10

    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, white );
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, white );
//	Set the sizes for the axes
	coneHeight = scale*0.1;
	coneRad = scale*0.10;
	axisRad = scale*0.015;
	axisLength = scale;
//  Draw the standard axis in Z-orientation
    glBegin(GL_QUAD_STRIP);
      glVertex3f( axisRad, axisRad,  axisLength );
      glVertex3f( axisRad, axisRad, -axisLength );
      glVertex3f(-axisRad, axisRad,  axisLength );
      glVertex3f(-axisRad, axisRad, -axisLength );
      glVertex3f(-axisRad,-axisRad,  axisLength );
      glVertex3f(-axisRad,-axisRad, -axisLength );
      glVertex3f( axisRad,-axisRad,  axisLength );
      glVertex3f( axisRad,-axisRad, -axisLength );
      glVertex3f( axisRad, axisRad,  axisLength );
      glVertex3f( axisRad, axisRad, -axisLength );
    glEnd();
    glPushMatrix();
    glTranslatef( 0.0, 0.0, axisLength );
    switch(whichAxis) {
    case 'x':
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, red );
    	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, red ); break;
    case 'y':
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, green );
    	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, green ); break;
    case 'z':
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, blue );
    	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, blue ); break;
	}
    glutSolidCone( coneHeight, coneRad, GRAN, GRAN );
    glPopMatrix();
}

void drawAxes(float scale)
{
//  Draw the Z-axis
    drawAxis(scale, 'z');
//  Draw the standard axis in X-orientation
    glPushMatrix();
    glRotatef( 90.0, 0.0, 1.0, 0.0 );
    drawAxis(scale, 'x');
    glPopMatrix();
//  Draw the standard axis in Y-orientation
    glPushMatrix();
    glRotatef( -90.0, 1.0, 0.0, 0.0 );
    drawAxis(scale, 'y');
    glPopMatrix();
}

//	Function to perform rotation around coordinate axes, not around object axes
//	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 + -
void rotate(void )
{

	glLoadIdentity();
	if (ch != ' ') {	// keyboard indicated a rotation...
	//	Put identity onto modelview stack and start with the initial rotation
		switch(ch) {
			case 'q':
			case 'o':
				glRotatef( ANGLE, 1.0, 0.0, 0.0); break;
			case 'w':
			case 'p':
				glRotatef(-ANGLE, 1.0, 0.0, 0.0); break;
        	case 'a':
        	case 'k':
            	glRotatef( ANGLE, 0.0, 1.0, 0.0); break;
        	case 's':
        	case 'l':
            	glRotatef(-ANGLE, 0.0, 1.0, 0.0); break;
        	case 'z':
        	case 'm':
            	glRotatef( ANGLE, 0.0, 0.0, 1.0); break;
        	case 'x':
        	case ',':
            	glRotatef(-ANGLE, 0.0, 0.0, 1.0); break;
	    }
	}
    //  NOW apply the rest of the modeling transformation by POST-multiplying
    //  by the saved matrix, and then save that and put the identity back on
    //  the stack.  Rebuild the overall modelview matrix by starting with the
    //  viewing transformation, saved from the reshape() function, and then
    //	multiplying by the modeling transformation created in the first two
    //	statements.
    	glMultMatrixf( saveState );
    	glGetFloatv( GL_MODELVIEW_MATRIX, saveState );
    	glLoadIdentity();
    	glMultMatrixf( viewProj );
    	glMultMatrixf( saveState );
}

void reshape(int w,int h)
{
	// globalize the width and height so the two viewports can
	// be properly sized
	width = w; height = h;

    glViewport(0,0,(GLsizei)w,(GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0,((float)w/2.0)/(float)h,1.0,30.0);
}

void keyboard(unsigned char key, int x, int y)
{
    ch = ' ';
    switch (key)
    {
        case 'q' :	// rotate around X; q,o = positive, w,p = negative
        case 'o' :
        case 'w' :
        case 'p' :
        case 'a' :	// rotate around Y; a,k = positive, s,l = negative
        case 'k' :
        case 's' :
        case 'l' :
        case 'z' :	// rotate around Z; z,m = positive, x,, = negative
        case 'm' :
        case 'x' :
        case ',' :
        	ch = key; break; // save the key
        case 'f' :	// move clip plane "forward" 'f' or "back" 'b'
        	myClipPlane[3]+=0.2; break;
        case 'b' :
        	myClipPlane[3]-=0.2; break;
    }
    glutPostRedisplay(); // perform display again
}

void options_menu(int input)
{
	switch(input) {
	case TAG1:			// parallel to cone axis - hyperbola
		myClipPlane[0] =  0.0;
		myClipPlane[1] =  -1.0;
		myClipPlane[2] =  0.0;
		myClipPlane[3] = 1.0;
		break;
	case TAG2:			// parallel to cone side - parabola
		myClipPlane[0] =  0.0;
		myClipPlane[1] =  -4.0;
		myClipPlane[2] = 3.0;
		myClipPlane[3] =  -2.0;
		break;
	case TAG3:			// perpendicular to cone axis - circle
		myClipPlane[0] =  0.0;
		myClipPlane[1] =  0.0;
		myClipPlane[2] =  -1.0;
		myClipPlane[3] =  -1.0;
		break;
	case TAG4:			// through cone center - line pair
		myClipPlane[0] =  0.0;
		myClipPlane[1] =  -1.0;
		myClipPlane[2] =  0.0;
		myClipPlane[3] =  0.0;
		break;
	case TAG5:			// oblique cutting plane - ellipse
		myClipPlane[0] =  0.0;
		myClipPlane[1] =  -1.0;
		myClipPlane[2] =  -1.0;
		myClipPlane[3] =  -1.0;
		break;
	case TAG6:			// move clip plane out of figure
		myClipPlane[0] =  1.0;
		myClipPlane[1] =  0.0;
		myClipPlane[2] =  0.0;
		myClipPlane[3] =  HEIGHT+1.0;
		break;
	case TAG8:
		giveHelp(); break;
	case TAG9:
		giveAbout(); break;
	}

	glutPostRedisplay();
}

void giveHelp(void)
{
	printf("*\tKeyboard controls are:\n");
	printf("\t\t - keys q/w/o/p rotate around X-axis,\n");
	printf("\t\t - keys a/s/k/l rotate around Y-axis,\n");
	printf("\t\t - keys z/x/m/, rotate around Z-axis,\n");
	printf("\t\t - keys f/b move the clipping plane.\n\n\n");
}

void giveAbout(void)
{
	printf("What does this program do?\n\n");
	printf("*\tClip parallel to cone axis -> hyperbola\n");
	printf("*\tClip parallel to cone side -> parabola\n");
	printf("*\tClip perpendicular to cone axis -> circle\n");
	printf("*\tClip through cone center -> pair of lines\n");
	printf("*\tClip plane oblique -> ellipse\n\n\n");
}

int main(int argc, char** argv)
{
//	Standard GLUT initialization
    glutInit(&argc,argv);
//	initialize for double buffering, RGB color, and depth tests
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(600,300);
    glutInitWindowPosition(70,70);
    glutCreateWindow("conic sections");
    glutKeyboardFunc(keyboard);     // enable keyboard callback
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

//	create the user menu to choose the visualization
	glutCreateMenu(options_menu);	// create menu and define entries
	glutAddMenuEntry("Clip parallel to cone axis", TAG1);		// 1
	glutAddMenuEntry("Clip parallel to cone side", TAG2);		// 2
	glutAddMenuEntry("Clip perpendicular to cone axis", TAG3);	// 3
	glutAddMenuEntry("Clip through cone center", TAG4);			// 4
	glutAddMenuEntry("Clip plane oblique", TAG5);				// 5
	glutAddMenuEntry("Clip plane disabled", TAG6);				// 6
	glutAddMenuEntry("---------------", TAG7);					// null choice
	glutAddMenuEntry("Help", TAG8);								// 6
	glutAddMenuEntry("About this program", TAG9);				// 7
	glutAttachMenu(GLUT_RIGHT_BUTTON); // give menu a name

    myinit();
    glutMainLoop();

	return 0;
}
