/*   Jordan Maynard
 *   email: maynard@rohan.sdsu.edu
 *
 *   Environment Mapping Demo
 *   CS496, Spring 2k
 *   Dr. Cunningham
 *
 *   PROGRAM DESCRIPTION:  This prgram uses OpenGL to visualize a 3D math function of
 *   the form z = x|y. 
 *  
 *   Also - the window titlebar displays the Frames Per Second (FPS) that the program 
 *   is currently running at - you can observe changes in FPS by toggling lighting/smoothing 
 *   or by the selecting the 'empty set' function...
 *
 *   PROGRAM USAGE:
 *
 *   Use the menu "Right Button" to select Fullscreen or Windowed mode,
 *   Toggle Lighting, Smoothing, Wireframe and Axises ON/OFF
 *   and to select different the different functions from a submenu...
 *
 *   Also, these shortcuts work for the menu items:
 *
 *   (Command-F): Fullscreen Mode
 *   (Command-W): Windowed Mode
 *   (Command-L): Toggle Lighting
 *   (Command-M): Toggle sMoothing   (S is normally reserved for "save")
 *   (Command-I): Toggle wIreframe
 *   (Command-A): Toggle Axes
 *   (Command-0...9): Select Function
 * 
 *   Use <esc> or (Command-Q) to quit
 *
 *   NOTES: 
 *
 *   This program has been compiled and tested on both an iMac DV and a PentiumIII
 *   CodeWarrior 5 was used on the iMac and VC++ was used on the PentiumIII
 *
 *   The hotkey shortcuts only work on MacOS...
 *
 *   NOTE: the texture loading uses a glAux function and might want to be replaced
 *   with a more generic function to read a raw image file.
 */

#include "glut.h"
#include "glu.h"			// Header File For The GLu32 Library
#include "aux.h"			// Header File For The Glaux Library

#include <stdio.h>	
#include <ctype.h>
#include <time.h>
// Uses math.h to perform calculations needed for the 3D functions 
#include "math.h"

#define SPACE 35.0
#define EDGELEN 0.09
#define POINTSPERSIDE 24
#define STARTCOORD (POINTSPERSIDE-1)*EDGELEN/2
#define FUNCTIONCOUNT 10

long t = 0 ;
long ot = 0;
int lighting = 1;
int smoothing = 1;
int wire = 1;
int axes = 1;
int tex = 1;
int color = 0;
int env = 1;
long frames = 0;
int fps= 0;
GLfloat lightPosition[] = {-3.0, 0.0, 1.5, 1.0};
char s[100];
GLuint	texture[1];	

// Defines a point in 3-Space...
struct Point3d
{
    GLfloat x, y, z;
};

// An array of 3-Space points to represent the functions...
struct Point3d p[POINTSPERSIDE][POINTSPERSIDE];

// To transform the view angle...
GLfloat spinX = 0.0, spinY = -50.0, spinZ = 0.0;

// To spin the function...
GLfloat  x = 0.0, y = 0.0, z = 0.7;

// Which display list to use (function)
int dispList = 5;

// Standard normalization 
void normalize(float v[3])
{
    float d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
    if (d != 0.0) /* avoid division by zero */
    {
	v[0] /= d;
	v[1] /= d;
	v[2] /= d;
    }
}

AUX_RGBImageRec *LoadBMP(char *Filename)				// Loads A Bitmap Image
{
        FILE *File=NULL;								// File Handle
        if (!Filename)									// Make Sure A Filename Was Given
        {
                return NULL;							// If Not Return NULL
        }
        File=fopen(Filename,"r");						// Check To See If The File Exists
        if (File)										// Does The File Exist?
        {
			fclose(File);								// Close The Handle
			return  auxRGBImageLoad(Filename);
        }
        return NULL;									// If Load Failed Return NULL
}

int LoadGLTextures()									// Load Bitmap And Convert To Textures
{
        int Status=FALSE;								// Status Indicator
        AUX_RGBImageRec *TextureImage[1];				// Create Storage Space For The Textures
        memset(TextureImage,0,sizeof(void *)*1);		// Set The Pointer To NULL

        if (TextureImage[0]=LoadBMP("HongKong.sgi"))	// Load Particle Texture
        {
			Status=TRUE;
			glGenTextures(1, &texture[0]);				// Create One Texture

			glBindTexture(GL_TEXTURE_2D, texture[0]);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,
						 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
        }

        if (TextureImage[0])							// If Texture Exists
		{
			if (TextureImage[0]->data)					// If Texture Image Exists
			{
				free(TextureImage[0]->data);			// Free The Texture Image Memory
			}
			free(TextureImage[0]);						// Free The Image Structure
		}
        return Status;									// Return The Status
}

// Calc normal cross product
void normCrossProd(float v1[3], float v2[3], float out[3])
{
    out[0] = v1[1]*v2[2] - v1[2]*v2[1];
    out[1] = v1[2]*v2[0] - v1[0]*v2[2];
    out[2] = v1[0]*v2[1] - v1[1]*v2[0];
    normalize(out);
}

void changeColor(GLfloat x, GLfloat y,GLfloat z)
{
    // make it so Blue gets added for positive and negative z values
    if(z<0)
    	z = -z;
	glColor4f(x,y,z,0.5);  
}

void drawaxis(void)
{
    glBegin(GL_LINES);
    changeColor(1.0,1.0,1.0);
	glVertex3f(    0.0,     0.0,     2.0);
	glVertex3f(    0.0,     0.0,     -2.0);
	glEnd();
	
	glBegin(GL_TRIANGLES);
    changeColor(1.0,1.0,1.0);
	glVertex3f(    0.0,     0.0,     2.0);
	glVertex3f(    0.0,     0.1,     1.9);
	glVertex3f(    0.0,     -0.1,     1.9);

	glVertex3f(    0.0,     0.0,     -2.0);
	glVertex3f(    0.0,     0.1,     -1.9);
	glVertex3f(    0.0,     -0.1,    -1.9);
	
	glEnd();
}

// Glut display callback....
void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    
    // Set view...
    glRotatef(spinY, 1.0, 0.0, 0.0);
    glRotatef(spinX, 0.0, 1.0, 0.0);
    glRotatef(spinZ, 0.0, 0.0, 1.0);
    
    // Call current functions display list
    glCallList(dispList);
   
	if(axes) {
		glPushMatrix();
		drawaxis();   // Z axis
		glRotatef(90.0, 0.0, 1.0, 0.0);
		drawaxis();   //  X axis
		glRotatef(90.0, 1.0, 0.0, 0.0);
		drawaxis();  //  Y axis
		glPopMatrix();
	}
	glPopMatrix();
	glPopMatrix();

    glFinish();
    glutSwapBuffers();
    frames++;
}

// Glut idle callback... spins!
void idle(void)
{   
	spinZ +=z;
	if (spinZ >= 360.0)
	    spinZ -= 360.0;
	glutPostRedisplay();
}

// Inits a Display list for each function func
void initDisplayList(int func)
{
    int i, j, k;
    
    // hold the max values for x,y, and z
    int maxz = 0;
    int maxx = 0;
    int maxy = 0;
    GLfloat x, y, v1[3], v2[3], v3[3],v4[3], normal[3], normal1[3], normal2[3], normal3[3], normal4[3];

    for (y=STARTCOORD, j=0; j<POINTSPERSIDE; y-=EDGELEN, j++) {
		for (x=-STARTCOORD, k=0; k<POINTSPERSIDE; x+=EDGELEN, k++) {
			p[k][j].x = x;
			p[k][j].y = y;
	    
	 	   // These are the 9 different functions z = x|y
			if(func == 0)
				p[k][j].z = 0.0;
	    	if(func == 4)
				p[k][j].z = cos(0.04*sqrt(x*SPACE*x*SPACE + y*SPACE*y*SPACE));
			if(func == 5)
	    		p[k][j].z = -cos(0.1*sqrt(x*SPACE*x*SPACE + y*SPACE*y*SPACE));
	    	if(func == 2)
	  			p[k][j].z = sin(x-y);
	    	if(func == 7)
	    		p[k][j].z = .05/-(x*x+y*y);
	    	if(func == 3)
	        	p[k][j].z = sin(y*cos(x*y)/0.5*sin(x*y));
	    	if(func == 1)    
	        	p[k][j].z = x*y;
	    	if(func == 6)
	    		p[k][j].z = x*x-y*y;
	    	if(func == 8)
	    		p[k][j].z = sin(0.01*sqrt(x*SPACE*x*SPACE + y*SPACE*y*SPACE))/x*y;
			if(p[k][j].z > maxz)
				maxz = p[k][j].z;
				if(maxz == 0.0)
					maxz = 1.0;
			if(p[k][j].x > maxx)
				maxx = p[k][j].x;
			if(p[k][j].y > maxy)
				maxy = p[k][j].y;
		}
    }

    // Create a new list with the specified function
    glNewList(func+1, GL_COMPILE);
    for (j=1; j<POINTSPERSIDE-2; j++) {
		for (i=1; i<POINTSPERSIDE-2; i++) {
			// Enable GL_COLOR_MATERIAL so we can easily change the color for each vertex
		 	glEnable(GL_COLOR_MATERIAL);
	     	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	    	glBegin(GL_QUADS);
	    // Calculate normals for lighting...
	    v1[0] = p[j][i].x - p[j+1][i].x;
	    v1[1] = p[j][i].y - p[j+1][i].y;
	    v1[2] = p[j][i].z - p[j+1][i].z;
	    
	    v2[0] = p[j][i].x - p[j-1][i].x;
	    v2[1] = p[j][i].y - p[j-1][i].y;
	    v2[2] = p[j][i].z - p[j-1][i].z;

		v3[0] = p[j][i].x - p[j][i+1].x;
	    v3[1] = p[j][i].y - p[j][i+1].y;
	    v3[2] = p[j][i].z - p[j][i+1].z;

		v4[0] = p[j][i].x - p[j][i-1].x;
	    v4[1] = p[j][i].y - p[j][i-1].y;
	    v4[2] = p[j][i].z - p[j][i-1].z;

	    normCrossProd(v1, v3, normal1);
        normCrossProd(v3, v2, normal2);
		normCrossProd(v2, v4, normal3);
		normCrossProd(v4, v1, normal4);

        normal[0] = -(normal1[0] + normal2[0] + normal3[0] + normal4[0])/4;
		normal[1] = -(normal1[1] + normal2[1] + normal3[1] + normal4[1])/4;
		normal[2] = -(normal1[2] + normal2[2] + normal3[2] + normal4[2])/4;

	    glNormal3fv(normal);

	 if(color == 1)
	    changeColor((GLfloat)p[j][i].x/maxx,(GLfloat)p[j][i].y/maxy,(GLfloat)p[j][i].z/maxz);
	    glVertex3f(    p[j][i].x,     p[j][i].y,     p[j][i].z);

		v1[0] = p[j+1][i].x - p[j+2][i].x;
	    v1[1] = p[j+1][i].y - p[j+2][i].y;
	    v1[2] = p[j+1][i].z - p[j+2][i].z;

	    v2[0] = p[j+1][i].x - p[j][i].x;
	    v2[1] = p[j+1][i].y - p[j][i].y;
	    v2[2] = p[j+1][i].z - p[j][i].z;

		v3[0] = p[j+1][i].x - p[j+1][i+1].x;
	    v3[1] = p[j+1][i].y - p[j+1][i+1].y;
	    v3[2] = p[j+1][i].z - p[j+1][i+1].z;

		v4[0] = p[j+1][i].x - p[j+1][i-1].x;
	    v4[1] = p[j+1][i].y - p[j+1][i-1].y;
	    v4[2] = p[j+1][i].z - p[j+1][i-1].z;

	    normCrossProd(v1, v3, normal1);
        normCrossProd(v3, v2, normal2);
		normCrossProd(v2, v4, normal3);
		normCrossProd(v4, v1, normal4);

        normal[0] = -(normal1[0] + normal2[0] + normal3[0] + normal4[0])/4;
		normal[1] = -(normal1[1] + normal2[1] + normal3[1] + normal4[1])/4;
		normal[2] = -(normal1[2] + normal2[2] + normal3[2] + normal4[2])/4;

	    glNormal3fv(normal);

	   if(color == 1)
	    changeColor((GLfloat)p[j+1][i].x/maxx,(GLfloat)p[j+1][i].y/maxy,(GLfloat)p[j+1][i].z/maxz);
	    glVertex3f(  p[j+1][i].x,   p[j+1][i].y,   p[j+1][i].z);

		v1[0] = p[j+1][i+1].x - p[j+2][i+1].x;
	    v1[1] = p[j+1][i+1].y - p[j+2][i+1].y;
	    v1[2] = p[j+1][i+1].z - p[j+2][i+1].z;

	    v2[0] = p[j+1][i+1].x - p[j][i+1].x;
	    v2[1] = p[j+1][i+1].y - p[j][i+1].y;
	    v2[2] = p[j+1][i+1].z - p[j][i+1].z;

		v3[0] = p[j+1][i+1].x - p[j+1][i+2].x;
	    v3[1] = p[j+1][i+1].y - p[j+1][i+2].y;
	    v3[2] = p[j+1][i+1].z - p[j+1][i+2].z;

		v4[0] = p[j+1][i+1].x - p[j+1][i].x;
	    v4[1] = p[j+1][i+1].y - p[j+1][i].y;
	    v4[2] = p[j+1][i+1].z - p[j+1][i].z;

	    normCrossProd(v1, v3, normal1);
        normCrossProd(v3, v2, normal2);
		normCrossProd(v2, v4, normal3);
		normCrossProd(v4, v1, normal4);

        normal[0] = -(normal1[0] + normal2[0] + normal3[0] + normal4[0])/4;
		normal[1] = -(normal1[1] + normal2[1] + normal3[1] + normal4[1])/4;
		normal[2] = -(normal1[2] + normal2[2] + normal3[2] + normal4[2])/4;

	    glNormal3fv(normal);

	  if(color == 1)
		changeColor((GLfloat)p[j+1][i+1].x/maxx,(GLfloat)p[j+1][i+1].y/maxy,(GLfloat)p[j+1][i+1].z/maxz);	    
	    glVertex3f(p[j+1][i+1].x, p[j+1][i+1].y, p[j+1][i+1].z);

		v1[0] = p[j][i+1].x - p[j+1][i+1].x;
	    v1[1] = p[j][i+1].y - p[j+1][i+1].y;
	    v1[2] = p[j][i+1].z - p[j+1][i+1].z;
	    
	    v2[0] = p[j][i+1].x - p[j-1][i+1].x;
	    v2[1] = p[j][i+1].y - p[j-1][i+1].y;
	    v2[2] = p[j][i+1].z - p[j-1][i+1].z;

		v3[0] = p[j][i+1].x - p[j][i+2].x;
	    v3[1] = p[j][i+1].y - p[j][i+2].y;
	    v3[2] = p[j][i+1].z - p[j][i+2].z;

		v4[0] = p[j][i+1].x - p[j][i].x;
	    v4[1] = p[j][i+1].y - p[j][i].y;
	    v4[2] = p[j][i+1].z - p[j][i].z;

	    normCrossProd(v1, v3, normal1);
        normCrossProd(v3, v2, normal2);
		normCrossProd(v2, v4, normal3);
		normCrossProd(v4, v1, normal4);

        normal[0] = -(normal1[0] + normal2[0] + normal3[0] + normal4[0])/4;
		normal[1] = -(normal1[1] + normal2[1] + normal3[1] + normal4[1])/4;
		normal[2] = -(normal1[2] + normal2[2] + normal3[2] + normal4[2])/4;

	    glNormal3fv(normal);

     if(color == 1)
		changeColor((GLfloat)p[j][i+1].x/maxx,(GLfloat)p[j][i+1].y/maxy,(GLfloat)p[j][i+1].z/maxz);
	    glVertex3f(  p[j][i+1].x,   p[j][i+1].y,   p[j][i+1].z);
	    
	    glEnd();
	}
    }
    glEndList(); 
}

void init(void)
{   
    int i=0;
	
    // set up lighting and shading models
    GLfloat lightPosition[] = {-3.0, 0.0, 1.5, 1.0};
    GLfloat matSpecular[] = {0.9, 0.9, 0.9, 1.0};
    GLfloat matShininess[] = {60.0};

    // Use black as clear color...
    glClearColor(0.0,0.0,0.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);

    glMaterialfv(GL_FRONT, GL_SPECULAR, matSpecular);
    glMaterialfv(GL_FRONT, GL_SHININESS, matShininess);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    
   LoadGLTextures();						// Jump To Texture Loading Routine
    
    // create FUNCTIONCOUNT display lists for storing the functions...
    for (i=0; i< FUNCTIONCOUNT; i++)
	initDisplayList(i);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);	// Really Nice Perspective Calculations
	glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);				// Really Nice Point Smoothing
    glEnable(GL_TEXTURE_2D);							// Enable Texture Mapping
	glBindTexture(GL_TEXTURE_2D,texture[0]);
	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);  // These two lines tell OpenGL to 
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);  // do the environment mapping!

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_FALSE);
}

void keyboard(unsigned char key, int xPos, int yPos)
{
    switch (key)
    {
	case 27:	// <esc>  = Exit program
	    exit(0); break;
	case ' ':
		dispList++;
		dispList %= FUNCTIONCOUNT;
		break;
	}
}

void motion(int xPos, int yPos)
{
   // x = y = 0.0;
    spinX = (GLfloat)-xPos;
    spinY = (GLfloat)yPos;
}

void menu(int m)
{
 switch(m)
 {
 	case 1:
 		glutFullScreen();
        glutPositionWindow(0,0);
 		break;
 	case 2:
 		glutReshapeWindow(500, 500);
        glutPositionWindow(100,100);
        break;
    case 3:
 		if(lighting)
 		{
 			lighting = 0;
 			glDisable(GL_LIGHTING);
 		} else {
 			lighting = 1;
 			glEnable(GL_LIGHTING);
 			}
        break;
    case 4:
 		if(smoothing)
 		{
 			smoothing = 0;
 			glShadeModel(GL_FLAT);
 		} else {
 			smoothing = 1;
 			glShadeModel(GL_SMOOTH);
        }
        break;
        
    case 5:
 		if(wire)
 		{
 			wire = 0;
 			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 		} else {
 			wire = 1;
 			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        }
        break;  
        
    case 6:
    	if(axes)
 		axes = 0;
 		else
 		axes = 1;
        break;   
        
        case 7:

       if(tex)
 		{
 			tex = 0;
 			glDisable(GL_TEXTURE_2D);
 		} else {
 			tex = 1;
 			glEnable(GL_TEXTURE_2D);
        }
        break;  
       
        case 8:
       if(env)
 		{
 			env = 0;
			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
 		} else {
 			env = 1;
 			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
        }
        break;       
 	}
 }
 
void submenu(int m)
{
 switch(m)
 {
    case 0:
 		dispList = 0;
        break;
 	case 1:
 		dispList = 1;
 		break;
 	case 2:
 		dispList = 2;
        break;
    case 3:
 		dispList = 3;
        break;
    case 4:
 		dispList = 4;
        break;
    case 5:
 		dispList = 5;
        break;
    case 6:
 		dispList = 6;
        break;
    case 7:
 		dispList = 7;
        break;
    case 8:
 		dispList = 8;
        break;
    case 9:
 		dispList = 9;
        break;
 }
}

void reshape(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.5);
}

int main(int argc, char** argv)
{
     int mymenu;
	// This is all pretty standard stuff to setup a window using glut. 
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100,100);
    glutCreateWindow("Maynard Project 3 / FPS:???");
    mymenu = glutCreateMenu(submenu);
    glutSetMenu(glutCreateMenu(menu));
    
    glutAddMenuEntry("FullScreen Mode/F",1);
    glutAddMenuEntry("Windowed Mode/W",2);
    glutAddMenuEntry("- SEPERATOR",0);
    glutAddMenuEntry("Toggle Lighting/L",3);
    glutAddMenuEntry("Toggle Smoothing/M",4);
    glutAddMenuEntry("Toggle Wireframe/I",5);
    glutAddMenuEntry("Toggle Axes/A",6);
    glutAddMenuEntry("Toggle Texture/T",7);
    glutAddMenuEntry("Toggle Environment Mapping/E",8);
    glutAddMenuEntry("- SEPERATOR",0);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    glutAddSubMenu("Function",mymenu);
    glutSetMenu(mymenu);
    glutAddMenuEntry("z = [empty set]/0",0);
    glutAddMenuEntry("z = 0/1",1);
    glutAddMenuEntry("z = x*y/2",2);
    glutAddMenuEntry("z = sin[x-y]/3",3);
    glutAddMenuEntry("z = sin[y*cos[x*y] div sin[x*y]]/4",4);
    glutAddMenuEntry("z = cos{sqrt{x*x + y*y}}/5",5);
    glutAddMenuEntry("z = {-cos{sqrt{x*x + y*y}}}/6",6);
    glutAddMenuEntry("z = x*x-y*y/7",7);
    glutAddMenuEntry("z = 1 div -{x*x+y*y}/8",8);
    glutAddMenuEntry("z = sin{sqrt{x*x + y*y}} div x*y/9",9);
    
    init();
    
    // Set glut callbacks...
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutMotionFunc(motion);
   
    glutIdleFunc(idle);
    dispList = 4;
    // Main Loop!
    glutMainLoop();
    
    return 0;
}