/*
	Sean Dobbs
	Period 3
	Makes a mountain. This time depth buffering and lighting work, since 
	we are using the GLUT libraries. Only code that has changed from
	the other program has been commented.
*/

#include <iostream.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include "glaux.h"
#include "GL/gl.h"
#include "GL/glu.h"
#include "GL/glut.h"


// Globals
struct colorval
{
	double r, g, b;
};

const int MAX = 40;
static double 	altitude[MAX][MAX], spin=0.0, z=0.0, spin2=0.0,
		red=1.0, green=1.0, blue=1.0;
static colorval colors[MAX][MAX];

// Lets make a window with GL functions

// Here we enable most of the stuff for lighting
static void Init(void)
{	// material properties
	GLfloat mat_specular[] = {0.3,0.3,0.3,1.0};
	GLfloat mat_shininess[] = {40.0};
	// where the light starts out
	GLfloat light_position[] = {40.0,0.0,0.0,0.0};

	// Here is where set it all
	glClearColor(0.2, 0.2, 0.2, 1.0);
	glShadeModel( GL_SMOOTH );
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glColorMaterial(GL_FRONT, GL_DIFFUSE);
	// turn it all on
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);
}

static void Reshape( int width, int height )
{
	glViewport(0, 0, (GLint)width, (GLint)height);
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)width / (GLfloat)height, 1.0, MAX * 2.0);
	glMatrixMode(GL_MODELVIEW);
}

int infront(int r, int c, int front, int done[MAX][MAX])
{
	switch(front)
	{
		case 1: return done[r][c+1];
		case 2: return done[r+1][c];
		case 3: return done[r][c-1];
		case 4: return done[r-1][c];
	}
}

int inright(int r, int c, int front, int done[MAX][MAX])
{
        switch(front)
        {
                case 1: return done[r+1][c];
                case 2: return done[r][c-1];
                case 3: return done[r-1][c];
                case 4: return done[r][c+1];
        }
}

void SetAltitude()
{
	int r, c, front=1, done[MAX][MAX], numloop=0, k;
	double temp, n=0;

	for(r=0;r<MAX;r++)
		for(c=0;c<MAX;c++)
			altitude[r][c]=0;

        for(r=0;r<MAX;r++)
                for(c=0;c<MAX;c++)
                        done[r][c]=0;
	for(r=0;r<MAX;r++)
	{
		done[r][0]=1;
		done[r][MAX-1]=1;
	}
	for(c=0;c<MAX;c++)
	{
		done[0][c]=1;
		done[MAX-1][c]=1;
	}
	
	r=1;	c=1;
        while(done[r][c]!=1)
	{
		k=0;
		if(done[r-1][c-1]==1) k++;
                if(done[r-1][c]==1) k++;
                if(done[r-1][c+1]==1) k++;
                if(done[r][c-1]==1) k++;
                if(done[r][c+1]==1) k++;
                if(done[r+1][c-1]==1) k++;
                if(done[r+1][c]==1) k++;
                if(done[r+1][c+1]==1) k++;
		k-=3;
		if(k<0) k=0;
		temp=(altitude[r-1][c-1]+altitude[r-1][c]+altitude[r-1][c+1]+
			altitude[r][c-1]+altitude[r][c+1]+altitude[r+1][c-1]+
			altitude[r+1][c]+altitude[r+1][c+1])/(8+k);
		temp+=(((random()%30)/10)+n)-(((random()%10)/10)*(n));
		altitude[r][c]=temp;
		done[r][c]=1;
		if(infront(r,c,front,done)==1) 
		{
			switch(front)
			{
				case 4: front = 1; numloop++; n+=(.3+(((double)numloop)/50)); break;
				default: front++; break;
			}
		}
		switch(front)
		{
			case 1: c++; break;
			case 2: r++; break;
			case 3: c--; break;
			case 4: r--; break;
		}
		if((r<0)||(r>MAX)||(c<0)||(c>MAX)) 
		{
			cout<<"out of bounds."<<endl;
			exit(0);
		}
	}

}

void SetColors()
{
    double temp;
    for(int x=0;x<MAX;x++)
	for(int y=0;y<MAX;y++)
	{
		temp = altitude[x][y];
		colors[x][y].g = temp / 15.0;
		colors[x][y].r = temp / 25.0;
		colors[x][y].b = 1.0 - (temp / 100.0);
	}
}

// The next three functions are used to compute the normal vector that is
// perpendicular to the face of the polygon that we drew.
void normalize(float v[3])
{
  GLfloat d=sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
    if(d== 0.0) return;

  v[0]/=d;
  v[1]/=d;
  v[2]/=d;
}

void normcrossprod(float v1[3], float v2[3], float out[3])
{
  GLint i,j;
  GLfloat length;

  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 trinormal(float x1,float y1,float z1,float x2,float y2,float z2,
               float x3,float y3,float z3)
{
  GLfloat d1[3],d2[3],norm[3];

  int j;

  d1[0]=x2-x1;
  d1[1]=y2-y1;
  d1[2]=z2-z1;
  d2[0]=x3-x2;
  d2[1]=y3-y2;
  d2[2]=z3-z2;

  normcrossprod(d1,d2,norm);
  glNormal3fv(norm);
}

// Defined keys
static void key_esc() { auxQuit(); }

static void spinleft() { spin -= 5; }

static void spinright() { spin += 5; }

static void zoomout() { z += 5; }

static void zoomin() { z -= 5; }

static void spinup() { spin2 += 5; }

static void spindown() { spin2 -= 5; }

// Draw function. It is the same, except that we had to change to drawing 
// triangles, and add the code to compute the normal vectors
void DrawMyStuff()
{
	glLoadIdentity();
	gluLookAt(0.0, MAX/4.0, 2.0*MAX, 0.0, 0.0, -10.0, 0.0, 1.0, 0.0);
	glTranslatef(0.0, 0.0, MAX - z);
	glRotatef(spin, 0.0, 1.0, 0.0);
	glRotatef(spin2, 1.0, 0.0, 0.0);
	for(int x=1;x<MAX-1;x++)
	{
		glBegin(GL_TRIANGLE_STRIP);
		glColor3f(colors[x][0].r,colors[x][0].g,colors[x][0].b);
                glVertex3f(x - MAX/2.0, altitude[x][0], 0 - MAX/2.0);
                glColor3f(colors[x+1][0].r,colors[x+1][0].g,colors[x+1][0].b);
                glVertex3f(x+1- MAX/2.0, altitude[x+1][0], 0 - MAX/2.0);

		for(int y=1;y<MAX-1;y++)
		{
                        glColor3f(colors[x][y].r,colors[x][y].g,colors[x][y].b);
                        glVertex3f(x - MAX/2.0, altitude[x][y], y - MAX/2.0);
                        trinormal(x- MAX/2.0,altitude[x][y-1],y-1- MAX/2.0,
                                x- MAX/2.0,altitude[x][y],y- MAX/2.0,
                                x+1- MAX/2.0,altitude[x+1][y-1],y-1- MAX/2.0);
                        glColor3f(colors[x+1][y].r,colors[x+1][y].g,colors[x+1][y].b);
                        glVertex3f(x+1- MAX/2.0, altitude[x+1][y], y - MAX/2.0);
                        trinormal(x+1- MAX/2.0,altitude[x+1][y],y- MAX/2.0,
                                x- MAX/2.0,altitude[x][y],y- MAX/2.0,
                                x-1- MAX/2.0,altitude[x-1][y-1],y-1- MAX/2.0);
		}
		glEnd();
	}

}

// used for GLUT keypresses
static void keypress(unsigned char key, int x, int y)
{
	switch(key)
	{
		case 'n': spinleft(); break;
		case 'm': spinright(); break;
		case 'q': key_esc(); break;
		case 'a': spinup(); break;
		case 'z': spindown(); break;
		case 'o': zoomout(); break;
		case 'p': zoomin(); break;
	}
}

// The main loop
static void display()
{
	// code for moving the light back and forth
	static double ltspin=10;
	static int dir=1;
	ltspin += (dir * 2);
	if((ltspin>=175)||(ltspin<=5)) dir*=-1;
        GLfloat light_position[] = {5.0,5.0,5.0,0.0};

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glPushMatrix();
	glRotatef(spin, 0.0, 1.0, 0.0);
	glPushMatrix();
	glRotatef(ltspin, 0.0, 0.0, 1.0);
	glRotatef(spin, 0.0, 1.0, 0.0);
        glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glPopMatrix();		
	DrawMyStuff();
	glPopMatrix();
	glFlush();
	glutSwapBuffers();
	glutPostRedisplay();
}

// This all does the same things as the other program, just with different functions
int main(int argc, char **argv)
{
	long trash;
	srandom((long)time(&trash));
        glutInit(&argc, argv);
	SetAltitude();
	SetColors();
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(550,550);
	glutInitWindowPosition(50,50);
	glutCreateWindow(argv[0]);
	Init();
	glutKeyboardFunc(keypress);
	glutDisplayFunc(display);
	glutReshapeFunc(Reshape);
	glutMainLoop();
	return 0;	
}
