#ifndef FractalGeometryObjects_HXX
#define FractalGeometryObjects_HXX

#include "stdafx.h"
#include "stdlib.h"
#include "iostream"
#include "Vec3f.hxx"
#include "SmoothTriangle.hxx"
#include "Scene.hxx"
#include "ReflectiveTexturedPhongShader.hxx"
#include "Material.hxx"

/* Fractal Geometry
 *
 * Given four base points and a height, the algorithm subdivides the quadrilateral, adds random height perturbations
 * and thereby creates a realistical shape of a mountain.
 *
 * Miguel Granados, Richard Socher
 * Core Lecture in Computer Graphics by Prof. Dr. Philipp Slusallek
 * University of Saarland
 * WS2006/2007
 */


class FractalGeometryObjects
{
private:

	// General
	Scene* myScene;

	////////////////////
	// Mountain Parameters
	// the base for the mountain is a quadrilateral (of any shape, for ease of reading it is called rectangle),
	// A------B
	//  \     |
	//   D----C
	// this is used for a 1:4 triangle subdivision
	Vec3f* baseRectA;
	Vec3f* baseRectB;
	Vec3f* baseRectC;
	Vec3f* baseRectD;
	// number of times all rectangles are split up
	int recursionSteps;
	// maximum Height of the peak
	float maxHeight;
	// minimal height, prevents inverse peaks
	// its influence is set to 0 if it is equal to maxHeight
	float minHeight;
	// Data structure for holding all the quadrilateral points
	// which are then transformed into triangles in a final recSteps
	vector<Vec3f*> cornerA;
	vector<Vec3f*> cornerB;
	vector<Vec3f*> cornerC;
	vector<Vec3f*> cornerD;

	vector<Vec3f*> allVertices;
	Shader* myShader;
	// end - mountain parameters
	////////////////////


public:

	// standard constructor defines only the scene where the models are added
	FractalGeometryObjects::FractalGeometryObjects(Scene* newScene)
	{
		this->myScene = newScene;
	};


	// return point and add it to the list, iff it did not exist before
	Vec3f* addToAllUniquePoints(Vec3f* v) {
		for(unsigned int i=0; i<allVertices.size(); i++) {
			if(*allVertices[i] == *v) return allVertices[i];
		}
		allVertices.push_back(v);
		return v;
	}


	// this is the main function for adding mountains to a scene
	void FractalGeometryObjects::addMountainToScene(
		Vec3f newA, Vec3f newB, Vec3f newC, Vec3f newD,
		int newRecursionSteps,float newMaxHeight,float newMinHeight, int newSeed)
	{
		clock_t t_before = clock();
		// initialisation
		baseRectA = new Vec3f(newA.x,newA.y,newA.z);
		baseRectB = new Vec3f(newB.x,newB.y,newB.z);
		baseRectC = new Vec3f(newC.x,newC.y,newC.z);
		baseRectD = new Vec3f(newD.x,newD.y,newD.z);
		this->maxHeight=newMaxHeight; // so we have the height at the beginning of the mountains
		this->minHeight=newMinHeight+newA.z;
		this->recursionSteps = newRecursionSteps;

		// this changes the shape of the mountain ;)
		srand(newSeed);

		// actual mountain creation
		int numOfTriangles = addTrianglePoints(baseRectA,baseRectB,baseRectC,baseRectD,recursionSteps,maxHeight);

		addTrianglesToScene();

		clock_t t_after = clock();
		cout << numOfTriangles <<" triangles added to the scene in "
			<< (t_after - t_before) / (float)CLOCKS_PER_SEC << "s" << endl;
	};




	// this function calculates all the triangles and puts them into cornerX
	// starts out with the original 4 base points and the corresponding quadrilateral and subdivides
	int FractalGeometryObjects::addTrianglePoints(
		Vec3f* tempA, Vec3f* tempB, Vec3f* tempC, Vec3f* tempD,
		int recSteps, float tempMaxHeight)
	{
		int numOfTriangles = 0;
		// end of recursion reached - add all the small triangles to scene
		if(recSteps == 0) {

			if (tempA->z < minHeight) tempA->z=minHeight;
			if (tempB->z < minHeight) tempB->z=minHeight;
			if (tempC->z < minHeight) tempC->z=minHeight;
			if (tempD->z < minHeight) tempD->z=minHeight;
			cornerA.push_back(tempA);
			cornerB.push_back(tempB);
			cornerC.push_back(tempC);
			cornerD.push_back(tempD);
			return 1;
		}

		// heightChange is equal to the maximum height in this recSteps times a random number between [-1,1]
		// the smaller the range of the RV the more like a roundish cone the mounatin is going to be
		// if it biased towards the positive side, the lower parts of the mounatin will be higher: [-0.5,1]
		float heightChange = 1.5f*tempMaxHeight * ((1.5f * (rand()/(RAND_MAX+1.0f)))-0.5f);


		// to make sure we really get a mountain no matter what the seed is
		// if used for a complete terrain, this should be removed
		if (recursionSteps == recSteps && heightChange < 0.9f*maxHeight){
			heightChange = maxHeight;
		}

		// if it happens to get negative, attenuate it a little (less sharp points on the way to the top)
		if(heightChange<0){
			heightChange = heightChange * 0.4f;
		}


		// for any terrain that is not supposed to be one big mountain,
		// we could change the height of other coordinates also (or just decrease the initial height)
		Vec3f* center = new Vec3f(
			((*tempB+*tempC)+(*tempA+*tempD))*0.5f/2.0f // get in the middle of the base
			+ Vec3f(0,0,heightChange)); 	// add new height to this point

		allVertices.push_back(center);


		// calculate all points for the 4 new quadrilaterals,
		// these are all in the middle between the 4 original points
		// A-----bAB-----B
		// \      |      |
		// bAD----C----bBC
		//   \    |      |
		//    D---bDC----C
		Vec3f* betweenAB = new Vec3f((*tempA+*tempB)/2);
		Vec3f* betweenBC = new Vec3f((*tempB+*tempC)/2);
		Vec3f* betweenCD = new Vec3f((*tempC+*tempD)/2);
		Vec3f* betweenDA = new Vec3f((*tempD+*tempA)/2);


		betweenAB=addToAllUniquePoints(betweenAB);
		betweenBC=addToAllUniquePoints(betweenBC);
		betweenCD=addToAllUniquePoints(betweenCD);
		betweenDA=addToAllUniquePoints(betweenDA);

		// the 4 new quadrilaterals should not form new peaks
		float newH= tempMaxHeight/2;
		int rSteps = recSteps-1;
		// recursively subdivide the quadrilateral and go to next finer level
		numOfTriangles += addTrianglePoints(tempA,betweenAB,center,betweenDA,rSteps,newH);
		numOfTriangles += addTrianglePoints(betweenAB,tempB,betweenBC,center,rSteps,newH);
		numOfTriangles += addTrianglePoints(center,betweenBC,tempC,betweenCD,rSteps,newH);
		numOfTriangles += addTrianglePoints(betweenDA,center,betweenCD,tempD,rSteps,newH);

		return numOfTriangles;
	};


	// add generated geometry to scene
	void FractalGeometryObjects::addTrianglesToScene() {

		// all corner vectors should have the same amount of elements
		int numOfCornA = (int)cornerA.size();

		// go over all quadrilaterals
		for(int i=0; i<numOfCornA; i++) {

			// Calculate trianlge vertex normals
			// for each point, find all other triangles that have this point and find their average normal
			// one point can at most be in 6 different triangles,
			// this could be made more efficient... like adding a vector of normals to each point during the time of creation
			Vec3f* nA= new Vec3f(0,0,0);
			Vec3f* nB= new Vec3f(0,0,0);
			Vec3f* nC= new Vec3f(0,0,0);
			Vec3f* nD= new Vec3f(0,0,0);
			int allSharingTriangles = 0;
			for(int j=0; j<numOfCornA && allSharingTriangles<7; j++) {
				//get avg normal for point A
				if(cornerA[i]==cornerA[j] || cornerA[i] == cornerB[j]|| cornerA[i] == cornerC[j]){
					nA = new Vec3f(*nA + Triangle::getn(*cornerA[j],*cornerB[j],*cornerC[j]));
				}
				if(cornerA[i]==cornerA[j] || cornerA[i] == cornerC[j]|| cornerA[i] == cornerD[j]){
					nA = new Vec3f(*nA + Triangle::getn(*cornerA[j],*cornerC[j],*cornerD[j]));
				}
				//get avg normal for point B
				if(cornerB[i]==cornerA[j] || cornerB[i] == cornerB[j]|| cornerB[i] == cornerC[j]){
					nB = new Vec3f(*nB + Triangle::getn(*cornerA[j],*cornerB[j],*cornerC[j]));
				}
				if(cornerB[i]==cornerA[j] || cornerB[i] == cornerC[j]|| cornerB[i] == cornerD[j]){
					nB = new Vec3f(*nB + Triangle::getn(*cornerA[j],*cornerC[j],*cornerD[j]));
				}
				//get avg normal for point C
				if(cornerC[i]==cornerA[j] || cornerC[i] == cornerB[j]|| cornerC[i] == cornerC[j]){
					nC = new Vec3f(*nC + Triangle::getn(*cornerA[j],*cornerB[j],*cornerC[j]));
				}
				if(cornerC[i]==cornerA[j] || cornerC[i] == cornerC[j]|| cornerC[i] == cornerD[j]){
					nC = new Vec3f(*nC + Triangle::getn(*cornerA[j],*cornerC[j],*cornerD[j]));
				}
				//get avg normal for point D
				if(cornerD[i]==cornerA[j] || cornerD[i] == cornerB[j]|| cornerD[i] == cornerC[j]){
					nD = new Vec3f(*nD + Triangle::getn(*cornerA[j],*cornerB[j],*cornerC[j]));
				}
				if(cornerD[i]==cornerA[j] || cornerD[i] == cornerC[j]|| cornerD[i] == cornerD[j]){
					nD = new Vec3f(*nD + Triangle::getn(*cornerA[j],*cornerC[j],*cornerD[j]));
				}
			}

			Normalize(*nA);
			Normalize(*nB);
			Normalize(*nC);
			Normalize(*nD);

			Shader* tempShader;

			// defining the shader (that takes everything from Material)
			tempShader = new ReflectiveTexturedPhongShader(myScene);

			// defining the material
			Material* m = new Material;
			m->illum =  2;
			// changing color of material depending on height
			float h = cornerA[i]->z - minHeight;
			float r = (rand()/(RAND_MAX+1.0f));
			float max = maxHeight;

			if (h/max < 0.3f + (r*0.1)){ // green
				m->Kd = Vec3f(0.0,0.4,0.00);
				m->Ks = Vec3f(0);
			} else if (h/max < 0.7f + r*0.06){
				m->Kd = (h/max+(1-0.85)+r*0.1)*Vec3f(0.5,0.5,0.5);
				m->Ks = Vec3f(0);
			} else {
				m->Kd = (h/max)*Vec3f(1,1,1);
				m->Ks = Vec3f(0.2);
			}

			// creating 2 triangles out of the quadrilateral
			// Add the to the scene
			myScene->Add(new SmoothTriangle( *cornerA[i], *cornerB[i], *cornerC[i],
				*nA,*nB,*nC,tempShader, m));
			myScene->Add(new SmoothTriangle( *cornerC[i], *cornerD[i], *cornerA[i],
				*nC,*nD,*nA,tempShader, m));
		}
	};
};

#endif

