#include "terrain.h"


//Empty Constructor
Terrain::Terrain(){
	width = 256;
	scanDepth = 80.0;
	terrainMul = 50.0;
	textureMul = 0.25;
	widthScale = 5.0;
	heightMul = 175.0;
	fogColor[0] = 0.75f;
	fogColor[1] = 0.9f;
	fogColor[2] = 1.0f;
	fogColor[3] = 1.0f;

	//Object attributes
	position = Vector3(0,0,0);
	velocity = Vector3(0,0,0);
	acceleration = Vector3(0,0,0);
	size = (width * terrainMul)/2;

	buildTerrain(width, 0.5f);
}


//Constructor with a random factor and width
Terrain::Terrain(int w, float rFactor){
	width = w;
	scanDepth = 80.0;
	terrainMul = 50.0;
	textureMul = 0.25;
	widthScale = 5.0;
	heightMul = 175.0;
	fogColor[0] = 0.75f;
	fogColor[1] = 0.9f;
	fogColor[2] = 1.0f;
	fogColor[3] = 1.0f;

	//Object attributes
	position = Vector3(0,0,0);
	velocity = Vector3(0,0,0);
	acceleration = Vector3(0,0,0);
	size = width*terrainMul/2;

	buildTerrain(width, rFactor);
}

void Terrain::buildTerrain(int w, float rFactor){
	width = w;
	heightMap = new float[width*width];
	makeTerrainPlasma(heightMap, width, rFactor);
	
	// load texture
	terrainTex[0].loadTexture("ground.tga");
	glGenTextures(1, &terrainTex[0].texID);
	glBindTexture(GL_TEXTURE_2D, terrainTex[0].texID);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	//or GL_CLAMP
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);	//or GL_CLAMP

	switch (terrainTex[0].textureType){
		case BMP:
			gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, terrainTex[0].width, terrainTex[0].height,
						GL_RGB, GL_UNSIGNED_BYTE, terrainTex[0].data);
			break;
		case PCX:
			gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, terrainTex[0].width, terrainTex[0].height,
						GL_RGBA, GL_UNSIGNED_BYTE, terrainTex[0].data);
			break;
		case TGA:
			gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, terrainTex[0].width, terrainTex[0].height,
						GL_RGB, GL_UNSIGNED_BYTE, terrainTex[0].data);
			break;
		default:
			break;
	}
}


//collision detection
void Terrain::onCollision(Object* collisionObject){}


//draw
void Terrain::onDraw(Camera* camera){
	int z, x;

	glEnable(GL_DEPTH_TEST);

	glFogi(GL_FOG_MODE, GL_LINEAR);
	glFogfv(GL_FOG_COLOR, fogColor);
	glFogf(GL_FOG_START, scanDepth * 0.1f);
	glFogf(GL_FOG_END, scanDepth * 2);
	glHint(GL_FOG_HINT, GL_FASTEST);
	glEnable(GL_FOG);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER,0.0);
	glDisable(GL_ALPHA_TEST);

	// push/pop objects that move with the camera (e.g. a sun, the sky)
	//glTranslatef(camera->x, camera->y, camera->z);

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, terrainTex[0].texID);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glColor3f(1.0, 1.0, 1.0);
	//for ( z = 0; z < width-1; z++)
	// use below line for repeating terrain
//	for (z = (int)(camera->position.z / terrainMul - scanDepth); (int)(z < camera->position.z / terrainMul + scanDepth); z++)
	for (z = (int)(camera->position.z / terrainMul - scanDepth), z = z < 0 ? 0 : z; (z < camera->position.z / terrainMul + scanDepth) && z < width-1; z++){
		glBegin(GL_TRIANGLE_STRIP);

		for (x = (int)(camera->position.x / terrainMul - scanDepth), x=x<0?0:x; (x < camera->position.x / terrainMul + scanDepth) && x < width-1; x++){

			glTexCoord2f(textureMul * x, textureMul * z);
			glVertex3f((float)x*terrainMul, (float)heightMap[x + z*width]*heightMul, (float)z*terrainMul);

			glTexCoord2f(textureMul * (x+1), textureMul * z);
			glVertex3f((float)(x+1)*terrainMul, (float)heightMap[x+1 + z*width]*heightMul, (float)z*terrainMul);

			glTexCoord2f(textureMul * x, textureMul * (z+1));
			glVertex3f((float)x*terrainMul, (float)heightMap[x + (z+1)*width]*heightMul, (float)(z+1)*terrainMul);

			glTexCoord2f(textureMul * (x+1), textureMul * (z+1));
			glVertex3f((float)(x+1)*terrainMul, (float)heightMap[x+1 + (z+1)*width]*heightMul, (float)(z+1)*terrainMul);
		}
		glEnd();
	}
}


//Returns a random number between v1 and v2
float Terrain::rangedRandom(float v1, float v2){
	return v1 + (v2 - v1)*((float)rand()) /  ((float)RAND_MAX);
}


//Given a height field, normalize it so that the minimum altitude
//is 0.0 and the maximum altitude is 1.0
void Terrain::normalizeTerrain(float field[], int size){
	float maxVal,minVal,dh;
	int i;

	//Find the maximum and minimum values in the height field
	 
	maxVal = field[0];
	minVal = field[0];

	for (i = 1; i < size * size; i++){
		if (field[i] > maxVal){
			maxVal = field[i];
		}

		else if (field[i] < minVal){
			minVal = field[i];
		}
	}

	//Find the altitude range (dh)
	
	if (maxVal <= minVal) 
		return;
	
	dh = maxVal-minVal;

	//Scale all the values so they are in the range 0-1

	for (i = 0; i < size * size; i++){
		field[i] = (field[i] - minVal) / dh;
	}

}


//filterHeightBand applies a FIR filter across a row or column of the height field
void Terrain::filterHeightBand(float *band, int stride, int count, float filter){
	int i,j=stride;
	float v = band[0];

	for (i = 0; i < count - 1; i++){
		band[j] = filter*v + (1-filter)*band[j];
		v = band[j];
		j+=stride;
	}
}


//Erodes a terrain in all 4 directions
void Terrain::filterHeightField(float field[], int size, float filter){
	int i;

	// Erode rows left to right
	for (i = 0; i < size; i++){
		filterHeightBand(&field[size * i], 1, size, filter);
	}
	
	// Erode rows right to left
	for (i = 0; i < size; i++){
		filterHeightBand(&field[size * i + size - 1],-1,size,filter);
	}

	// Erode columns top to bottom
	for (i = 0; i < size; i++){
		filterHeightBand(&field[i], size, size, filter);
	}

	// Erode columns bottom to top
	for (i = 0; i < size; i++){
		filterHeightBand(&field[size * (size - 1) + i], -size, size, filter);
	}
}


//genereate terrain using diamond-square (plasma) algorithm
void Terrain::makeTerrainPlasma(float field[], int size, float rough){
	int i, j, ni, nj, mi, mj, pmi, pmj, rectSize = size;
	float dh = (float)rectSize/2,r = (float)pow(2,-1*rough);

//	Since the terrain wraps, all 4 "corners" are represented by the value at 0,0,
//		so seeding the heightfield is very straightforward
//	Note that it doesn't matter what we use for a seed value, since we're going to
//		renormalize the terrain after we're done
	field[0] = 0;


	while(rectSize > 0){
		
		/*
		Diamond step -

		Find the values at the center of the retangles by averaging the values at 
		the corners and adding a random offset:


		a.....b
		.     .  
		.  e  .
		.     .
		c.....d   

		e  = (a+b+c+d)/4 + random

		In the code below:
		a = (i,j)
		b = (ni,j)
		c = (i,nj)
		d = (ni,nj)
		e = (mi,mj)

		*/
		       
		for (i = 0; i < size; i += rectSize)

		for (j = 0; j < size; j += rectSize){
			
			ni = (i+rectSize)%size;
			nj = (j+rectSize)%size;

			mi = (i+rectSize/2);
			mj = (j+rectSize/2);

			field[mi+mj*size] = (field[i+j*size] + field[ni+j*size] + field[i+nj*size] + field[ni+nj*size])/4 + rangedRandom(-dh/2,dh/2);
		}

		/*
		Square step -

		Find the values on the left and top sides of each rectangle
		The right and bottom sides are the left and top sides of the neighboring rectangles,
		  so we don't need to calculate them

		The height field wraps, so we're never left hanging.  The right side of the last
			rectangle in a row is the left side of the first rectangle in the row.  The bottom
			side of the last rectangle in a column is the top side of the first rectangle in
			the column

              .......
		      .     .
		      .     .
		      .  d  .
		      .     .
		      .     .
		......a..g..b
		.     .     .
		.     .     .
		.  e  h  f  .
		.     .     .
		.     .     .
		......c......

		g = (d+f+a+b)/4 + random
		h = (a+c+e+f)/4 + random
		
		In the code below:
			a = (i,j) 
			b = (ni,j) 
			c = (i,nj) 
			d = (mi,pmj) 
			e = (pmi,mj) 
			f = (mi,mj) 
			g = (mi,j)
			h = (i,mj)

		*/
		for (i = 0; i < size; i += rectSize)

		for (j = 0;j < size;j += rectSize){

			ni = (i + rectSize)%size;
			nj = (j + rectSize)%size;

			mi = (i + rectSize / 2);
			mj = (j + rectSize / 2);

			pmi = (i - rectSize / 2+ size)%size;
			pmj = (j - rectSize/ 2 + size)%size;

			//Calculate the square value for the top side of the rectangle
			field[mi + j * size] = (field[i + j * size] + field[ni + j * size] + field[mi + pmj * size] + field[mi + mj * size]) / 4 + rangedRandom(-dh/2, dh/2);

			//Calculate the square value for the left side of the rectangle
			field[i + mj * size] = (field[i + j * size] + field[i + nj * size] + field[pmi+mj*size] + field[mi+mj*size])/4 + rangedRandom(-dh/2,dh/2);

		}

		//Setup values for next iteration
		//At this point, the height field has valid values at each of the coordinates that fall on a rectSize/2 boundary

		rectSize /= 2;
		dh *= r;
	}
	
	//Normalize terrain so minimum value is 0 and maximum value is 1
	normalizeTerrain(field,size);
}
