// Welcome to third in my terrain tutorials. This has come a little later than expected
// simply because of school. In this tutorial I will be adding detail texture, where I
// first map a texture, stretched across the whole terrain, then I map another texture
// using the glMultiTexCoord2fARB extension to each individual triangle strip. This will
// allow for a terrain which can have its colours all drawn within a RAW file and then
// mapped onto the terrain, instead of assigning different textures for different heights.
// So where you would have the visible blocks between 2 textures, you now have seamless
// blending without all the fussy coding.

// I suppose you could call this a bit of a step backwards, as I have removed all of the
// lighting. I found that the lighting worked will for a static world, but when the
// world was rotated through the camera, my lighting was continuously changing.

// In this tutorial I have also fixed the terrain, so that it no longer appears as a
// rectangle. It takes the shape of the raw file, which in this case, is a square.

//--------------------------------------------------------------------------------------
// Section 1: Variables

// I have added a few variables to this tutorial. Most of them control the camera which I
// have integrated from my camera tutorials with a few modifications.

// So in detail, here are the new variables.

// This variable controls how detailed our terrain is. The lower the number, the more
// triangle strips are drawn, and the more detailed the ground will look.
// int LOD = 10;

// I have added another scaling variable, this will control the height and is seperate
// to the variable which will scale the length and width of the terrain.
// float scaleheight = 5;

// This simply controls the speed of the camera movement. The lower the number, the slower
// the speed.
// float camerascale = 0.25;

// These next few variables, control our camera system.
// double xpos = 0;
// double ypos = 0;
// double zpos = 0;
// double xrot = 0;
// double yrot = 150;
// double zrot = 0;
// double lastx;
// double lasty;

// This when added to the camera ypos, will allow for a neat little bobbing effect.
// double bounce;

// This will control the scaling of our detail texture. The higher the number, the smaller 
// the texture will appear, but it will also fill up the object with more of them. So 
// lowering the number will make the texture larger. >0 will make it smaller, <0 will make
// it bigger, eg: 0.9, 0.5 will make it larger. 
// float terrainDetailLevel = 2;

// These will just let us know which texture is assigned to which number.
// int tground = 0, tdetail = 1;

// Now because this uses an OpenGL extension. This next portion of the code is 
// EXTREMELY IMPORTANT!! Without these, our effect is impossible. These allow for us
// to set more than one texture coordinate per vertex. It can also be used for bump 
// mapping later on with a few extra calls.
//-----------------------
// These are the parameters for our multi texturing.
// #define GL_TEXTURE0_ARB                     0x84C0 // the first texture
// #define GL_TEXTURE1_ARB                     0x84C1 // the second texture

// #define GL_COMBINE_ARB						0x8570 // this gives us the ability to combine our textures (through r,g,b)
// #define GL_RGB_SCALE_ARB					0x8573 // this lets us scale our texture colours

// Here we tell our program that the following functions exist, these are needed for the whole multi texturing experience
// typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t);
// typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target);

// Now we link the above functions to our calls.
// extern PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;
// extern PFNGLACTIVETEXTUREARBPROC   glActiveTextureARB   = NULL;
//----------------------


//--------------------------------------------------------------------------------
// Section 2: Loading the Heightmap

// I have made the following adjustment to our LoadHeightmap function:
// for (mapX = 1; mapX < (Width)-1; mapX ++){ 
// for (mapZ = 1; mapZ < (Height)-1; mapZ++){ 

// I found that this is slightly more effective decreasing the risk of errors around
// the edge of terrain, where a height would be undefined and would show a wall of
// some sort.

//-------------------------------------------------------------------------------
// Section 3: Displaying the Heightmap

// This is where most of our changes will take place as we now need to add the 
// new texture coordinates and attach our multi texturing.
// void DisplayHeightMap (void) {
// float Height;

// glPushMatrix();

// glActiveTextureARB(GL_TEXTURE0_ARB); //set our current texture to texture0
// glEnable(GL_TEXTURE_2D); //enable texturing

// glBindTexture(GL_TEXTURE_2D, texture[tground]); //bind our ground texture

// glMatrixMode(GL_TEXTURE); // go to our texture matrix
// glLoadIdentity(); //clear our texture matrix

//this rotation will correct our texture so that it matches up with our heightmap
// glRotatef(180,1,0,0); //rotate our texture on the x axis 180 degrees
// glRotatef(180,0,0,1); //rotate our texture on the z axis 180 degrees
// glRotatef(90,0,0,1); //rotate our texture on the y axis 90 degrees

// glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

// glActiveTextureARB(GL_TEXTURE1_ARB); //activate our next texture, texture1
// glEnable(GL_TEXTURE_2D); //enable texturing again

// glBindTexture(GL_TEXTURE_2D, texture[tdetail]); //bind our detail texture

// glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB); //set the texture mode to combine the two
// glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2); //scale the color of this texture by 2

// glMatrixMode(GL_TEXTURE); //switch to our texture matrix again
// glLoadIdentity(); //clear the matrix

// glScalef((float)terrainDetailLevel, (float)terrainDetailLevel, 1); //scale the size of the texture

// glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

// for (mapX = 1; mapX < MapWidth; mapX +=LOD){ 
// for (mapZ = 1; mapZ < MapHeight; mapZ +=LOD){ 

// 	glBegin(GL_TRIANGLE_STRIP);
// 	Height = HeightMap[mapX][mapZ];
// 	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, (float)mapX/(float)MapWidth, ((float)mapZ)/(float)MapHeight); //attach our ground texture, stretching it across the heightmap
// 	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0); //attach our detail texture, setting it to the current triangle strip
// scaling the mapX point by 3, I am able to get my terrain back to a square as it should.
// 	glVertex3f(mapX*scale*3,Height*scaleheight,mapZ*scale); //here i am now changing the x scale by multilying it by 3

// 	Height = HeightMap[mapX][mapZ+LOD];
// 	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
// 	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
// 	glVertex3f(mapX*scale*3,Height*scaleheight,(mapZ+LOD)*scale);

// 	Height = HeightMap[mapX+LOD][mapZ];
// 	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, (float)mapZ/(float)MapHeight);
// 	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
// 	glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,mapZ*scale);

// 	Height = HeightMap[mapX+LOD][mapZ+LOD];
// 	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
// 	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
// 	glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,(mapZ+LOD)*scale);
// 	glEnd();
// 
// }
// }

// Turn the second multitexture pass off
// glActiveTextureARB(GL_TEXTURE1_ARB);
// glDisable(GL_TEXTURE_2D);

// Turn the first multitexture pass off
// glActiveTextureARB(GL_TEXTURE0_ARB);		
// glDisable(GL_TEXTURE_2D);

// glPopMatrix();
// }

//----------------------------------------------------------------------------
// Section 4: Initialization

// void init (void) {
// Here we initialize our multitexturing functions
// 	glActiveTextureARB		= (PFNGLACTIVETEXTUREARBPROC)		wglGetProcAddress("glActiveTextureARB");
// 	glMultiTexCoord2fARB	= (PFNGLMULTITEXCOORD2FARBPROC)		wglGetProcAddress("glMultiTexCoord2fARB");
	
// Next we load our two textures
// 	texture[tground] = LoadTextureRAW("ground.raw", 512, 512);
// 	texture[tdetail] = LoadTextureRAW("detail.raw", 256, 256);

// And finally we load our heightmap
// 	LoadHeightMap ("height.raw", 128, 128);
// }

//-------------------------------------------------------------------------------

// And there we have it. We can now texture our terrain using the glMultiTexCoord2fARB
// extension to make our terrain look, in my opinion, much better.

// If you have any questions, please email me at swiftless@gmail.com

#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 = 128;
const MapHeight = 384;

int LOD = 10;

float scale = 5; 
float scaleheight = 5;

float camerascale = 0.25;

double xpos = 0;
double ypos = 0;
double zpos = 0;
double xrot = 0;
double yrot = 150;
double zrot = 0;
double lastx;
double lasty;

double bounce;

float terrainDetailLevel = 1.5;

int tground = 0, tdetail = 1;
GLuint texture[10];

BYTE HeightMap[MapWidth][MapHeight];

//-----------------------
// These are the parameters for our multi texturing.
#define GL_TEXTURE0_ARB                     0x84C0 // the first texture
#define GL_TEXTURE1_ARB                     0x84C1 // the second texture

#define GL_COMBINE_ARB						0x8570 // this gives us the ability to combine our textures (through r,g,b)
#define GL_RGB_SCALE_ARB					0x8573 // this lets us scale our texture colours

// Here we tell our program that the following functions exist, these are needed for the whole multi texturing experience
typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t);
typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target);

// Now we link the above functions to our calls.
extern PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;
extern PFNGLACTIVETEXTUREARBPROC   glActiveTextureARB   = NULL;
//----------------------

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 = 1; smoothen < 1; smoothen++){
for (mapX = 1; mapX < (Width)-1; mapX ++){ 
for (mapZ = 1; mapZ < (Height)-1; 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) {
float Height;

glPushMatrix();

glActiveTextureARB(GL_TEXTURE0_ARB); //set our current texture to texture0
glEnable(GL_TEXTURE_2D); //enable texturing

glBindTexture(GL_TEXTURE_2D, texture[tground]); //bind our ground texture

glMatrixMode(GL_TEXTURE); // go to our texture matrix
glLoadIdentity(); //clear our texture matrix

//this rotation will correct our texture so that it matches up with our heightmap
glRotatef(180,1,0,0); //rotate our texture on the x axis 180 degrees
glRotatef(180,0,0,1); //rotate our texture on the z axis 180 degrees
glRotatef(90,0,0,1); //rotate our texture on the y axis 90 degrees

glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

glActiveTextureARB(GL_TEXTURE1_ARB); //activate our next texture, texture1
glEnable(GL_TEXTURE_2D); //enable texturing again

glBindTexture(GL_TEXTURE_2D, texture[tdetail]); //bind our detail texture

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB); //set the texture mode to combine the two
glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2); //scale the color of this texture by 2

glMatrixMode(GL_TEXTURE); //switch to our texture matrix again
glLoadIdentity(); //clear the matrix

glScalef((float)terrainDetailLevel, (float)terrainDetailLevel, 1); //scale the size of the texture

glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

for (mapX = 1; mapX < MapWidth; mapX +=LOD){ 
for (mapZ = 1; mapZ < MapHeight; mapZ +=LOD){ 

	glBegin(GL_TRIANGLE_STRIP);
	Height = HeightMap[mapX][mapZ];
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, (float)mapX/(float)MapWidth, ((float)mapZ)/(float)MapHeight); //attach our ground texture, stretching it across the heightmap
	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0); //attach our detail texture, setting it to the current triangle strip
	glVertex3f(mapX*scale*3,Height*scaleheight,mapZ*scale);

	Height = HeightMap[mapX][mapZ+LOD];
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
	glVertex3f(mapX*scale*3,Height*scaleheight,(mapZ+LOD)*scale);

	Height = HeightMap[mapX+LOD][mapZ];
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, (float)mapZ/(float)MapHeight);
	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
	glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,mapZ*scale);

	Height = HeightMap[mapX+LOD][mapZ+LOD];
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
	glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
	glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,(mapZ+LOD)*scale);
	glEnd();

}
}

// Turn the second multitexture pass off
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);

// Turn the first multitexture pass off
glActiveTextureARB(GL_TEXTURE0_ARB);		
glDisable(GL_TEXTURE_2D);

glPopMatrix();
}

void enable (void) {
	glEnable(GL_DEPTH_TEST); //enable the depth testing
	glDepthFunc(GL_LEQUAL); //set the depth function
	glFrontFace(GL_CCW); //set which face is facing forward
	glCullFace(GL_BACK); //set the face to be culled
	glEnable(GL_CULL_FACE);	//enable culling to speed up the processing time
	glEnable(GL_TEXTURE_2D); //enable texturing
}

void camera (void) {
	int posX = xpos;
	int posZ = zpos;

	ypos = HeightMap[posX][posZ];
	ypos += sin((3.141592654*bounce) * 2) / 2;
	ypos += 10;
	glRotatef(xrot,1.0,0.0,0.0);  //rotate our camera on teh x-axis (left and right)
	glRotatef(yrot,0.0,1.0,0.0);  //rotate our camera on the y-axis (up and down)
	glTranslated(-xpos *scale *3,-ypos*scaleheight,-zpos *scale); //translate the screen to the position of our camera
}

void display (void) {
	glClearColor (0.0,0.0,0.0,1.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity(); 
	enable();
	camera();
	DisplayHeightMap(); //display our heightmap
	glutSwapBuffers();
}

void init (void) {
	// Here we initialize our multitexturing functions
	glActiveTextureARB		= (PFNGLACTIVETEXTUREARBPROC)		wglGetProcAddress("glActiveTextureARB");
	glMultiTexCoord2fARB	= (PFNGLMULTITEXCOORD2FARBPROC)		wglGetProcAddress("glMultiTexCoord2fARB");
	
	texture[tground] = LoadTexture("ground.raw", 512, 512);
	texture[tdetail] = LoadTexture("detail.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 (60, (GLfloat)w / (GLfloat)h, 0.1, 10000.0);
	glMatrixMode (GL_MODELVIEW);
}

void keyboard (unsigned char key, int x, int y) {
	if (key=='r')
	{
	xrot += 1;
	if (xrot >360) xrot -= 360;
	}

	if (key=='f')
	{
	xrot -= 1;
	if (xrot < -360) xrot += 360;
	}

	if (key=='w')
	{
	float xrotrad, yrotrad;
	yrotrad = (yrot / 180 * 3.141592654f);
	xrotrad = (xrot / 180 * 3.141592654f); 
	xpos += float(sin(yrotrad)) * camerascale;
	zpos -= float(cos(yrotrad)) * camerascale * 3;
	ypos -= float(sin(xrotrad)) ;
	bounce += 0.04;
	}

	if (key=='s')
	{
	float xrotrad, yrotrad;
	yrotrad = (yrot / 180 * 3.141592654f);
	xrotrad = (xrot / 180 * 3.141592654f); 
	xpos -= float(sin(yrotrad)) * camerascale;
	zpos += float(cos(yrotrad)) * camerascale * 3;
	ypos += float(sin(xrotrad));
	bounce += 0.04;
	}

	if (key=='d')
	{
    float yrotrad;
	yrotrad = (yrot / 180 * 3.141592654f);
	xpos += float(cos(yrotrad)) * camerascale;
	zpos += float(sin(yrotrad)) * camerascale * 3;
	}

	if (key=='a')
	{
	float yrotrad;
	yrotrad = (yrot / 180 * 3.141592654f);
	xpos -= float(cos(yrotrad)) * camerascale;
	zpos -= float(sin(yrotrad)) * camerascale * 3;
	}

	if (key==27)
	{
	exit(0);
	}
}

void mouseMovement(int x, int y) {
	int diffx=x-lastx; //check the difference between the current x and the last x position
	int diffy=y-lasty; //check the difference between the current y and the last y position
	lastx=x; //set lastx to the current x position
	lasty=y; //set lasty to the current y position
	xrot += (float) diffy; //set the xrot to xrot with the addition of the difference in the y position
	if (xrot > 90)
	{
	xrot = 90;
	}
	if (xrot < -90)
	{
	xrot = -90;
	}
	yrot += (float) diffx;	//set the xrot to yrot with the addition of the difference in the x position
}

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

	glutPassiveMotionFunc(mouseMovement); //check for mouse movement

	glutKeyboardFunc (keyboard); 

	glutMainLoop ();

	return 0;
}
