/*
   Program to present conic sections by creating a cone and using
   a clipping plane to create planar figures.
   
   Presentation includes creating a stereo pair for enhanced viewing

   Proof of concept for mathematics project for intro graphics course
   
   (c) 1999, Steve Cunningham
*/

#include "glut.h"
#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);

//	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

int Tag1=1, Tag2=2, Tag3=3, Tag4=4, Tag5=5, Tag6=6, Tag7=7, Tag8=8,
	Tag9=9, selection;
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
typedef GLfloat point3[3];
int		argc;
char	**argv;
GLdouble myClipPlane[] = { 1.0, 0.0, 0.0, HEIGHT+1.0 };	// plane x=-HEIGHT-1
long i = 1;

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

        glLightModeliv(GL_LIGHT_MODEL_TWO_SIDE, &i ); // 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();
}

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 )
{
	//	eye offset from center
	float offset = 1.0;
	
//	left-hand viewport
	glViewport(0,0,300,300);
    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);
	//	save the original viewing projection for later use
	glGetFloatv( GL_MODELVIEW_MATRIX, viewProj );
	rotate();
	// note that the axes are drawn with the clip plane *OFF*
	glDisable(GL_CLIP_PLANE1);
	glCallList(2);	//	draw the axes without the clipping plane
	glEnable(GL_CLIP_PLANE1);
	glClipPlane(GL_CLIP_PLANE1, myClipPlane);
	glCallList(1);	//	draw the cone with the clipping plane
//	right-hand viewport
	glViewport(300,0,300,300);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(ep+offset, ep, ep, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
	glGetFloatv( GL_MODELVIEW_MATRIX, viewProj );
	rotate();
	glDisable(GL_CLIP_PLANE1);
	glCallList(2);
	glEnable(GL_CLIP_PLANE1);
	glClipPlane(GL_CLIP_PLANE1, myClipPlane);
	glCallList(1);
	glutSwapBuffers();	//	
}

void drawAxis(float scale, char whichAxis)
{
	//	granularity of cone
	#define GRAN 10
	// 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;


    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) {	// draw the axis ends in coded colors
    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)
{
    glViewport(0,0,(GLsizei)w,(GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0,1.0,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)
{
	if (input == Tag1) {			// parallel to cone axis - hyperbola
			myClipPlane[0] =  0.0;
			myClipPlane[1] =  -1.0;
			myClipPlane[2] =  0.0;
			myClipPlane[3] = 1.0;
			}
	if (input == Tag2) {			// parallel to cone side - parabola
			myClipPlane[0] =  0.0;
			myClipPlane[1] =  -4.0;
			myClipPlane[2] = 3.0;
			myClipPlane[3] =  -2.0;
			}
	if (input == Tag3) {			// perpendicular to cone axis - circle
			myClipPlane[0] =  0.0;
			myClipPlane[1] =  0.0;
			myClipPlane[2] =  -1.0;
			myClipPlane[3] =  -1.0;
			}
	if (input == Tag4) {			// through cone center - line pair
			myClipPlane[0] =  0.0;
			myClipPlane[1] =  -1.0;
			myClipPlane[2] =  0.0;
			myClipPlane[3] =  0.0;
			}
	if (input == Tag5) {			// oblique cutting plane - ellipse
			myClipPlane[0] =  0.0;
			myClipPlane[1] =  -1.0;
			myClipPlane[2] =  -1.0;
			myClipPlane[3] =  -1.0;
			}
	if (input == Tag6) {			// move clip plane out of figure
			myClipPlane[0] =  1.0;
			myClipPlane[1] =  0.0;
			myClipPlane[2] =  0.0;
			myClipPlane[3] =  HEIGHT+1.0;
			}
	glutPostRedisplay();
}

void 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);
    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
	glutAttachMenuName(GLUT_RIGHT_BUTTON, "Options")

    myinit();
    glutMainLoop();
}