/*
	Surface that is the sum of two functions, to allow experimentation with
	the periods and amplitudes of various wave trains and circular wave
	functions.  The surface is animated -- each function is from a one-parameter
	family of surfaces, and that parameter is animated by the  idle  function
   
	Source file to be used with
	Cunningham, Computer Graphics: Programming in OpenGL for Visual Communication, Prentice-Hall, 2007

	Intended for class use only
*/
#include <GLUT/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

typedef GLfloat point3[3];

static char ch; // the character from the keyboard that controls the rotation
GLfloat t = 0.0;

//  Define the parameters of the surface grid
//  For an N x M grid you need XSIZE = N+1 and ZSIZE = M+1
#define XSIZE 201	// the larger this is, the better the surface and
					// the slower the animation
#define YSIZE XSIZE
#define MINX  -9.0
#define MAXX   9.0
#define MINY  -9.0
#define MAXY   9.0
#define EPSILON .001
#define ANGLE 2.

//	choose whether you want circular or train waves with these #define statements
#define CIRCULAR
#undef  TRAIN

//	some sample circular and train wave functions
#ifdef CIRCULAR
#define f1(x,y)  0.2*cos(sqrt((3.0*(x-3.14))*(3.0*(x-3.14))+(3.0*y)*(3.0*y)+t))
#define f2(x,y)  0.5*cos(sqrt((4.0*(x+1.57))*(4.0*(x+1.57))+(4.0*y)*(4.0*y)+t))
#endif

#ifdef TRAIN
#define f1(x,y)  0.1*sin(3.0*x+2.0*y+t)
#define f2(x,y)  0.2*sin(2.0*x+3.0*y+t)
#endif

static GLfloat vertices[XSIZE][YSIZE];
GLfloat xAngle = 0., yAngle = 0., zAngle = 0.;

// function prototypes follow
GLfloat XX( int );
GLfloat YY( 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 YY(int j) {
     return (MINY+((MAXY-MINY)/(float)(YSIZE-1))*(float)(j));
     }

void myinit(void)
{
        long i = 1;

//      set up overall light data
        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
        
        GLfloat mat_specular[] = { 0.8, 0.8, 0.8, 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_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 );
        
        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_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
}

void surface(void)
{
    point3 vec1, vec2, triNormal;
    
    int  i, j;
    float x, y, v1,v2;

//  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
    for ( i=0; i<XSIZE; i++ )
       for ( j=0; j<YSIZE; j++ )
       {
          x = XX(i);
          y = YY(j);
          v1=f1(x,y);
          v2=f2(x,y);
          vertices[i][j] = v1+v2;
          }

/*  actually draw the surface */
		for ( i=0; i<XSIZE-1; i++ )
		for ( j=0; j<YSIZE-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);
             vec1[0] = XX(i+1)-XX(i);
             vec1[1] = YY(j)-YY(j);
             vec1[2] = vertices[i+1][j]-vertices[i][j];
             vec2[0] = XX(i+1)-XX(i+1);
             vec2[1] = YY(j+1)-YY(j);
             vec2[2] = vertices[i+1][j+1]-vertices[i+1][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),  YY(j),  vertices[i  ][j  ]);
             glVertex3f(XX(i+1),YY(j),  vertices[i+1][j  ]);
             glVertex3f(XX(i+1),YY(j+1),vertices[i+1][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)-YY(i);
             vec1[1] = YY(j+1)-YY(j);
             vec1[2] = vertices[i+1][j+1]-vertices[i][j];
             vec2[0] = XX(i)-XX(i+1);
             vec2[1] = YY(j+1)-YY(j+1);
             vec2[2] = vertices[i][j+1]-vertices[i+1][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),  YY(j),  vertices[i  ][j  ]);
             glVertex3f(XX(i+1),YY(j+1),vertices[i+1][j+1]);
             glVertex3f(XX(i),  YY(j+1),vertices[i  ][j+1]);
          glEnd();
       }
}

void display( void )
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

	glRotatef(xAngle, 1.0, 0.0, 0.0);
	glRotatef(yAngle, 0.0, 1.0, 0.0);
	glRotatef(zAngle, 0.0, 0.0, 1.0);
	if (ch != ' ') {	// keyboard indicated a rotation...
	//	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 + -
	switch(ch) {
		case 'q':
		case 'o':
			xAngle += ANGLE;
			glRotatef( ANGLE, 1.0, 0.0, 0.0); break;
		case 'w':
		case 'p':
			xAngle -= ANGLE;
			glRotatef(-ANGLE, 1.0, 0.0, 0.0); break;
		case 'a':
		case 'k':
			yAngle += ANGLE;
			glRotatef( ANGLE, 0.0, 1.0, 0.0); break;
		case 's':
		case 'l':
			yAngle -= ANGLE;
			glRotatef(-ANGLE, 0.0, 1.0, 0.0); break;
		case 'z':
		case 'm':
			zAngle += ANGLE;
			glRotatef( ANGLE, 0.0, 0.0, 1.0); break;
		case 'x':
		case ',':
			zAngle -= ANGLE;
			glRotatef(-ANGLE, 0.0, 0.0, 1.0); break;
	    }
    }
	ch = ' ';	// ensure any residual rotation is killed

    surface();

    glPopMatrix();
    glutSwapBuffers();
 }

void reshape(int w,int h)
{
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(30.0,1.0,1.0,100.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	/*           eye point        center of view      up   */
	gluLookAt( 0.0, 40.0, 20.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
}

void keyboard(unsigned char key, int x, int y)
{
	ch = ' ';
	switch (key)
        {
        case 'q' :     // rotate around X; i = positive, j = negative
           ch = key; break;
        case 'w' :
           ch = key; break;
        case 'a' :    // rotate around Y; k = positive, m = negative
           ch = key; break;
        case 's' :
           ch = key; break;
        case 'z' :    // rotate around Z; a = positive, s = negative
           ch = key; break;
        case 'x' :
           ch = key; break;
        }
	glutPostRedisplay();
}

void animate(void)
{
    t += 1.5; // 0.7;
    glutPostRedisplay();
}

int main(int argc, char** argv)
{
        
// Standard GLUT initialization
	glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 800);
	glutInitWindowPosition(0, 0);
#ifdef CIRCULAR
	glutCreateWindow("Circular standing waves");
#endif
#ifdef TRAIN
	glutCreateWindow("Standing wave train");
#endif
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutIdleFunc(animate);      // an empty function this time
	glutKeyboardFunc(keyboard); // enable keyboard callback

	myinit();

	glutMainLoop();
}