//================================================================
// Create fractal terrain and change it interactively
//
// Plugin for EQUINOX-3D
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Mar-12 19:06:18
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <EMalloc.h>
#include <EPlugins.h>

#include <Xe/Label.h>
#include <Xe/Matrix.h>
#include <Xe/Scale.h>
#include <Xe/Separator.h>
#include <Xe/ToggleButton.h>

#include <Color/XColor.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>
#include <E3D/UI/StatusPanel.h>

static EPlugin*  _Plugin = NULL;

EpIconDef3I(static, Terrain);


enum
{
	E3dFRACTAL_MIDPOINT_TRIANGLE = 0,
	E3dFRACTAL_SQUARE_SQUARE
};


static XeOptionMenuRec  _Types[]=
{
	{ "Midpoint-triangle",  (EPointer)E3dFRACTAL_MIDPOINT_TRIANGLE,  NULL,  (EPointer)E3dFRACTAL_MIDPOINT_TRIANGLE,  NULL },
	{ "Square-square",      (EPointer)E3dFRACTAL_SQUARE_SQUARE,      NULL,  (EPointer)E3dFRACTAL_SQUARE_SQUARE,      NULL }
};

typedef struct
{
	E3dCoord3  Position;
	int8_t     MaterialID;
} E3dFrVertex;

typedef struct E3dFrEdge
{
	unsigned int  Start, End, Bisector, BisectorEdge0, BisectorEdge1;
} E3dFrEdge;

typedef struct E3dFrTriangle
{
  unsigned int  Vertices[3];
	unsigned int  Edges[3];
	char          MaterialID;
} E3dFrTriangle;

// Values for a regular triangle (facing forward on the Z axis)
//
#define E3dREGTRIV0    0.57735026918962576451  // 1.0/sqrt(3)
#define E3dREGTRIV1   -0.288675134             // (sqrt(3)/2-1/sqrt(3))


typedef struct
{
	XeWidget	TypesOptionMenuW,
	          SmoothW, TrianglesW,
	          WaterLevelW, RockLevelW, SnowLevelW,
	          EdgeLengthW, IterationsW, HeightW, RandomSeedW,
	          ProgressBarW, ShowProgressW;
} E3dResourcesGUI;

static XeDWidget  _MenuButton = NULL,
                 _ToolBarButton = NULL;

static EcRGBAiColor  _WaterLevelColor = { 0xAD00, 0xD800, 0xE600, 0xFFFF }, _RockLevelColor = { 0x8B00, 0x4500, 0x1300, 0xFFFF }, _SnowLevelColor = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF };

static EBool  E3d_Busy = FALSE;

static int _TrianglePolyIndices[1][3] =
{
	{  0, 1, 2 }
};

static int  _TrianglePolyEdgeIndices[1][3] =
{
	{ 0, 1, 2 },
};

static int  _TriangleEdgeIndices[3][2]=
{
	{ 0, 1 }, { 1, 2 }, { 0, 2 }
};

#define	E3dFractalMAT_Water  0
#define	E3dFractalMAT_Grass  1
#define	E3dFractalMAT_Soil   2
#define	E3dFractalMAT_Rock   3
#define	E3dFractalMAT_Snow   4

static E3dMaterial*  _WaterMaterial = NULL;
static E3dMaterial*  _GrassMaterial = NULL;
static E3dMaterial*  _SoilMaterial = NULL;
static E3dMaterial*  _RockMaterial = NULL;
static E3dMaterial*  _SnowMaterial = NULL;

#define EM_TriNormal() do\
{\
	LV0 = LTrianglesS[LC].Vertices[0];\
	LV1 = LTrianglesS[LC].Vertices[1];\
	LV2 = LTrianglesS[LC].Vertices[2];\
	E3d_TriangleNormal(&LSVertex[LV0].Position, &LSVertex[LV1].Position, &LSVertex[LV2].Position,  &LPolygon->Normal);\
} while(0)



typedef struct
{
	E3dObjectComponentCore;

	EList(, E3dFrVertex, FrVertices);
	int           Type, RandomSeed, Iterations, NumOfProgressSteps, ProgressStep;
	float         EdgeLength;
	float         Height, WaterLevel, RockLevel, SnowLevel,
	              RockRoughness;
	EBool         Smooth, Triangles, ShowProgress, HiResWater;
} E3dTerrainInfo;


#define ROffset(field)  (void*)(EPtrOffset(E3dTerrainInfo, field))
static EResource  _Resources[]=
{
	{ "Type",         EResINT32,    ROffset(Type) },

	{ "EdgeLength",   EResFLOAT32,  ROffset(EdgeLength) },
	{ "Height",       EResFLOAT32,  ROffset(Height) },

	{ "WaterLevel",   EResFLOAT32,  ROffset(WaterLevel) },
	{ "RockLevel",    EResFLOAT32,  ROffset(RockLevel) },
	{ "SnowLevel",    EResFLOAT32,  ROffset(SnowLevel) },

	{ "Iterations",   EResINT32,    ROffset(Iterations) },
	{ "RandomSeed",   EResINT32,    ROffset(RandomSeed) },

	{ "Smooth",       EResBOOLEAN,  ROffset(Smooth) },
	{ "Triangles",    EResBOOLEAN,  ROffset(Triangles) },
};
#undef ROffset



static E3dObjectComponentClass*  _ComponentClass = NULL;

//================================================
// Compute Vertex coordinates of the fractal
//================================================
static void _SetVertices(E3dVertex* PVertices, const E3dVertexSize PVertexSize,  E3dTerrainInfo* PInfo)
{
	const E3dCoordinate  LLen    = PInfo->EdgeLength;
	const E3dCoordinate  LHeight = PInfo->Height;

	switch(PInfo->Type)
	{
		case E3dFRACTAL_MIDPOINT_TRIANGLE:
			{
				const E3dCoordinate  LWaterLevel = (PInfo->WaterLevel - 0.5) * 0.5;
				EIndex               LVNum = 3;

				{
					EIndex  LC = 0, LENum = 3, LPNum = 1;for(;LC < PInfo->Iterations;LC++)
					{
						LVNum += LENum;LENum = LENum * 2 + LPNum * 3;LPNum *= 4;
					}
				}

				E3dFrVertex*  LSVertex = PInfo->FrVertices;

				{
					EIndex  LVC = 0;E3dVertex*  LVertex = PVertices;for(;LVC < LVNum;LVC++, LSVertex++, EStruct_Inc(LVertex, PVertexSize))
					{
						const E3dCoordinate  LX = LSVertex->Position.X, LY = LSVertex->Position.Y, LZ = LSVertex->Position.Z;
						LVertex->Position.X = LX * LLen;LVertex->Position.Y = LY * LHeight;LVertex->Position.Z = LZ * LLen;
					}
				}

// Set coordinates for single-Polygon water
//
				if(!PInfo->HiResWater)
				{
					E3dVertex*  LVertex = EStruct_ByIndex(PVertices, PVertexSize, LVNum);
					LVertex->Position.X =  0.0;       LVertex->Position.Y = LWaterLevel;LVertex->Position.Z = E3dREGTRIV0 * LLen;EStruct_Inc(LVertex, PVertexSize);
					LVertex->Position.X =  0.5 * LLen;LVertex->Position.Y = LWaterLevel;LVertex->Position.Z = E3dREGTRIV1 * LLen;EStruct_Inc(LVertex, PVertexSize);
					LVertex->Position.X = -0.5 * LLen;LVertex->Position.Y = LWaterLevel;LVertex->Position.Z = E3dREGTRIV1 * LLen;
				}
			}
		break;
	}
}


//========================================
// Mid-point triangle method
//========================================
static EBool _MidPointTriangle(E3dMesh* PMesh, E3dTerrainInfo* PInfo)
{
	E3dFrVertex*    LSVertex;
	E3dFrTriangle*  LTrianglesA = NULL;
	E3dFrTriangle*  LTrianglesB = NULL;
	E3dFrTriangle*  LTrianglesS;
	E3dFrTriangle*  LTrianglesD;
	E3dFrTriangle*  LTriangleS;
	E3dFrTriangle*  LTriangleD;
	E3dFrEdge*      LEdgesA = NULL;
	E3dFrEdge*      LEdgesB = NULL;
	E3dFrEdge*      LEdgesS;
	E3dFrEdge*      LEdgesD;
	unsigned int*   LEdgesTS;
	unsigned int*   LVerticesTS;
	int             LNumOfIterations = PInfo->Iterations;
	EIndex          LCED, LV0, LV1, LV2;
	EIndex          LWaterPolyNum, LGrassPolyNum, LSoilPolyNum, LRockPolyNum, LSnowPolyNum;
	EIndex          LMeshVNum, LH;
	EBool           LSuccess = FALSE;


	EIndex  LVNum = 3, LENum = 3, LPNum = 1;

	{
		EIndex  LC = 0;for(;LC < LNumOfIterations;LC++)
		{
			LVNum += LENum;LENum = LENum * 2 + LPNum * 3;LPNum *= 4;
		}
	}

	if(!(PInfo->HiResWater)) LMeshVNum = LVNum + 3;
	else LMeshVNum = LVNum;

// We call XeApp_Sync during sensitive operations, so we have to lock this part of the program...
//
	E3d_Busy = TRUE;

	if(PInfo->FrVertices_Count != LVNum) EList_FreeChk(PInfo->FrVertices);

	if(PInfo->FrVertices == NULL)
	{
		ENewN(LVNum, PInfo->FrVertices);
		if(PInfo->FrVertices) PInfo->FrVertices_Count = LVNum;
	}

	if(PInfo->FrVertices == NULL) return(FALSE);

	LSVertex = PInfo->FrVertices;

	if(LNumOfIterations & 1)  // Odd # of iterations,  buffer A is 'one step' smaller
	{
		ENewN(LPNum / 4, LTrianglesA);
		ENewN(LPNum, LTrianglesB);
		ENewN(LENum, LEdgesA);
		ENewN(LENum, LEdgesB);
	}
	else    // Even # of iterations, buffer B is 'one step' smaller
	{
		ENewN(LPNum, LTrianglesA);
		ENewN(LPNum/4, LTrianglesB);
		ENewN(LENum, LEdgesA);
		ENewN(LENum, LEdgesB);
	}

	if(LTrianglesA && LTrianglesB && LEdgesA && LEdgesB)
	{
// Store original regular triangle vertices
//
		LH = 0;
		LSVertex[LH].Position.X =  0.0;LSVertex[LH].Position.Y = 0.0;LSVertex[LH].Position.Z = E3dREGTRIV0;LSVertex[LH++].MaterialID = E3dFractalMAT_Grass;
		LSVertex[LH].Position.X =  0.5;LSVertex[LH].Position.Y = 0.0;LSVertex[LH].Position.Z = E3dREGTRIV1;LSVertex[LH++].MaterialID = E3dFractalMAT_Grass;
		LSVertex[LH].Position.X = -0.5;LSVertex[LH].Position.Y = 0.0;LSVertex[LH].Position.Z = E3dREGTRIV1;LSVertex[LH++].MaterialID = E3dFractalMAT_Grass;

		LTrianglesS = LTrianglesA;LTrianglesD = LTrianglesB;LEdgesS = LEdgesA;LEdgesD = LEdgesB;

// Copy triangle to the current triangle list
//
		{
			EIndex  LC = 0;for(;LC < 1;LC++)
			{
				LTrianglesS[LC].Vertices[0] = _TrianglePolyIndices[LC][0];
				LTrianglesS[LC].Vertices[1] = _TrianglePolyIndices[LC][1];
				LTrianglesS[LC].Vertices[2] = _TrianglePolyIndices[LC][2];
				LTrianglesS[LC].Edges[0] = _TrianglePolyEdgeIndices[LC][0];
				LTrianglesS[LC].Edges[1] = _TrianglePolyEdgeIndices[LC][1];
				LTrianglesS[LC].Edges[2] = _TrianglePolyEdgeIndices[LC][2];
			}
		}

// Copy triangle edges to the current edge list
//
		{
			EIndex  LC = 0;for(;LC < 3;LC++)
			{
				LEdgesS[LC].Start = _TriangleEdgeIndices[LC][0];
				LEdgesS[LC].End   = _TriangleEdgeIndices[LC][1];
			}
		}

		const E3dCoordinate  LHeight = PInfo->Height;
		const E3dCoordinate  LWaterLevel = (PInfo->WaterLevel - 0.5) * 0.5 / LHeight;
		const E3dCoordinate  LRockLevel  = (PInfo->RockLevel - 0.5)  * 0.5 / LHeight;
		const E3dCoordinate  LSoilLevel  = (PInfo->RockLevel - 0.5)  * 0.5 / LHeight;
		const E3dCoordinate  LSnowLevel  = (PInfo->SnowLevel - 0.5)  * 0.5 / LHeight;

		E3dCoordinate        LLevelRandomness = 20.0;

		E3dCoordinate  LR = 0.1 * PInfo->Height, LRgh = 1.0;

		const E3dCoordinate  LRandMaxInv = 1.0 / (E3dCoordinate)RAND_MAX;srandom(PInfo->RandomSeed);
#define _Random()  random() * LRandMaxInv - 0.5

//		ERandomState   LRandomState[5];E_RandomInit(PInfo->RandomSeed, LRandomState);
//#define _Random()  E_RandomDN(LRandomState)

		EIndex  LCIter = 0, LNEdg = 3, LNTri = 1;for(;LCIter < LNumOfIterations;LCIter++, LNTri *= 4, LR *= 0.5)
		{
// Compute bisector points of the edges and add bisector edges to the new edge list
//
			LCED = 0;
			EIndex  LC = 0;for(;LC < LNEdg;LC++)
			{
				E3dFrVertex*  LSVertex0 = LSVertex + LEdgesS[LC].Start;
				E3dFrVertex*  LSVertex1 = LSVertex + LEdgesS[LC].End;
				LEdgesS[LC].Bisector = LH;

				E3dCoordinate  LX = LSVertex0->Position.X + LSVertex1->Position.X, LY = LSVertex0->Position.Y + LSVertex1->Position.Y, LZ = LSVertex0->Position.Z + LSVertex1->Position.Z;
				LX *= 0.5;LY *= 0.5;LZ *= 0.5;

				if(LCIter > 1)
				{
					if(LY >= LRockLevel + (_Random()) * LR * LLevelRandomness)
					{
						LRgh = 4.0;LSVertex[LH].MaterialID = E3dFractalMAT_Rock;
					}
					else
					{
						if(LY >= LSoilLevel + (_Random()) * LR * LLevelRandomness)
						{
							LRgh = 1.6;LSVertex[LH].MaterialID = E3dFractalMAT_Soil;
						}
						else
						{
							LRgh = 1.0;LSVertex[LH].MaterialID = E3dFractalMAT_Grass;
						}
					}
				}
				else LSVertex[LH].MaterialID = E3dFractalMAT_Grass;

				if(LY >= LSnowLevel + (_Random()) * LR * LLevelRandomness) LSVertex[LH].MaterialID = E3dFractalMAT_Snow;

				LSVertex[LH].Position.X = LX;LSVertex[LH].Position.Y = LY + (_Random()) * LR * LRgh;LSVertex[LH].Position.Z = LZ;
				LY = LSVertex[LH].Position.Y;

				LEdgesS[LC].BisectorEdge0 = LCED;
				LEdgesD[LCED].Start = LEdgesS[LC].Start;LEdgesD[LCED].End = LH;LCED++;
				LEdgesS[LC].BisectorEdge1 = LCED;
				LEdgesD[LCED].Start = LEdgesS[LC].End;LEdgesD[LCED].End = LH;LCED++;
				LH++;

//				LLevelRandomness *= 0.999;
			}

// Subdivide triangles
//
			LTriangleD = LTrianglesD;
			for(LC = 0, LTriangleS = LTrianglesS;LC < LNTri;LC++, LTriangleS++)
			{
				LVerticesTS = LTriangleS->Vertices;
				LEdgesTS = LTriangleS->Edges;
				LTriangleD->Vertices[0] = LVerticesTS[0];
				LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[0]].Bisector;
				LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[2]].Bisector;
				if(LEdgesD[LTriangleD->Edges[0] = LEdgesS[LEdgesTS[0]].BisectorEdge0].Start != LVerticesTS[0]) LTriangleD->Edges[0] += 1;
				LTriangleD->Edges[1] = LCED;

				if(LEdgesD[LTriangleD->Edges[2] = LEdgesS[LEdgesTS[2]].BisectorEdge0].Start != LVerticesTS[0]) LTriangleD->Edges[2] += 1;
				LEdgesD[LCED].Start = LEdgesS[LEdgesTS[0]].Bisector;LEdgesD[LCED].End = LEdgesS[LEdgesTS[2]].Bisector;LCED++;
				LTriangleD++;

				LTriangleD->Vertices[0] = LVerticesTS[1];
				LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[1]].Bisector;
				LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[0]].Bisector;

				if(LEdgesD[LTriangleD->Edges[0] = LEdgesS[LEdgesTS[1]].BisectorEdge0].Start != LVerticesTS[1]) LTriangleD->Edges[0] += 1;
				LTriangleD->Edges[1] = LCED;

				if(LEdgesD[LTriangleD->Edges[2] = LEdgesS[LEdgesTS[0]].BisectorEdge0].Start != LVerticesTS[1]) LTriangleD->Edges[2] += 1;
				LEdgesD[LCED].Start = LEdgesS[LEdgesTS[1]].Bisector;LEdgesD[LCED].End = LEdgesS[LEdgesTS[0]].Bisector;LCED++;
				LTriangleD++;

				LTriangleD->Vertices[0] = LVerticesTS[2];
				LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[2]].Bisector;
				LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[1]].Bisector;
				if(LEdgesD[LTriangleD->Edges[0] = LEdgesS[LEdgesTS[2]].BisectorEdge0].Start != LVerticesTS[2]) LTriangleD->Edges[0] += 1;
				LTriangleD->Edges[1] = LCED;
				if(LEdgesD[LTriangleD->Edges[2] = LEdgesS[LEdgesTS[1]].BisectorEdge0].Start != LVerticesTS[2]) LTriangleD->Edges[2] += 1;
				LEdgesD[LCED].Start = LEdgesS[LEdgesTS[2]].Bisector;LEdgesD[LCED].End = LEdgesS[LEdgesTS[1]].Bisector;LCED++;
				LTriangleD++;

				LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[1]].Bisector;
				LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[2]].Bisector;
				LTriangleD->Vertices[0] = LEdgesS[LEdgesTS[0]].Bisector;
				LTriangleD->Edges[2] = LCED-3;
				LTriangleD->Edges[0] = LCED-2;
				LTriangleD->Edges[1] = LCED-1;
				LTriangleD++;
			}

			if(PInfo->ShowProgress)
			{
				E3dProgressIndicator_Set((float)(PInfo->ProgressStep) / (float)(PInfo->NumOfProgressSteps), NULL, (EPointer)NULL);XeApp_Sync(Xe_App);PInfo->ProgressStep += 1;
			}

			LNEdg = LCED;

// Swap source and destination buffers
//
			if(LTrianglesS == LTrianglesA)
			{
				LTrianglesS = LTrianglesB;LTrianglesD = LTrianglesA;LEdgesS = LEdgesB;LEdgesD = LEdgesA;
			}
			else
			{
				LTrianglesS = LTrianglesA;LTrianglesD = LTrianglesB;LEdgesS = LEdgesA;LEdgesD = LEdgesB;
			}
		}
#undef _Random


// Before allocating the Edges for the Mesh, free the temporary
// Edge and triangle lists that are not used any more
//
		if(LEdgesS == LEdgesA)
		{
			EFreeClr(LEdgesB);
		}
		else
		{
			EFreeClr(LEdgesA);
		}

		if(LTrianglesS == LTrianglesA)
		{
			EFreeClr(LTrianglesB);
		}
		else
		{
			EFreeClr(LTrianglesA);
		}

		{
			E3dEdge*  LEdges = E3dMesh_EdgesNew(PMesh, LNEdg, TRUE);if(LEdges)
			{
				EIndex  LC = 0;for(;LC < LNEdg;LC++)
				{
					LEdges[LC].Start = LEdgesS[LC].Start;LEdges[LC].End = LEdgesS[LC].End;
				}
			}
		}

		EFreeChkClr(LEdgesA);
		EFreeChkClr(LEdgesB);

		{
			EIndex  LC = 0;E3dFrVertex*  LSVertex0 = LSVertex;for(;LC < LVNum;LC++, LSVertex0++)
			{
				if(LSVertex0->Position.Y < LWaterLevel) LSVertex0->Position.Y = LWaterLevel;
			}
		}

		if(LMeshVNum != PMesh->Vertices.Count)
		{
			E3dMesh_VerticesFree(PMesh);
			E3dMesh_VerticesAdd(PMesh, LMeshVNum);
		}

		if(PMesh->Vertices.Count > 0)
		{
			_SetVertices(PMesh->Vertices.Items, PMesh->VertexSize, PInfo);

			if(PInfo->ShowProgress)
			{
				E3dProgressIndicator_Set((float)(PInfo->ProgressStep) / (float)(PInfo->NumOfProgressSteps), NULL, (EPointer)NULL);XeApp_Sync(Xe_App);PInfo->ProgressStep += 1;
			}

			LWaterPolyNum = 0;LGrassPolyNum = 0;LSoilPolyNum = 0;LRockPolyNum = 0;LSnowPolyNum = 0;

			{
				EIndex  LC = 0;for(;LC < LNTri;LC++)
				{
					LV0 = LTrianglesS[LC].Vertices[0];
					LV1 = LTrianglesS[LC].Vertices[1];
					LV2 = LTrianglesS[LC].Vertices[2];

//				const E3dCoordinate  LX0 = LSVertex[LV0].Position.X;
					const E3dCoordinate  LY0 = LSVertex[LV0].Position.Y;
//				const E3dCoordinate  LZ0 = LSVertex[LV0].Position.Z;

//				const E3dCoordinate  LX1 = LSVertex[LV1].Position.X;
					const E3dCoordinate  LY1 = LSVertex[LV1].Position.Y;
//				const E3dCoordinate  LZ1 = LSVertex[LV1].Position.Z;

//				const E3dCoordinate  LX2 = LSVertex[LV2].Position.X;
					const E3dCoordinate  LY2 = LSVertex[LV2].Position.Y;
//				const E3dCoordinate  LZ2 = LSVertex[LV2].Position.Z;

					if((LY0 <= LWaterLevel) && (LY1 <= LWaterLevel) && (LY2 <= LWaterLevel))
					{
						LTrianglesS[LC].MaterialID = E3dFractalMAT_Water;LWaterPolyNum++;
					}
					else
					{
						switch(LTrianglesS[LC].MaterialID = LSVertex[LTrianglesS[LC].Vertices[0]].MaterialID)
						{
							case E3dFractalMAT_Grass:	LGrassPolyNum++;break;
							case E3dFractalMAT_Soil:	LSoilPolyNum++;break;
							case E3dFractalMAT_Rock:	LRockPolyNum++;break;
							case E3dFractalMAT_Snow:	LSnowPolyNum++;break;
						}
					}
				}
			}

// Add polygons to the appropriate PolyGroups
//
//printf("Polys:\n-------------------\n");fflush(stdout);
			{
				E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[0];
				const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

				E_AssignToMember(_WaterMaterial, LPolyGroup,Material);
				E3dPolyGroup_FreePolygons(LPolyGroup);
				if(PInfo->HiResWater)
				{
//printf("Water: %p %d polys %d\n", LPolyGroup, LWaterPolyNum, LPolyGroup->Polygons_Count);fflush(stdout);
					if(LWaterPolyNum)
					{
						E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items = E3dPolygons_New(LWaterPolyNum);if(LPolygon)
						{
							LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LWaterPolyNum;
							EIndex  LC = 0;for(;LC < LNTri;LC++)
							{
								if(LTrianglesS[LC].MaterialID == E3dFractalMAT_Water)
								{
									E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LTrianglesS[LC].Vertices[0], LTrianglesS[LC].Vertices[1], LTrianglesS[LC].Vertices[2]);

									LPolygon->Normal.X = 0.0;
									LPolygon->Normal.Y = 1.0;
									LPolygon->Normal.Z = 0.0;

									LPolygon++;
								}
							}
						}
					}
				}
				else
				{
					if(LWaterPolyNum)
					{
						E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items = E3dPolygons_New(1);if(LPolygon)
						{
							LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = 1;
							E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LVNum, LVNum + 1, LVNum + 2);
							LPolygon->Normal.X = 0.0;
							LPolygon->Normal.Y = 1.0;
							LPolygon->Normal.Z = 0.0;
						}
					}
				}

				LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
				LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
				E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
				E3dMeshPolyGroup_RefreshNormals(PMesh, LPolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);
			}


			{
				E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[1];
				const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

				E3dPolyGroup_FreePolygons(LPolyGroup);

				if(LGrassPolyNum)
				{
					E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items = E3dPolygons_New(LGrassPolyNum);if(LPolygon)
					{
						LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LGrassPolyNum;
						EIndex  LC = 0;for(;LC < LNTri;LC++)
						{
							if(LTrianglesS[LC].MaterialID == E3dFractalMAT_Grass)
							{
								E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LTrianglesS[LC].Vertices[0], LTrianglesS[LC].Vertices[1], LTrianglesS[LC].Vertices[2]);

								EM_TriNormal();

								LPolygon++;
							}
						}
					}

					LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
					LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
					E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
					E3dMeshPolyGroup_RefreshNormals(PMesh, LPolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);
				}
			}


			{
				E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[2];
				const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

				E3dPolyGroup_FreePolygons(LPolyGroup);
				if(LSoilPolyNum)
				{
					E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items = E3dPolygons_New(LSoilPolyNum);if(LPolygon)
					{
						LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LSoilPolyNum;
						EIndex  LC = 0;for(;LC < LNTri;LC++)
						{
							if(LTrianglesS[LC].MaterialID == E3dFractalMAT_Soil)
							{
								E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LTrianglesS[LC].Vertices[0], LTrianglesS[LC].Vertices[1], LTrianglesS[LC].Vertices[2]);

								EM_TriNormal();

								LPolygon++;
							}
						}
					}
				}
			}


			{
				E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[3];
				const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

				E3dPolyGroup_FreePolygons(LPolyGroup);
				if(LRockPolyNum)
				{
					E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items = E3dPolygons_New(LRockPolyNum);if(LPolygon)
					{
						LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LRockPolyNum;
						EIndex  LC = 0;for(;LC < LNTri;LC++)
						{
							if(LTrianglesS[LC].MaterialID == E3dFractalMAT_Rock)
							{
								E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LTrianglesS[LC].Vertices[0], LTrianglesS[LC].Vertices[1], LTrianglesS[LC].Vertices[2]);

								EM_TriNormal();

								LPolygon++;
							}
						}
					}
				}
			}


			{
				E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[4];
				const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

				E3dPolyGroup_FreePolygons(LPolyGroup);
				if(LSnowPolyNum)
				{
					E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items = E3dPolygons_New(LSnowPolyNum);if(LPolygon)
					{
						LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LSnowPolyNum;
						EIndex  LC = 0;for(;LC < LNTri;LC++)
						{
							if(LTrianglesS[LC].MaterialID == E3dFractalMAT_Snow)
							{
								E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LTrianglesS[LC].Vertices[0], LTrianglesS[LC].Vertices[1], LTrianglesS[LC].Vertices[2]);

								EM_TriNormal();

								LPolygon++;
							}
						}
					}
				}
			}

			LSuccess = TRUE;
		}
	}


	EFreeChkClr(LTrianglesA);
	EFreeChkClr(LTrianglesB);LTrianglesB = NULL;
	EFreeChkClr(LEdgesA);LEdgesA = NULL;
	EFreeChkClr(LEdgesB);LEdgesB = NULL;

	EList_FreeChk(PInfo->FrVertices);

	if(LSuccess)
	{
		if(PInfo->ShowProgress)
		{
			E3dProgressIndicator_Set((float)(PInfo->ProgressStep)/(float)(PInfo->NumOfProgressSteps), NULL, (EPointer)NULL);XeApp_Sync(Xe_App);PInfo->ProgressStep += 1;
		}
	}

	return(LSuccess);
}


#define _M_AddQuad(mV0, mV1, mV2, mV3) do\
{\
	E3dPolygon_InitVa(LPolygon, LVertexNodeSize, 4,  mV0, mV1, mV2, mV3);\
	ENewN(2, LPolygon->ITriangles);E3dITriangle*  lITriangle = LPolygon->ITriangles;\
	LPolygon->ITriangles_Count = 2;\
	lITriangle->VertexNodes[0] = 0;lITriangle->VertexNodes[1] = 1;lITriangle->VertexNodes[2] = 2;lITriangle++;\
	lITriangle->VertexNodes[0] = 2;lITriangle->VertexNodes[1] = 3;lITriangle->VertexNodes[2] = 0;\
	LPolygon++;\
} while(0)


#define _MtlFromY(pFrVertex) do\
{\
	E3dFrVertex*         lFrVertex = pFrVertex;\
	const E3dCoordinate  lY = lFrVertex->Position.Y + (E_RandomDN(LRandomState2) - 0.5) * LUnitLength * 200.0;\
	if(lY >= LRockLevel)\
	{\
		lFrVertex->MaterialID = E3dFractalMAT_Rock;\
	}\
	else\
	{\
		lFrVertex->MaterialID = E3dFractalMAT_Grass;\
	}\
} while(0)


#define _ProcessPG(p) do\
{\
	E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[E3dFractalMAT_##p];\
\
	EList_FreeChk_s(LPolyGroup->Edges);\
\
	if(PInfo->Smooth)\
	{\
		LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;\
		LPolyGroup->SmoothingAngle = 180.0;\
		LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;\
	}\
	else\
	{\
		LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;\
		LPolyGroup->SmoothingAngle = 180.0;\
		LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;\
	}\
\
	if(LPolyGroup->Polygons.Items && LPolyGroup->Polygons.Count != L##p##PolyNum) E3dPolyGroup_FreePolygons(LPolyGroup);\
	if(LPolyGroup->Polygons.Items == NULL) LPolyGroup->Polygons.Items = E3dPolygons_New(L##p##PolyNum);\
\
	E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;if(LPolygon)\
	{\
		LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = L##p##PolyNum;\
\
		const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;\
\
		const EIndex  LPointsX = LDimX + 1;\
		EIndex  LPC = 0, LCZ = 0, LRowA = 0, LNextRowA = LPointsX;for(;LCZ < LDimZ;LCZ++, LRowA += LPointsX, LNextRowA += LPointsX)\
		{\
			EIndex  LCX = 0;const E3dFrVertex*  LFVertex = LVerticesS + LRowA;for(;LCX < LDimX;LCX++, LFVertex++)\
			{\
				if(LFVertex->MaterialID == E3dFractalMAT_##p)\
				{\
					(void)LPC;\
/*printf("%s() %s:%d  %4d : %d %d %d %d\n", __FUNCTION__, __FILE__, __LINE__, LPC, LRowA + LCX, LNextRowA + LCX, LNextRowA + LCX + 1, LRowA + LCX + 1);fflush(stdout);*/\
					_M_AddQuad(LRowA + LCX, LNextRowA + LCX, LNextRowA + LCX + 1, LRowA + LCX + 1);\
					LPC++;\
				}\
			}\
		}\
		E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);\
		E3dMeshPolyGroup_RefreshPolygonNormals(PMesh, LPolyGroup);\
		E3dMeshPolyGroup_RefreshNormals(PMesh, LPolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);\
	}\
} while(0)


//========================================
// Square-square subdivision method
//========================================
static EBool _SquareSquareSubdiv(E3dMesh* PMesh,  E3dCoordinate PEdgeXLen, E3dCoordinate PEdgeZLen,  const EIndex PStartDimX, const EIndex PStartDimZ,  E3dTerrainInfo* PInfo)
{
	int     LNumOfIterations = PInfo->Iterations;
	EIndex  LDimX = PStartDimX;
	EIndex  LDimZ = PStartDimZ;
	EIndex  L2Pow = 2;

	{
		EIndex  LC = 0;for(;LC < LNumOfIterations;LC++)
		{
			LDimX = LDimX * 2 - 1;LDimZ = LDimZ * 2 - 1;L2Pow *= 2;
		}
	}

	const EIndex  LNVertices = (LDimX + 1) * (LDimZ + 1);
	E3dFrVertex*  LVerticesA = NULL;
	E3dFrVertex*  LVerticesB = NULL;


	if(LNumOfIterations > 0)
	{
// Size of the buffer before the last (one 'step' smaller)
//
		const EIndex  LNLVertices = ((LDimX + 1) / 2 + 1) * ((LDimZ + 1) / 2 + 1);

		if(LNumOfIterations & 1)  // Odd # of iterations, buffer A is one 'step' smaller
		{
			ENewN(LNLVertices, LVerticesA);
			ENewN(LNVertices, LVerticesB);
		}
		else    // Even # of iterations,  buffer B is one 'step' smaller
		{
			ENewN(LNVertices, LVerticesA);
			ENewN(LNLVertices, LVerticesB);
		}
	}
	else ENewN(LNVertices, LVerticesA);


	EBool          LSuccess = FALSE;


		const E3dCoordinate  LRandMaxInv = 1.0 / (E3dCoordinate)RAND_MAX;srandom(PInfo->RandomSeed);
#define _Random()  ((E3dCoordinate)random() * LRandMaxInv)

//		ERandomState   LRandomState[5];E_RandomInit(PInfo->RandomSeed, LRandomState);
//#define _Random()  E_RandomDN(LRandomState)

	ERandomState   LRandomState2[5];E_RandomInit(32, LRandomState2);

	const E3dCoordinate  LHeight = PInfo->Height;
	const E3dCoordinate  LRockLevel = PInfo->RockLevel / LHeight;

	if((LVerticesA && LVerticesB) || LNumOfIterations == 0)
	{
// Create initial random height field
//
		E3dFrVertex*  LVerticesS = LVerticesA;
		E3dFrVertex*  LVerticesD = LVerticesB;

		{
			EIndex  LZC = 0;E3dFrVertex*  LFVertex = LVerticesS;for(;LZC < PStartDimZ + 1;LZC++)
			{
				EIndex  LXC = 0;for(;LXC < PStartDimX + 1;LXC++)
				{
//					LFVertex->Position.Y = _Random() - 0.5;
					LFVertex->Position.Y = (E3dCoordinate)random() * LRandMaxInv - 0.5;

					LFVertex++;
				}
			}
		}


// Do the refinement
//
		{
			const E3dCoordinate  LOneSixteenth = 0.0625, LRockRoughness = PInfo->RockRoughness;
			E3dCoordinate        LRoughnessScaler = 2.4, LMaxRoughness;


			EIndex         LCurrntDimX = PStartDimX, LCurrntDimZ = PStartDimZ;
			E3dCoordinate  LUnitLength = 1.0;
			EIndex  LC = 0;for(;LC < LNumOfIterations;LC++)
			{
				E3dFrVertex*  LFVertex = LVerticesS;
				E3dFrVertex*  LFVertexD = LVerticesD;
				const EIndex  LNxtDimX = LCurrntDimX * 2;
//				const EIndex  LNxtDimZ = LCurrntDimZ * 2;
				EIndex  LZC = 0;for(;LZC < LCurrntDimZ;LZC++)
				{
					EIndex  LXC = 0;for(;LXC < LCurrntDimX;LXC++)
					{
						const E3dCoordinate  LY0 = LFVertex[0].Position.Y;
						const E3dCoordinate  LY1 = LFVertex[1].Position.Y;
						const E3dCoordinate  LY2 = LFVertex[LCurrntDimX + 1].Position.Y;
						const E3dCoordinate  LY3 = LFVertex[LCurrntDimX + 2].Position.Y;

// Terrain roughness height-dependence
//
						E3dCoordinate  LY = (LY0 + LY1 + LY2 + LY3) * 0.25;

						LY = E3d_Abs(E3d_Pow(E3d_Abs(LY * LRoughnessScaler), (E3dCoordinate)1.5));
//						LY = E3d_Abs(-E3d_Cos(LY * E3dPI)) - 0.5;

						if(LY + (E_RandomDN(LRandomState2) - 0.5) * LUnitLength * 200.0 >= LRockLevel)
//						if(LY >= LRockLevel)
						{
							const E3dCoordinate  LAboveRockLevel = LY - LRockLevel;
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
							LMaxRoughness = LRockRoughness + LAboveRockLevel * 50.0;
						}
						else
						{
							LMaxRoughness = 1.0;
						}

						if(LY > LUnitLength * 4.0 * LMaxRoughness) LY = LUnitLength * 4.0 * LMaxRoughness;


						LFVertexD[0].Position.Y =            (LY0 * 9.0 + LY1 * 3.0 + LY2 * 3.0 + LY3) * LOneSixteenth + (_Random() - 0.5) * LUnitLength * 4.0 * LY;
						_MtlFromY(LFVertexD);

						LFVertexD[1].Position.Y =            (LY0 * 3.0 + LY1 * 9.0 + LY2 * 3.0 + LY3) * LOneSixteenth + (_Random() - 0.5) * LUnitLength * 4.0 * LY;
						_MtlFromY(LFVertexD + 1);

						LFVertexD[LNxtDimX].Position.Y =     (LY0 * 3.0 + LY1 + LY2 * 9.0 + LY3 * 3.0) * LOneSixteenth + (_Random() - 0.5) * LUnitLength * 4.0 * LY;
						_MtlFromY(LFVertexD + LNxtDimX);

						LFVertexD[LNxtDimX + 1].Position.Y = (LY0 + LY1 * 3.0 + LY2 * 3.0 + LY3 * 9.0) * LOneSixteenth + (_Random() - 0.5) * LUnitLength * 4.0 * LY;
						_MtlFromY(LFVertexD + LNxtDimX + 1);


						LFVertex++;
						LFVertexD += 2;
					}
					LFVertex++;
					LFVertexD += LNxtDimX;
				}


// Swap source and destination buffers
//
				if(LVerticesS == LVerticesA)
				{
					LVerticesS = LVerticesB;LVerticesD = LVerticesA;
				}
				else
				{
					LVerticesS = LVerticesA;LVerticesD = LVerticesB;
				}

				LUnitLength *= 0.5;
// Increment dimensions
//
				LCurrntDimX = LCurrntDimX * 2 - 1;LCurrntDimZ = LCurrntDimZ * 2 - 1;

				if(PInfo->ShowProgress)
				{
					E3dProgressIndicator_Set((float)(PInfo->ProgressStep) / (float)(PInfo->NumOfProgressSteps), NULL, (EPointer)NULL);XeApp_Sync(Xe_App);PInfo->ProgressStep += 1;
				}
			}
		}
#undef _Random


// Set Xs and Zs for the final vertices
//
		PEdgeXLen = PEdgeXLen * LDimX / L2Pow;
		PEdgeZLen = PEdgeZLen * LDimZ / L2Pow;

		const E3dCoordinate  LXStep = PEdgeXLen / (E3dCoordinate)LDimX;
		const E3dCoordinate  LZStep = PEdgeZLen / (E3dCoordinate)LDimZ;

		{
			EIndex  LZC = 0;E3dCoordinate  LZV = -PEdgeZLen * 0.5;E3dFrVertex*  LFVertex = LVerticesS;for(;LZC < LDimZ + 1;LZC++, LZV += LZStep)
			{
				EIndex  LXC = 0;E3dCoordinate  LXV = -PEdgeXLen * 0.5;for(;LXC < LDimX + 1;LXC++, LXV += LXStep)
				{
					LFVertex->Position.X = LXV;
					LFVertex->Position.Z = LZV;

					LFVertex++;
				}
			}
		}

		if(LNVertices != PMesh->Vertices.Count)
		{
			E3dMesh_VerticesFree(PMesh);
			E3dMesh_VerticesAdd(PMesh, LNVertices);
		}

		const E3dVertexSize  LVertexSize = PMesh->VertexSize;
		if(PMesh->Vertices.Count > 0)
		{
			{
				EIndex  LC = 0;const E3dFrVertex*  LFVertex = LVerticesS;E3dVertex*  LVertex = PMesh->Vertices.Items;for(;LC < LNVertices;LC++, LFVertex++, EStruct_Inc(LVertex, LVertexSize))
				{
					LVertex->Position = E3dCoord3_s(LFVertex->Position.X, LFVertex->Position.Y * LHeight, LFVertex->Position.Z);
				}
			}

			E3d_GridBuildEdges(LDimX, LDimZ,  0,  &PMesh->Edges.Items, &PMesh->Edges.Count, PInfo->Triangles);
			PMesh->Edges.CountAllocated = PMesh->Edges.Count;


//		EIndex  LWaterPolyNum = 0, LGrassPolyNum = 0, LSoilPolyNum = 0, LRockPolyNum = 0, LSnowPolyNum = 0;
			EIndex  LGrassPolyNum = 0, LRockPolyNum = 0;

			{
				const EIndex  LPointsX = LDimX + 1;
				EIndex  LCZ = 0, LRowA = 0, LNextRowA = LPointsX;for(;LCZ < LDimZ;LCZ++, LRowA += LPointsX, LNextRowA += LPointsX)
				{
					EIndex  LCX = 0;const E3dFrVertex*  LFVertex = LVerticesS + LRowA;for(;LCX < LDimX;LCX++, LFVertex++)
					{
						if(LFVertex->MaterialID == E3dFractalMAT_Rock) LRockPolyNum++;
						else if(LFVertex->MaterialID == E3dFractalMAT_Grass) LGrassPolyNum++;
						else
						{
//printf("%s() %s:%d  %d %d  %d\n", __FUNCTION__, __FILE__, __LINE__, LCX, LCZ, LFVertex->MaterialID);fflush(stdout);
						}
					}
				}
			}


//printf("%s() %s:%d  %lld + %lld = %lld  total should be %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LGrassPolyNum, (long long)LRockPolyNum, (long long)(LGrassPolyNum + LRockPolyNum), (long long)(LDimX * LDimZ));fflush(stdout);

			_ProcessPG(Grass);
			_ProcessPG(Rock);



//			if(PInfo->ShowProgress) { E3dProgressIndicator_Set((float)(PInfo->ProgressStep) / (float)(PInfo->NumOfProgressSteps), NULL, (EPointer)NULL);XeApp_Sync(Xe_App);PInfo->ProgressStep += 1; }

			LSuccess = TRUE;
		}

// Free vertex buffers
//
		EFreeChkClr(LVerticesA);
		EFreeChkClr(LVerticesB);
	}
	return(LSuccess);
}


//================================================
// Copy editable fields
//================================================
static void _CopyFields(const E3dTerrainInfo* PSrc, E3dTerrainInfo* PDst)
{
#define _CopyField(p)  PDst->p = PSrc->p
	_CopyField(RandomSeed);
	_CopyField(Type);
	_CopyField(EdgeLength);

	_CopyField(Height);

	_CopyField(WaterLevel);
	_CopyField(RockLevel);_CopyField(RockRoughness);
	_CopyField(SnowLevel);

	_CopyField(Iterations);
	_CopyField(Smooth);
	_CopyField(Triangles);
	_CopyField(ShowProgress);
	_CopyField(HiResWater);
#undef _CopyField
}


//========================================
// Update() (create/change) method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PComponentTemplate,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	if(PCheckOnly) return(NULL);
	if(E3d_Busy) return(NULL);
	while(E3d_Busy) XeApp_Sync(Xe_App);

	E3dMesh*         LMesh;
	E3dTerrainInfo*  LInfo;
	EBool            LGoOn = FALSE;

	if(PObject == NULL)
	{
		E_New(E3dMesh, LMesh);
		PObject = (E3dObject*)LMesh;

		LInfo = (E3dTerrainInfo*)E3dObjectComponent_Add((E3dObject*)LMesh, _ComponentClass);
		if(PComponentTemplate && PComponentTemplate->Class == _ComponentClass) _CopyFields((E3dTerrainInfo*)PComponentTemplate, LInfo);
	}
	else
	{
		if(!EObject_IsSubClass(PObject, E3dMesh)) return(NULL);

		LMesh = (E3dMesh*)PObject;
		LInfo = (E3dTerrainInfo*)E3dObjectComponent_ByClass((E3dObject*)LMesh, _ComponentClass, FALSE);
		if(!PCheckOnly && LInfo->AttributeChangeCount == 0 && LInfo->InputChangeCount == 0) return(NULL);    // If no attributes or inputs changed, bail
	}

	if(_WaterMaterial == NULL)
	{
		E3dScene*  LScene = E3d_Scene;
		E3dMaterial*  LMaterial;

		LMaterial = E3dMaterial_New(E3dPhongShader_class);if(LMaterial)
		{
			E3dPhongShader*  LShader = (E3dPhongShader*)(LMaterial->Shader);

			EObject_SetName(LMaterial, EStrDup("Water"), FALSE);
			EcColor_InitRGB(&LShader->Diffuse,   0.0, 0.2, 0.3);
			EcColor_InitRGB(&LShader->Specular,  0.0, 0.1, 0.2);
			E3dMaterial_UpdateForDisplay(LMaterial, NULL, E3dChgMaterialTYPE, E3dTxtUF_ALL);
			LShader->Shininess = 16.0;

			E_Assign(LMaterial, _WaterMaterial);
			E3dScene_MaterialAdd(LScene, LMaterial);
		}

		LMaterial = E3dMaterial_New(E3dLambertShader_class);if(LMaterial)
		{
			E3dLambertShader*  LShader = (E3dLambertShader*)(LMaterial->Shader);

			EObject_SetName(LMaterial, EStrDup("Grass"), FALSE);
			EcColor_InitRGB(&LShader->Diffuse,  0.1, 0.4, 0.2);
			E3dMaterial_UpdateForDisplay(LMaterial, NULL, E3dChgMaterialTYPE, E3dTxtUF_ALL);

			E_Assign(LMaterial, _GrassMaterial);
			E3dScene_MaterialAdd(LScene, LMaterial);
		}

		LMaterial = E3dMaterial_New(E3dLambertShader_class);if(LMaterial)
		{
			E3dLambertShader*  LShader = (E3dLambertShader*)(LMaterial->Shader);

			EObject_SetName(LMaterial, EStrDup("Soil"), FALSE);
			EcColor_InitRGB(&LShader->Diffuse,  0.35, 0.28, 0.21);
			E3dMaterial_UpdateForDisplay(LMaterial, NULL, E3dChgMaterialTYPE, E3dTxtUF_ALL);

			E_Assign(LMaterial, _SoilMaterial);
			E3dScene_MaterialAdd(LScene, LMaterial);
		}

		LMaterial = E3dMaterial_New(E3dLambertShader_class);if(LMaterial)
		{
			E3dLambertShader*  LShader = (E3dLambertShader*)(LMaterial->Shader);

			EObject_SetName(LMaterial, EStrDup("Rock"), FALSE);
			EcColor_InitRGB(&LShader->Diffuse,  0.5, 0.42, 0.35);
			E3dMaterial_UpdateForDisplay(LMaterial, NULL, E3dChgMaterialTYPE, E3dTxtUF_ALL);

			E_Assign(LMaterial, _RockMaterial);
			E3dScene_MaterialAdd(LScene, LMaterial);
		}

		LMaterial = E3dMaterial_New(E3dLambertShader_class);if(LMaterial)
		{
			E3dLambertShader*  LShader = (E3dLambertShader*)(LMaterial->Shader);

			EObject_SetName(LMaterial, EStrDup("Snow"), FALSE);
			EcColor_InitRGB(&LShader->Diffuse,  0.8, 0.8, 0.8);
			E3dMaterial_UpdateForDisplay(LMaterial, NULL, E3dChgMaterialTYPE, E3dTxtUF_ALL);

			E_Assign(LMaterial, _SnowMaterial);
			E3dScene_MaterialAdd(LScene, LMaterial);
		}
	}

// Free Mesh Vertices, Edges and Polygons
//
	E3dMesh_FreeCaches(LMesh);
	E3dMesh_PolygonsFree(LMesh);
	E3dMesh_EdgesFree(LMesh);
	E3dMesh_VerticesFree(LMesh);

	if(LInfo->ShowProgress)
	{
		E3dProgressIndicator_SetMessage("Building terrain");
	}
	LInfo->ProgressStep = 0;


	if(LMesh->PolyGroups.Count < 5)
	{
		if(LMesh->PolyGroups.Count == 0)
		{
			E3dPolyGroup*  LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);EObject_SetName(LPolyGroup, EStrDup("waterPG"), FALSE);
			E_AssignToMember(_WaterMaterial, LPolyGroup,Material);
		}

		if(LMesh->PolyGroups.Count == 1)
		{
			E3dPolyGroup*  LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);EObject_SetName(LPolyGroup, EStrDup("grassPG"), FALSE);
			E_AssignToMember(_GrassMaterial, LPolyGroup,Material);
		}

		if(LMesh->PolyGroups.Count == 2)
		{
			E3dPolyGroup*  LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);EObject_SetName(LPolyGroup, EStrDup("soilPG"), FALSE);
			E_AssignToMember(_SoilMaterial, LPolyGroup,Material);
		}

		if(LMesh->PolyGroups.Count == 3)
		{
			E3dPolyGroup*  LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);EObject_SetName(LPolyGroup, EStrDup("rockPG"), FALSE);
			E_AssignToMember(_RockMaterial, LPolyGroup,Material);
		}

		if(LMesh->PolyGroups.Count == 4)
		{
			E3dPolyGroup*  LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);EObject_SetName(LPolyGroup, EStrDup("snowPG"), FALSE);
			E_AssignToMember(_SnowMaterial, LPolyGroup,Material);
		}
	}

	switch(LInfo->Type)
	{
		case E3dFRACTAL_MIDPOINT_TRIANGLE:
			LInfo->NumOfProgressSteps = LInfo->Iterations + 3 + 4;
			LGoOn = _MidPointTriangle(LMesh, LInfo);

			if(LGoOn)
			{
				if(LInfo->ShowProgress)
				{
					E3dProgressIndicator_Set((float)(LInfo->ProgressStep) / (float)(LInfo->NumOfProgressSteps), NULL, (EPointer)NULL);XeApp_Sync(Xe_App);LInfo->ProgressStep += 1;
				}
				E3dMesh_CreateEdgesFromPolygons(LMesh, NULL, FALSE, FALSE);
			}
		break;

		case E3dFRACTAL_SQUARE_SQUARE:
			LInfo->NumOfProgressSteps = LInfo->Iterations + 1 + 3;
			LGoOn = _SquareSquareSubdiv(LMesh, LInfo->EdgeLength, LInfo->EdgeLength, 2, 2, LInfo);
			E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, FALSE, FALSE);
		break;
	}

	if(LInfo->ShowProgress)
	{
		E3dProgressIndicator_Set((float)LInfo->ProgressStep / (float)LInfo->NumOfProgressSteps, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);LInfo->ProgressStep += 1;
	}

	if(LGoOn)
	{
		E3dMesh_GLRefreshPolyVertices(LMesh);
		E3dMesh_TextureMappingUpdate(LMesh, TRUE);
		if(LInfo->ShowProgress)
		{
			E3dProgressIndicator_Set((float)LInfo->ProgressStep / (float)LInfo->NumOfProgressSteps, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);LInfo->ProgressStep += 1;
		}
	}

	E3dProgressIndicator_Set(-1.0, NULL, (EPointer)NULL);

	if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags = E3dUpdateALL & ~E3dUpdateEDGES;
	LInfo->AttributeChangeCount = LInfo->InputChangeCount = 0;

	E3d_Busy = FALSE;
	return(PObject);
}


//========================================
// Dialog-box callbacks
//========================================
static void _DCB_SetType(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;if(LObject == NULL) return;
	E3dTerrainInfo*  LInfo = (E3dTerrainInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);if(LInfo == NULL) return;
	EChangeFlags     LChanged = 0;

	const int  LType = (EPointerInt)(((XeOptionMenuCallbackStruct*)PCallData)->Value);
	if(LInfo->Type != LType)
	{
		EList_FreeChk(LInfo->FrVertices);
		LInfo->Type = LType;

		LInfo->AttributeChangeCount++;
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
}


static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*                LScene = E3d_Scene;
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*               LObject = LPanel->Object;if(LObject == NULL) return;
	E3dTerrainInfo*          LInfo = (E3dTerrainInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);if(LInfo == NULL) return;
	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _ComponentClass);if(LDrawer == NULL) return;
	E3dResourcesGUI*         LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;
	EChangeFlags             LChanged = 0;

	if(PW == LGUI->EdgeLengthW)
	{
		LInfo->EdgeLength = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
		LInfo->AttributeChangeCount++;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->HeightW)
	{
		LInfo->Height = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
		LInfo->AttributeChangeCount++;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->IterationsW)
	{
		switch(LInfo->Type)
		{
			case E3dFRACTAL_MIDPOINT_TRIANGLE:
			case E3dFRACTAL_SQUARE_SQUARE:
				LInfo->Iterations = ((XeScaleCallbackStruct*)PCallData)->Value.i32;
				LInfo->AttributeChangeCount++;
				LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
			break;
		}
	}
	else if(PW == LGUI->RandomSeedW)
	{
		switch(LInfo->Type)
		{
			case E3dFRACTAL_MIDPOINT_TRIANGLE:
			case E3dFRACTAL_SQUARE_SQUARE:
				LInfo->RandomSeed = ((XeScaleCallbackStruct*)PCallData)->Value.i32;
				LInfo->AttributeChangeCount++;
				LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
			break;
		}
	}
	else if(PW == LGUI->SmoothW)
	{
		E3dMesh*  LMesh = (E3dMesh*)LPanel->Object;
		LInfo->Smooth = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

		EIndex  LC = 0;const EIndex  LN = LMesh->PolyGroups.Count;for(;LC < LN;LC++)
		{
			E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LC];

			if(LInfo->Smooth)
			{
				LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
				LPolyGroup->SmoothingAngle = 180.0;
				LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
				E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
				E3dMeshPolyGroup_RefreshNormals(LMesh, LPolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);
			}
			else
			{
				E3dPolyGroup_VertexAttributeDel(LPolyGroup,  E3dVtxAttribute_Normal, 0);
				LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
				LPolyGroup->SmoothingAngle = 180.0;
				LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
			}

			LChanged |= E3dChgVERTEX_NORMAL;
//		E3dMeshPolyGroup_RefreshPolygonNormals(LMesh, LPolyGroup);
	}

		E3dMesh_GLRefreshPolyVertices(LMesh);
	}
	else if(PW == LGUI->TrianglesW)
	{
		LInfo->Triangles = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		LInfo->AttributeChangeCount++;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->ShowProgressW)
	{
		LInfo->ShowProgress = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	}
	else if(PW == LGUI->WaterLevelW)
	{
		LInfo->WaterLevel = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
		LInfo->AttributeChangeCount++;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->RockLevelW)
	{
		LInfo->RockLevel = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
		LInfo->AttributeChangeCount++;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->SnowLevelW)
	{
		LInfo->SnowLevel = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
		LInfo->AttributeChangeCount++;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


//========================================
// Customize Scale widget
//========================================
#define _SetLevelScale() do\
{\
	EXeSetArg(XeNTextFieldDigits, 5);\
	EXeSetArg(XeNScaleType, XeTHERMOMETER);\
	EXeSetArg(XeNThermoShadowThickness, 2);\
} while(0)


//========================================
// GUI
//========================================
static void _GUI(E3dObject* LObject, E3dObjectComponent* LInfoIn, void* LPanelIn, void* LDrawerIn, int LAction)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)LPanelIn;
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)LDrawerIn;
	E3dTerrainInfo*          LInfo = (E3dTerrainInfo*)LInfoIn;

	switch(LAction)
	{
		case E_UPDATE:
			if(LDrawer->ResourcesGUI)
			{
				E3dResourcesGUI*  LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;

				XeOptionMenu_SetOptionByValue(LGUI->TypesOptionMenuW, (EPointer)(EPointerInt)LInfo->Type, FALSE);

				XeScale_SetValue_f32(LGUI->EdgeLengthW, LInfo->EdgeLength, FALSE);
				XeScale_SetValue_f32(LGUI->HeightW, LInfo->Height, FALSE);

				XeScale_SetValue_f32(LGUI->WaterLevelW, LInfo->WaterLevel, FALSE);
				XeScale_SetValue_f32(LGUI->RockLevelW, LInfo->RockLevel, FALSE);
				XeScale_SetValue_f32(LGUI->SnowLevelW, LInfo->SnowLevel, FALSE);

				XeScale_SetValue(LGUI->IterationsW, LInfo->Iterations, FALSE);
				XeScale_SetValue(LGUI->RandomSeedW, LInfo->RandomSeed, FALSE);

				XeToggleButton_SetState(LGUI->SmoothW, LInfo->Smooth, FALSE);
				XeToggleButton_SetState(LGUI->TrianglesW, LInfo->Triangles, FALSE);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LHMatrixW, LLeftMatrixW, LMatrixW, LW;
				XeWidget  LIndentWs[7];int  LIndentN;      // For indenting the ScaleWidgets
				XeWidget  LTraverseWs[7];int  LTraverseN;  // For TextField traversal
				EArg      Args[256];int  ArgCnt;

				E3dResourcesGUI*  LGUI;ENew(LGUI);LDrawer->ResourcesGUI = LGUI;

				EXeStart;
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				LHMatrixW = XeWidget_ManagedNew("HMatrixW", XeMatrixWidget_class, LTopMatrixW, Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNOrientation, XeVERTICAL);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNSpacingY, 0);
				EXeSetArg(XeNResizableY, FALSE);EXeSetArg(XeNAlignmentY, XeAlignBEGINNING);
				LLeftMatrixW = XeWidget_ManagedNew("LeftMatrixW", XeMatrixWidget_class, LHMatrixW, Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 12);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				LMatrixW = XeWidget_ManagedNew("LMatrixW", XeMatrixWidget_class, LLeftMatrixW, Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Smooth);
				LGUI->SmoothW = LW = XeWidget_ManagedNew("Smooth", XeToggleButton_class, LMatrixW, Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Triangles);
				LGUI->TrianglesW = LW = XeWidget_ManagedNew("Triangles", XeToggleButton_class, LMatrixW, Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);


				const int  LSpacingR = 2;

				EXeStart;
				EXeSetArg(XeNSpacingRight, LSpacingR);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNButtonColorPtr, &Xe_ButtonRGBAiColor);
				EXeSetArg(XeNFontList, XeMenu_FontList);
				EXeSetArg(XeNLabelFontList, Xe_LabelFontList);
				EXeSetArg(XeNMenuPaneBackgroundColor, &XeOptionMenu_RGBAiColor);
				LGUI->TypesOptionMenuW = XeOptionMenu_CreateAndInitManaged(LMatrixW, NULL, _Types, sizeof(_Types) / sizeof(XeOptionMenuRec), (EPointer)((EPointerInt)(LInfo->Type)), "Type", _DCB_SetType, (EPointer)LPanel, Args, ArgCnt);


				EXeStart;
				EXeSetArg(XeNSpacingTop, 4);
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LLeftMatrixW, Args, ArgCnt);


				LTraverseN = 0;
				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNSpacingRight, LSpacingR);
				EXeSetArg(XeNSpacingTop, 2);
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 4);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->EdgeLengthW = LW = XeScale_ManagedNew_f32(LLeftMatrixW, "Width", Args, ArgCnt);
				XeScale_SetValueAndRanges_f32(LW, LInfo->EdgeLength, 0.001, 1000000.0,  0.1, 100.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNSpacingRight, LSpacingR);
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->HeightW = LW = XeScale_ManagedNew_f32(LLeftMatrixW, "Height", Args, ArgCnt);
				XeScale_SetValueAndRanges_f32(LW, LInfo->Height, 0.001, 1000000.0,  0.1, 100.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);


				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNSpacingRight, LSpacingR);
				EXeSetArg(XeNTextFieldDigits, 8);
				EXeSetArg(XeNMinimum, 1);EXeSetArg(XeNMaximum, 12);EXeSetArg(XeNScaleMaximum, 11);EXeSetArg(XeNValue, LInfo->Iterations);
				EXeSetArg(XeNIncrement, 1);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->IterationsW = LW = XeWidget_ManagedNew("Iterations", XeScaleWidget_class, LLeftMatrixW, Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNSpacingRight, LSpacingR);
				EXeSetArg(XeNTextFieldDigits, 8);
				EXeSetArg(XeNMinimum, 2);EXeSetArg(XeNMaximum, 100000);EXeSetArg(XeNValue, LInfo->RandomSeed);
				EXeSetArg(XeNIncrement, 1);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->RandomSeedW = LW = XeWidget_ManagedNew("Random seed", XeScaleWidget_class, LLeftMatrixW, Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);

				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNSpacingTop, 2);
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LLeftMatrixW, Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				LMatrixW = XeWidget_ManagedNew("LMatrixW", XeMatrixWidget_class, LLeftMatrixW, Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LInfo->ShowProgress);
				EXeSetArg(XeNResizableX, FALSE);
				LGUI->ShowProgressW = LW = XeWidget_ManagedNew("Show progress", XeToggleButton_class, LMatrixW, Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNOrientation, XeVERTICAL);
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNResizableX, FALSE);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LHMatrixW, Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNThermoColorPtr, &_WaterLevelColor);
				EXeSetArg(XeNDecimalPoints, 2);
				EXeSetArg(XeNOrientation, XeVERTICAL);
				_SetLevelScale();
				EXeSetArg(XeNResizableX, FALSE);
				LTraverseWs[LTraverseN++] = LGUI->WaterLevelW = LW = XeScale_ManagedNew_f32(LHMatrixW, "Water", Args, ArgCnt);
				XeScale_SetValueAndRanges_f32(LW, LInfo->WaterLevel, -10.0, 1000000.0,  -10.0, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNThermoColor, &_RockLevelColor);
				EXeSetArg(XeNDecimalPoints, 2);
				EXeSetArg(XeNOrientation, XeVERTICAL);
				_SetLevelScale();
				EXeSetArg(XeNResizableX, FALSE);
				LTraverseWs[LTraverseN++] = LGUI->RockLevelW = LW = XeScale_ManagedNew_f32(LHMatrixW, "Rock", Args, ArgCnt);
				XeScale_SetValueAndRanges_f32(LW, LInfo->RockLevel, -10.0, 1000000.0,  -10.0, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNThermoColor, &_SnowLevelColor);
				EXeSetArg(XeNDecimalPoints, 2);
				EXeSetArg(XeNOrientation, XeVERTICAL);
				_SetLevelScale();
				EXeSetArg(XeNResizableX, FALSE);
				LTraverseWs[LTraverseN++] = LGUI->SnowLevelW = LW = XeScale_ManagedNew_f32(LHMatrixW, "Snow", Args, ArgCnt);
				XeScale_SetValueAndRanges_f32(LW, LInfo->SnowLevel, -10.0, 1000000.0,  -10.0, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

// Set up TextField traversal
//
				XeTraversal_SetRing(LTraverseWs, LTraverseN);
			}
		break;
	}
}


//================================================
// Menu callback (create new terrain)
//================================================
static void _MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*   LScene = E3d_Scene;
	E3dObject*  LObject = NULL;

  E3dModel*  LModel = E3dModel_CustomObjectNew(E3d_Scene, (E3dObjectComponentClass*)_ComponentClass, &LObject, (E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME) != 0);if(LModel)
	{
		E3dGeometry*  LGeometry = (E3dGeometry*)LObject;

		LGeometry->AutoLOD = FALSE;

		E3dScene_NodeHrcAdd(E3d_Scene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);
	}

	if(LObject)
	{
		if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) E3dObject_Edit(LObject, _ComponentClass);
		E3dWindows_RedrawByChangeFlags(LScene, E3dObject_GetChangeFlagsByClass(LObject)|E3dChgHIERARCHY);
	}
}


//================================================
// Constructor
//================================================
static int _Init(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dTerrainInfo*  LThis = (E3dTerrainInfo*)PInfo;

	EList_Init(LThis->FrVertices);
	LThis->RandomSeed = 33334;
//	LThis->RandomSeed = 42308;
//	LThis->RandomSeed = 15791;
//	LThis->RandomSeed = 52913;  // Nice rocks and a crater
	LThis->Type = E3dFRACTAL_SQUARE_SQUARE;

	LThis->EdgeLength = 100.0;
	LThis->Height     = 1.0;

	LThis->WaterLevel = 0.5;
	LThis->RockLevel  = 1.0;LThis->RockRoughness = 5000.0;
	LThis->SnowLevel  = 3.0;

	LThis->Iterations   = 7;
	LThis->Smooth       = TRUE;
	LThis->Triangles    = FALSE;
	LThis->ShowProgress = TRUE;
	LThis->HiResWater   = FALSE;

	return(E_SUCCESS);
}


//========================================
// Copy-constructor
//========================================
static void _Copy(const E3dObject* PSObject, const E3dObjectComponent* PSInfo,  E3dObject* PDObject, E3dObjectComponent* PDInfoIn)
{
	const E3dTerrainInfo*  LSrc = (E3dTerrainInfo*)PSInfo;
	E3dTerrainInfo*        LDst = (E3dTerrainInfo*)PDInfoIn;

	EList_Init(LDst->FrVertices);

	_CopyFields(LSrc, LDst);
}


//================================================
// Constructor
//================================================
static int _Free(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dTerrainInfo*  LThis = (E3dTerrainInfo*)PInfo;

	EList_FreeChk(LThis->FrVertices);

	return(E_SUCCESS);
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;

	{
		E3dObjectComponentClass  LComponentClass=
		{
			.Name=        "Fractal terrain",
			.StructSize=  sizeof(E3dTerrainInfo),
			.Resources=   EListS_s(_Resources),

			.Init=        _Init,
			.Copy=        _Copy,
			.Free=        _Free,

			.Update1=      _Update1,
			.GUI=         _GUI
		};

		_ComponentClass = E3dObjectComponent_ClassRegister(&LComponentClass);if(_ComponentClass)
		{
			EpM_CreateIconStates3_sp(Terrain);E_Assign(_Image_Terrain, _ComponentClass->Icon);
			XePushButton_AddImg3(_ToolBarButton, Terrain, "Tool->Create", "Terrain", '\0', NULL, NULL, TRUE, "Create fractal terrain", _MCB, (EPointer)0);
			_MenuButton = XePushButton_Add(Xe_App, "Menu->Create", "Fractal terrain", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)0);
		}
	}

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		XeItemFree(_ToolBarButton);
		XeItemFree(_MenuButton);
	}

	if(_ComponentClass)
	{
		E3dObjectComponent_ClassDeactivate(_ComponentClass);_ComponentClass = NULL;
	}

	E_DeleteChk(_WaterMaterial);
	E_DeleteChk(_GrassMaterial);
	E_DeleteChk(_SoilMaterial);
	E_DeleteChk(_RockMaterial);
	E_DeleteChk(_SnowMaterial);


	EpIconFree3(Terrain);

	return(E_SUCCESS);
}
