/* terrain.cpp
 * Copyright (C) 2003 Marcus Bäckman, pingvin@varberg.se
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Usage:terrain *myTerrain=new terrain("a .tga-file",true or false);
 * then use myTerrain->drawSurface();
 * PictureReader.cpp, PictureReader.h, Reader.cpp, Reader.h and 3DMath.h needed
 */



#include "terrain.h"
#include <stdio.h>
#include "../Classes/PictureReader.h"

terrain::terrain(char *heightMap, bool useCallList)
{
	this->useCallList=useCallList;
	readHeightmap(heightMap);
	if (useCallList==true) {
		callListID = glGenLists(1);
		glNewList(callListID, GL_COMPILE);
		initSurface();
		glEndList();
	}
}

void terrain::readHeightmap(char *heightMap) 
{
	int i,j;
	PictureReader *hMap;

	hMap = new PictureReader;
	hMap->setFilePath(heightMap);
	hMap->process();
	hMap->setBitsPerPixel(24);
	width=hMap->getWidth();
	height=hMap->getHeight();
	texStep=1.0/width;
	zScale=(width+height)/10.0f;
//	zScale=20;
	unsigned char *byteMap = (unsigned char *)hMap->getPic();
	delete hMap;
 
	heightData = (float**)malloc(height*sizeof(float*));
	for(i=0;i<height;i++)
		heightData[i] = (float*)malloc(width*sizeof(float));

	normalData = (Vector3**)malloc(height*sizeof(Vector3*));
	for(i=0;i<height;i++)
		normalData[i] = (Vector3*)malloc(width*sizeof(Vector3));


	// Each pixel takes three bytes(RGB)
	for (i=0,j=0;j<height*width;i+=3,j++) { 
		// Convert to grayscale:(R+G+B)/3, scaled in the range [0,1] (divide by the larges value)
		heightData[j/height][j%width]=(float)((byteMap[i] + byteMap[i+1] + byteMap[i+2])/(3.0f*255.0f) );
	}
}

void terrain::calcNormals()
{
	Vector3 normal,v,u;
	int i,j;

	for (i=0;i<height;i++) 
		for (j=0;j<width;j++) {
			if (i!=height-1) {
				if (j!=0) {
					v=Vector3(1,0,(heightData[i+1][j]-heightData[i][j])*zScale);
					u=Vector3(0,-1,(heightData[i][j-1]-heightData[i][j])*zScale);
				} else { // edge
					v=Vector3(1,0,(heightData[i+1][j]-heightData[i][j])*zScale);
					u=Vector3(0,1,(heightData[i][j+1]-heightData[i][j])*zScale);
				}
			} else { 
				if (j!=width-1) {
					v=Vector3(-1,0,(heightData[i-1][j]-heightData[i][j])*zScale);
					u=Vector3(0,1,(heightData[i][j+1]-heightData[i][j])*zScale);
				} else { // edge
					v=Vector3(-1,0,(heightData[i-1][j]-heightData[i][j])*zScale);
					u=Vector3(0,-1,(heightData[i][j-1]-heightData[i][j])*zScale);
				}
			}
			u.cross(v,normal);
			normal.normalize();
			normalData[i][j]=normal;
		}
	// sum and smooth the normals, not the edges though(Im too laze:))

	Vector3 **newnData;
	newnData = (Vector3**)malloc(height*sizeof(Vector3*));
	for(i=0;i<height;i++)
		newnData[i] = (Vector3*)malloc(width*sizeof(Vector3));

	Vector3 nNormal;
	for (i=0;i<height-1;i++) 
		for (j=0;j<width-1;j++) {
			if (j!=0  && i!=0 ) {
				nNormal=Vector3(normalData[i][j].x+normalData[i-1][j-1].x+normalData[i][j-1].x+normalData[i+1][j].x+normalData[i][j+1].x, normalData[i][j].y+normalData[i-1][j-1].y+normalData[i][j-1].y+normalData[i+1][j].y+normalData[i][j+1].y, normalData[i][j].z+normalData[i-1][j-1].z+normalData[i][j-1].z+normalData[i+1][j].z+normalData[i][j+1].z);
				nNormal.normalize();
				newnData[i][j]=nNormal;
			} 
		}

	// Copy the new normals
	for (i=0;i<height-1;i++) 
		for (j=0;j<width-1;j++) {
			if (j!=0  && i!=0 ) {
				normalData[i][j]=newnData[i][j];
			} 
		}

	// Free up some memory
	for (i=0;i<height;i++)
		free(newnData[i]);
	free(newnData);
}

void terrain::initSurface(void) 
{
	float x[2]={1,1-texStep},y[2]={1,1};
	float du=1.0f,dv=1.0f;
	Vector3 normal,v,u;

	glColor3f(1,0,0);
	glDisable(GL_BLEND);
	calcNormals();

	for (int i=0;i<height-1;i++) {
		glBegin(GL_TRIANGLE_STRIP);
		for (int j=0;j<width;j++) {
			glNormal3f(normalData[i][j].x,normalData[i][j].y,normalData[i][j].z);
			glTexCoord2d(x[0],y[0]); glVertex3f(i,j,heightData[i][j]*zScale);

/*			glBegin(GL_LINES);
				glVertex3f(i, j, heightData[i][j]*zScale);
				glVertex3f(normalData[i][j].x+i, normalData[i][j].y+j, normalData[i][j].z+heightData[i][j]*zScale);
			glEnd();*/

			glNormal3f(normalData[i+1][j].x,normalData[i+1][j].y,normalData[i+1][j].z);
			glTexCoord2d(x[1],y[1]); glVertex3f(i+1,j,heightData[i+1][j]*zScale);

			du-=texStep;
			if (du+texStep<=0) { // End of texture, starts over
				du=1;
				y[0]=y[1]=du;
				glEnd();
				glBegin(GL_TRIANGLE_STRIP); 
				glTexCoord2d(x[0],y[0]); glVertex3f(i,j,heightData[i][j]*zScale);
				glTexCoord2d(x[1],y[1]); glVertex3f(i+1,j,heightData[i+1][j]*zScale);
				du-=texStep;
			} 
			y[0]=du; 
			y[1]=du;
		}
		glEnd();

		dv-=texStep;
		if (dv+texStep<=texStep)
			dv=1;

		du=1; 
		x[0]=dv;				    y[0]=du;
		x[1]=dv-texStep;		    y[1]=du;
	}
}

float terrain::getHeight(float x, float y)
{
	if (x<0 || y<0 || x>width || y>height)
		return (0.0f);
	Vector3 normal=normalData[(int)x][(int)y];
	// The planes equation: n.x(x-a)+n.y(y-b)+n.z(z-c)=0
	// z=(-n.x(x-a)-n.y(y-b) + n.z*c)/n.z
	return( (-normal.x*(x-(int)x)-normal.y*(y-(int)y)+normal.z*heightData[(int)x][(int)y]*zScale )/normal.z );
//	return(heightData[(int)x][(int)y]*zScale);
}

void terrain::drawSurface(void)
{
	if (useCallList==true) {
		glCallList(callListID);
		return;
	} else
		initSurface();
}

