/*
   Mathematical surface, with the ability to move the surface around and
   see all parts.  The surface is animated -- it's from a one-parameter
   family of surfaces, and that parameter is animated by the  idle  function
   
   X-rotations  :  q  w
   Y-rotations  :  a  s
   Z-rotations  :  z  x
   
   (c) February 2001, Steve Cunningham & Ken Brown
*/
#define OSX

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

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#define TRUE 1
#define FALSE 0

// variables to keep track of the current rotation on each axis
GLfloat camPitch = 1.0, camYaw = 0.0, camRadius = 16.0;
int mx,my; // used to calculate mouse displacement
int mdown; // is a button down?
int buttonDown; // if so, which button?

typedef GLfloat point3[3];

GLfloat t=0.0;        // parameter for the family of functions

//  Define the parameters of the surface grid
//  For an N x M grid you need XSIZE = N+1 and ZSIZE = M+1
#define XSIZE 125  // needs to be much larger than this for best results
#define ZSIZE XSIZE
#define MINX  -3.0 // was -3..3, -3..3 with original equation -- CHOICE = 4
#define MAXX   3.0
#define MINZ  -3.0
#define MAXZ   3.0

// the equation to use for the surface
int CHOICE = 4;

static GLfloat vertices[XSIZE][ZSIZE];

// function prototypes follow
GLfloat XX( int );
GLfloat ZZ( int );
void myinit( void );
void surface( void );
void display( void );
void reshape( int ,int );
void keyboard(unsigned char, int, int );
void animate( void );

// two functions to return X and Z values for array indices  i  and  j  respectively
GLfloat XX(int i) {
     return (MINX+((MAXX-MINX)/(float)(XSIZE-1))*(float)(i));
}

GLfloat ZZ(int j) {
     return (MINZ+((MAXZ-MINZ)/(float)(ZSIZE-1))*(float)(j));
}

// initialize OpenGL and all necessary global variables
void myinit(void)
{
        GLfloat light_pos0[]={  0.0, 10.0, 10.0,  1.0 }; // first light over z-axis
        GLfloat light_col0[]={  1.0,  0.0,  0.0,  1.0 }; // and red
        GLfloat amb_color0[]={  0.3,  0.0,  0.0,  1.0 }; // even ambiently
        
        GLfloat light_pos1[]={  5.0, 10.0, -7.26, 1.0 }; // second light back/right
        GLfloat light_col1[]={  0.0,  1.0,  0.0,  1.0 }; // and green
        GLfloat amb_color1[]={  0.0,  0.3,  0.0,  1.0 }; // even ambiently
        
        GLfloat light_pos2[]={ -5.0, 10.0, -7.26, 1.0 }; // third light back/left
        GLfloat light_col2[]={  0.0,  0.0,  1.0,  1.0 }; // and blue
        GLfloat amb_color2[]={  0.0,  0.0,  0.3,  1.0 }; // even ambiently
        
//      set up overall light data
        GLfloat mat_specular[]  ={ 0.8, 0.8, 0.8, 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_pos0 ); // light 0
        glLightfv(GL_LIGHT0, GL_AMBIENT, amb_color0 );
        glLightfv(GL_LIGHT0, GL_SPECULAR, light_col0 );
        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_col0 );
        
        glLightfv(GL_LIGHT1, GL_POSITION, light_pos1 ); // light 1
        glLightfv(GL_LIGHT1, GL_AMBIENT, amb_color1 );
        glLightfv(GL_LIGHT1, GL_SPECULAR, light_col1 );
        glLightfv(GL_LIGHT1, GL_DIFFUSE, light_col1 );
        
        glLightfv(GL_LIGHT2, GL_POSITION, light_pos2 ); // light 2
        glLightfv(GL_LIGHT2, GL_AMBIENT, amb_color2 );
        glLightfv(GL_LIGHT2, GL_SPECULAR, light_col2 );
        glLightfv(GL_LIGHT2, GL_DIFFUSE, light_col2 );
        
        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_LIGHT1);     // ... and LIGHT1
        glEnable(GL_LIGHT2);     // ... and LIGHT2
        glEnable(GL_DEPTH_TEST); // allow z-buffer display
        glEnable(GL_NORMALIZE);  // make normal vectors 1-unit long after transform
}

// draw the surface
void surface(void)
{
    point3 vec1, vec2, triNormal;
    
    int  i, j;
    float x, z;

//  Set up the surface color
    GLfloat white[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat yellow[]= {1.0, 1.0, 0.0, 1.0};
    GLfloat mat_shininess[]={ 30.0 };
    
//  Calculate the points of the surface on the grid

    #define EPSILON .001;
    for ( i=0; i<XSIZE; i++ )
       for ( j=0; j<ZSIZE; j++ )
       {
          x = XX(i);
          z = ZZ(j);
          switch (CHOICE) {
          case 1: vertices[i][j] = x*(z+t)*(x*x-(z+t)*(z*+t)); break;
          case 2: vertices[i][j] = (x*x+2.0*z*z)/exp(x*x+2.0*z*z+t); break;
          case 3: vertices[i][j] = x*x + z*z + t*x*x*x + t*z*z*z -1; break;
          case 4: vertices[i][j] = 0.3*cos(x*x+z*z+t); break;
          }
       }

/*  actually draw the surface */

    
    for ( i=0; i<XSIZE-1; i++ )
       for ( j=0; j<ZSIZE-1; j++ )
       {
       // first triangle in the quad, front face
          glMaterialfv(GL_FRONT, GL_DIFFUSE, white );
          glMaterialfv(GL_BACK, GL_DIFFUSE, yellow );
          glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );
          glBegin(GL_POLYGON);
			 // build the normal for the triangle
             vec1[0] = XX(i+1)-XX(i);
             vec1[1] = vertices[i+1][j]-vertices[i][j];
             vec1[2] = ZZ(j)-ZZ(j);
             vec2[0] = XX(i+1)-XX(i+1);
             vec2[1] = vertices[i+1][j+1]-vertices[i+1][j];
             vec2[2] = ZZ(j+1)-ZZ(j);
             triNormal[0] = vec1[1] * vec2[2] - vec1[2] * vec2[1];
             triNormal[1] = vec1[2] * vec2[0] - vec1[0] * vec2[2];
             triNormal[2] = vec1[0] * vec2[1] - vec1[1] * vec2[0];
             glNormal3fv(triNormal); // hack together the normal vector...

             glVertex3f(XX(i),vertices[i  ][j  ],ZZ(j));
             glVertex3f(XX(i+1),vertices[i+1][j  ],ZZ(j));
             glVertex3f(XX(i+1),vertices[i+1][j+1],ZZ(j+1));
          glEnd();

        // second triangle in the quad, front face  
          glMaterialfv(GL_FRONT, GL_DIFFUSE, white );
          glMaterialfv(GL_BACK, GL_DIFFUSE, yellow );
          glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess );
          glBegin(GL_POLYGON);
             vec1[0] = XX(i+1)-ZZ(i);
             vec1[1] = vertices[i+1][j+1]-vertices[i][j];
             vec1[2] = ZZ(j+1)-ZZ(j);
             vec2[0] = XX(i)-XX(i+1);
             vec2[1] = vertices[i][j+1]-vertices[i+1][j+1];
             vec2[2] = ZZ(j+1)-ZZ(j+1);
             triNormal[0] = vec1[1] * vec2[2] - vec1[2] * vec2[1];
             triNormal[1] = vec1[2] * vec2[0] - vec1[0] * vec2[2];
             triNormal[2] = vec1[0] * vec2[1] - vec1[1] * vec2[0];
             glNormal3fv(triNormal);

             glVertex3f(XX(i),vertices[i  ][j  ],ZZ(j));
             glVertex3f(XX(i+1),vertices[i+1][j+1],ZZ(j+1));
             glVertex3f(XX(i),vertices[i  ][j+1],ZZ(j+1));
          glEnd();
       }
}

void display( void )
{
	static GLfloat camX, camY, camZ;

	// convert from spherical coords to cartesian
	camX = sin(camYaw) * cos(camPitch) * camRadius;
	camY = sin(camPitch) * camRadius;
	camZ = cos(camYaw) * cos(camPitch) * camRadius;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //  Put the identity onto the modelview stack to start the viewing transform
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    /*           eye point        center of view      up   */
    gluLookAt( camX, camY, camZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

    surface();
    
    glutSwapBuffers();
 }

void reshape(int w,int h)
{
        glViewport(0,0,(GLsizei)w,(GLsizei)h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(30.0,(float)w/(float)h,1.0,100.0);
}

void keyboard(unsigned char key, int x, int y)
{
        switch (key)
        {
			case 'w' :
				camYaw += .08;
				if(camYaw > 6.28)
					camYaw = camYaw-6.28;
				break;
    	    case 'q' :
				camYaw -= .08;
				if(camYaw < 0.0)
					camYaw = 6.28+camYaw;
				break;
    	    case 's' :
				camPitch += .08;
				if(camPitch > 1.57)
					camPitch = 1.57;
				break;
    	    case 'a' :
				camPitch -= .08;
				if(camPitch < -1.57)
					camPitch = -1.57;
				break;
    	    case 'x' :
				camRadius += 0.5; break;
    	    case 'z' :
    			camRadius -= 0.5;
				if(camRadius < 1.0)
					camRadius = 1.0;
				break;
			case '1' :
				CHOICE = 1; break;
			case '2' :
				CHOICE = 2; break;
			case '3' :
				CHOICE = 3; break;
			case '4' :
				CHOICE = 4; break;
        }
}

void mouse(int button, int state, int x, int y) {
	// save the mouse position for the camera rotation
	mx = x;
	my = y;
	if(state == GLUT_DOWN)
		mdown = TRUE;
	else
		mdown = FALSE;

	buttonDown = button; // remember which button is down
}

void motion(int x, int y) {
	if(buttonDown == GLUT_LEFT_BUTTON) {
		// adjust the camera's yaw
		camYaw += 0.02*(GLfloat)(mx-x);

		// wrap the yaw values at 0 and 2*pi
		if(camYaw < 0.0)
				camYaw = 6.28+camYaw;
		else if(camYaw > 6.28)
				camYaw = camYaw-6.28;

		// adjust the camera's pitch
		camPitch += 0.02*(GLfloat)(y-my);

		// make sure we don't pitch too far
		if(camPitch > 1.57)
			camPitch = 1.57;
		else if(camPitch < -1.57)
			camPitch = -1.57;
	}
	else {
		// zoom in and out if right mouse button is down
		camRadius += 0.5*(GLfloat)(y-my);
		if(camRadius < 1.0)
			camRadius = 1.0;
	}

	mx = x;
	my = y;
}

// update the surface
void animate(void)
{
    switch (CHOICE) {
       case 1: t += 0.01; break;
       case 2: t += 0.1; break;
       case 3: t += 0.01; break;
       case 4: t += 0.3; break;
    }
    glutPostRedisplay();
}

int main(int argc, char** argv)
{
        // Standard GLUT initialization
        glutInit(&argc,argv);
        glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
        glutInitWindowSize(500,500);
        glutInitWindowPosition(70,70);
        glutCreateWindow("moving waves of math");
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutIdleFunc(animate);
        glutKeyboardFunc(keyboard); // enable keyboard callback
        glutMouseFunc(mouse);
        glutMotionFunc(motion);

        myinit();

        glutMainLoop();

        return 0;
}
