#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>
#include "paulslib.h"
#include "opengllib.h"
#include "bitmaplib.h"

/*
	Great circle interactive for Sarah
*/

void Display(void);
void CreateEnvironment(void);
void MakeGeometry(void);
void MakeLighting(void);
void HandleKeyboard(unsigned char key,int x, int y);
void HandleSpecialKeyboard(int key,int x, int y);
void HandleMouse(int,int,int,int);
void HandleMainMenu(int);
void HandlePlaneMenu(int);
void HandleSphereMenu(int);
void HandleSpinMenu(int);
void HandleSpeedMenu(int);
void HandleVisibility(int vis);
void HandleReshape(int,int);
void HandleMouseMotion(int,int);
void HandlePassiveMotion(int,int);
void HandleIdle(void);
void GiveUsage(char *);
void RotateCamera(int,int,int);
void TranslateCamera(int,int);
void CameraHome(int);

typedef struct {
   XYZ vp;              /* View position           */
   XYZ vd;              /* View direction vector   */
   XYZ vu;              /* View up direction       */
   XYZ pr;              /* Point to rotate about   */
   double focallength;  /* Focal Length along vd   */
   double aperture;     /* Camera aperture         */
   double eyesep;       /* Eye separation          */
	int screenwidth,screenheight;
} CAMERA;

/* Flags */
int fullscreen = FALSE;
int stereo = FALSE;
int showconstruct = FALSE;
int showobject = 0;
int windowdump = FALSE;
int planetype = 1;
int spheretype = 1;
int lineset = 1;
int autospin = 0;

int currentbutton = -1;
double rotatespeed = 1;
double dtheta = 1;
CAMERA camera;
XYZ origin = {0.0,0.0,0.0};
double anglex=0,angley=0,anglez=0;

GLfloat specularcolour[4] = {1.0,1.0,1.0,1.0};
GLfloat shininess[1]      = {20.0};

int main(int argc,char **argv)
{
	int i;
	int mainmenu,spinmenu,planemenu,spheremenu;

	/* Parse the command line arguments */
	for (i=1;i<argc;i++) {
		if (strstr(argv[i],"-h") != NULL) 
 			GiveUsage(argv[0]);
		if (strstr(argv[i],"-f") != NULL)
 			fullscreen = TRUE;
		if (strstr(argv[i],"-s") != NULL)
 			stereo = TRUE;
		if (strstr(argv[i],"-c") != NULL)
 			showconstruct = TRUE;
	}

	/* Set things up and go */
	glutInit(&argc,argv);
	if (!stereo)
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	else
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STEREO);
	glutCreateWindow("Great Circle Model");
	glutReshapeWindow(400,300);
	if (fullscreen)
		glutFullScreen();
	glutDisplayFunc(Display);
	glutReshapeFunc(HandleReshape);
	glutVisibilityFunc(HandleVisibility);
	glutKeyboardFunc(HandleKeyboard);
	glutSpecialFunc(HandleSpecialKeyboard);
	glutMouseFunc(HandleMouse);
	glutMotionFunc(HandleMouseMotion);
	glutSetCursor(GLUT_CURSOR_NONE);
	CreateEnvironment();
	CameraHome(0);

	/* Spin menu */
   spinmenu = glutCreateMenu(HandleSpinMenu);
   glutAddMenuEntry("Stop",1);
   glutAddMenuEntry("About all axes",2);
   glutAddMenuEntry("About x axis",3);
   glutAddMenuEntry("About y axis",4);
   glutAddMenuEntry("About z axis",5);

   /* Plane menu */
   planemenu = glutCreateMenu(HandlePlaneMenu);
   glutAddMenuEntry("Mesh",1);
   glutAddMenuEntry("Boundary",2);
   glutAddMenuEntry("Semi Transparent",3);
	glutAddMenuEntry("Opaque",4);
	glutAddMenuEntry("None",5);

   /* Sphere menu */
   spheremenu = glutCreateMenu(HandleSphereMenu);
   glutAddMenuEntry("Grid lines",1);
   glutAddMenuEntry("Solid",2);
   glutAddMenuEntry("Solid + equator and meridians",3);
	glutAddMenuEntry("None",4);

	/* Set up the main menu */
	mainmenu = glutCreateMenu(HandleMainMenu);
	glutAddMenuEntry("Toggle construction lines",1);
	glutAddSubMenu("Plane",planemenu);
	glutAddSubMenu("Sphere",spheremenu);
	glutAddSubMenu("Autospin",spinmenu);
	glutAddMenuEntry("Move camera to home position",8);
	glutAddMenuEntry("Quit",9);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	/* Ready to go! */
	glutMainLoop();
	return(0);
}

/*
	This is where global OpenGL/GLUT settings are made, 
	that is, things that will not change in time 
*/
void CreateEnvironment(void)
{
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_LINE_SMOOTH);
	glDisable(GL_POINT_SMOOTH);
	glEnable(GL_POLYGON_SMOOTH); 
	glShadeModel(GL_FLAT);    
	glDisable(GL_DITHER);
	glDisable(GL_CULL_FACE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	glLineWidth(1.0);
	glPointSize(1.0);

	glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
	glFrontFace(GL_CW);
	glClearColor(0.0,0.0,0.0,0.0);         /* Background colour */
	glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_COLOR_MATERIAL);
	glPixelStorei(GL_UNPACK_ALIGNMENT,1);
	glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); 
}

/*
	This is the basic display callback routine
	It creates the geometry, lighting, and viewing position
	In this case it rotates the camera around the scene
*/
void Display(void)
{
	XYZ right,focus;

	/* Clear the buffers */
	glDrawBuffer(GL_BACK_LEFT);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	if (stereo) {
		glDrawBuffer(GL_BACK_RIGHT);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	/* Determine the focal point */
	Normalise(&camera.vd);
	focus.x = camera.vp.x + camera.focallength * camera.vd.x;
	focus.y = camera.vp.y + camera.focallength * camera.vd.y;
	focus.z = camera.vp.z + camera.focallength * camera.vd.z;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(camera.aperture,
		camera.screenwidth/(double)camera.screenheight,0.1,10000.0);

	if (stereo) {

	/* Derive the two eye positions */
	CROSSPROD(camera.vd,camera.vu,right);
	Normalise(&right);
	right.x *= camera.eyesep / 2.0;
	right.y *= camera.eyesep / 2.0;
	right.z *= camera.eyesep / 2.0;

	glMatrixMode(GL_MODELVIEW);
	glDrawBuffer(GL_BACK_RIGHT);
	glLoadIdentity();
	gluLookAt(camera.vp.x + right.x,
		  camera.vp.y + right.y,
		  camera.vp.z + right.z,
		  focus.x,focus.y,focus.z,
		  camera.vu.x,camera.vu.y,camera.vu.z);
	MakeGeometry();
	MakeLighting();

	glMatrixMode(GL_MODELVIEW);
	glDrawBuffer(GL_BACK_LEFT);
	glLoadIdentity();
	gluLookAt(camera.vp.x - right.x,
		  camera.vp.y - right.y,
		  camera.vp.z - right.z,
		  focus.x,focus.y,focus.z,
		  camera.vu.x,camera.vu.y,camera.vu.z);
	MakeGeometry();
	MakeLighting();
	} else {
		glMatrixMode(GL_MODELVIEW);
		glDrawBuffer(GL_BACK_LEFT);
		glLoadIdentity();
		gluLookAt(camera.vp.x,
		  camera.vp.y,
		  camera.vp.z,
		  focus.x,focus.y,focus.z,
		  camera.vu.x,camera.vu.y,camera.vu.z);
		MakeGeometry();
		MakeLighting();
	}

	/* glFlush(); This isn't necessary for double buffers */
	glutSwapBuffers();

	if (windowdump)
		WindowDump(camera.screenwidth,camera.screenheight,stereo,5);
}

/*
	Create the geometry for the pulsar
*/
void MakeGeometry(void)
{
	int i,j,k,n;
	double x,y,z,dx,dy,dz,a,b,c,mu;
	double r;
	XYZ norm;
	XYZ p[4],q[2];

	/* Create the cutting plane */
	p[0].x = -1.2; p[0].y = 0; p[0].z = -1.2;
	p[1].x =  1.2; p[1].y = 0; p[1].z = -1.2;
	p[2].x =  1.2; p[2].y = 0; p[2].z =  1.2;
	p[3].x = -1.2; p[3].y = 0; p[3].z =  1.2;
	for (i=0;i<4;i++) {
		p[i] = RotateX(p[i],anglex*DTOR);
		p[i] = RotateY(p[i],angley*DTOR);
		p[i] = RotateZ(p[i],anglez*DTOR);
	}

	/* Update the autospin angles */
	switch (autospin) {
	case 1:
		break;
	case 2:
      anglex += 1;
      angley += 0.5;
      anglez += 0.25;
		break;
	case 3:
		anglex += 1;
		break;
	case 4:
		angley += 1;
		break;
	case 5:
		anglez += 1;
		break;
	}

	/* Draw the intersection points */
	n = 300;
	glColor3f(1.0,0.0,0.0);
	glBegin(GL_POINTS);
	for (i=0;i<=n;i++) {
		q[0].x = p[0].x + i * (p[1].x - p[0].x) / n;
		q[1].x = p[3].x + i * (p[1].x - p[0].x) / n;
		q[0].y = p[0].y + i * (p[1].y - p[0].y) / n;
		q[1].y = p[3].y + i * (p[1].y - p[0].y) / n;
		q[0].z = p[0].z + i * (p[1].z - p[0].z) / n;
		q[1].z = p[3].z + i * (p[1].z - p[0].z) / n;
		dx = q[1].x - q[0].x;
		dy = q[1].y - q[0].y;
		dz = q[1].z - q[0].z;
		a = dx*dx + dy*dy + dz*dz;
		b = 2 * (dx * q[0].x + dy * q[0].y + dz * q[0].z);
		c = q[0].x*q[0].x + q[0].y*q[0].y + q[0].z*q[0].z - 1;
		if (b*b - 4*a*c <= 0)
 			continue;
		mu = (-b+sqrt(b*b - 4*a*c)) / (2*a);
		x = q[0].x + mu * (q[1].x - q[0].x);
		y = q[0].y + mu * (q[1].y - q[0].y);
		z = q[0].z + mu * (q[1].z - q[0].z);
		glVertex3f(x,y,z);
		mu = (-b-sqrt(b*b - 4*a*c)) / (2*a);
		x = q[0].x + mu * (q[1].x - q[0].x);
		y = q[0].y + mu * (q[1].y - q[0].y);
		z = q[0].z + mu * (q[1].z - q[0].z);
		glVertex3f(x,y,z);
	}
	for (i=0;i<=n;i++) {
		q[0].x = p[0].x + i * (p[3].x - p[0].x) / n;
		q[1].x = p[1].x + i * (p[3].x - p[0].x) / n;
		q[0].y = p[0].y + i * (p[3].y - p[0].y) / n;
		q[1].y = p[1].y + i * (p[3].y - p[0].y) / n;
		q[0].z = p[0].z + i * (p[3].z - p[0].z) / n;
		q[1].z = p[1].z + i * (p[3].z - p[0].z) / n;
		dx = q[1].x - q[0].x;
		dy = q[1].y - q[0].y;
		dz = q[1].z - q[0].z;
		a = dx*dx + dy*dy + dz*dz;
		b = 2 * (dx * q[0].x + dy * q[0].y + dz * q[0].z);
		c = q[0].x*q[0].x + q[0].y*q[0].y + q[0].z*q[0].z - 1;
		if (b*b - 4*a*c <= 0)
 			continue;
		mu = (-b+sqrt(b*b - 4*a*c)) / (2*a);
		x = q[0].x + mu * (q[1].x - q[0].x);
		y = q[0].y + mu * (q[1].y - q[0].y);
		z = q[0].z + mu * (q[1].z - q[0].z);
		glVertex3f(x,y,z);
		mu = (-b-sqrt(b*b - 4*a*c)) / (2*a);
		x = q[0].x + mu * (q[1].x - q[0].x);
		y = q[0].y + mu * (q[1].y - q[0].y);
		z = q[0].z + mu * (q[1].z - q[0].z);
		glVertex3f(x,y,z);
	}
	glEnd();

	/* Optionally show the normal to the plane */
	if (showconstruct) {
		norm = CalcNormal(p[0],p[1],p[2]);
		glColor3f(1.0,0.0,0.0);
		glBegin(GL_LINES);
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(norm.x,norm.y,norm.z);
		glEnd();
	}

	/* Draw the axes */
	if (showconstruct) {
		glBegin(GL_LINES);
		glColor3f(1.0,0.0,0.0);
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(1.0,0.0,0.0);
		glEnd();
		glBegin(GL_LINES);
		glColor3f(0.0,1.0,0.0);
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(0.0,1.0,0.0);
		glEnd();
		glBegin(GL_LINES);
		glColor3f(0.0,0.0,1.0);
		glVertex3f(0.0,0.0,0.0);
		glVertex3f(0.0,0.0,1.0);
		glEnd();
	}

	/* Sphere types */
	switch (spheretype) {
	case 2:
		glColor3f(0.5,0.5,0.5);
		glEnable(GL_LIGHTING);
		glShadeModel(GL_SMOOTH);
		CreateSphere(origin,1.0,64);
		glShadeModel(GL_FLAT);
		glDisable(GL_LIGHTING);
		break;
	case 3:
      glEnable(GL_LIGHTING);
      glShadeModel(GL_SMOOTH);
		glColor3f(0.5,0.5,0.5);
      CreateSphere(origin,1.0,64);
      glShadeModel(GL_FLAT);
      glDisable(GL_LIGHTING);

      for (j=0;j<360;j+=30) {
         glPushMatrix();
         glRotatef((double)j,0.0,1.0,0.0);
         glColor3f(0.0,0.0,1.0);
         glBegin(GL_LINE_STRIP);
			r = 1.005;
         for (i=-90;i<=90;i++) {
            x = r * cos(i*DTOR);
            y = r * sin(i*DTOR);
            z = 0;
            glVertex3f(x,y,z);
         }
         glEnd();
         glPopMatrix();
      }
		
      for (i=-60;i<=60;i+=20) {
         if (i == 0)
            glColor3f(1.0,1.0,0.0);
         else
            glColor3f(0.0,0.0,0.0);
         glBegin(GL_LINE_STRIP);
         r = 1.005;
         for (j=0;j<=360;j++) {
            x = r * cos(i*DTOR) * cos(j*DTOR);
            y = r * sin(i*DTOR);
            z = r * cos(i*DTOR) * sin(j*DTOR);
            glVertex3f(x,y,z);
         }
         glEnd();
      }
		break;
	case 1:
		for (j=0;j<360;j+=20) {
 			glPushMatrix();
 			glRotatef((double)j,0.0,1.0,0.0);
			glColor3f(0.5,0.5,0.5);
 			glBegin(GL_LINE_STRIP);
			r = 1;
 			for (i=-90;i<=90;i++) {
	 			x = r * cos(i*DTOR);
	 			y = r * sin(i*DTOR);
	 			z = 0;
	 			glVertex3f(x,y,z);	
 			}	
 			glEnd();
 			glPopMatrix();		
		}
		for (i=-80;i<=80;i+=10) {
			if (i == 0) 
				glColor3f(1.0,1.0,0.0);
			else
				glColor3f(0.5,0.5,0.5);
 			glBegin(GL_LINE_STRIP);
			r = 1;
 			for (j=0;j<=360;j++) {
	 			x = r * cos(i*DTOR) * cos(j*DTOR);
	 			y = r * sin(i*DTOR);
	 			z = r * cos(i*DTOR) * sin(j*DTOR);
	 			glVertex3f(x,y,z);
 			}
 			glEnd();
		}
		break;
	}

	/* Plane type */
	switch (planetype) {
	case 3:
      glColor4f(0.0,1.0,0.0,0.5);
      glBegin(GL_QUADS);
      for (i=0;i<4;i++)
         glVertex3f(p[i].x,p[i].y,p[i].z);
      glEnd();
		break;
	case 4:
      glColor4f(0.0,1.0,0.0,1.0);
      glBegin(GL_QUADS);
      for (i=0;i<4;i++)
         glVertex3f(p[i].x,p[i].y,p[i].z);
      glEnd();
		break;
	case 2:
      glColor3f(0.0,0.5,0.0);
      glBegin(GL_LINE_STRIP);
		glVertex3f(p[0].x,p[0].y,p[0].z);
      glVertex3f(p[1].x,p[1].y,p[1].z);
      glVertex3f(p[2].x,p[2].y,p[2].z);
      glVertex3f(p[3].x,p[3].y,p[3].z);
      glVertex3f(p[0].x,p[0].y,p[0].z);
      glEnd();
		break;
   case 1:
      n = 8;
      glColor3f(0.0,0.5,0.0);
      glBegin(GL_LINES);
      for (i=0;i<=n;i++) {
         q[0].x = p[0].x + i * (p[1].x - p[0].x) / n;
         q[1].x = p[3].x + i * (p[1].x - p[0].x) / n;
         q[0].y = p[0].y + i * (p[1].y - p[0].y) / n;
         q[1].y = p[3].y + i * (p[1].y - p[0].y) / n;
         q[0].z = p[0].z + i * (p[1].z - p[0].z) / n;
         q[1].z = p[3].z + i * (p[1].z - p[0].z) / n;
         glVertex3f(q[0].x,q[0].y,q[0].z);
         glVertex3f(q[1].x,q[1].y,q[1].z);
         q[0].x = p[0].x + i * (p[3].x - p[0].x) / n;
         q[1].x = p[1].x + i * (p[3].x - p[0].x) / n;
         q[0].y = p[0].y + i * (p[3].y - p[0].y) / n;
         q[1].y = p[1].y + i * (p[3].y - p[0].y) / n;
         q[0].z = p[0].z + i * (p[3].z - p[0].z) / n;
         q[1].z = p[1].z + i * (p[3].z - p[0].z) / n;
         glVertex3f(q[0].x,q[0].y,q[0].z);
         glVertex3f(q[1].x,q[1].y,q[1].z);
      }
      glEnd();
		break;
   }

}

/*
	Set up the lighing environment
*/
void MakeLighting(void)
{
	GLfloat position[4] = {0.0,2.0,0.0,0.0};
	GLfloat ambient[4]  = {0.2,0.2,0.2,1.0};
	GLfloat diffuse[4]  = {1.0,1.0,1.0,1.0};
	GLfloat specular[4] = {1.0,1.0,1.0,1.0};

	/* Turn off all the lights */
	glDisable(GL_LIGHT0);
	glDisable(GL_LIGHT1);
	glDisable(GL_LIGHT2);
	glDisable(GL_LIGHT3);
	glDisable(GL_LIGHT4);
	glDisable(GL_LIGHT5);
	glDisable(GL_LIGHT6);
	glDisable(GL_LIGHT7);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_FALSE);

	/* Turn on the appropriate lights */
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
	glLightfv(GL_LIGHT0,GL_POSITION,position);
	glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
	glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
	glLightfv(GL_LIGHT0,GL_SPECULAR,specular);
	glEnable(GL_LIGHT0);

	/* Sort out the shading algorithm */
	glShadeModel(GL_FLAT);

   glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,specularcolour);
   glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,shininess);
}

/*
	Deal with plain key strokes
*/
void HandleKeyboard(unsigned char key,int x, int y)
{
	switch (key) {
	case ESC: 									/* Quit */
	case 'Q':
	case 'q': 
		exit(0); 
		break;
	case 'c':									/* Toggle constructs */
	case 'C':
		showconstruct = !showconstruct;
		break;
	case 'h':                           /* Go home     */
	case 'H':
		CameraHome(0);
		break;
	case '[':                           /* Roll anti clockwise */
		RotateCamera(0,0,-1);
		break;
	case ']':                           /* Roll clockwise */
		RotateCamera(0,0,1);
		break;
	case 'i':                           /* Translate camera up */
	case 'I':
		TranslateCamera(0,1);
		break;
	case 'k':                           /* Translate camera down */
	case 'K':
		TranslateCamera(0,-1);
		break;
	case 'j':                           /* Translate camera left */
	case 'J':
		TranslateCamera(-1,0);
		break;
	case 'l':                           /* Translate camera right */
	case 'L':
		TranslateCamera(1,0);
		break;
	case 'x':
		anglex += dtheta;
		break;
	case 'X':
		anglex -= dtheta;
		break;
	case 'y':
		angley += dtheta;
		break;
	case 'Y':
		angley -= dtheta;
		break;
	case 'z':
		anglez += dtheta;
		break;
	case 'Z':
		anglez -= dtheta;
		break;
	case 'w':                           /* Write the image to disk */
	case 'W':
		windowdump = !windowdump;
		break;
	case '1':
		showobject = 1;
		break;
	case '2':
		showobject = 2;
		break;
	case '3':
		showobject = 3;
		break;
	case '4':
		showobject = 4;
		break;
	case '0':
		showobject = 0;
		break;
	}
}

/*
	Deal with special key strokes
*/
void HandleSpecialKeyboard(int key,int x, int y)
{
	switch (key) {
	case GLUT_KEY_LEFT:
		RotateCamera(-1,0,0);
		break;
	case GLUT_KEY_RIGHT:
		RotateCamera(1,0,0);
		break;
	case GLUT_KEY_UP:
		RotateCamera(0,1,0);
		break;
	case GLUT_KEY_DOWN:
		RotateCamera(0,-1,0);
		break;
	}
}

/*
	Rotate (ix,iy) or roll (iz) the camera about the focal point
	ix,iy,iz are flags, 0 do nothing, +- 1 rotates in opposite directions
	Correctly updating all camera attributes
*/
void RotateCamera(int ix,int iy,int iz)
{
	XYZ vp,vu,vd;
	XYZ right;
	XYZ newvp,newr;
	double radius,dd,radians;
	double dx,dy,dz;

	vu = camera.vu;
	Normalise(&vu);
	vp = camera.vp;
	vd = camera.vd;
	Normalise(&vd);
	CROSSPROD(vd,vu,right);
	Normalise(&right);
	radians = dtheta * PI / 180.0;

	/* Handle the roll */
	if (iz != 0) {
		camera.vu.x += iz * right.x * radians;
		camera.vu.y += iz * right.y * radians;
		camera.vu.z += iz * right.z * radians;
		Normalise(&camera.vu);
		return;
	}

	/* Distance from the rotate point */
	dx = camera.vp.x - camera.pr.x;
	dy = camera.vp.y - camera.pr.y;
	dz = camera.vp.z - camera.pr.z;
	radius = sqrt(dx*dx + dy*dy + dz*dz);

	/* Determine the new view point */
	dd = radius * radians;
	newvp.x = vp.x + dd * ix * right.x + dd * iy * vu.x - camera.pr.x;
	newvp.y = vp.y + dd * ix * right.y + dd * iy * vu.y - camera.pr.y;
	newvp.z = vp.z + dd * ix * right.z + dd * iy * vu.z - camera.pr.z;
	Normalise(&newvp);
	camera.vp.x = camera.pr.x + radius * newvp.x;
	camera.vp.y = camera.pr.y + radius * newvp.y;
	camera.vp.z = camera.pr.z + radius * newvp.z;

	/* Determine the new right vector */
	newr.x = camera.vp.x + right.x - camera.pr.x;
	newr.y = camera.vp.y + right.y - camera.pr.y;
	newr.z = camera.vp.z + right.z - camera.pr.z;
	Normalise(&newr);
	newr.x = camera.pr.x + radius * newr.x - camera.vp.x;
	newr.y = camera.pr.y + radius * newr.y - camera.vp.y;
	newr.z = camera.pr.z + radius * newr.z - camera.vp.z;

	camera.vd.x = camera.pr.x - camera.vp.x;
	camera.vd.y = camera.pr.y - camera.vp.y;
	camera.vd.z = camera.pr.z - camera.vp.z;
	Normalise(&camera.vd);

	/* Determine the new up vector */
	CROSSPROD(newr,camera.vd,camera.vu);
	Normalise(&camera.vu);
}

/*
	Translate (pan) the camera view point
	In response to i,j,k,l keys
	Also move the camera rotate location in parallel
*/
void TranslateCamera(int ix,int iy)
{
	XYZ vp,vu,vd;
	XYZ right;
	XYZ newvp,newr;
	double radians,delta;

	vu = camera.vu;
	Normalise(&vu);
	vp = camera.vp;
	vd = camera.vd;
	Normalise(&vd);
	CROSSPROD(vd,vu,right);
	Normalise(&right);
	radians = dtheta * PI / 180.0;
	delta = dtheta * camera.focallength / 90.0;

	camera.vp.x += iy * vu.x * delta;
	camera.vp.y += iy * vu.y * delta;
	camera.vp.z += iy * vu.z * delta;
	camera.pr.x += iy * vu.x * delta;
	camera.pr.y += iy * vu.y * delta;
	camera.pr.z += iy * vu.z * delta;

	camera.vp.x += ix * right.x * delta;
	camera.vp.y += ix * right.y * delta;
	camera.vp.z += ix * right.z * delta;
	camera.pr.x += ix * right.x * delta;
	camera.pr.y += ix * right.y * delta;
	camera.pr.z += ix * right.z * delta;
}

/*
	Handle mouse events
	Right button events are passed to menu handlers
*/
void HandleMouse(int button,int state,int x,int y)
{
	if (state == GLUT_DOWN) {
		if (button == GLUT_LEFT_BUTTON) {
 			currentbutton = GLUT_LEFT_BUTTON;
		} else if (button == GLUT_MIDDLE_BUTTON) {
 			currentbutton = GLUT_MIDDLE_BUTTON;
		} 
	}
}

/*
	Handle the main menu
*/
void HandleMainMenu(int whichone)
{
	switch (whichone) {
	case 1:
		showconstruct = !showconstruct;
		break;
	case 8:
		CameraHome(0);
		break;
	case 9: 
		exit(0); 
		break;
	}
}

void HandlePlaneMenu(int which)
{
	planetype = which;
}
void HandleSphereMenu(int which)
{
   spheretype = which;
}
void HandleSpinMenu(int which)
{
   autospin = which;
}

/*
	Handle the speed menu
	The rotate speed is in degrees
*/
void HandleSpeedMenu(int whichone)
{
	switch (whichone) {
	case 1: rotatespeed = 0.0; break;
	case 2: rotatespeed = 0.3; break;
	case 3: rotatespeed = 1;   break;
	case 4: rotatespeed = 3;   break;
	case 5: rotatespeed = 10;  break;
	}
}

/*
	How to handle visibility
*/
void HandleVisibility(int visible)
{
	if (visible == GLUT_VISIBLE)
		glutIdleFunc(HandleIdle);
	else
		glutIdleFunc(NULL);
}

/*
	What to do on an idle event
*/
void HandleIdle(void)
{
	glutPostRedisplay();
}

/*
	Handle a window reshape/resize
*/
void HandleReshape(int w,int h)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
	camera.screenwidth = w;
	camera.screenheight = h;
}

/*
Display the program usage information
*/
void GiveUsage(char *cmd)
{
	fprintf(stderr,"Usage: %s [-h] [-f] [-s] [-c] [-q n]\n",cmd);
	fprintf(stderr,"          -h   this text\n");
	fprintf(stderr,"          -f   full screen\n");
	fprintf(stderr,"          -s   stereo\n");
	fprintf(stderr,"          -c   show construction lines\n");
	fprintf(stderr,"Key Strokes\n");
	fprintf(stderr,"  arrow keys   rotate camera left/right/up/down\n");
	fprintf(stderr,"  left mouse   rotate camera about x and z axis\n");
	fprintf(stderr,"middle mouse   roll camera about y axis\n");
	fprintf(stderr,"           c   toggle construction lines\n");
	fprintf(stderr,"           i   translate up\n");
	fprintf(stderr,"           k   translate down\n");
	fprintf(stderr,"           j   translate left\n");
	fprintf(stderr,"           l   translate right\n");
	fprintf(stderr,"           [   roll clockwise\n");
	fprintf(stderr,"           ]   roll anti clockwise\n");
	fprintf(stderr,"           x   rotate cutting plane about  x axis\n");
	fprintf(stderr,"           X   rotate cutting plane about -x axis\n");
	fprintf(stderr,"           y   rotate cutting plane about  y axis\n");
	fprintf(stderr,"           Y   rotate cutting plane about -y axis\n");
	fprintf(stderr,"           z   rotate cutting plane about  z axis\n");
	fprintf(stderr,"           Z   rotate cutting plane about -z axis\n");
	fprintf(stderr,"           q   quit\n");
	exit(-1);
}

/*
	Move the camera to the home position 
*/
void CameraHome(int mode)
{
	camera.aperture = 60;
	camera.focallength = 3;
	camera.eyesep = camera.focallength / 20;
	camera.pr = origin;

	camera.vp.x = 0;
	camera.vp.y = 0; 
	camera.vp.z = camera.focallength;

	camera.vd.x = 0; 
	camera.vd.y = 0; 
	camera.vd.z = -1;

	camera.vu.x = 0;  
	camera.vu.y = 1; 
	camera.vu.z = 0;

	anglex = 0;
	angley = 0;
	anglez = 0;
}

/*
	Handle mouse motion
*/
void HandleMouseMotion(int x,int y)
{
	static int xlast=-1,ylast=-1;
	int dx,dy;

	dx = x - xlast;
	dy = y - ylast;
	if (dx < 0)      dx = -1;
	else if (dx > 0) dx =  1;
	if (dy < 0)      dy = -1;
	else if (dy > 0) dy =  1;

	if (currentbutton == GLUT_LEFT_BUTTON) {
		RotateCamera(-dx,dy,0);
	} else if (currentbutton == GLUT_MIDDLE_BUTTON) {
		RotateCamera(0,0,dx);
	}

	xlast = x;
	ylast = y;
}

#include "/usr/users/pbourke/include/paulslib.c"
#include "/usr/users/pbourke/include/opengllib.c"
#include "/usr/users/pbourke/include/bitmaplib.c"

