//Well this is gonna be quite the little tutorial

//--------------------------------------------------
//First section: Variables

//In this application, we use the variables:
// int mapX;
// int mapY;
// int mapZ;
//To hold our X,Y and Z coordinates for our heightmap
//during the application.

//We use:
// const MapWidth = 384;
// const MapHeight = 384;
//To hold the width and height(length) for our heightmap
//these have to be 3 times the size of the width and height
//of our RAW file as our RAW file is parsed in intervals
//of 3. So our 128 X 128 heightmap is really 384 X 384.

//We then set up our texture storage space with:
// GLuint texture[10];
//I have set up enough storage for 11 textures
//for the next installment of terrain generation.

//Now the next variable holds all our heights with:
// BYTE HeightMap[MapWidth][MapHeight];
//I have set it to BYTE as all our data from our RAW
//file is read in BYTES as Binary.

//---------------------------------------------------
//Section 2: Loading the HeightMap Heights

//Our first function is called LoadHeightMap
//with arguments: (char* Filename, int Width, int Height)
//this will be called on startup to read in our HeightMap
//and allocate the data accordingly in our HeightMap array.

//Within this I have the variable int smoothen, this will be
//used later on in a loop to parse the heights and average
//them amongst there surrounding heights for smoother
//looking terrain.

//We also have the variable FILE *File which will be the
//file we are opening (heightmap.raw).

//Next we set our File to our currently selected filename
//and read it with the properties "rb" meaning read-binary.

//Then we acctually read the file into our Heightmap array
//in intervals of 1 byte per array for as many times as
//our files width X height X 3 (remembering our RAW file
//is parsed in intervals of 3).

//Now that we have all the data within our array, we close
//the file.

//Next comes the smoothing, to smooth, I have gone through
//every height within our array and averaged it out amongst
//its surrounding heights. Simple addition and division to
//get the average. I have parsed this through 3 times to get
//an alright looking terrain.

//---------------------------------------------------
//Section 3: Displaying the HeightMap

//To get the optimum look out of the terrain, I am loading every
//fourth height and setting them 4 units apart. If I set them
//1 unit apart and call every 1 then the terrain looks jagged.
//And if I set them further apart and call less, then it looks
//too smooth for me. So you can change these as you see fit.

//Now acctually displaying them is simple enough. I am using triangle
//strips and am calling a triangle strip for every width and height.
//Within this I am also setting our texture coordinates. Self explanatory
//if you know about triangle strips.

//---------------------------------------------------
//Section 4: Optimization

//To optimise our terrain to get the best amount of frames
//per second, I have added some Culling in the enabling
//code to discard faces we dont need (remember to set the front face to CCW
//- counter clock wise -as triangle strips set there faces opposite to most
//shapes).

//Also by calling only every fourth height, I am saving the processing
//times and thus also increasing the frames per second.

#include <GL/gl.h>
#include <GL/glut.h>
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <iostream.h>

int mapX;
int mapY;
int mapZ;

const MapWidth = 384;
const MapHeight = 384;

GLuint texture[10];

BYTE HeightMap[MapWidth][MapHeight];

GLuint LoadTexture( const char * filename, int width, int height )
{
	GLuint texture;
	unsigned char * data;
	FILE * file;

	//The following code will read in our RAW file
	file = fopen( filename, "rb" );
	if ( file == NULL ) return 0;
	data = (unsigned char *)malloc( width * height * 3 );
	fread( data, width * height * 3, 1, file );
	fclose( file );

	glGenTextures( 1, &texture ); //generate the texture with the loaded data
	glBindTexture( GL_TEXTURE_2D, texture ); //bind the texture to it's array
	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); //set texture environment parameters

	//here we are setting what textures to use and when. The MIN filter is which quality to show
	//when the texture is near the view, and the MAG filter is which quality to show when the texture
	//is far from the view.

	//The qualities are (in order from worst to best)
	//GL_NEAREST
	//GL_LINEAR
	//GL_LINEAR_MIPMAP_NEAREST
	//GL_LINEAR_MIPMAP_LINEAR

	//And if you go and use extensions, you can use Anisotropic filtering textures which are of an
	//even better quality, but this will do for now.
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR );

	//Here we are setting the parameter to repeat the texture instead of clamping the texture
	//to the edge of our shape. 
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

	//Generate the texture with mipmaps
	gluBuild2DMipmaps( GL_TEXTURE_2D, 3, width, height, GL_RGB, GL_UNSIGNED_BYTE, data ); 
	free( data ); //free the texture
	return texture; //return whether it was successfull
}

void FreeTexture( GLuint texture )
{
  glDeleteTextures( 1, &texture ); 
}

void LoadHeightMap (char* Filename, int Width, int Height) {

int smoothen;

FILE * File;

File = fopen( Filename, "rb" );

fread( HeightMap, 1, Width * Height * 3, File );

fclose( File );

for (smoothen = 0; smoothen < 3; smoothen++){
for (mapX = 1; mapX < Width; mapX ++){ 
for (mapZ = 1; mapZ < Height * 3; mapZ++){ 
mapY = HeightMap[mapX][mapZ];
mapY += HeightMap[mapX-1][mapZ];
mapY += HeightMap[mapX+1][mapZ];
mapY += HeightMap[mapX][mapZ-1];
mapY += HeightMap[mapX][mapZ+1];
mapY += HeightMap[mapX-1][mapZ-1];
mapY += HeightMap[mapX-1][mapZ+1];
mapY += HeightMap[mapX+1][mapZ-1];
mapY += HeightMap[mapX+1][mapZ+1];
mapY = mapY/9;
HeightMap[mapX][mapZ] = mapY ;
}
}
}
}

void DisplayHeightMap (void) {
int Height;

for (mapX = 1; mapX < MapWidth; mapX +=4){ 
for (mapZ = 1; mapZ < MapHeight * 3; mapZ+=4){ 
	glBegin(GL_TRIANGLE_STRIP);
	Height = HeightMap[mapX][mapZ];
	glTexCoord2f(0,0); 
	glVertex3f(float(mapX),Height,float(mapZ));
	
	Height = HeightMap[mapX][mapZ+4];
	glTexCoord2f(0,1); 
	glVertex3f(float(mapX),Height,float(mapZ+4));

	Height = HeightMap[mapX+4][mapZ];
	glTexCoord2f(1,0); 
	glVertex3f(float(mapX+4),Height,float(mapZ));

	Height = HeightMap[mapX+4][mapZ+4];
	glTexCoord2f(1,1); 
	glVertex3f(float(mapX+4),Height,float(mapZ+4));
	glEnd();
}
}
}

void enable (void) {
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);	
	glShadeModel (GL_SMOOTH); 
	glEnable(GL_TEXTURE_2D);
}

void display (void) {
	glClearColor (0.0,0.0,0.0,1.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity(); 
	enable();
	glTranslatef(30, 0, 30);
	glRotatef(150, 0,1,0);
	glColor3f(1,1,1);
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	DisplayHeightMap();
	glutSwapBuffers();
}

void init (void) {
	texture[0] = LoadTexture("texture.raw", 256, 256);
	LoadHeightMap ("height.raw", 128, 128);
}

void reshape (int w, int h) {
	glViewport (0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective (100, (GLfloat)w / (GLfloat)h, 0.01, 500.0);
	glMatrixMode (GL_MODELVIEW);
}

int main (int argc, char **argv) {
    glutInit (&argc, argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
	glutInitWindowSize (500, 500); 
	glutInitWindowPosition (100, 100);
	glutCreateWindow ("A basic OpenGL Window");

	init();

    glutDisplayFunc (display);
	glutIdleFunc (display);
	glutReshapeFunc (reshape);

	glutMainLoop ();

	return 0;
}
