#include <MLR\MLRHeaders.hpp>

#if !defined(HFSLIMMZL_HPP)
	#include "HFslimMZL.hpp"
	#include "terra\terra.hpp"
#endif

const Scalar One_Over_Three = 1.0f/3.0f;
bool doWater = false;
Scalar waterLevel;
bool doBSP = false;

extern FILE *analyzeFile;

DynamicArrayOf<PointStruct> uniquePoints;
int usedUniquePoints;

PointStruct *TriangleStruct::points = NULL;

DynamicArrayOf<TriangleStruct> triangleIndices;
int usedTriangleIndices;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	FindPoint(Point3D& point)
{
	for(int i=0;i<usedUniquePoints;i++)
	{
		if(Close_Enough(uniquePoints[i].p.x, point.x) && Close_Enough(uniquePoints[i].p.z, point.z))
		{
			return i;
		}
	}
	
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	FindAddPoint(Point3D& point)
{
	for(int i=0;i<usedUniquePoints;i++)
	{
		if(Close_Enough(uniquePoints[i].p, point))
		{
			return i;
		}
	}
	
	if(i==usedUniquePoints)
	{
		uniquePoints[i].p = point;
		usedUniquePoints++;
	}

	return i;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
float
	GetAngle(Point3D& p0, Point3D& p1, Point3D& p2)
{
	Vector3D v1, v2;

	v1.Subtract(p0, p1);
	v2.Subtract(p2, p1);

	v1.y = 0.0f;
	v2.y = 0.0f;

	Scalar l1 = v1.GetLength(), l2 = v2.GetLength();

	if(Small_Enough(l1*l2))
	{
		return 0.0f;
	}

	l1 = (v1*v2)/(l1*l2);

	l1 = l1 > 1.0f ? 1.0f : l1;
	l1 = l1 < -1.0f ? -1.0f : l1;

	return static_cast<float> (acos(l1));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	PointStruct::Add (TriangleStruct *ts, int pos)
{
	Verify(Close_Enough(p.x, ts->GetPoint(pos).x) && Close_Enough(p.z, ts->GetPoint(pos).z));
	myTri[connectedTo].tri = ts;

	myTri[connectedTo].pos = pos;
	myTri[connectedTo].angle = GetAngle(ts->GetPoint((pos-1+3)%3), ts->GetPoint(pos), ts->GetPoint((pos+1)%3));

	Verify(myTri[connectedTo].angle<=Pi);
	
	if(++connectedTo==myTri.GetLength())
	{
		myTri.SetLength(myTri.GetLength()+12);
	}

	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	PointStruct::CalculateAngles()
{
	Check_Object(this);

	for(int i=0;i<connectedTo;i++)
	{
		myTri[i].angle = GetAngle(
			myTri[i].tri->GetPoint((myTri[i].pos-1+3)%3),
			myTri[i].tri->GetPoint(myTri[i].pos),
			myTri[i].tri->GetPoint((myTri[i].pos+1)%3)
		);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	PointStruct::CheckAngles(Scalar xMax, Scalar zMax)
{
	Check_Object(this);

	if(connectedTo==0)
	{
		return true;
	}

	float angle = 0.0f;
	for(int j=0;j<connectedTo;j++)
	{
		angle += myTri[j].angle;
	}

	if( (p.x == 0.0f || p.x==xMax) && 
		(p.z == 0.0f || p.z==zMax) )
	{
		if(!Close_Enough(angle, Pi*0.5f, 0.005f))
		{
			return false;
		}
	}
	else
	if( (p.x == 0.0f || p.x==xMax) ||
		(p.z == 0.0f || p.z==zMax) )
	{
		if(!Close_Enough(angle, Pi, 0.005f))
		{
			return false;
		}
	}
	else 
	{
		if(!Close_Enough(angle, 2.0f*Pi, 0.005f))
		{
			return false;
		}
	}
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	PointStruct::TestInstance() const
{
	for(int i=0;i<connectedTo;i++)
	{
		Verify(myTri[i].tri->v[0] < usedUniquePoints);
		Verify(myTri[i].tri->v[1] < usedUniquePoints);
		Verify(myTri[i].tri->v[2] < usedUniquePoints);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TriangleStruct::GetSurfaceAreaAndCentroid()
{
	Verify(points!=NULL);
	//
	//---------------------
	// 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;
	plane.normal.x = 0.0f;
	plane.normal.y = 0.0f;
	plane.normal.z = 1.0f;
	plane.offset = 0.0f;


	if(v[0]==v[1] || v[0]==v[2] || v[1]==v[2])
	{
		return;
	}

	//
	//-----------------------------------------------
	// Generate all the information on the first pass
	//-----------------------------------------------
	//
	position_a = GetPoint(0);
	position_b = GetPoint(1);
	position_c = GetPoint(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)
	{
		plane.BuildPlane(
			GetPoint(0),
			GetPoint(1),
			GetPoint(2)
		);

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*
bool
	ToDrawTriangle::IntersectTriangle(ToDrawTriangle *tri)
{
	Scalar x, dx, dz, dx1, dz1;
	int i, i1, j, j1;

	for(i=0;i<3;i++)
	{
		i1 = i<2 ? i+1 : 0;

		dx1 = v[i1].x - v[i].x;
		dz1 = v[i1].z - v[i].z;

		for(j=0;j<3;j++)
		{
			j1 = j<2 ? j+1 : 0;

			dx = tri->v[j1].x - tri->v[j].x;
			dz = tri->v[j1].z - tri->v[j].z;

			if(dx<=SMALL && dx1<=SMALL)
			{
				continue;
			}

			if(dz<=SMALL && dz1<=SMALL)
			{
				continue;
			}

			if( Close_Enough(dz*dx1, dz1*dx) )
			{
				continue;
			}

			x = (dx*dx1*v[i].z - tri->v[j].z*dx*dx1 - dz1*dx*v[i].x + dz*dx1*tri->v[j].x) / (dz*dx1 - dz1*dx);

			if(v[i].x < v[i1].x)
			{
				if(tri->v[j].x < tri->v[j1].x)
				{
					if(x>v[i].x && x<v[i1].x && x>tri->v[j].x && x<tri->v[j1].x)
					{
						return true;
					}
				}
				else
				{
					if(x>v[i].x && x<v[i1].x && x>tri->v[j1].x && x<tri->v[j].x)
					{
						return true;
					}
				}
			}
			else
			{
				if(tri->v[j].x < tri->v[j1].x)
				{
					if(x>v[i1].x && x<v[i].x && x>tri->v[j].x && x<tri->v[j1].x)
					{
						return true;
					}
				}
				else
				{
					if(x>v[i1].x && x<v[i].x && x>tri->v[j1].x && x<tri->v[j].x)
					{
						return true;
					}
				}

			}
		}
	}

	return false;
}
*/

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
HFSlimMZL::HFSlimMZL()
{
	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;

	simpleTerrainGroup.SetLength(0);

	borderPixelFun = 0.0f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
HFSlimMZL::~HFSlimMZL()
{
	for(int i=0;i<simpleTerrainGroup.GetLength();i++)
	{
		Unregister_Object(simpleTerrainGroup[i]);
		delete simpleTerrainGroup[i];
		simpleTerrainGroup[i] = NULL;
	}
	simpleTerrainGroup.SetLength(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Stuff::ExtentBox
	HFSlimMZL::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
	HFSlimMZL::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
	HFSlimMZL::FillFromImage(Image &image, int smooth)
{
	X = image.GetWidth()+1;
	Z = image.GetHeight()+1;

	Verify(((X-1)%256 == 0) && ((Z-1)%256 == 0));
	xDim = image.GetWidth()/256;
	zDim = image.GetHeight()/256;
	
	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);
}

Scalar xGridSize, zGridSize;
int tX, tY;

#ifdef TCTD2
extern void TCTDrawLine(int x1,int y1,int x2,int y2,COLORREF ref=0xffffff);
extern void TCTClearLines();
#endif

void GetFacesCB(Triangle &tri, void*)
{
	Point3D point;

	point.x = (float)tri.point1()[0];
	point.y = 0.0f;
	point.z = (float)tri.point1()[1];

	triangleIndices[usedTriangleIndices].v[0] = FindAddPoint(point);

	point.x = (float)tri.point2()[0];
	point.y = 0.0f;
	point.z = (float)tri.point2()[1];

	triangleIndices[usedTriangleIndices].v[2] = FindAddPoint(point);

	point.x = (float)tri.point3()[0];
	point.y = 0.0f;
	point.z = (float)tri.point3()[1];

	triangleIndices[usedTriangleIndices].v[1] = FindAddPoint(point);

	usedTriangleIndices++;

#ifdef TCTD2
//	TCTDrawLine((int)tri.point1()[0], (int)tri.point1()[1], (int)tri.point2()[0], (int)tri.point2()[1], 0xf7777f);
//	TCTDrawLine((int)tri.point2()[0], (int)tri.point2()[1], (int)tri.point3()[0], (int)tri.point3()[1], 0xf7777f);
//	TCTDrawLine((int)tri.point3()[0], (int)tri.point3()[1], (int)tri.point1()[0], (int)tri.point1()[1], 0xf7777f);
#endif
}

#ifdef TCTD2

void GetEdgesCB(Edge *edge, void *ptr)
{
	Vec2 from = edge->Org();
	Vec2 to = edge->Dest();

	int x1 = (int)from[0] & ~31;
	int z1 = (int)from[1] & ~31;

	int x2 = (int)to[0] & ~31;
	int z2 = (int)to[1] & ~31;

	int color = 0xffffff;

	if(x1!=x2)
	{
		if(abs(x1-x2) > 32)
		{
			color &= 0xff00ff;
		}
		else
		{
			if(x1<x2)
			{
				if((int)to[0] & 31)
				{
					color &= 0xff00ff;
				}
			}
			else
			{
				if((int)from[0] & 31)
				{
					color &= 0xff00ff;
				}
			}
		}
	}
	if(z1!=z2)
	{
		if(abs(z1-z2) > 32)
		{
			color &= 0xffff00;
		}
		else
		{
			if(z1<z2)
			{
				if((int)to[1] & 31)
				{
					color &= 0xffff00;
				}
			}
			else
			{
				if((int)from[1] & 31)
				{
					color &= 0xffff00;
				}
			}
		}
	}
//	TCTDrawLine((int)from[0], (int)from[1], (int)to[0], (int)to[1], color);
}
#endif

#define TERRAIN2_TEST

bool
	HFSlimMZL::CreateMesh(
		ListElement *parent,
		int listIndex,
		int diffLevel,
		DynamicArrayOf<TriangleStruct*>& tdtrilist,
		frameArray *frame,
		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
	gos_PushCurrentHeap(MidLevelRenderer::Heap);
	MLR_Terrain2 *erf_mesh = new MLR_Terrain2;
	gos_PopCurrentHeap();

	erf_mesh->SetDepthData(maxAllDepth-diffLevel, maxAllDepth);
	erf_mesh->SetTileData(tX, tY);
	erf_mesh->SetBorderPixel(borderPixelFun);

//	erf_mesh->SetOffset(xOffset, zOffset);
//	erf_mesh->SetGrid(xGridSize, zGridSize);
	for(int ii=0;ii<8;ii++)
	{
		erf_mesh->SetFrame(ii, (*frame)[ii][0], (*frame)[ii][1], (*frame)[ii][2],  (*frame)[ii][3]);
	}
#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, k, l, 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]->GetPoint(0).x,
		maxx=tdtrilist[0]->GetPoint(0).x,
		miny=tdtrilist[0]->GetPoint(0).y,
		maxy=tdtrilist[0]->GetPoint(0).y,
		minz=tdtrilist[0]->GetPoint(0).z,
		maxz=tdtrilist[0]->GetPoint(0).z;

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

	int *uPoints = new int [3*polygon_count];
	point_count = 0;
	for(i=0;i<polygon_count;++i)
	{
		center3 += tdtrilist[i]->center;

		for(k=0;k<3;k++)
		{
			for(l=0;l<point_count;l++)
			{
				if(uPoints[l]==tdtrilist[i]->v[k])
				{
					break;
				}
			}
			if(l==point_count)
			{
				uPoints[point_count++] = tdtrilist[i]->v[k];
				center0 += tdtrilist[i]->GetPoint(k);
			}

			if(tdtrilist[i]->GetPoint(k).x < minx)
			{
				minX[0] = i;
				minX[1] = k;
				minx = tdtrilist[i]->GetPoint(k).x;
			} else if(tdtrilist[i]->GetPoint(k).x > maxx)
			{
				maxX[0] = i;
				maxX[1] = k;
				maxx = tdtrilist[i]->GetPoint(k).x;
			}
			if(tdtrilist[i]->GetPoint(k).y < miny)
			{
				minY[0] = i;
				minY[1] = k;
				miny = tdtrilist[i]->GetPoint(k).y;
			} else if(tdtrilist[i]->GetPoint(k).y > maxy)
			{
				maxY[0] = i;
				maxY[1] = k;
				maxy = tdtrilist[i]->GetPoint(k).y;
			}
			if(tdtrilist[i]->GetPoint(k).z < minz)
			{
				minZ[0] = i;
				minZ[1] = k;
				minz = tdtrilist[i]->GetPoint(k).z;
			} else if(tdtrilist[i]->GetPoint(k).z > maxz)
			{
				maxZ[0] = i;
				maxZ[1] = k;
				maxz = tdtrilist[i]->GetPoint(k).z;
			}
		}
	}

	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]]->GetPoint(maxX[1]), tdtrilist[minX[0]]->GetPoint(minX[1]));
	d[0] = v3.GetLengthSquared();

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

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

	if(d[0] > d[1])
	{
		if(d[0]>d[2])
		{
			center1 = tdtrilist[maxX[0]]->GetPoint(maxX[1]);
			center1 += tdtrilist[minX[0]]->GetPoint(minX[1]);
			center1 *= 0.5f;
		}
		else
		{
			center1 = tdtrilist[maxZ[0]]->GetPoint(maxZ[1]);
			center1 += tdtrilist[minZ[0]]->GetPoint(minZ[1]);
			center1 *= 0.5f;
		}
	}
	else
	{
		if(d[1]>d[2])
		{
			center1 = tdtrilist[maxY[0]]->GetPoint(maxY[1]);
			center1 += tdtrilist[minY[0]]->GetPoint(minY[1]);
			center1 *= 0.5f;
		}
		else
		{
			center1 = tdtrilist[maxZ[0]]->GetPoint(maxZ[1]);
			center1 += tdtrilist[minZ[0]]->GetPoint(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);

	Scalar radiusSquared, 
		maxRadius0 = 0.0f,
		maxRadius1 = 0.0f,
		maxRadius2 = 0.0f,
		maxRadius3 = 0.0f;


	for(i=0;i<point_count;i++)
	{
		coords[i] = uniquePoints[uPoints[i]].p;

		Verify(coords[i].x<=(*frame)[maxAllDepth][2] && coords[i].x>=(*frame)[maxAllDepth][0]);
		Verify(coords[i].z<=(*frame)[maxAllDepth][3] && coords[i].z>=(*frame)[maxAllDepth][1]);

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

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

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

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

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

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

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

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

#ifndef TERRAIN2_TEST
		texCoords[i][0] = 
			borderPixelFun + (1.0f-2*borderPixelFun)*(((*frame)[maxAllDepth][2] - coords[i].x)/((*frame)[maxAllDepth][2]-uvFrame->minX));
		texCoords[i][1] =
			borderPixelFun + (1.0f-2*borderPixelFun)*(((*frame)[maxAllDepth][3] - coords[i].z)/((*frame)[maxAllDepth][3]-uvFrame->minZ));
#endif
	}

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

	for(i=0,l=0;i<polygon_count;i++)
	{
		for(k=0;k<3;k++)
		{
			for(j=0;j<point_count;j++)
			{
				if(uPoints[j]==tdtrilist[i]->v[k])
				{
					break;
				}
			}
			Verify(j<point_count);
			
			index[l++] = j;
		}

		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) || vCross.y<=SMALL)
		{
			l-=3;
			continue;
		}
	}

	gos_PushCurrentHeap(MidLevelRenderer::Heap);
	erf_mesh->SetSubprimitiveLengths(NULL, l/3);

	erf_mesh->SetCoordData(coords, point_count);

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

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

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

	erf_mesh->FindFacePlanes();

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

	erf_mesh->SetReferenceState(state1, 1);

	gos_PushCurrentHeap(ElementRenderer::Heap);
	ShapeElement* Shape = new ShapeElement;
	Register_Object(Shape);
	gos_PopCurrentHeap();

	gos_PushCurrentHeap(MidLevelRenderer::Heap);
	MLRShape *shape = new MLRShape(1);
	Register_Object(shape);
	gos_PopCurrentHeap();

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

	Shape->SetMLRShape(shape);

	Shape->m_localOBB.localToParent = LinearMatrix4D::Identity;
	Shape->m_localOBB.localToParent.BuildTranslation(center);
	
	Shape->m_localOBB.sphereRadius = maxRadius;
	parent->AttachIndexedChild(listIndex, Shape);
	Shape->SetVolumeCullMode();

	return true;
}

int
	primCounter, maxPrims;

bool
	HFSlimMZL::BinSort(
		ListElement *parent,
		int index,
		int levDiff,
		DynamicArrayOf<TriangleStruct*>& tdtrilist,
		int binSize,
		frameArray *frame,
		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, frame, 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, frame, 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);
	gos_PushCurrentHeap(ElementRenderer::Heap);
	ListElement *group = new ListElement;
	Register_Object(group);
	group->SetSize(2);
	gos_PopCurrentHeap();
	//
	//----------------------------------
	// 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<TriangleStruct*>
		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.GetDistanceTo(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,
		frame,
		state))
	{
		worked |= 1;
	}

	if (BinSort(
		group,
		1,
		levDiff,
		group_b,
		binSize,
		frame,
		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
	HFSlimMZL::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);

	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];
		}
	Offset = 0.0;

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

	point_limit = depth;

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

	mesh.overEdges(GetEdgesCB, NULL);

	usedUniquePoints = 0;
	usedTriangleIndices = 0;

	uniquePoints.SetLength(2*mesh.pointCount());
	TriangleStruct::points = uniquePoints.GetData();

	triangleIndices.SetLength(8*mesh.pointCount());
	
	mesh.overFaces(GetFacesCB, NULL);

	for(i=0;i<usedUniquePoints;i++)
	{
		uniquePoints[i].p.y = Offset + static_cast<float>(dY*mesh.eval(
			(int)(uniquePoints[i].p.x),
			(int)(uniquePoints[i].p.z)
		));

		uniquePoints[i].p.x *= dX;
		uniquePoints[i].p.z *= dZ;
	}

	for(i=0;i<usedTriangleIndices;i++)
	{
		triangleIndices[i].GetSurfaceAreaAndCentroid();
	}

	OptPolyCount = usedTriangleIndices;

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

	for(i=0;i<usedTriangleIndices;++i)
	{
		if(! (triangleIndices[i].plane.normal.y > Stuff::SMALL) )
		{
			STOP(("Found a triangle with a face normal not pointing up, consider revising map !"));
		}
	}

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

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
	HFSlimMZL::CreateTile(
		ElementRenderer::ListElement *list,
		DynamicArrayOf<TriangleStruct*>& tempTrianglesPtr,
		int index,
		int depth, int maxdepth,
		int maxNumOfTrianglesPerMesh,
		Scalar xGridOffset,
		Scalar zGridOffset,
		Scalar xGrid,
		Scalar zGrid,
		int tileX,
		int tileZ,
		const char *texRoot
	)
{
	char textureName[1024];
	Scalar x0, z0, x1, z1;

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

	MLRState state;

	gos_PushCurrentHeap(ElementRenderer::Heap);
	ElementRenderer::GroupElement *group = new GroupElement;
	Register_Object(group);

	list->AttachIndexedChild(index, group);
	group->NeedNewBounds();
	group->SetVolumeCullMode();
	ElementRenderer::ListElement *subList = new ListElement;
	Register_Object(subList);
	subList->SetSize(1);
	group->AttachChild(subList);
	subList->NeedNewBounds();
	subList->SetVolumeCullMode();
	gos_PopCurrentHeap();

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

#if 0
	sprintf(textureName, "%s_%1d_%02x%02x", 
		texRoot,
		depth,
		(1<<depth) - tileX/(1<<(maxdepth-depth)) - 1,
		(1<<depth) - tileZ/(1<<(maxdepth-depth)) - 1
	);
#else
	sprintf(textureName, "%s_0_0000", texRoot);
#endif

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

/*		AnalyzeThis(
		tempTrianglesPtr,
		x0,
		z0,
		x1,
		z1
		);
*/
	int i = 1<<(maxdepth - depth);


	Scalar frame[8][4];

	frame[0][0] = xOffset;
	frame[0][1] = zOffset;
	frame[0][2] = xOffset + 8*xGrid;
	frame[0][3] = zOffset + 8*zGrid;

	frame[1][0] = xOffset + 4*(tileX/4)*xGrid;
	frame[1][1] = zOffset + 4*(tileZ/4)*zGrid;
	frame[1][2] = frame[1][0] + 4*xGrid;
	frame[1][3] = frame[1][1] + 4*zGrid;

	for(i=2;i<8;i++)
	{
		frame[i][0] = xOffset + tileX*xGrid;
		frame[i][1] = zOffset + tileZ*zGrid;
		frame[i][2] = frame[i][0] + xGrid;
		frame[i][3] = frame[i][1] + zGrid;
	}

/*
	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 = Offset+256.0f;
	box.minZ = z0;
	box.maxZ = z1;
*/

	if(!BinSort(
		subList,
		0,
		maxdepth - depth,
		tempTrianglesPtr,
		maxNumOfTrianglesPerMesh,
		&frame,
		&state
		)
	)
	{
		PAUSE(("Unstable triangle mesh created!"));
	}

	group->Sync();
	group->LockBounds();
	group->SetNeverCullMode();
	subList->LockBounds();
	subList->SetNeverCullMode();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	HFSlimMZL::BuildTile(
		DynamicArrayOf<TriangleStruct*>& tempTrianglesPtr,
		int index,
		int depth, int maxdepth,
		int numOfTriangles,
		DynamicArrayOf<unsigned char>& onOffPoints,
		int maxNumOfTrianglesPerMesh,
		Scalar xGridOffset,
		Scalar zGridOffset,
		Scalar xGrid,
		Scalar zGrid,
		int tileX,
		int tileZ
	)
{
	int orgUsedTriangleIndices = usedTriangleIndices;
		
	int i, j, inCount, newCount = 0, extraTriangles = 0, refused = 0;
	int k, l, m;

	Scalar x0, z0, x1, z1;

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

	for(i=0,j=0;i<numOfTriangles;j+=3,++i)
	{
		Verify(i<numOfTriangles);
		Verify(j<3*numOfTriangles);

		m = 0;
		for(k=0;k<3;k++)
		{
			if(triangleIndices[i].v[k]==2)
			{
				m++;
			}
		}

		if(m==1 && (z0==800.0f || z1==800.0f))
		{
			m--;
		}

		if(onOffPoints[i] == 0)
		{
			inCount = 0;
			int clipIt[3] = {0, 0, 0}, orIt = 0, addIt = 0xf;

			for(k=0;k<3;k++)
			{
				if(triangleIndices[i].GetPoint(k).x < x0)
				{
					clipIt[k] |= 1;
				}
				if(triangleIndices[i].GetPoint(k).x > x1)
				{
					clipIt[k] |= 4;
				}
				if(triangleIndices[i].GetPoint(k).z < z0)
				{
					clipIt[k] |= 8;
				}
				if(triangleIndices[i].GetPoint(k).z > z1)
				{
					clipIt[k] |= 2;
				}

				orIt |= clipIt[k];
				addIt &= clipIt[k];
			}

			if(orIt == 0)
			{
				onOffPoints[i] = 1;

				tempTrianglesPtr[newCount++] = &triangleIndices[i];

				uniquePoints[triangleIndices[i].v[0]].Add(&triangleIndices[i], 0);
				uniquePoints[triangleIndices[i].v[1]].Add(&triangleIndices[i], 1);
				uniquePoints[triangleIndices[i].v[2]].Add(&triangleIndices[i], 2);
			}
			else if(addIt==0)
			{
				if(orIt==1 || orIt==2 || orIt==4 || orIt==8)
				{
					Point3D triangle[3], clipPoints[6];
					int index[6];
					float angles[6];

					for(k=0;k<3;k++)
					{
						triangle[k] = triangleIndices[i].GetPoint(k);
					}

					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)
										
										);
										clipPoints[l++].x = x0;
									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)
										);
										clipPoints[l++].z = z1;
									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)
										);
										clipPoints[l++].x = x1;
									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)
										);
										clipPoints[l++].z = z0;
									break;
								}
								break;
							}
							mask <<= 1;
						}
					}
					Verify(l>2);

					for(m=0;m<l;m++)
					{
						index[m] = FindAddPoint(clipPoints[m]);
					}

					int ba = -1;
					float a = -360.0f;
					for(m=0;m<l;m++)
					{
						angles[m] = GetAngle(
							uniquePoints[index[(m-1+l)%l]].p,
							uniquePoints[index[m]].p,
							uniquePoints[index[(m+l)%l]].p
						);

						if(angles[m] > a)
						{
							a = angles[m];
							ba = m;
						}
					}

					for(m=1;m<l-1;m++)
					{
						triangleIndices[usedTriangleIndices].v[0] = index[ba];
						triangleIndices[usedTriangleIndices].v[1] = index[(ba+m)%l];
						triangleIndices[usedTriangleIndices].v[2] = index[(ba+m+1)%l];

						if(	(triangleIndices[usedTriangleIndices].v[0] != triangleIndices[usedTriangleIndices].v[1]) && 
							(triangleIndices[usedTriangleIndices].v[0] != triangleIndices[usedTriangleIndices].v[2]) &&
							(triangleIndices[usedTriangleIndices].v[1] != triangleIndices[usedTriangleIndices].v[2]) )
						{
							uniquePoints[triangleIndices[usedTriangleIndices].v[0]].Add(&triangleIndices[usedTriangleIndices], 0);
							uniquePoints[triangleIndices[usedTriangleIndices].v[1]].Add(&triangleIndices[usedTriangleIndices], 1);
							uniquePoints[triangleIndices[usedTriangleIndices].v[2]].Add(&triangleIndices[usedTriangleIndices], 2);

							triangleIndices[usedTriangleIndices++].GetSurfaceAreaAndCentroid();
						}
						else
						{
							refused++;
						}
					}
				}
				else
				{
					int key=0, len[2] = {0, 0};
					Point3D clipPoints[2][6];
					int index[6];
					float angles[6];
					int clipMeToo[2][6];

					for(k=0;k<3;k++)
					{
						clipPoints[key][k] = triangleIndices[i].GetPoint(k);
					}

					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)
										);
										clipPoints[!key][len[!key]++].x = x0;
									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)
										);
										clipPoints[!key][len[!key]++].z = z1;
									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)
										);
										clipPoints[!key][len[!key]++].x = x1;
									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)
										);
										clipPoints[!key][len[!key]++].z = z0;
									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=0;m<len[key];m++)
					{
						index[m] = FindAddPoint(clipPoints[key][m]);
					}

					int ba = -1;
					float a = -360.0f;
					for(m=0;m<len[key];m++)
					{
						angles[m] = GetAngle(
							uniquePoints[index[(m-1+len[key])%len[key]]].p,
							uniquePoints[index[m]].p,
							uniquePoints[index[(m+len[key])%len[key]]].p
						);

						if(angles[m] > a)
						{
							a = angles[m];
							ba = m;
						}
					}

					for(m=1;m<len[key]-1;m++)
					{
						triangleIndices[usedTriangleIndices].v[0] = index[ba];
						triangleIndices[usedTriangleIndices].v[1] = index[(ba+m)%len[key]];
						triangleIndices[usedTriangleIndices].v[2] = index[(ba+m+1)%len[key]];

						if(	(triangleIndices[usedTriangleIndices].v[0] != triangleIndices[usedTriangleIndices].v[1]) && 
							(triangleIndices[usedTriangleIndices].v[0] != triangleIndices[usedTriangleIndices].v[2]) &&
							(triangleIndices[usedTriangleIndices].v[1] != triangleIndices[usedTriangleIndices].v[2]) )
						{
							uniquePoints[triangleIndices[usedTriangleIndices].v[0]].Add(&triangleIndices[usedTriangleIndices], 0);
							uniquePoints[triangleIndices[usedTriangleIndices].v[1]].Add(&triangleIndices[usedTriangleIndices], 1);
							uniquePoints[triangleIndices[usedTriangleIndices].v[2]].Add(&triangleIndices[usedTriangleIndices], 2);

							triangleIndices[usedTriangleIndices++].GetSurfaceAreaAndCentroid();
						}
						else
						{
							refused++;
						}
					}
				}
			}
		}
	}

	for(i=orgUsedTriangleIndices;i<usedTriangleIndices;i++)
	{
		tempTrianglesPtr[newCount++] = &triangleIndices[i];
	}

	tempTrianglesPtr.SetLength(newCount);

#if _ARMOR
	for(i=0,j=0;i<newCount;++i)
	{
		if(onOffPoints[i] == 0)
		{
			Verify(tempTrianglesPtr[i]->GetPoint(0).x >= x0);
			Verify(tempTrianglesPtr[i]->GetPoint(0).x <= x1);
			Verify(tempTrianglesPtr[i]->GetPoint(0).z >= z0);
			Verify(tempTrianglesPtr[i]->GetPoint(0).z <= z1);

			Verify(tempTrianglesPtr[i]->GetPoint(1).x >= x0);
			Verify(tempTrianglesPtr[i]->GetPoint(1).x <= x1);
			Verify(tempTrianglesPtr[i]->GetPoint(1).z >= z0);
			Verify(tempTrianglesPtr[i]->GetPoint(1).z <= z1);

			Verify(tempTrianglesPtr[i]->GetPoint(2).x >= x0);
			Verify(tempTrianglesPtr[i]->GetPoint(2).x <= x1);
			Verify(tempTrianglesPtr[i]->GetPoint(2).z >= z0);
			Verify(tempTrianglesPtr[i]->GetPoint(2).z <= z1);
		}
	}
#endif

	brokenTriangles += newCount;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	HFSlimMZL::SetMegaTexture(
		int nrOfLevels,
		int bucketSize,
		const char *theName
	)
{
	static char megaBaseName[1024];


	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.SetTextureWrapMode(MLRState::TextureWrap);
		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, triangleIndices.GetLength());

	int i, j, k, l, level;
	simpleTerrainGroup.SetLength(xDim*zDim);

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

	int len = 1<<nrOfLevels;

	xGridSize = (allBox.maxX-allBox.minX)/(len*xDim);
	zGridSize = (allBox.maxZ-allBox.minZ)/(len*zDim);

	int numOfTriangles = usedTriangleIndices;

	DynamicArrayOf<TriangleStruct*> *tempTrianglesPtr;

	tempTrianglesPtr = new DynamicArrayOf<TriangleStruct*> [zDim*xDim*len*len];

	for(l=0;l<zDim;l++)
	{
		for(k=0;k<xDim;k++)
		{
			brokenTriangles = 0;

			xOffset = k*dX*256;
			zOffset = l*dZ*256;

			for(j=0;j<len;j++)
			{
				for(i=0;i<len;i++)
				{
					level = nrOfLevels;

					tX = i;
					tY = j;

					tempTrianglesPtr[(l*xDim+k)*len*len + j*len + i].SetLength(numOfTriangles);

					BuildTile(
						tempTrianglesPtr[(l*xDim+k)*len*len + j*len + i],
						j*len+i,
						level,
						nrOfLevels,
						numOfTriangles,
						onOffTriangles,
						bucketSize,
						xOffset,
						zOffset,
						xGridSize,
						zGridSize,
						i,
						j
					);
				}
			}

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


	int tJunction = 0;
	for(i=0;i<usedUniquePoints;i++)
	{
		if(uniquePoints[i].CheckAngles(GetXinM(), GetZinM())==false)
		{
			tJunction++;
		}
	}

	double one_over_dX = 1.0f/dX;
	double one_over_dZ = 1.0f/dZ;
	bool redo;

	tJunction = 0;
	for(i=0;i<usedUniquePoints;i++)
	{
		if(uniquePoints[i].connectedTo==0)
		{
			continue;
		}

		redo = false;

		Point3D point;

		point = uniquePoints[i].p;

		if(!Small_Enough(static_cast<Scalar>(fmod(uniquePoints[i].p.x, dX))))
		{
			point.x = static_cast<Scalar>(dX*floor(uniquePoints[i].p.x*one_over_dX + 0.5));
			redo = true;
		}
		if(!Small_Enough(static_cast<Scalar>(fmod(uniquePoints[i].p.z, dZ))))
		{
			point.z = static_cast<Scalar>(dZ*floor(uniquePoints[i].p.z*one_over_dZ + 0.5));
			redo = true;
		}

		if(redo==true)
		{
			point.y = GetHeight((int)(point.x/dX), (int)(point.z/dZ));

			int pi = FindPoint(point);

			uniquePoints[i].p = point;

			if(pi<0 || i==pi)
			{
				for(j=0;j<uniquePoints[i].connectedTo;j++)
				{
					uniquePoints[uniquePoints[i].myTri[j].tri->v[0]].CalculateAngles();
					uniquePoints[uniquePoints[i].myTri[j].tri->v[1]].CalculateAngles();
					uniquePoints[uniquePoints[i].myTri[j].tri->v[2]].CalculateAngles();
				}
				uniquePoints[i].CalculateAngles();
			}
			else
			{
				int from, to;

				if(i<pi)
				{
					from = pi;
					to = i;
				}
				else
				{
					from = i;
					to = pi;
				}

				for(j=0;j<uniquePoints[from].connectedTo;j++)
				{
					for(k=0;k<uniquePoints[to].connectedTo;k++)
					{
						if(uniquePoints[from].myTri[j].tri==uniquePoints[to].myTri[k].tri)
						{
							break;
						}
					}
					if(k==uniquePoints[to].connectedTo)
					{
						uniquePoints[to].Add(uniquePoints[from].myTri[j].tri, uniquePoints[from].myTri[j].pos);
					}
				}
				
				for(j=0;j<uniquePoints[to].connectedTo;j++)
				{
					for(k=0;k<3;k++)
					{
						if(uniquePoints[to].myTri[j].tri->v[k] == from)
						{
							uniquePoints[to].myTri[j].tri->v[k] = to;
						}
					}
				}

				for(k=0;k<uniquePoints[i].connectedTo;k++)
				{
					uniquePoints[uniquePoints[i].myTri[k].tri->v[0]].CalculateAngles();
					uniquePoints[uniquePoints[i].myTri[k].tri->v[1]].CalculateAngles();
					uniquePoints[uniquePoints[i].myTri[k].tri->v[2]].CalculateAngles();
				}

				uniquePoints[to].CalculateAngles();

				uniquePoints[from].connectedTo = 0;
			}
		}
	}

	tJunction = 0;
	for(i=0;i<usedUniquePoints;i++)
	{
		if(false == uniquePoints[i].CheckAngles(GetXinM(), GetZinM()))
		{
			tJunction++;
		}
	}

	Verify(tJunction==0);

	for(i=0;i<usedTriangleIndices;i++)
	{
		triangleIndices[i].GetSurfaceAreaAndCentroid();
	}

	int smallOne = 0, tiltedOne = 0;
	for(i=0;i<usedTriangleIndices;i++)
	{
		if(triangleIndices[i].area > Stuff::SMALL && triangleIndices[i].plane.normal.y < Stuff::SMALL)
		{
			int pmin, pmid, pmax;
			bool xCase = false, zCase = false;

			if(
				Close_Enough(triangleIndices[i].GetPoint(0).x, triangleIndices[i].GetPoint(1).x) &&
				Close_Enough(triangleIndices[i].GetPoint(0).x, triangleIndices[i].GetPoint(2).x)
			)
			{
				if(triangleIndices[i].GetPoint(0).z < triangleIndices[i].GetPoint(1).z)
				{
					if(triangleIndices[i].GetPoint(0).z < triangleIndices[i].GetPoint(2).z)
					{
						pmin = triangleIndices[i].v[0];
						if(triangleIndices[i].GetPoint(1).z < triangleIndices[i].GetPoint(2).z)
						{
							pmid = triangleIndices[i].v[1];
							pmax = triangleIndices[i].v[2];
						}
						else
						{
							pmid = triangleIndices[i].v[2];
							pmax = triangleIndices[i].v[1];
						}
					}
					else
					{
						pmin = triangleIndices[i].v[2];
						pmid = triangleIndices[i].v[0];
						pmax = triangleIndices[i].v[1];
					}
				}
				else
				{
					if(triangleIndices[i].GetPoint(1).z < triangleIndices[i].GetPoint(2).z)
					{
						pmin = triangleIndices[i].v[1];
						if(triangleIndices[i].GetPoint(0).z < triangleIndices[i].GetPoint(2).z)
						{
							pmid = triangleIndices[i].v[0];
							pmax = triangleIndices[i].v[2];
						}
						else
						{
							pmid = triangleIndices[i].v[2];
							pmax = triangleIndices[i].v[0];
						}
					}
					else
					{
						pmin = triangleIndices[i].v[2];
						pmid = triangleIndices[i].v[1];
						pmax = triangleIndices[i].v[0];
					}
				}

				xCase = true;
			}
			if(
				Close_Enough(triangleIndices[i].GetPoint(0).z, triangleIndices[i].GetPoint(1).z) &&
				Close_Enough(triangleIndices[i].GetPoint(0).z, triangleIndices[i].GetPoint(2).z)
			)
			{
				if(triangleIndices[i].GetPoint(0).x < triangleIndices[i].GetPoint(1).x)
				{
					if(triangleIndices[i].GetPoint(0).x < triangleIndices[i].GetPoint(2).x)
					{
						pmin = triangleIndices[i].v[0];
						if(triangleIndices[i].GetPoint(1).x < triangleIndices[i].GetPoint(2).x)
						{
							pmid = triangleIndices[i].v[1];
							pmax = triangleIndices[i].v[2];
						}
						else
						{
							pmid = triangleIndices[i].v[2];
							pmax = triangleIndices[i].v[1];
						}
					}
					else
					{
						pmin = triangleIndices[i].v[2];
						pmid = triangleIndices[i].v[0];
						pmax = triangleIndices[i].v[1];
					}
				}
				else
				{
					if(triangleIndices[i].GetPoint(1).x < triangleIndices[i].GetPoint(2).x)
					{
						pmin = triangleIndices[i].v[1];
						if(triangleIndices[i].GetPoint(0).x < triangleIndices[i].GetPoint(2).x)
						{
							pmid = triangleIndices[i].v[0];
							pmax = triangleIndices[i].v[2];
						}
						else
						{
							pmid = triangleIndices[i].v[2];
							pmax = triangleIndices[i].v[0];
						}
					}
					else					{
						pmin = triangleIndices[i].v[2];
						pmid = triangleIndices[i].v[1];
						pmax = triangleIndices[i].v[0];
					}
				}

				zCase = true;
			}
			Verify(!(zCase && xCase));
			Verify(uniquePoints[pmin].p.x <= uniquePoints[pmid].p.x && uniquePoints[pmid].p.x <= uniquePoints[pmax].p.x);
			Verify(uniquePoints[pmin].p.z <= uniquePoints[pmid].p.z && uniquePoints[pmid].p.z <= uniquePoints[pmax].p.z);

			int pfaraway = -1;
			int pminDI = -1, pminVI = -1;
			for(j=0;j<uniquePoints[pmin].connectedTo;j++)
			{
				if(uniquePoints[pmin].myTri[j].tri->area < Stuff::SMALL)
				{
					continue;
				}
				if(&triangleIndices[i]==uniquePoints[pmin].myTri[j].tri)
				{
					if(pminVI>=0)
					{
						STOP(("What, the hell, happend ?"));
					}
					pminVI = j;

					continue;
				}

				for(k=0;k<3;k++)
				{
					if(uniquePoints[pmin].myTri[j].tri->v[k]==pmax)
					{
						break;
					}
				}
				if(k<3)
				{
					for(k=0;k<3;k++)
					{
						if(uniquePoints[pmin].myTri[j].tri->v[k]!=pmax && uniquePoints[pmin].myTri[j].tri->v[k]!=pmin)
						{
							pfaraway = uniquePoints[pmin].myTri[j].tri->v[k];
							break;
						}
					}
					Verify(pfaraway>=0);

					if(pminDI<0)
					{
						pminDI = j;
					}
					else
					{
						STOP(("Now we are in real trouble !"));
					}
				}
			}

			int pmaxDI = -1, pmaxVI = -1;
			for(j=0;j<uniquePoints[pmax].connectedTo;j++)
			{
				if(uniquePoints[pmax].myTri[j].tri->area < Stuff::SMALL)
				{
					continue;
				}
				if(&triangleIndices[i]==uniquePoints[pmax].myTri[j].tri)
				{
					if(pmaxVI>=0)
					{
						STOP(("What, the hell, happend ?"));
					}
					pmaxVI = j;

					continue;
				}

				for(k=0;k<3;k++)
				{
					if(uniquePoints[pmax].myTri[j].tri->v[k]==pmin)
					{
						break;
					}
				}
				if(k<3)
				{
					for(k=0;k<3;k++)
					{
						if(uniquePoints[pmax].myTri[j].tri->v[k]!=pmax && uniquePoints[pmax].myTri[j].tri->v[k]!=pmin)
						{
							Verify(pfaraway == uniquePoints[pmax].myTri[j].tri->v[k]);
							break;
						}
					}

					if(pmaxDI<0)
					{
						pmaxDI = j;
					}
					else
					{
						STOP(("Now we are in real trouble !"));
					}
				}
			}

			if(pminDI>=0 && pmaxDI>=0)
			{
				for(k=0;k<3;k++)
				{
					if(uniquePoints[pmin].myTri[pminDI].tri->v[k]==pmax)
					{
						uniquePoints[pmin].myTri[pminDI].tri->v[k] = pmid;
						uniquePoints[pmid].Add(uniquePoints[pmin].myTri[pminDI].tri, k);
						uniquePoints[pmin].myTri[pminDI].tri->GetSurfaceAreaAndCentroid();
						break;
					}
				}
				uniquePoints[pmin].connectedTo--;
				for(j=pminVI;j<uniquePoints[pmin].connectedTo;j++)
				{
					uniquePoints[pmin].myTri[j] = uniquePoints[pmin].myTri[j+1];
				}

				for(k=0;k<3;k++)
				{
					if(uniquePoints[pmax].myTri[pmaxVI].tri->v[k]==pmin)
					{
						uniquePoints[pmax].myTri[pmaxVI].tri->v[k] = pfaraway;
						uniquePoints[pfaraway].Add(uniquePoints[pmax].myTri[pmaxVI].tri, k);
						uniquePoints[pmax].myTri[pmaxVI].tri->GetSurfaceAreaAndCentroid();
						break;
					}
				}
				uniquePoints[pmax].connectedTo--;
				for(j=pmaxDI;j<uniquePoints[pmax].connectedTo;j++)
				{
					uniquePoints[pmax].myTri[j] = uniquePoints[pmax].myTri[j+1];
				}

				uniquePoints[pmin].CalculateAngles();
				uniquePoints[pmid].CalculateAngles();
				uniquePoints[pmax].CalculateAngles();
				uniquePoints[pfaraway].CalculateAngles();

				tiltedOne++;
			}
			else
			{
				if(pminDI<0 && pmaxDI<0)
				{
					triangleIndices[i].area = 0.0f;
				}
				else
				{
					STOP(("Very odd indeed !"));
				}
			}

		}
	}

	DynamicArrayOf<TriangleStruct*> tileTrianglesPtr;

	for(l=0;l<zDim;l++)
	{
		for(k=0;k<xDim;k++)
		{
			sprintf(megaBaseName, "Maps\\%s\\%c%c\\%s_%c%c", theName, 'A'+l, 'A'+k, theName, 'A'+l, 'A'+k);

			xOffset = k*dX*256;
			zOffset = l*dZ*256;

			gos_PushCurrentHeap(ElementRenderer::Heap);
			simpleTerrainGroup[l*xDim+k] = new GridElement(len, len, zOffset, xOffset, dZ*256, dX*256);
			Register_Object(simpleTerrainGroup[l*xDim+k]);
			simpleTerrainGroup[l*xDim+k]->SetName("OptimizedTerrain");
			gos_PopCurrentHeap();

			for(j=0;j<len;j++)
			{
				for(i=0;i<len;i++)
				{
					int m, length = tempTrianglesPtr[(l*xDim+k)*len*len + j*len + i].GetLength();
					int usable = 0;
						
					tileTrianglesPtr.SetLength(length);
					for(m=0;m<length;m++)
					{
						if(tempTrianglesPtr[(l*xDim+k)*len*len + j*len + i][m]->area < SMALL)
						{
							continue;
						}
						else if(tempTrianglesPtr[(l*xDim+k)*len*len + j*len + i][m]->plane.normal.y < Stuff::SMALL)
						{
							continue;
						}

						tileTrianglesPtr[usable++] = tempTrianglesPtr[(l*xDim+k)*len*len + j*len + i][m];
					}
					tileTrianglesPtr.SetLength(usable);

					tX = i;
					tY = j;

					CreateTile(
						simpleTerrainGroup[l*xDim+k],
						tileTrianglesPtr,
						j*len+i,
						level,
						nrOfLevels,
						bucketSize,
						xOffset,
						zOffset,
						xGridSize,
						zGridSize,
						i,
						j,
						megaBaseName
					);

				}
			}

			simpleTerrainGroup[l*xDim+k]->LockBounds();
		}
	}

	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	HFSlimMZL::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;
}
