#include <MLR\MLRHeaders.hpp>

#if !defined(HFSLIM_HPP)
	#include "HFslim.hpp"
	#include "terra\terra.hpp"
#endif

const Scalar One_Over_Three = 1.0f/3.0f;

extern FILE *analyzeFile;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ToDrawTriangle::GetSurfaceAreaAndCentroid()
{
	//
	//---------------------
	// Set up the variables
	//---------------------
	//
	Point3D
		position_a = Point3D::Identity,
		position_b,
		position_c = Point3D::Identity;
	Vector3D
		leg_1,
		leg_2 = Vector3D::Identity;

	//
	//-----------------------------------
	// Spin through, testing the vertices
	//-----------------------------------
	//
	area = 0.0f;
	center = Point3D::Identity;

	//
	//-----------------------------------------------
	// Generate all the information on the first pass
	//-----------------------------------------------
	//
	position_a = v[0];
	position_b = v[1];
	position_c = v[2];

	leg_1.Subtract(position_b, position_a);
	leg_2.Subtract(position_c, position_a);

	//
	//-----------------------------------------------------------------
	// Compute the cross-product of the two legs to get the area of the
	// triangle
	//-----------------------------------------------------------------
	//
	Vector3D vcp;
	vcp.Cross(leg_1, leg_2);

	//
	//-------------------------------------------------------------------
	// Add the three triangle points together and multiply by the area of
	// the triangle to give a weighted sum for the polygon centroid
	//-------------------------------------------------------------------
	//
	Point3D centroid;
	centroid.Add(position_a, position_b);
	centroid += position_c;

	Scalar wedge_area = vcp.GetLength() * 0.5f;

	if (area <= SMALL)
	{
		if (wedge_area > SMALL)
		{
			area += wedge_area;
			centroid *= wedge_area;
		}
		center = centroid;
	}
	else
	{
		if (wedge_area > SMALL)
		{
			area += wedge_area;
			centroid *= wedge_area;
			center += centroid;
		}
	}

	if(area > SMALL)
	{
		center *= (One_Over_Three/area);
	}
	else
	{
		center *= One_Over_Three;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
HFSlim::HFSlim()
{
	X = (int)0.0f;
	Z = (int)0.0f;
	
	dX = 2.0f;
	dY = 0.15f;
	dZ = 2.0f;

	xOffset = 0.0f;
	zOffset = 0.0f;

	Offset = 0.0f;
	visHeight = 0;
	nrOfIZs = 0;

	terrainGroup = new GroupElement();
	Register_Object(terrainGroup);

	fullTerrainGroup = NULL;
	simpleTerrainGroup = NULL;

	borderPixelFun = 0.0f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
HFSlim::~HFSlim()
{
	if(fullTerrainGroup!=NULL)
	{
		Unregister_Object(fullTerrainGroup);
		delete fullTerrainGroup;
		fullTerrainGroup = NULL;
	}

	if(simpleTerrainGroup!=NULL)
	{
		Unregister_Object(simpleTerrainGroup);
		delete simpleTerrainGroup;
		simpleTerrainGroup = NULL;
	}

	Unregister_Object(terrainGroup);
	delete terrainGroup;
	terrainGroup = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Stuff::ExtentBox
	HFSlim::GetExtents()
{
	Stuff::ExtentBox ebox;

	ebox.minX = GetX(0);
	ebox.minZ = GetZ(0);

	ebox.maxX = GetX(X-1);
	ebox.maxZ = GetZ(Z-1);

	int i, j;
	Scalar h;
	ebox.maxY = ebox.minY = GetHeight(0, 0);

	for (j=1;j<Z;j++)
		for (i=1;i<X;i++)
		{
			h = GetHeight(i, j);
			if(ebox.maxY < h)
			{
				ebox.maxY = h;
			}
			if(ebox.minY > h)
			{
				ebox.minY = h;
			}
		}

	return ebox;
}

//---------------------------------------------------------------------------
void
	HFSlim::Blur2D (int smooth)
{

    // Precondition for stability:  0 < scale < exp(0.25)
    // Good choice for iterative blurring is scale = exp(0.125)
	float scale = static_cast<float>(exp(0.125));
	float** temp = new float*[Z];
	int x, y;
	for (y = 0; y < Z; y++)
		temp[y] = new float[X];

	for(int i=0;i<smooth;i++)
	{
		float logscale = float(log(scale));

		for (y = 0; y < Z; y++) 
		{
			float ryp = y+scale, rym = y-scale;
			int yp = (int) floor(ryp), ym = (int) ceil(rym);

			for (x = 0; x < X; x++) 
			{
				float rxp = x+scale, rxm = x-scale;
				int xp = (int) floor(rxp), xm = (int) ceil(rxm);

				// x portion of second central difference
				float xsum = -2*field[y*X+x];
				if ( xp >= X-1 )  // use boundary value
					xsum += field[y*X+X-1];
				else  // linearly interpolate
					xsum += field[y*X+xp]+(rxp-xp)*(field[y*X+xp+1]-field[y*X+xp]);
				if ( xm <= 0 )  // use boundary value
					xsum += field[y*X+0];
				else  // linearly interpolate
					xsum += field[y*X+xm]+(rxm-xm)*(field[y*X+xm]-field[y*X+xm-1]);

				// y portion of second central difference
				float ysum = -2*field[y*X+x];
				if ( yp >= Z-1 )  // use boundary value
					ysum += field[(Z-1)*X+x];
				else  // linearly interpolate
					ysum += field[yp*X+x]+(ryp-yp)*(field[(yp+1)*X+x]-field[yp*X+x]);
				if ( ym <= 0 )  // use boundary value
					ysum += field[0*X+x];
				else  // linearly interpolate
					ysum += field[ym*X+x]+(rym-ym)*(field[ym*X+x]-field[(ym-1)*X+x]);

				temp[y][x] = field[y*X+x]+logscale*(xsum+ysum);
			}
		}

		for (y = 0; y < Z; y++)
			for (x = 0; x < X; x++)
				field[y*X+x] = temp[y][x];
	}

    for (y = 0; y < Z; y++)
        delete[] temp[y];
    delete[] temp;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	HFSlim::FillFromImage(Image &image, int smooth)
{
	X = image.GetWidth()+1;
	Z = image.GetHeight()+1;
	
	Verify(image.GetBpp()==8);
	
	field.SetLength(X*Z);
	
	unsigned char *ptr;
	ptr=(unsigned char *)image.Lock();

	int i, j;
	for(i=0;i<Z-1;i++)
	{
		for(j=0;j<X-1;j++)
		{
			field[i*X+j] = ptr[(image.GetHeight()-i-1)*(X-1)+image.GetWidth()-j-1];
		}
		field[i*X+j] = field[i*X+j-1];
	}
	for(j=0;j<X-1;j++)
	{
		field[i*X+j] = field[(i-1)*X+j];
	}
	field[i*X+j] = field[i*X+j-1];

	image.UnLock();

	Blur2D(smooth);
}

int triangleCounter = 0;

Scalar xGridSize, zGridSize;
int tX, tY;

void GetFacesCB(Triangle &tri,void *cbp)
{
	ToDrawTriangle *tdtrilist = (ToDrawTriangle *)cbp;
	tdtrilist[triangleCounter].SetPoint(0,(float)tri.point1()[0],0.0f,(float)tri.point1()[1]);
	tdtrilist[triangleCounter].SetPoint(1,(float)tri.point2()[0],0.0f,(float)tri.point2()[1]);
	tdtrilist[triangleCounter].SetPoint(2,(float)tri.point3()[0],0.0f,(float)tri.point3()[1]);

	triangleCounter++;
}

#define TERRAIN2_TEST

bool
	HFSlim::CreateMesh(
		ListElement *parent,
		int listIndex,
		int diffLevel,
		DynamicArrayOf<ToDrawTriangle*>& tdtrilist,
		ExtentBox *uvFrame,
		MLRState *state
	)
{
	int polygon_count = tdtrilist.GetLength();
	if(polygon_count==0 || polygon_count*3 >= Limits::Max_Number_Vertices_Per_Mesh)
	{
		return false;
	}

#ifndef TERRAIN2_TEST
	MLR_I_DeT_TMesh *erf_mesh = new MLR_I_DeT_TMesh;
#else
	MLR_Terrain2 *erf_mesh = new MLR_Terrain2;
	erf_mesh->SetOffset(0.0f, 0.0f);
	erf_mesh->SetDepthData(maxAllDepth-diffLevel, maxAllDepth);
	erf_mesh->SetTileData(tX, tY);
	erf_mesh->SetGrid(xGridSize, zGridSize);
	erf_mesh->SetBorderPixel(borderPixelFun);
#endif
	Register_Object(erf_mesh);

	erf_mesh->SetDetailData(
		detailInfo.xOff,
		detailInfo.zOff,
		detailInfo.xFac*(1<<diffLevel),
		detailInfo.zFac*(1<<diffLevel),
		detailInfo.dStart,
		detailInfo.dEnd
	);

    DynamicArrayOf<int> vert_id(X*Z);
	memset(vert_id.GetData(), 0, vert_id.GetSize());
	
	int i, j, x, z, point_count = 0;
	
	int minX[2]={0,0},
		maxX[2]={0,0},
		minY[2]={0,0},
		maxY[2]={0,0},
		minZ[2]={0,0},
		maxZ[2]={0,0};
	Scalar
		minx=tdtrilist[0]->v[0].x,
		maxx=tdtrilist[0]->v[0].x,
		miny=tdtrilist[0]->v[0].y,
		maxy=tdtrilist[0]->v[0].y,
		minz=tdtrilist[0]->v[0].z,
		maxz=tdtrilist[0]->v[0].z;

	Point3D
		center0 = Point3D::Identity,
		center1 = Point3D::Identity,
		center2 = Point3D::Identity,
		center3 = Point3D::Identity;

	for(i=0;i<polygon_count;++i)
	{
		center3 += tdtrilist[i]->center;

		if(tdtrilist[i]->v[0].x < minx)
		{
			minX[0] = i;
			minX[1] = 0;
			minx = tdtrilist[i]->v[0].x;
		} else if(tdtrilist[i]->v[0].x > maxx)
		{
			maxX[0] = i;
			maxX[1] = 0;
			maxx = tdtrilist[i]->v[0].x;
		}
		if(tdtrilist[i]->v[0].y < miny)
		{
			minY[0] = i;
			minY[1] = 0;
			miny = tdtrilist[i]->v[0].y;
		} else if(tdtrilist[i]->v[0].y > maxy)
		{
			maxY[0] = i;
			maxY[1] = 0;
			maxy = tdtrilist[i]->v[0].y;
		}
		if(tdtrilist[i]->v[0].z < minz)
		{
			minZ[0] = i;
			minZ[1] = 0;
			minz = tdtrilist[i]->v[0].z;
		} else if(tdtrilist[i]->v[0].z > maxz)
		{
			maxZ[0] = i;
			maxZ[1] = 0;
			maxz = tdtrilist[i]->v[0].z;
		}
		x = (int)(tdtrilist[i]->v[0].x/dX);
		z = (int)(tdtrilist[i]->v[0].z/dZ);
		vert_id[x + X*z]++;
		if(vert_id[x + X*z]==1)
		{
			center0 += tdtrilist[i]->v[0];
			point_count++;
		}

		if(tdtrilist[i]->v[2].x < minx)
		{
			minX[0] = i;
			minX[1] = 2;
			minx = tdtrilist[i]->v[2].x;
		} else if(tdtrilist[i]->v[2].x > maxx)
		{
			maxX[0] = i;
			maxX[1] = 2;
			maxx = tdtrilist[i]->v[2].x;
		}
		if(tdtrilist[i]->v[2].y < miny)
		{
			minY[0] = i;
			minY[1] = 2;
			miny = tdtrilist[i]->v[2].y;
		} else if(tdtrilist[i]->v[2].y > maxy)
		{
			maxY[0] = i;
			maxY[1] = 2;
			maxy = tdtrilist[i]->v[2].y;
		}
		if(tdtrilist[i]->v[2].z < minz)
		{
			minZ[0] = i;
			minZ[1] = 2;
			minz = tdtrilist[i]->v[2].z;
		} else if(tdtrilist[i]->v[2].z > maxz)
		{
			maxZ[0] = i;
			maxZ[1] = 2;
			maxz = tdtrilist[i]->v[2].z;
		}
		x = (int)(tdtrilist[i]->v[2].x/dX);
		z = (int)(tdtrilist[i]->v[2].z/dZ);
		vert_id[x + X*z]++;
		if(vert_id[x + X*z]==1)
		{
			center0 += tdtrilist[i]->v[1];
			point_count++;
		}
		
		if(tdtrilist[i]->v[1].x < minx)
		{
			minX[0] = i;
			minX[1] = 0;
			minx = tdtrilist[i]->v[1].x;
		} else if(tdtrilist[i]->v[1].x > maxx)
		{
			maxX[0] = i;
			maxX[1] = 1;
			maxx = tdtrilist[i]->v[1].x;
		}
		if(tdtrilist[i]->v[1].y < miny)
		{
			minY[0] = i;
			minY[1] = 1;
			miny = tdtrilist[i]->v[1].y;
		} else if(tdtrilist[i]->v[1].y > maxy)
		{
			maxY[0] = i;
			maxY[1] = 1;
			maxy = tdtrilist[i]->v[1].y;
		}
		if(tdtrilist[i]->v[1].z < minz)
		{
			minZ[0] = i;
			minZ[1] = 1;
			minz = tdtrilist[i]->v[1].z;
		} else if(tdtrilist[i]->v[1].z > maxz)
		{
			maxZ[0] = i;
			maxZ[1] = 1;
			maxz = tdtrilist[i]->v[1].z;
		}
		x = (int)(tdtrilist[i]->v[1].x/dX);
		z = (int)(tdtrilist[i]->v[1].z/dZ);
		vert_id[x + X*z]++;
		if(vert_id[x + X*z]==1)
		{
			center0 += tdtrilist[i]->v[2];
			point_count++;
		}
	}

	center0 *= 1.0f/point_count;

	center0.x = GetX(center0.x);
	center0.y += Offset;
	center0.z = GetZ(center0.z);

	center3 *= 1.0f/polygon_count;
	center3.x = GetX(center3.x);
	center3.y += Offset;
	center3.z = GetZ(center3.z);

	Vector3D v3;
	Scalar d[3];

	v3.Subtract(tdtrilist[maxX[0]]->v[maxX[1]], tdtrilist[minX[0]]->v[minX[1]]);
	d[0] = v3.GetLengthSquared();

	v3.Subtract(tdtrilist[maxY[0]]->v[maxY[1]], tdtrilist[minY[0]]->v[minY[1]]);
	d[1] = v3.GetLengthSquared();

	v3.Subtract(tdtrilist[maxZ[0]]->v[maxZ[1]], tdtrilist[minZ[0]]->v[minZ[1]]);
	d[2] = v3.GetLengthSquared();

	if(d[0] > d[1])
	{
		if(d[0]>d[2])
		{
			center1 = tdtrilist[maxX[0]]->v[maxX[1]];
			center1 += tdtrilist[minX[0]]->v[minX[1]];
			center1 *= 0.5f;
		}
		else
		{
			center1 = tdtrilist[maxZ[0]]->v[maxZ[1]];
			center1 += tdtrilist[minZ[0]]->v[minZ[1]];
			center1 *= 0.5f;
		}
	}
	else
	{
		if(d[1]>d[2])
		{
			center1 = tdtrilist[maxY[0]]->v[maxY[1]];
			center1 += tdtrilist[minY[0]]->v[minY[1]];
			center1 *= 0.5f;
		}
		else
		{
			center1 = tdtrilist[maxZ[0]]->v[maxZ[1]];
			center1 += tdtrilist[minZ[0]]->v[minZ[1]];
			center1 *= 0.5f;
		}
	}

	center1.x = GetX(center1.x);
	center1.y += Offset;
	center1.z = GetZ(center1.z);

	center2.x = GetX((maxx+minx)*0.5f);
	center2.y = (maxy+miny)*0.5f;
	center2.z = GetZ((maxz+minz)*0.5f);

	center2.y += Offset;

	Point3D *coords = new Point3D [point_count];
	Vector2DScalar *texCoords = new Vector2DScalar[point_count];
	
	UnitVector3D sun;
	sun = Vector3D(-1.0f, -1.0f, 0.0f);

    int nrOfPoints = 0;
	Scalar radiusSquared, 
		maxRadius0 = 0.0f,
		maxRadius1 = 0.0f,
		maxRadius2 = 0.0f,
		maxRadius3 = 0.0f;

	for(z=0;z<Z;z++)
		for(x=0;x<X;x++)
		{
			if( vert_id[x + X*z] > 0 )
			{
				Verify(nrOfPoints<point_count);
				vert_id[z*X+x] = nrOfPoints;

				coords[nrOfPoints].x = GetX(x);
				coords[nrOfPoints].y = GetHeight(x, z);
				coords[nrOfPoints].z = GetZ(z);

				v3.Subtract(coords[nrOfPoints], center0);

				radiusSquared = v3.GetLengthSquared();
				maxRadius0 = radiusSquared>maxRadius0 ? radiusSquared:maxRadius0;

				v3.Subtract(coords[nrOfPoints], center1);

				radiusSquared = v3.GetLengthSquared();
				maxRadius1 = radiusSquared>maxRadius1 ? radiusSquared:maxRadius1;

				v3.Subtract(coords[nrOfPoints], center2);

				radiusSquared = v3.GetLengthSquared();
				maxRadius2 = radiusSquared>maxRadius2 ? radiusSquared:maxRadius2;

				v3.Subtract(coords[nrOfPoints], center3);

				radiusSquared = v3.GetLengthSquared();
				maxRadius3 = radiusSquared>maxRadius3 ? radiusSquared:maxRadius3;

#ifndef TERRAIN2_TEST
				texCoords[nrOfPoints][0] = 
					borderPixelFun + (1.0f-2*borderPixelFun)*((uvFrame->maxX - coords[nrOfPoints].x)/(uvFrame->maxX-uvFrame->minX));
				texCoords[nrOfPoints][1] =
					borderPixelFun + (1.0f-2*borderPixelFun)*((uvFrame->maxZ - coords[nrOfPoints].z)/(uvFrame->maxZ-uvFrame->minZ));
#endif
				nrOfPoints++;
			}
			else
				vert_id[z*X+x] = -1;
		}

	maxRadius0 = static_cast<Scalar>(sqrt(maxRadius0));
	maxRadius1 = static_cast<Scalar>(sqrt(maxRadius1));
	maxRadius2 = static_cast<Scalar>(sqrt(maxRadius2));
	maxRadius3 = static_cast<Scalar>(sqrt(maxRadius3));

	Point3D center;
	Scalar maxRadius;

	if(maxRadius0<maxRadius1)
	{
		if(maxRadius0<maxRadius2)
		{
			if(maxRadius0<maxRadius3)
			{
				maxRadius = maxRadius0;
				center = center0;
			}
			else
			{
				maxRadius = maxRadius3;
				center = center3;
			}
		}
		else
		{
			if(maxRadius2<maxRadius3)
			{
				maxRadius = maxRadius2;
				center = center2;
			}
			else
			{
				maxRadius = maxRadius3;
				center = center3;
			}
		}
	}
	else
	{
		if(maxRadius1<maxRadius2)
		{
			if(maxRadius1<maxRadius3)
			{
				maxRadius = maxRadius1;
				center = center1;
			}
			else
			{
				maxRadius = maxRadius3;
				center = center3;
			}
		}
		else
		{
			if(maxRadius2<maxRadius3)
			{
				maxRadius = maxRadius2;
				center = center2;
			}
			else
			{
				maxRadius = maxRadius3;
				center = center3;
			}
		}
	}

	unsigned short	*index = new unsigned short [polygon_count*3];

	int k, l;

	for(i=0,l=0;i<polygon_count;i++)
	{
		k = 0;

		x = (int)(tdtrilist[i]->v[0].x/dX);
		z = (int)(tdtrilist[i]->v[0].z/dZ);
		j = x + X*z;
		Verify(vert_id[j]>=0);
		index[l++] = vert_id[j];

		if(z==Z-1)
			k++;

		x = (int)(tdtrilist[i]->v[2].x/dX);
		z = (int)(tdtrilist[i]->v[2].z/dZ);
		j = x + X*z;
		Verify(vert_id[j]>=0);
		index[l++] = vert_id[j];
		if(z==Z-1)
			k++;
		
		x = (int)(tdtrilist[i]->v[1].x/dX);
		z = (int)(tdtrilist[i]->v[1].z/dZ);
		j = x + X*z;
		Verify(vert_id[j]>=0);
		index[l++] = vert_id[j];
		if(z==Z-1)
			k++;

		if(k>=2)
		{
			j = 0;
		}

		if(
			index[l-3]==index[l-2] ||
			index[l-2]==index[l-1] ||
			index[l-1]==index[l-3]
		)
		{
			l-=3;
			continue;
		}

		Vector3D vCross;
		vCross.Cross(
			coords[index[l-1]],
			coords[index[l-2]],
			coords[index[l-3]]
		);

		if(Small_Enough(vCross))
		{
			l-=3;
			continue;
		}
	}

	erf_mesh->SetSubprimitiveLengths(NULL, l/3);

	erf_mesh->SetCoordData(coords, nrOfPoints);

#ifdef TERRAIN2_TEST
	erf_mesh->SetCurrentDepth(maxAllDepth-diffLevel);
#else
	erf_mesh->SetTexCoordData(texCoords, nrOfPoints);
#endif

	erf_mesh->SetIndexData(index, l);
	erf_mesh->FindFacePlanes();

	delete [] coords;
	delete [] texCoords;
	delete [] index;


	if(state)
	{
		erf_mesh->SetReferenceState(*state);
	}
	else
	{
		erf_mesh->SetReferenceState(state0);
	}

	erf_mesh->SetReferenceState(state1, 1);

	ShapeElement* Shape = new ShapeElement;
	Register_Object(Shape);

	MLRShape *shape = new MLRShape(1);
	Register_Object(shape);

	shape->Add(erf_mesh);
	erf_mesh->DetachReference();

	Shape->SetMLRShape(shape);

	Shape->localOBB.localToParent = Stuff::LinearMatrix4D::Identity;
	Shape->localOBB.localToParent.BuildTranslation(center);
	
	Shape->localOBB.sphereRadius = maxRadius;

//	Shape->SetVolumeCullMode();
	Shape->SetRootMode();

	parent->AttachIndexedChild(listIndex, Shape);

	Shape->SetVolumeCullMode();

	return true;
}

int
	primCounter, maxPrims;

bool
	HFSlim::BinSort(
		ListElement *parent,
		int index,
		int levDiff,
		DynamicArrayOf<ToDrawTriangle*>& tdtrilist,
		int binSize,
		ExtentBox *uvFrame,
		MLRState *state
	)
{
	int polygon_count = tdtrilist.GetLength();

	if (polygon_count==0)
	{
		return false;
	}

	//
	//---------------------------------------------------------
	// If the polygon mesh is already small enough, just return
	//---------------------------------------------------------
	//
	if (polygon_count <= binSize)
	{
		return CreateMesh(parent, index, levDiff, tdtrilist, uvFrame, state);
	}

	DynamicArrayOf<Point3D> centroids(polygon_count);
	unsigned i;
	for (i=0; i<polygon_count; ++i)
	{
		centroids[i] = tdtrilist[i]->center;
	}

	//
	//------------------------------------------------------------------------
	// Calculate the dividing plane, and if none can be found, don't do nothin
	//------------------------------------------------------------------------
	//
	Plane plane;
	if (!plane.ComputeBestDividingPlane(centroids))
	{
		return CreateMesh(parent, index, levDiff, tdtrilist, uvFrame, state);
	}

	//
	//-------------------------------------------------------------------------
	// The mesh is too big, so we have to cut it up.  Make a group proxy to
	// hold the new mesh collection
	//-------------------------------------------------------------------------
	//
	Check_Object(parent);
	ListElement *group = new ListElement;
	Register_Object(group);
	group->SetSize(2);
	//
	//----------------------------------
	// Set the position of the group
	//----------------------------------
	//
	LinearMatrix4D m;
	m = parent->GetLocalToParent();

	parent->SetLocalToParent(LinearMatrix4D::Identity);
	group->SetLocalToParent(m);

#if 0
	const char *name;
	if ((name=parent->GetName())!=NULL)
	{
		parent->SetName(NULL);
		group->SetName(name);
	}
#endif

	DynamicArrayOf<ToDrawTriangle*>
		group_a(polygon_count),
		group_b(polygon_count);
	unsigned
		count_a = 0,
		count_b = 0;

	//
	//------------------------------------------------------------------
	// Sort each of the centroids against the plane into one of two bins
	//------------------------------------------------------------------
	//
	for (i=0; i<polygon_count; ++i)
	{
		if (plane.DistanceTo(centroids[i]) < 0.0f)
			group_b[count_b++] = tdtrilist[i];
		else
			group_a[count_a++] = tdtrilist[i];
	}

	//
	//---------------------------------
	// Now add the polygons to each bin
	//---------------------------------
	//
	Verify(count_a>0);
	group_a.SetLength(count_a);
//	bin_a->AddPolygons(process, group_a);
	Verify(count_b>0);
	group_b.SetLength(count_b);
//	bin_b->AddPolygons(process, group_b);

	//
	//-------------------------------------------------------------------
	// Now that the mesh has been split up, Bin_Sort each smaller mesh and
	// destroy this mesh
	//-------------------------------------------------------------------
	//
	int worked = 0;

	if (BinSort(
		group,
		0,
		levDiff,
		group_a,
		binSize,
		uvFrame,
		state))
	{
		worked |= 1;
	}

	if (BinSort(
		group,
		1,
		levDiff,
		group_b,
		binSize,
		uvFrame,
		state))
	{
		worked |= 2;
	}

	if(worked != 3)
	{
		if(worked == 0)
			return false;

		ListElement *smallgroup = new ListElement;
		Register_Object(smallgroup);
		group->SetSize(1);

		Element *element;
		if(worked==1)
		{
			element = group->GetIndexedElement(0);
			element->DetachFromParent();
		}
		else
		{
			element = group->GetIndexedElement(1);
			element->DetachFromParent();
		}
		delete group;

		smallgroup->AttachIndexedChild(0, element);
		Check_Object(smallgroup);
		parent->AttachIndexedChild(index, smallgroup);
	}
	else
	{
		Check_Object(group);
		parent->AttachIndexedChild(index, group);
	}

	//
	//-----------------------------------------
	// Now set the bounding sphere of the group
	//-----------------------------------------
	//

	group->NeedNewBounds();
	group->SetVolumeCullMode();

	return true;
}

void
	HFSlim::OptimizeHField(int depth, int binSize)
{
	if(X*Z == 0)
	{
		return;
	}
	fprintf(stdout, "Going to optimize the terrain: MaxNr.Of Points: %d BinSize: %d\n", depth, binSize);

	tdtrilist.SetLength((X-1)*(Z-1));
	DirectMap<real> map(X,Z);

	int i, j;

	float maxY = 0;
	for(j=0;j<Z;j++)
		for(i=0;i<X;i++)
		{
			map.ref(i,j) = field[j*X + i];

			if(field[j*X + i] > maxY)
			{
				maxY = field[j*X + i];
			}
		}
	Offset = -maxY*dY;

	MASK = new ImportMask;
	MASK->width=X;
	MASK->height=Z;
	
	GreedySubdivision mesh(&map);
	point_limit=1024;

	if(depth==0)
		point_limit = GetFullPolyCount()/4;
	else
		point_limit = depth;

	triangleCounter = 0;
	int count=1;
	while(mesh.pointCount()<point_limit && mesh.maxError() > error_threshold)
	{
		mesh.greedyInsert();
	}
	fprintf(stdout, "The mesh was greedy.\n");

	mesh.overFaces(GetFacesCB, tdtrilist.GetData());
	for(i=0;i<triangleCounter;i++)
	{
		tdtrilist[i].v[0].y = Offset + static_cast<float>(dY*mesh.eval(
			(int)(tdtrilist[i].v[0].x),
			(int)(tdtrilist[i].v[0].z)
		));

		tdtrilist[i].v[1].y = Offset + static_cast<float>(dY*mesh.eval(
			(int)(tdtrilist[i].v[1].x),
			(int)(tdtrilist[i].v[1].z)
		));

		tdtrilist[i].v[2].y = Offset + static_cast<float>(dY*mesh.eval(
			(int)(tdtrilist[i].v[2].x),
			(int)(tdtrilist[i].v[2].z)
		));

		tdtrilist[i].v[0].x *= dX;
		tdtrilist[i].v[0].z *= dZ;

		tdtrilist[i].v[1].x *= dX;
		tdtrilist[i].v[1].z *= dZ;

		tdtrilist[i].v[2].x *= dX;
		tdtrilist[i].v[2].z *= dZ;

		tdtrilist[i].GetSurfaceAreaAndCentroid();
	}

	OptPolyCount = triangleCounter;

	if(triangleCounter == 0 || mesh.pointCount() == 0)
	{
		return;
	}

	toSortTriangles.SetLength(triangleCounter);

	for(i=0,j=0;i<triangleCounter;++i)
	{
		if(	!(tdtrilist[i].GetV0().x==0.0f && tdtrilist[i].GetV0().z==GetZinM()) &&
			!(tdtrilist[i].GetV1().x==0.0f && tdtrilist[i].GetV1().z==GetZinM()) &&
			!(tdtrilist[i].GetV2().x==0.0f && tdtrilist[i].GetV2().z==GetZinM())
		)
		{
			toSortTriangles[j++] = &tdtrilist[i];
		}
	}
	toSortTriangles.SetLength(j);

	fprintf(stdout, "optimized to %d triangles.\n", j);

	nrOfIZs = 1;
}

int brokenTriangles;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	AnalyzeThis(
		DynamicArrayOf<ToDrawTriangle*> &tempTrianglesPtr,
		Scalar x0,
		Scalar z0,
		Scalar x1,
		Scalar z1
	)
{
	int i, j, k, l, len = tempTrianglesPtr.GetLength();
	int divide = 2;
	Scalar x0n, z0n, x1n, z1n;

	fprintf(analyzeFile, "%d	", len);

	int in, out;

	for(i=0;i<divide;i++)
	{
		for(j=0;j<divide;j++)
		{
			x0n = x0 + j*(x1-x0)/divide;
			x1n = x0 + (j+1)*(x1-x0)/divide;
			z0n = z0 + i*(z1-z0)/divide;
			z1n = z0 + (i+1)*(z1-z0)/divide;

			in = 0;
			out = 0;
			for(k=0;k<len;k++)
			{
				l = 0;
				if(	tempTrianglesPtr[k]->GetV0().x >= x0n && tempTrianglesPtr[k]->GetV0().x <= x1n &&
					tempTrianglesPtr[k]->GetV0().z >= z0n && tempTrianglesPtr[k]->GetV0().z < z1n
					)
				{
					l++;
				}
				if(	tempTrianglesPtr[k]->GetV1().x >= x0n && tempTrianglesPtr[k]->GetV1().x <= x1n &&
					tempTrianglesPtr[k]->GetV1().z >= z0n && tempTrianglesPtr[k]->GetV1().z <= z1n
					)
				{
					l++;
				}
				if(	tempTrianglesPtr[k]->GetV2().x >= x0n && tempTrianglesPtr[k]->GetV2().x <= x1n &&
					tempTrianglesPtr[k]->GetV2().z >= z0n && tempTrianglesPtr[k]->GetV2().z <= z1n
					)
				{
					l++;
				}

				if(l==3)
				{
					in++;
				}
				else if(l>0)
				{
					if(	tempTrianglesPtr[k]->center.x >= x0n && tempTrianglesPtr[k]->center.x <= x1n &&
						tempTrianglesPtr[k]->center.z >= z0n && tempTrianglesPtr[k]->center.z <= z1n
						)
					{
						out++;
					}
				}
			}

			fprintf(analyzeFile, "%d	%d	", in, out);

		}
	}

	
	fprintf(analyzeFile, "\n");
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	HFSlim::BuildTile(
		ElementRenderer::ListElement *list,
		int index,
		int depth, int maxdepth,
		DynamicArrayOf<unsigned char>& onOffPoints,
		int maxNumOfTrianglesPerMesh,
		Scalar xGridOffset,
		Scalar zGridOffset,
		Scalar xGrid,
		Scalar zGrid,
		int tileX,
		int tileZ,
		const char *texRoot
	)
{
	char textureName[1024];
	int numOfTriangles = toSortTriangles.GetLength();

	DynamicArrayOf<ToDrawTriangle*> tempTrianglesPtr(numOfTriangles);
	DynamicArrayOf<ToDrawTriangle> tempTriangles(numOfTriangles);

	int i, j, inCount, newCount = 0, extraTriangles = 0;
	int k, l, m;

	Scalar x0, z0, x1, z1;

	x0 = xGridOffset + tileX*xGrid;
	z0 = zGridOffset + tileZ*zGrid;
	x1 = x0 + xGrid;
	z1 = z0 + zGrid;

	MLRState state;

/*
	state.SetBackFaceOn();
	state.SetDitherOff();
	state.SetTextureCorrectionOn();
	state.SetZBufferCompareOn();
	state.SetZBufferWriteOn();

	state.SetFilterMode(MLRState::BiLinearFilterMode);

	state.SetFogMode(MLRState::OverrideFogMode);
	state.SetFogData(
		0xff9090f0,
		0.0f,
		700.0f,
		1000.0f
	);
*/
	state.SetPriority(MLRState::DefaultPriority);

	sprintf(textureName, "%s_%1d_%02x%02x", 
		texRoot,
		depth,
		(1<<depth) - tileX/(1<<(maxdepth-depth)) - 1,
		(1<<depth) - tileZ/(1<<(maxdepth-depth)) - 1
	);

	MLRTexture *texture = MLRTexturePool::Instance->Add(textureName);
	texture->SetHint(gosHint_AGPMemory | gosHint_DontShrink);
	state.SetTextureHandle(texture->GetTextureHandle());
	state.SetTextureWrapMode(MLRState::TextureClamp);

	for(i=0,j=0;i<numOfTriangles;j+=3,++i)
	{
		Verify(i<numOfTriangles);
		Verify(j<3*numOfTriangles);
		if(onOffPoints[i] == 0)
		{
			inCount = 0;
			int clipIt[3] = {0, 0, 0}, orIt = 0, addIt = 0xf;

			if(toSortTriangles[i]->GetV0().x < x0)
			{
				clipIt[0] |= 1;
			}
			if(toSortTriangles[i]->GetV0().x > x1)
			{
				clipIt[0] |= 4;
			}
			if(toSortTriangles[i]->GetV0().z < z0)
			{
				clipIt[0] |= 8;
			}
			if(toSortTriangles[i]->GetV0().z > z1)
			{
				clipIt[0] |= 2;
			}

			orIt |= clipIt[0];
			addIt &= clipIt[0];

			if(toSortTriangles[i]->GetV1().x < x0)
			{
				clipIt[1] |= 1;
			}
			if(toSortTriangles[i]->GetV1().x > x1)
			{
				clipIt[1] |= 4;
			}
			if(toSortTriangles[i]->GetV1().z < z0)
			{
				clipIt[1] |= 8;
			}
			if(toSortTriangles[i]->GetV1().z > z1)
			{
				clipIt[1] |= 2;
			}

			orIt |= clipIt[1];
			addIt &= clipIt[1];

			if(toSortTriangles[i]->GetV2().x < x0)
			{
				clipIt[2] |= 1;
			}
			if(toSortTriangles[i]->GetV2().x > x1)
			{
				clipIt[2] |= 4;
			}
			if(toSortTriangles[i]->GetV2().z < z0)
			{
				clipIt[2] |= 8;
			}
			if(toSortTriangles[i]->GetV2().z > z1)
			{
				clipIt[2] |= 2;
			}

			orIt |= clipIt[2];
			addIt &= clipIt[2];

			if(orIt == 0)
			{
				onOffPoints[i] = 1;
				tempTrianglesPtr[newCount++] = toSortTriangles[i];
			}
			else if(addIt==0)
			{
				if(orIt==1 || orIt==2 || orIt==4 || orIt==8)
				{
					Point3D triangle[3], clipPoints[6];

					triangle[0].x = toSortTriangles[i]->GetV0().x;
					triangle[0].y = toSortTriangles[i]->GetV0().y;
					triangle[0].z = toSortTriangles[i]->GetV0().z;

					triangle[1].x = toSortTriangles[i]->GetV1().x;
					triangle[1].y = toSortTriangles[i]->GetV1().y;
					triangle[1].z = toSortTriangles[i]->GetV1().z;

					triangle[2].x = toSortTriangles[i]->GetV2().x;
					triangle[2].y = toSortTriangles[i]->GetV2().y;
					triangle[2].z = toSortTriangles[i]->GetV2().z;

					l = 0;
					for(k=0;k<3;k++)
					{
						int next = k+1>2?0:k+1;
						if(clipIt[k]==0)
						{
							clipPoints[l++] = triangle[k];
							if(clipIt[next]==0)
							{
								continue;
							}
						}
						else
						{
							if(clipIt[next]!=0)
							{
								continue;
							}
						}
						//
						//-----------------------------------------------------
						// Find the boundary conditions that match our clipping
						// plane
						//-----------------------------------------------------
						//
						int mask = 1;
						for (m=0; m<4; m++)
						{
							if((clipIt[k] | clipIt[next]) & mask)
							{
								switch(m)
								{
									case 0:
										clipPoints[l++].AddScaled(
											triangle[k],
											Vector3D(
												triangle[next].x - triangle[k].x,
												triangle[next].y - triangle[k].y,
												triangle[next].z - triangle[k].z
											),
											(x0-triangle[k].x)/(triangle[next].x-triangle[k].x)
										);
									break;
									case 1:
										clipPoints[l++].AddScaled(
											triangle[k],
											Vector3D(
												triangle[next].x - triangle[k].x,
												triangle[next].y - triangle[k].y,
												triangle[next].z - triangle[k].z
											),
											(z1-triangle[k].z)/(triangle[next].z-triangle[k].z)
										);
									break;
									case 2:
										clipPoints[l++].AddScaled(
											triangle[k],
											Vector3D(
												triangle[next].x - triangle[k].x,
												triangle[next].y - triangle[k].y,
												triangle[next].z - triangle[k].z
											),
											(x1-triangle[k].x)/(triangle[next].x-triangle[k].x)
										);
									break;
									case 3:
										clipPoints[l++].AddScaled(
											triangle[k],
											Vector3D(
												triangle[next].x - triangle[k].x,
												triangle[next].y - triangle[k].y,
												triangle[next].z - triangle[k].z
											),
											(z0-triangle[k].z)/(triangle[next].z-triangle[k].z)
										);
									break;
								}
								break;
							}
							mask <<= 1;
						}
					}
					Verify(l>2);
					for(m=1;m<l-1;m++)
					{
						tempTriangles[extraTriangles].SetPoint(0, clipPoints[0].x, clipPoints[0].y, clipPoints[0].z);
						tempTriangles[extraTriangles].SetPoint(1, clipPoints[m].x, clipPoints[m].y, clipPoints[m].z);
						tempTriangles[extraTriangles].SetPoint(2, clipPoints[m+1].x, clipPoints[m+1].y, clipPoints[m+1].z);

						tempTriangles[extraTriangles].GetSurfaceAreaAndCentroid();

						tempTrianglesPtr[newCount++] = &tempTriangles[extraTriangles];
						extraTriangles++;
					}
				}
				else
				{

					int key=0, len[2] = {0, 0};
					Point3D clipPoints[2][6];
					int clipMeToo[2][6];

					clipPoints[key][0].x = toSortTriangles[i]->GetV0().x;
					clipPoints[key][0].y = toSortTriangles[i]->GetV0().y;
					clipPoints[key][0].z = toSortTriangles[i]->GetV0().z;

					clipPoints[key][1].x = toSortTriangles[i]->GetV1().x;
					clipPoints[key][1].y = toSortTriangles[i]->GetV1().y;
					clipPoints[key][1].z = toSortTriangles[i]->GetV1().z;

					clipPoints[key][2].x = toSortTriangles[i]->GetV2().x;
					clipPoints[key][2].y = toSortTriangles[i]->GetV2().y;
					clipPoints[key][2].z = toSortTriangles[i]->GetV2().z;

					clipMeToo[key][0] = clipIt[0];
					clipMeToo[key][1] = clipIt[1];
					clipMeToo[key][2] = clipIt[2];
					
					len[key] = 3;

 					int next, mask = 1;
					for(m=0;m<4;++m)
					{
						if(orIt & mask)
						{
							for(k=0;k<len[key];k++)
							{
								next = (k+1) < len[key] ? k+1 : 0;
					
								if(!(clipMeToo[key][k] & mask))
								{
									clipPoints[!key][len[!key]++] = clipPoints[key][k];
									if(!(clipMeToo[key][next] & mask))
									{
										continue;
									}
								}
								else
								{
									if(clipMeToo[key][next] & mask)
									{
										continue;
									}
								}

								switch(m)
								{
									case 0:
										clipPoints[!key][len[!key]++].AddScaled(
											clipPoints[key][k],
											Vector3D(
												clipPoints[key][next].x - clipPoints[key][k].x,
												clipPoints[key][next].y - clipPoints[key][k].y,
												clipPoints[key][next].z - clipPoints[key][k].z
											),
											(x0-clipPoints[key][k].x)/(clipPoints[key][next].x-clipPoints[key][k].x)
										);
									break;
									case 1:
										clipPoints[!key][len[!key]++].AddScaled(
											clipPoints[key][k],
											Vector3D(
												clipPoints[key][next].x - clipPoints[key][k].x,
												clipPoints[key][next].y - clipPoints[key][k].y,
												clipPoints[key][next].z - clipPoints[key][k].z
											),
											(z1-clipPoints[key][k].z)/(clipPoints[key][next].z-clipPoints[key][k].z)
										);
									break;
									case 2:
										clipPoints[!key][len[!key]++].AddScaled(
											clipPoints[key][k],
											Vector3D(
												clipPoints[key][next].x - clipPoints[key][k].x,
												clipPoints[key][next].y - clipPoints[key][k].y,
												clipPoints[key][next].z - clipPoints[key][k].z
											),
											(x1-clipPoints[key][k].x)/(clipPoints[key][next].x-clipPoints[key][k].x)
										);
									break;
									case 3:
										clipPoints[!key][len[!key]++].AddScaled(
											clipPoints[key][k],
											Vector3D(
												clipPoints[key][next].x - clipPoints[key][k].x,
												clipPoints[key][next].y - clipPoints[key][k].y,
												clipPoints[key][next].z - clipPoints[key][k].z
											),
											(z0-clipPoints[key][k].z)/(clipPoints[key][next].z-clipPoints[key][k].z)
										);
									break;
								}
							}
							key = !key;
							for(k=0;k<len[key];k++)
							{
								clipMeToo[key][k] = 0;
								if(clipPoints[key][k].x < x0)
								{
									clipMeToo[key][k] |= 1;
								}
								if(clipPoints[key][k].x > x1)
								{
									clipMeToo[key][k] |= 4;
								}
								if(clipPoints[key][k].z < z0)
								{
									clipMeToo[key][k] |= 8;
								}
								if(clipPoints[key][k].z > z1)
								{
									clipMeToo[key][k] |= 2;
								}
							}
							len[!key] = 0;
						}
						mask <<= 1;
					}

					for(m=1;m<len[key]-1;m++)
					{
						tempTriangles[extraTriangles].SetPoint(0, 
							clipPoints[key][0].x, 
							clipPoints[key][0].y, 
							clipPoints[key][0].z
						);
						tempTriangles[extraTriangles].SetPoint(1, 
							clipPoints[key][m].x, 
							clipPoints[key][m].y, 
							clipPoints[key][m].z
						);
						tempTriangles[extraTriangles].SetPoint(2, 
							clipPoints[key][m+1].x, 
							clipPoints[key][m+1].y, 
							clipPoints[key][m+1].z
						);

						tempTriangles[extraTriangles].GetSurfaceAreaAndCentroid();

						if(tempTriangles[extraTriangles].area > SMALL)
						{
							tempTrianglesPtr[newCount++] = &tempTriangles[extraTriangles];
							extraTriangles++;
						}
					}
				}
			}
		}
	}
//	tempTriangles.SetLength(extraTriangles);
	tempTrianglesPtr.SetLength(newCount);

	if(newCount > 0)
	{
		AnalyzeThis(
			tempTrianglesPtr,
			x0,
			z0,
			x1,
			z1
			);

		int i = 1<<(maxdepth - depth);
		x0 = xGridOffset + (tileX/i)*(i*xGrid);
		z0 = zGridOffset + (tileZ/i)*(i*zGrid);
		x1 = x0 + i*xGrid;
		z1 = z0 + i*zGrid;

		Stuff::ExtentBox box;
		box.minX = x0;
		box.maxX = x1;
		box.minY = Offset;
		box.maxY = 0.0f;
		box.minZ = z0;
		box.maxZ = z1;

		if(!BinSort(
			list,
			index,
			maxdepth - depth,
			tempTrianglesPtr,
			maxNumOfTrianglesPerMesh,
			&box,
			&state
			)
		)
		{
		}
		else
		{
			brokenTriangles += newCount;
		}
	}
	else
	{
	}

	tempTrianglesPtr.SetLength(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	HFSlim::DescentToUnderMountain(
		int depth,
#ifdef GRID_TEST
		ElementRenderer::GridElement *group,
#else
		ElementRenderer::ListElement *group,
#endif
		DynamicArrayOf<unsigned char>& onOffPoints,
		int maxNumOfTrianglesPerMesh,
		Scalar x0,
		Scalar z0,
		Scalar x1,
		Scalar z1,
		int tileX,
		int tileZ,
		const char *texRoot
	)
{
	if((depth & 0xff) ==0)
	{
		char textureName[1024];
		int numOfTriangles = toSortTriangles.GetLength();

		DynamicArrayOf<ToDrawTriangle*> tempTrianglesPtr(numOfTriangles);
		DynamicArrayOf<ToDrawTriangle> tempTriangles(numOfTriangles);

		int i, j, inCount, newCount = 0, extraTriangles = 0;
		int k, l, m;

		MLRState state;

/*
		state.SetBackFaceOn();
		state.SetDitherOff();
		state.SetTextureCorrectionOn();
		state.SetZBufferCompareOn();
		state.SetZBufferWriteOn();

		state.SetFilterMode(MLRState::BiLinearFilterMode);

		state.SetFogMode(MLRState::OverrideFogMode);
		state.SetFogData(
			0xff9090f0,
			0.0f,
			700.0f,
			1000.0f
		);
*/
		state.SetPriority(MLRState::DefaultPriority);
		sprintf(textureName, "%s_%1d_%02x%02x", texRoot, (depth>>8)-depth&0xff, tileX, tileZ);
		MLRTexture *texture = MLRTexturePool::Instance->Add(textureName);
		state.SetTextureHandle(texture->GetTextureHandle());
		state.SetTextureWrapMode(MLRState::TextureClamp);

		for(i=0,j=0;i<numOfTriangles;j+=3,++i)
		{
			Verify(i<numOfTriangles);
			Verify(j<3*numOfTriangles);
			if(onOffPoints[i] == 0)
			{
				inCount = 0;
				int clipIt[3] = {0, 0, 0}, orIt = 0, addIt = 0xf;

				if(toSortTriangles[i]->GetV0().x < x0)
				{
					clipIt[0] |= 1;
				}
				if(toSortTriangles[i]->GetV0().x > x1)
				{
					clipIt[0] |= 4;
				}
				if(toSortTriangles[i]->GetV0().z < z0)
				{
					clipIt[0] |= 8;
				}
				if(toSortTriangles[i]->GetV0().z > z1)
				{
					clipIt[0] |= 2;
				}

				orIt |= clipIt[0];
				addIt &= clipIt[0];

				if(toSortTriangles[i]->GetV1().x < x0)
				{
					clipIt[1] |= 1;
				}
				if(toSortTriangles[i]->GetV1().x > x1)
				{
					clipIt[1] |= 4;
				}
				if(toSortTriangles[i]->GetV1().z < z0)
				{
					clipIt[1] |= 8;
				}
				if(toSortTriangles[i]->GetV1().z > z1)
				{
					clipIt[1] |= 2;
				}

				orIt |= clipIt[1];
				addIt &= clipIt[1];

				if(toSortTriangles[i]->GetV2().x < x0)
				{
					clipIt[2] |= 1;
				}
				if(toSortTriangles[i]->GetV2().x > x1)
				{
					clipIt[2] |= 4;
				}
				if(toSortTriangles[i]->GetV2().z < z0)
				{
					clipIt[2] |= 8;
				}
				if(toSortTriangles[i]->GetV2().z > z1)
				{
					clipIt[2] |= 2;
				}

				orIt |= clipIt[2];
				addIt &= clipIt[2];

				if(orIt == 0)
				{
					onOffPoints[i] = 1;
					tempTrianglesPtr[newCount++] = toSortTriangles[i];
				}
				else if(addIt==0)
				{
					if(orIt==1 || orIt==2 || orIt==4 || orIt==8)
					{
						Point3D triangle[3], clipPoints[6];

						triangle[0].x = toSortTriangles[i]->GetV0().x;
						triangle[0].y = toSortTriangles[i]->GetV0().y;
						triangle[0].z = toSortTriangles[i]->GetV0().z;

						triangle[1].x = toSortTriangles[i]->GetV1().x;
						triangle[1].y = toSortTriangles[i]->GetV1().y;
						triangle[1].z = toSortTriangles[i]->GetV1().z;

						triangle[2].x = toSortTriangles[i]->GetV2().x;
						triangle[2].y = toSortTriangles[i]->GetV2().y;
						triangle[2].z = toSortTriangles[i]->GetV2().z;

						l = 0;
						for(k=0;k<3;k++)
						{
							int next = k+1>2?0:k+1;
							if(clipIt[k]==0)
							{
								clipPoints[l++] = triangle[k];
								if(clipIt[next]==0)
								{
									continue;
								}
							}
							else
							{
								if(clipIt[next]!=0)
								{
									continue;
								}
							}
							//
							//-----------------------------------------------------
							// Find the boundary conditions that match our clipping
							// plane
							//-----------------------------------------------------
							//
							int mask = 1;
							for (m=0; m<4; m++)
							{
								if((clipIt[k] | clipIt[next]) & mask)
								{
									switch(m)
									{
										case 0:
											clipPoints[l++].AddScaled(
												triangle[k],
												Vector3D(
													triangle[next].x - triangle[k].x,
													triangle[next].y - triangle[k].y,
													triangle[next].z - triangle[k].z
												),
												(x0-triangle[k].x)/(triangle[next].x-triangle[k].x)
											);
										break;
										case 1:
											clipPoints[l++].AddScaled(
												triangle[k],
												Vector3D(
													triangle[next].x - triangle[k].x,
													triangle[next].y - triangle[k].y,
													triangle[next].z - triangle[k].z
												),
												(z1-triangle[k].z)/(triangle[next].z-triangle[k].z)
											);
										break;
										case 2:
											clipPoints[l++].AddScaled(
												triangle[k],
												Vector3D(
													triangle[next].x - triangle[k].x,
													triangle[next].y - triangle[k].y,
													triangle[next].z - triangle[k].z
												),
												(x1-triangle[k].x)/(triangle[next].x-triangle[k].x)
											);
										break;
										case 3:
											clipPoints[l++].AddScaled(
												triangle[k],
												Vector3D(
													triangle[next].x - triangle[k].x,
													triangle[next].y - triangle[k].y,
													triangle[next].z - triangle[k].z
												),
												(z0-triangle[k].z)/(triangle[next].z-triangle[k].z)
											);
										break;
									}
									break;
								}
								mask <<= 1;
							}
						}
						Verify(l>2);
						for(m=1;m<l-1;m++)
						{
							tempTriangles[extraTriangles].SetPoint(0, clipPoints[0].x, clipPoints[0].y, clipPoints[0].z);
							tempTriangles[extraTriangles].SetPoint(1, clipPoints[m].x, clipPoints[m].y, clipPoints[m].z);
							tempTriangles[extraTriangles].SetPoint(2, clipPoints[m+1].x, clipPoints[m+1].y, clipPoints[m+1].z);

							tempTriangles[extraTriangles].GetSurfaceAreaAndCentroid();

							tempTrianglesPtr[newCount++] = &tempTriangles[extraTriangles];
							extraTriangles++;
						}
					}
					else
					{

						int key=0, len[2] = {0, 0};
						Point3D clipPoints[2][6];
						int clipMeToo[2][6];

						clipPoints[key][0].x = toSortTriangles[i]->GetV0().x;
						clipPoints[key][0].y = toSortTriangles[i]->GetV0().y;
						clipPoints[key][0].z = toSortTriangles[i]->GetV0().z;

						clipPoints[key][1].x = toSortTriangles[i]->GetV1().x;
						clipPoints[key][1].y = toSortTriangles[i]->GetV1().y;
						clipPoints[key][1].z = toSortTriangles[i]->GetV1().z;

						clipPoints[key][2].x = toSortTriangles[i]->GetV2().x;
						clipPoints[key][2].y = toSortTriangles[i]->GetV2().y;
						clipPoints[key][2].z = toSortTriangles[i]->GetV2().z;

						clipMeToo[key][0] = clipIt[0];
						clipMeToo[key][1] = clipIt[1];
						clipMeToo[key][2] = clipIt[2];
						
						len[key] = 3;

 						int next, mask = 1;
						for(m=0;m<4;++m)
						{
							if(orIt & mask)
							{
								for(k=0;k<len[key];k++)
								{
									next = (k+1) < len[key] ? k+1 : 0;
						
									if(!(clipMeToo[key][k] & mask))
									{
										clipPoints[!key][len[!key]++] = clipPoints[key][k];
										if(!(clipMeToo[key][next] & mask))
										{
											continue;
										}
									}
									else
									{
										if(clipMeToo[key][next] & mask)
										{
											continue;
										}
									}

									switch(m)
									{
										case 0:
											clipPoints[!key][len[!key]++].AddScaled(
												clipPoints[key][k],
												Vector3D(
													clipPoints[key][next].x - clipPoints[key][k].x,
													clipPoints[key][next].y - clipPoints[key][k].y,
													clipPoints[key][next].z - clipPoints[key][k].z
												),
												(x0-clipPoints[key][k].x)/(clipPoints[key][next].x-clipPoints[key][k].x)
											);
										break;
										case 1:
											clipPoints[!key][len[!key]++].AddScaled(
												clipPoints[key][k],
												Vector3D(
													clipPoints[key][next].x - clipPoints[key][k].x,
													clipPoints[key][next].y - clipPoints[key][k].y,
													clipPoints[key][next].z - clipPoints[key][k].z
												),
												(z1-clipPoints[key][k].z)/(clipPoints[key][next].z-clipPoints[key][k].z)
											);
										break;
										case 2:
											clipPoints[!key][len[!key]++].AddScaled(
												clipPoints[key][k],
												Vector3D(
													clipPoints[key][next].x - clipPoints[key][k].x,
													clipPoints[key][next].y - clipPoints[key][k].y,
													clipPoints[key][next].z - clipPoints[key][k].z
												),
												(x1-clipPoints[key][k].x)/(clipPoints[key][next].x-clipPoints[key][k].x)
											);
										break;
										case 3:
											clipPoints[!key][len[!key]++].AddScaled(
												clipPoints[key][k],
												Vector3D(
													clipPoints[key][next].x - clipPoints[key][k].x,
													clipPoints[key][next].y - clipPoints[key][k].y,
													clipPoints[key][next].z - clipPoints[key][k].z
												),
												(z0-clipPoints[key][k].z)/(clipPoints[key][next].z-clipPoints[key][k].z)
											);
										break;
									}
								}
								key = !key;
								for(k=0;k<len[key];k++)
								{
									clipMeToo[key][k] = 0;
									if(clipPoints[key][k].x < x0)
									{
										clipMeToo[key][k] |= 1;
									}
									if(clipPoints[key][k].x > x1)
									{
										clipMeToo[key][k] |= 4;
									}
									if(clipPoints[key][k].z < z0)
									{
										clipMeToo[key][k] |= 8;
									}
									if(clipPoints[key][k].z > z1)
									{
										clipMeToo[key][k] |= 2;
									}
								}
								len[!key] = 0;
							}
							mask <<= 1;
						}
						for(m=1;m<len[key]-1;m++)
						{
							tempTriangles[extraTriangles].SetPoint(0, clipPoints[key][0].x, clipPoints[key][0].y, clipPoints[key][0].z);
							tempTriangles[extraTriangles].SetPoint(1, clipPoints[key][m].x, clipPoints[key][m].y, clipPoints[key][m].z);
							tempTriangles[extraTriangles].SetPoint(2, clipPoints[key][m+1].x, clipPoints[key][m+1].y, clipPoints[key][m+1].z);

							tempTriangles[extraTriangles].GetSurfaceAreaAndCentroid();

							tempTrianglesPtr[newCount++] = &tempTriangles[extraTriangles];
							extraTriangles++;
						}
					}
				}
			}
		}
	//	tempTriangles.SetLength(extraTriangles);
		tempTrianglesPtr.SetLength(newCount);

		if(newCount > 0)
		{
#ifdef GRID_TEST
			group->SetSize(1, 1);
#else
			group->SetSize(1);
#endif

			Stuff::ExtentBox box;
			box.minX = x0;
			box.maxX = x1;
			box.minY = Offset;
			box.maxY = 0.0f;
			box.minZ = z0;
			box.maxZ = z1;

			if(!BinSort(
				group,
				0,
				0,
				tempTrianglesPtr,
				maxNumOfTrianglesPerMesh,
				&box,
				&state
				)
			)
			{
#ifdef GRID_TEST
				group->SetSize(0, 0);
#else
				group->SetSize(0);
#endif
			}
			else
			{
				fprintf(analyzeFile, "%d\n", newCount);
				brokenTriangles += newCount;
			}
		}
		else
		{
#ifdef GRID_TEST
			group->SetSize(0, 0);
#else
			group->SetSize(0);
#endif
		}

		tempTrianglesPtr.SetLength(0);
	}
	else
	{
#ifdef GRID_TEST
		ElementRenderer::GridElement
#else
		ElementRenderer::ListElement
#endif
			*group00, *group01, *group10, *group11;


#ifdef GRID_TEST
		group->SetSize(2, 2);
		group00 = new ElementRenderer::GridElement(2, 2);
#else
		group->SetSize(4);
		group00 = new ElementRenderer::ListElement;
#endif
		Register_Object(group00);

#ifdef GRID_TEST
		group->AttachIndexedChild(0, 0, group00);
#else
		group->AttachIndexedChild(0, group00);
		group00->NeedNewBounds();
		group00->SetVolumeCullMode();
#endif


#ifdef GRID_TEST
		group01 = new ElementRenderer::GridElement(2, 2);
#else
		group01 = new ElementRenderer::ListElement;
#endif
		Register_Object(group01);

#ifdef GRID_TEST
		group->AttachIndexedChild(0, 1, group01);
#else
		group->AttachIndexedChild(1, group01);
		group01->NeedNewBounds();
		group01->SetVolumeCullMode();
#endif


#ifdef GRID_TEST
		group10 = new ElementRenderer::GridElement(2, 2);
#else
		group10 = new ElementRenderer::ListElement;
#endif
		Register_Object(group10);

#ifdef GRID_TEST
		group->AttachIndexedChild(1, 0, group10);
#else
		group->AttachIndexedChild(2, group10);
		group10->NeedNewBounds();
		group10->SetVolumeCullMode();
#endif


#ifdef GRID_TEST
		group11 = new ElementRenderer::GridElement(2, 2);
#else
		group11 = new ElementRenderer::ListElement;
#endif
		Register_Object(group11);

#ifdef GRID_TEST
		group->AttachIndexedChild(1, 1, group11);
#else
		group->AttachIndexedChild(3, group11);
		group11->NeedNewBounds();
		group11->SetVolumeCullMode();
#endif


		DescentToUnderMountain(
			depth-1, group00, onOffPoints, maxNumOfTrianglesPerMesh,
			x0, z0, x0 + (x1-x0)/2.0f, z0 + (z1-z0)/2.0f, 2*tileX, 2*tileZ, texRoot
		);
		DescentToUnderMountain(
			depth-1, group01, onOffPoints, maxNumOfTrianglesPerMesh,
			x0 + (x1-x0)/2.0f, z0, x1, z0 + (z1-z0)/2.0f, 2*tileX+1, 2*tileZ, texRoot
		);
		DescentToUnderMountain(
			depth-1, group10, onOffPoints, maxNumOfTrianglesPerMesh,
			x0, z0 + (z1-z0)/2.0f, x0 + (x1-x0)/2.0f, z1, 2*tileX, 2*tileZ+1, texRoot
		);
		DescentToUnderMountain(
			depth-1, group11, onOffPoints, maxNumOfTrianglesPerMesh,
			x0 + (x1-x0)/2.0f, z0 + (z1-z0)/2.0f, x1, z1, 2*tileX+1, 2*tileZ+1, texRoot
		);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	HFSlim::SetMegaTexture(
		int nrOfLevels,
		int bucketSize,
		const char *mega_base_name,
		DetailLevelInfo *dli
	)
{
	if(detailInfo.name)
	{
/*
		state1.SetBackFaceOn();
		state1.SetDitherOff();
		state1.SetTextureCorrectionOn();
		state1.SetZBufferCompareOn();
		state1.SetZBufferWriteOff();


		state1.SetFilterMode(MLRState::BiLinearFilterMode);

		state1.SetFogMode(MLRState::DisableFogMode);
		state1.SetFogData(
			0xff000000,
			0.0f,
			90.0f,
			100.0f
		);
*/
		state1.SetAlphaMode(detailInfo.alphaMode);
		state1.SetPriority(MLRState::DefaultPriority + 1);
		MLRTexture *texture = MLRTexturePool::Instance->Add(detailInfo.name);
		state1.SetTextureHandle(texture->GetTextureHandle());
	}

	DynamicArrayOf<unsigned char> onOffTriangles;
	onOffTriangles.AssignValue(0, toSortTriangles.GetLength());

	if(simpleTerrainGroup)
	{
		Unregister_Object(simpleTerrainGroup);
		delete simpleTerrainGroup;
		simpleTerrainGroup = NULL;
	}

	Stuff::ExtentBox allBox;
	allBox = GetExtents();

	brokenTriangles = 0;

#ifdef GRID_TEST
	int len = 1<<nrOfLevels;
	simpleTerrainGroup = new GridElement(len, len);

	Register_Object(simpleTerrainGroup);
	simpleTerrainGroup->SetName("OptimizedTerrain");

	int i, j, level;
	xGridSize = (allBox.maxX-allBox.minX)/len;
	zGridSize = (allBox.maxZ-allBox.minZ)/len;

	for(j=0;j<len;j++)
	{
		for(i=0;i<len;i++)
		{
			if(dli->width*dli->height==0)
			{
				level = nrOfLevels;
			}
			else
			{
				Verify(dli->width == len);
				Verify(dli->height == len);
				switch(dli->array[(dli->height-j-1)*len+dli->width-i-1])
				{
					case 0:
						level = nrOfLevels-2;
					break;
					case 1:
						level = nrOfLevels-1;
					break;
					case 2:
						level = nrOfLevels;
					break;
					default:
						level = nrOfLevels;
				}
			}

			tX = i;
			tY = j;

			BuildTile(
				simpleTerrainGroup,
				j*len+i,
				level,
				nrOfLevels,
				onOffTriangles,
				bucketSize,
				allBox.minX,
				allBox.minZ,
				(allBox.maxX-allBox.minX)/len,
				(allBox.maxZ-allBox.minZ)/len,
				i,
				j,
				mega_base_name
			);
		}
	}

	simpleTerrainGroup->LockBounds();

	simpleTerrainGroup->SetDimensions(0.0f, 0.0f, GetXinM(), GetZinM());
#else
	simpleTerrainGroup = new ListElement;

	Register_Object(simpleTerrainGroup);
	simpleTerrainGroup->SetName("OptimizedTerrain");

	DescentToUnderMountain
	(
		(nrOfLevels<<8) + nrOfLevels,
		simpleTerrainGroup,
		bucketSize,
		allBox.minZ,
		allBox.maxX,
		allBox.maxZ,
		0,
		0,
	);

	simpleTerrainGroup->NeedNewBounds();
#endif

	fprintf(stdout, "\"broken\" triangles: %d\n", brokenTriangles);

	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	HFSlim::GetHeight(int x, int z)
{
	if (x<0) x = 0;
	if (x>GetX()-1)
	{
		x = GetX() - 1;
	}
	if (z<0) z = 0;
	if (z>GetZ()-1)
	{
		z = GetZ() - 1;
	}
	return field[z*X + x]*dY + Offset;
}

void HFSlim::DisplayFull(bool bl)
{
	ElementRenderer::Element *element;

	if(bl==true)
	{
		element=terrainGroup->FindElement(fullTerrainGroup->GetName());
		if(element==NULL)
			terrainGroup->AttachChild(fullTerrainGroup);
	}
	else
	{
		element=terrainGroup->FindElement(fullTerrainGroup->GetName());
		if(element!=NULL)
			terrainGroup->DetachChild(fullTerrainGroup);
	}
}

void HFSlim::DisplayOpt(bool bl) 
{
	ElementRenderer::Element *element;
	
	if(bl==true)
	{
		element=terrainGroup->FindElement(simpleTerrainGroup->GetName());
		if(element==NULL)
			terrainGroup->AttachChild(simpleTerrainGroup);
	}
	else
	{
		element=terrainGroup->FindElement(simpleTerrainGroup->GetName());
		if(element!=NULL)
			terrainGroup->DetachChild(simpleTerrainGroup);
	}
}

void ToDrawTriangle::SetPoint(int idx,float x,float y,float z)
{
	Verify(idx>=0 && idx<3);

	v[idx].x=x;
	v[idx].y=y;
	v[idx].z=z;
}


