/*
	Sean Dobbs
	Period 3
	Makes a pretty landscape; a mountain to be exact. It is defined by a 
	two-dimentional matrix that represents three dimentional points
	(e.g. the point (x,y,z) is stored as altitude[x][z]=y)
	Unfortunately, the depth buffer doesn't work right. That will be fixed
	in the next porgram, when we add lighting.
*/

#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], 		// here's the mountain
		spin=0.0, z=0.0, spin2=0.0,	// for moving the POV
		red=1.0, green=1.0, blue=1.0;	// for computing colors
static colorval colors[MAX][MAX]; // we precompute the colors for each vetex
				  // to save time

// Lets make a window with GL functions
static void Init(void)
{
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glShadeModel( GL_FLAT );
}

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);
}

// returns what's in "front" of the current position. This and the next 
// function are used just to make the code a little less icky
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];
	}
}

// return what's to the "right" of the current position
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];
        }
}

// yucky, isn't it? we start at [1][1] and spiral inwards,computing all the way
void SetAltitude()
{
	int 	r, c,	// for looping 
		front=1, //which way we're facing
		done[MAX][MAX], //keeps track of if whether we've done it
		numloop=0, k;
	double temp, n=0;
	// a little initializing
	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;
	// keep the edges at zero to make it look better
	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;	// the number of adjacent items that have already been done
		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;
		// the weighted average
		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);
		// here's the random part
		temp+=(((random()%30)/10)+n)-(((random()%10)/10)*(n));
		altitude[r][c]=temp;
		done[r][c]=1;
		// if we've already done the one in front of it, turn to thght
		if(infront(r,c,front,done)==1) 
		{
			switch(front)
			{// if we've gone all the way around, increase n
				case 4: front = 1; numloop++; n+=(.3+(((double)numloop)/50)); break;
				default: front++; break;
			}
		}
		// otherwise, move forward
		switch(front)
		{
			case 1: c++; break;
			case 2: r++; break;
			case 3: c--; break;
			case 4: r--; break;
		}
	}

}

// precompute the colors
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);
	}
}

// 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
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=0;x<MAX-1;x++)
	{
		glBegin(GL_QUAD_STRIP);
		for(int y=0;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);
                        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);

		}
		glEnd();
	}
}

// The main loop
static void display()
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glPushMatrix();
	glRotatef(spin, 0.0, 1.0, 0.0);
	DrawMyStuff();
	glPopMatrix();
	glFlush();
	auxSwapBuffers();
}

void main()
{
	long trash;
	srandom((long)time(&trash));	// seed the random generator
	SetAltitude();
	SetColors();
	auxInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	auxInitPosition(50,50,550,550);

	// If something goes wrong, bail
	if(auxInitWindow("3D Landscape") == GL_FALSE)
	{
		auxQuit();
	}

	Init();

	auxExposeFunc(Reshape);
	auxReshapeFunc(Reshape);
	// attach functions to keys
	auxKeyFunc( AUX_UP, zoomout );
	auxKeyFunc( AUX_DOWN, zoomin );
	auxKeyFunc( AUX_LEFT, spinleft );
	auxKeyFunc( AUX_RIGHT, spinright );
	auxKeyFunc( AUX_a, spinup );
	auxKeyFunc( AUX_z, spindown );
	auxMainLoop( display );
}
