#include "../../Scene/Terrain/RuEntityOptimalTerrain.h"

#include "../../Helper/RuHelper_Entity.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuWorld_Backdrop, IRuEntity_Renderable)
ruHEAP_DEFINE_SUBCLASS(CRuWorld_Backdrop, "CRuWorld_Backdrop", "IRuEntity_Renderable")
ruCLASSGUID_DEFINE(CRuWorld_Backdrop, 0x7E6810AB, 0x6D214f2e, 0xB609794D, 0xE03D0193)

ruRTTI_DEFINE_SUBCLASS(CRuOptimalTerrain_Chunk, IRuEntity_Renderable)
ruHEAP_DEFINE_SUBCLASS(CRuOptimalTerrain_Chunk, "CRuOptimalTerrain_Chunk", "IRuEntity_Renderable")
ruCLASSGUID_DEFINE(CRuOptimalTerrain_Chunk, 0xB998108E, 0x642643e0, 0x9C2AA68C, 0x5B9AEF03)

ruRTTI_DEFINE_SUBCLASS(IRuOptimalTerrain_DataSource, IRuObject)
ruHEAP_DEFINE_SUBCLASS(IRuOptimalTerrain_DataSource, "IRuOptimalTerrain_DataSource", "IRuObject")
ruCLASSGUID_DEFINE(IRuOptimalTerrain_DataSource, 0xBD950906, 0xC73D402f, 0x97E1DBAB, 0x2EE3B408)

ruRTTI_DEFINE_SUBCLASS(CRuOptimalTerrain_DataSource_DS, IRuOptimalTerrain_DataSource)
ruHEAP_DEFINE_SUBCLASS(CRuOptimalTerrain_DataSource_DS, "CRuOptimalTerrain_DataSource_DS", "IRuOptimalTerrain_DataSource")
ruCLASSGUID_DEFINE(CRuOptimalTerrain_DataSource_DS, 0x27269EB9, 0x3D544baf, 0x9EB00671, 0xDB75F4AA)

ruRTTI_DEFINE_SUBCLASS(CRuOptimalTerrain, IRuEntity_Container)
ruHEAP_DEFINE_SUBCLASS(CRuOptimalTerrain, "CRuOptimalTerrain", "IRuEntity_Container")
ruCLASSGUID_DEFINE(CRuOptimalTerrain, 0xC3F9DBB3, 0x21C445c2, 0x9686D314, 0x22D1AE0B)

// ************************************************************************************************************************************************************

CRuWorld_Backdrop::CRuWorld_Backdrop(INT32 vertexAllocation, INT32 primitiveAllocation)
:	m_actualVertexCount(0),
	m_actualPrimitiveCount(0),
	m_mesh(NULL),
	m_material(NULL)
{
	// Create mesh
	m_mesh = ruNEW CRuMesh();
	m_mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, vertexAllocation, primitiveAllocation, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE);

	// Clear all batches
	m_mesh->ClearBatches();

	// Create default material
	m_material = ruNEW CRuMaterial();
	m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED_BACKDROP"));
}

CRuWorld_Backdrop::~CRuWorld_Backdrop()
{
	ruSAFE_RELEASE(m_mesh);
	ruSAFE_RELEASE(m_material);
}

BOOL CRuWorld_Backdrop::InsertChunk(UINT32 chunkKey, CRuOptimalTerrain_Chunk *chunk)
{
	if(ContainsChunk(chunkKey))
	{
		return TRUE;
	}

	IRuMesh *mesh;
	IRuMaterial *material;

	if(chunk->GetMesh(0, &mesh, &material))
	{
		INT32 numChunkVertices = mesh->GetNumVertices();
		INT32 numChunkPrimitives = mesh->GetNumPrimitives();
		INT32 numChunkIndices = mesh->GetNumIndices();

		if(	numChunkVertices > m_mesh->GetNumVertices() - m_actualVertexCount ||
			numChunkPrimitives > m_mesh->GetNumPrimitives() - m_actualPrimitiveCount)
		{
			return FALSE;
		}

		INT32 startingVertex = m_actualVertexCount;
		INT32 startingIndex = m_actualPrimitiveCount * 3;

		// Copy
		const CRuVector3 *srcPos = mesh->GetMorphTarget(0)->GetPosition();
		CRuVector3 *dstPos = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetPosition();

		const DWORD *srcColor = mesh->GetMorphTarget(0)->GetDiffuse();
		DWORD *dstColor = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetDiffuse();

		for(INT32 i = 0, j = startingVertex; i < numChunkVertices; ++i, ++j)
		{
			dstPos[j] = srcPos[i];
			dstColor[j] = srcColor[i];
		}

		const UINT16 *srcIndices = mesh->GetIndices();
		UINT16 *dstIndices = m_mesh->GetIndices();

		for(INT32 i = 0, j = startingIndex; i < numChunkIndices; ++i, ++j)
		{
			dstIndices[j] = srcIndices[i] + startingVertex;
		}

		// Adjust vertex and primitive count
		m_actualVertexCount += numChunkVertices;
		m_actualPrimitiveCount += numChunkPrimitives;

		// Initialize batches
		m_mesh->ClearBatches();
		m_mesh->AddBatch(0, m_actualPrimitiveCount);
		m_mesh->IncrementMeshIteration();
		m_mesh->IncrementTopologyIteration();

		// Create chunk descriptor for inserted chunk
		INT32 descIndex = m_containedChunks.Count();
		m_containedChunks.Add(ChunkDescriptor());
		m_containedChunks[descIndex].m_chunkKey = chunkKey;

		if(m_containedChunks.Count() == 1)
		{
			m_boundingBox = chunk->GetBoundingBox();
		}
		else
		{
			m_boundingBox.Merge(chunk->GetBoundingBox());
		}

		m_mesh->SetBounds(CRuSphere(CRuVector3((m_boundingBox.Maxima() + m_boundingBox.Minima()) / 2.0f), (m_boundingBox.Maxima() - m_boundingBox.Minima()).Magnitude()));
		this->SetLocalEntityBounds(m_mesh->GetBounds());
	}

	return TRUE;
}

BOOL CRuWorld_Backdrop::ContainsChunk(UINT32 chunkKey)
{
	for(INT32 i = 0; i < m_containedChunks.Count(); ++i)
	{
		if(m_containedChunks[i].m_chunkKey == chunkKey)
		{
			return TRUE;
		}
	}

	return FALSE;
}

const CRuAABB &CRuWorld_Backdrop::GetBoundingBox() const
{
	return m_boundingBox;
}

INT32 CRuWorld_Backdrop::GetNumMeshes() const
{
	if(m_mesh && m_material)
	{
		return 1;
	}

	return 0;
}

BOOL CRuWorld_Backdrop::GetMesh(INT32 meshIndex, IRuMesh **meshOut, IRuMaterial **materialOut)
{
	if(meshIndex == 0 && m_mesh && m_material)
	{
		*meshOut = m_mesh;
		*materialOut = m_material;

		return TRUE;
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

CRuOptimalTerrain_Chunk::CRuOptimalTerrain_Chunk()
:	m_dataValidated(FALSE),
	m_dimension(64),
	m_gridSize(10.0f),
	m_lodLevels(1),
	m_numVertices(0),
	m_numIndices(0),
	m_vertexFlags(NULL),
	m_mesh(NULL),
	m_colStructure(NULL),
	m_collisionMesh(NULL),
	m_lodMeshes(NULL),
	m_currentLOD(0),
	m_splatIDMap(NULL),
	m_alphaTextureLOD(0)
{
	m_material = ruNEW CRuMaterial();
	m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED"));
//	m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED_PROJECTION"));
}

CRuOptimalTerrain_Chunk::~CRuOptimalTerrain_Chunk()
{
	delete[] m_vertexFlags;
	ruSAFE_RELEASE(m_mesh);
	ruSAFE_RELEASE(m_colStructure);
	ruSAFE_RELEASE(m_material);
	ruSAFE_RELEASE(m_collisionMesh);
	ruSAFE_RELEASE(m_splatIDMap);

	if(m_lodMeshes)
	{
		for(INT32 i = 0; i < m_lodLevels; ++i)
		{
			ruSAFE_RELEASE(m_lodMeshes[i])
		}

		delete[] m_lodMeshes;
	}

	for(INT32 i = 0; i < m_waterLayers.Count(); ++i)
	{
		ruSAFE_RELEASE(m_waterLayers[i]);
	}
}

BOOL CRuOptimalTerrain_Chunk::MakeDataConformant(RuOptimalTerrainChunkLoadMode loadMode)
{
	switch(loadMode)
	{
		case ruOPTIMALTERRAINCHUNKLOADMODE_ANY:
			{
				if(m_numVertices <= 0 || m_numIndices <= 0 || m_mesh == NULL)
					return FALSE;
			}

			break;

		case ruOPTIMALTERRAINCHUNKLOADMODE_FULL:
			{
				if(m_numVertices <= 0 || m_numIndices <= 0 || m_mesh == NULL)
					return FALSE;

				if(m_splatIDMap == NULL)
					return FALSE;
			}

			break;

		case ruOPTIMALTERRAINCHUNKLOADMODE_PARTIAL:
			{
				if(m_numVertices <= 0 || m_numIndices <= 0 || m_mesh == NULL)
					return FALSE;

				ReleaseSplatIDMap();
			}

			break;

		case ruOPTIMALTERRAINCHUNKLOADMODE_COLLISION:
			{
				if(m_numVertices <= 0 || m_numIndices <= 0 || m_mesh == NULL)
					return FALSE;
			}

			break;
	}

	return TRUE;
}

BOOL CRuOptimalTerrain_Chunk::SerializeFrom(IRuStream *stream, RuOptimalTerrainChunkLoadMode loadMode)
{
	CRuGUID classGUID;

	// Read class GUID
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

	// Read chunk iteration
	stream->Read(&m_chunkIteration, sizeof(INT32));

	// Read chunk dimension
	stream->Read(&m_dimension, sizeof(INT32));

	// Read grid size
	stream->Read(&m_gridSize, sizeof(float));

	// Read default number of lod levels
	stream->Read(&m_lodLevels, sizeof(INT32));

	// Read chunk bounds
	stream->Read(&m_bounds_AABB.Minima().x, sizeof(float));
	stream->Read(&m_bounds_AABB.Minima().y, sizeof(float));
	stream->Read(&m_bounds_AABB.Minima().z, sizeof(float));
	stream->Read(&m_bounds_AABB.Maxima().x, sizeof(float));
	stream->Read(&m_bounds_AABB.Maxima().y, sizeof(float));
	stream->Read(&m_bounds_AABB.Maxima().z, sizeof(float));

	// Read coordinate offset values
	stream->Read(&m_origin.m_x, sizeof(float));
	stream->Read(&m_origin.m_z, sizeof(float));

	switch(loadMode)
	{
		case ruOPTIMALTERRAINCHUNKLOADMODE_MINIMAL:
			{
				m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_TERRAIN_PACKED_BACKDROP"));

				INT32 lodDimension = m_dimension;
				INT32 lodStride = 1;
				REAL lodGridSize = m_gridSize;
				for(INT32 lod = 0; lod < (m_lodLevels - 1); ++lod, lodDimension /= 2, lodStride *= 2, lodGridSize *= 2.0f);

				// Number of vertices = (LOD dimension + 1)^2 + (LOD dimension + 1) * 4 + (LOD dimension + 1) * 4
				m_numVertices = (lodDimension + 1) * (lodDimension + 1) + (lodDimension + 1) * 4;

				// Number of indices at the LOD level = (LOD dimension^2) * 6 + skirt indices(LOD dimension * 6 * 4)
				m_numIndices += ((lodDimension * lodDimension * 2) * 3) + (((lodDimension * 2) * 3) * 4);

				// Construct mesh object
				m_mesh = ruNEW CRuMesh();
				m_mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, m_numVertices, m_numIndices / 3, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE);

				// Allocate space for vertex flags
				m_vertexFlags = ruNEW BYTE [m_numVertices];

				// Read height values and normal/vertex data
				INT32 numHeightValues = (m_dimension + 1) * (m_dimension + 1) + (m_dimension + 1) * 4;
				REAL *heightField = ruNEW REAL [numHeightValues];
				DWORD *vertexData = ruNEW DWORD [numHeightValues];
				stream->Read(heightField, sizeof(REAL) * numHeightValues);
				stream->Read(vertexData, sizeof(DWORD) * numHeightValues);

				// Setup XYZ, diffuse, and vertex flags
				CRuVector3 *vertices = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetPosition();
				UINT16 *indices = m_mesh->GetIndices();
				DWORD *diffuse = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetDiffuse();

				for(INT32 z = 0, vertexIndex = 0, i = 0; z <= lodDimension; ++z)
				{
					float xF = m_origin.m_x;
					float zF = lodGridSize * z + m_origin.m_z;
					for(INT32 x = 0; x <= lodDimension; ++x, ++vertexIndex, xF += lodGridSize)
					{
						// Set XZ position
						vertices[vertexIndex].m_x = xF;
						vertices[vertexIndex].m_y = heightField[x * lodStride + (z * lodStride) * (m_dimension + 1)];
						vertices[vertexIndex].m_z = zF;
						diffuse[vertexIndex] = vertexData[x * lodStride + (z * lodStride) * (m_dimension + 1)];
						m_vertexFlags[vertexIndex] = (BYTE) ((diffuse[vertexIndex] & 0xFF000000) >> 24);
					}
				}

				// Alter settings to fit the lowest LOD
				m_dimension = lodDimension;
				m_gridSize = lodGridSize;
				m_lodLevels = 1;

				// Update skirt vertices
				UpdateSkirtVertices();

				// Rebuild indices
				BuildChunkTopology(TRUE, FALSE);

				delete[] heightField;
				delete[] vertexData;
			}

			break;
		default:
			{
				// If the mesh has not yet been loaded, load it now
				if(m_numVertices <= 0 || m_numIndices <= 0 || m_mesh == NULL)
				{
					// Number of vertices = (m_dimension + 1)^2 + (m_dimension + 1) * 4
					m_numVertices = (m_dimension + 1) * (m_dimension + 1) + (m_dimension + 1) * 4;

					// Calculate number of indices
					m_numIndices = 0;
					for(INT32 lod = 0, curDimension = m_dimension; lod < m_lodLevels; ++lod, curDimension /= 2)
					{
						// Number of indices per LOD level = (LOD dimension^2) * 6 + skirt indices(LOD dimension * 6 * 4)
						m_numIndices += ((curDimension * curDimension * 2) * 3) + (((curDimension * 2) * 3) * 4);
					}

					// Construct mesh object
					ruSAFE_RELEASE(m_mesh);
					m_mesh = ruNEW CRuMesh();
					m_mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, m_numVertices, m_numIndices / 3, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE);

					// Allocate space for vertex flags
					m_vertexFlags = ruNEW BYTE [m_numVertices];

					// Read height values and normal/vertex data
					INT32 numHeightValues = (m_dimension + 1) * (m_dimension + 1) + (m_dimension + 1) * 4;
					REAL *heightField = ruNEW REAL [numHeightValues];
					DWORD *vertexData = ruNEW DWORD [numHeightValues];
					stream->Read(heightField, sizeof(REAL) * numHeightValues);
					stream->Read(vertexData, sizeof(DWORD) * numHeightValues);

					// Setup XYZ, diffuse, and vertex flags
					CRuVector3 *vertices = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetPosition();
					UINT16 *indices = m_mesh->GetIndices();
					DWORD *diffuse = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetDiffuse();

					for(INT32 z = 0, vertexIndex = 0, i = 0; z <= m_dimension; ++z)
					{
						float xF = m_origin.m_x;
						float zF = m_gridSize * z + m_origin.m_z;
						for(INT32 x = 0; x <= m_dimension; ++x, ++vertexIndex, xF += m_gridSize)
						{
							// Set XZ position
							vertices[vertexIndex].m_x = xF;
							vertices[vertexIndex].m_y = heightField[x + z * (m_dimension + 1)];
							vertices[vertexIndex].m_z = zF;
							diffuse[vertexIndex] = vertexData[x + z * (m_dimension + 1)];
							m_vertexFlags[vertexIndex] = (BYTE) ((diffuse[vertexIndex] & 0xFF000000) >> 24);
						}
					}

					// Delete temporaries
					delete[] heightField;
					delete[] vertexData;

					// Update skirt vertices
					UpdateSkirtVertices();

					// Rebuild indices
					BuildChunkTopology(FALSE, FALSE);
				}
				else
				{
					// Skip height values
					stream->Seek(sizeof(REAL) * m_numVertices, ruSSM_Current);

					// Skip packed normal and vertex flag data
					stream->Seek(sizeof(DWORD) * m_numVertices, ruSSM_Current);
				}

				// If the splat map is not loaded but is required, load it now
				if(loadMode == ruOPTIMALTERRAINCHUNKLOADMODE_FULL && m_splatIDMap == NULL)
				{
					// Read splat resolution
					stream->Read(&m_splatResolution, sizeof(INT32));

					// Read splat
					IRuObject *idMapObj = g_ruObjectFactory.CreateObject(stream);

					if(idMapObj && idMapObj->GetType() == CRuSplatIDMap::Type)
					{
						m_splatIDMap = static_cast<CRuSplatIDMap *>(idMapObj);
					}
					else
					{
						// Display error here
						m_splatIDMap = NULL;
						ruSAFE_RELEASE(idMapObj);
					}
				}
				else
				{
					// Skip splat resolution
					stream->Seek(sizeof(INT32), ruSSM_Current);

					// Skip loading the ID map entirely
					if(CRuSplatIDMap::SkipStream(stream) == FALSE)
					{
						return FALSE;
					}
				}

				if(m_dataValidated == FALSE)
				{
					// Read collision structure if it is not already loaded
					if(loadMode != ruOPTIMALTERRAINCHUNKLOADMODE_MINIMAL)
					{
						IRuObject *colObject;
						colObject = g_ruObjectFactory.CreateObject(stream);
						if(colObject)
						{
							if(colObject && colObject->GetType().IsSubClassOf(IRuCollisionStructure::Type))
							{
								m_colStructure = static_cast<IRuCollisionStructure *>(colObject);
								m_colStructure->AttachToMesh(m_collisionMesh);
							}
							else
							{
								ruSAFE_RELEASE(colObject);
							}
						}

						// Read number of entity descriptor GUIDs
						INT32 numEntityDescriptorGUIDs;
						stream->Read(&numEntityDescriptorGUIDs, sizeof(INT32));

						// Read all entity descriptor GUIDs
						m_entityDescriptorGUIDs.SetArraySize(numEntityDescriptorGUIDs);
						m_entityDescriptorGUIDs.SetNumEntries(numEntityDescriptorGUIDs);
						for(INT32 i = 0; i < numEntityDescriptorGUIDs; ++i)
						{
							RuReadGUID(stream, m_entityDescriptorGUIDs[i]);
						}

						if(loadMode != ruOPTIMALTERRAINCHUNKLOADMODE_COLLISION)
						{
							// Read number of grass particles
							INT32 numGrassParticles;
							stream->Read(&numGrassParticles, sizeof(INT32));

							// Read all particles
							for(INT32 i = 0; i < numGrassParticles; ++i)
							{
								// Create new particle
								m_grassParticles.Add(CRuParticle_Grass());

								stream->Read(&m_grassParticles[i].m_position.x, sizeof(REAL));
								stream->Read(&m_grassParticles[i].m_position.y, sizeof(REAL));
								stream->Read(&m_grassParticles[i].m_position.z, sizeof(REAL));
								stream->Read(&m_grassParticles[i].m_textureIndex, sizeof(UINT16));
								stream->Read(&m_grassParticles[i].m_subTextureIndex, sizeof(UINT8));
								stream->Read(&m_grassParticles[i].m_modelStyle, sizeof(UINT8));
								stream->Read(&m_grassParticles[i].m_scale, sizeof(REAL));
								stream->Read(&m_grassParticles[i].m_swayMagnitude, sizeof(REAL));
							}

							// Read number of doodad clusters
							INT32 numDoodadClusters = 0;
							stream->Read(&numDoodadClusters, sizeof(INT32));

							if(numDoodadClusters > 0)
							{
								m_doodadLists.SetArraySize(numDoodadClusters);
								m_doodadLists.SetNumEntries(numDoodadClusters);

								// Read each doodad cluster
								for(INT32 i = 0; i < numDoodadClusters; ++i)
								{
									// Create new doodad cluster
									m_doodadLists[i] = ruNEW CRuWorld_DoodadCluster();

									// Read template GUID
									RuReadGUID(stream, m_doodadLists[i]->m_guid);

									// Read number of doodads
									INT32 numDoodads = 0;
									stream->Read(&numDoodads, sizeof(INT32));

									// Allocate space for doodads
									m_doodadLists[i]->m_doodads.SetArraySize(numDoodads);
									m_doodadLists[i]->m_doodads.SetNumEntries(numDoodads);

									// Read each doodad
									for(INT32 j = 0; j < numDoodads; ++j)
									{
										CRuDoodad_Descriptor &doodadDescriptor = m_doodadLists[i]->m_doodads[j];

										stream->Read(&doodadDescriptor.m_textureIndex, sizeof(INT32));
										stream->Read(&doodadDescriptor.m_scale, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_rotation.x, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_rotation.y, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_rotation.z, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_rotation.w, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_translation.x, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_translation.y, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_translation.z, sizeof(REAL));
										stream->Read(&doodadDescriptor.m_swayMagnitude, sizeof(REAL));
									}
								}
							}

							// Read number of water layers
							INT32 numWaterLayers;
							stream->Read(&numWaterLayers, sizeof(INT32));

							// Read each water layer
							for(INT32 i = 0; i < numWaterLayers; ++i)
							{
								// Serialize water layer from store
								m_waterLayers.Add(ruNEW CRuWorld_Water());
								m_waterLayers[i]->SerializeFrom(stream);

								// Build collision structure for the water layer
								m_waterLayers[i]->BuildCollisionStructure();
							}
						}
					}
				}

				m_dataValidated = TRUE;
			}

			break;
	}

	// Set entity bounds to enclose the chunk's AABB
	CRuVector3 center = (m_bounds_AABB.Minima() + m_bounds_AABB.Maxima()) / 2.0f;
	REAL radius = (m_bounds_AABB.Maxima() - m_bounds_AABB.Minima()).Magnitude();
	this->SetLocalEntityBounds(CRuSphere(center, radius));

	// If the splat map exists, set the LOD
	if(m_splatIDMap)
	{
		for(INT32 i = 0; i < m_splatIDMap->GetNumAlphaMaps(); ++i)
		{
			m_splatIDMap->GetAlphaMap(i)->SetLOD(m_alphaTextureLOD);
		}
	}

	// Setup the alpha texture UV modifier
	m_alphaUVModifier[0] = m_bounds_AABB.Minima().x;
	m_alphaUVModifier[1] = m_bounds_AABB.Minima().z;
	m_alphaUVModifier[2] = 1.0f / (m_bounds_AABB.Maxima().x - m_bounds_AABB.Minima().x);
	m_alphaUVModifier[3] = 1.0f / (m_bounds_AABB.Maxima().z - m_bounds_AABB.Minima().z);

	for(INT32 i = 0; i < m_lodLevels; ++i)
	{
		m_lodMeshes[i]->GetBatchParameterBlock(0)->SetParameter(ruPARAMTYPE_SPLAT_ALPHAUVMODIFIER, (DWORD) m_alphaUVModifier);
	}

	return TRUE;
}

BOOL CRuOptimalTerrain_Chunk::SerializeFrom(IRuStream *stream)
{
	return SerializeFrom(stream, ruOPTIMALTERRAINCHUNKLOADMODE_FULL);
}

BOOL CRuOptimalTerrain_Chunk::SerializeTo(IRuStream *stream)
{
	return FALSE;
}

INT32 CRuOptimalTerrain_Chunk::GetNumMeshes() const
{
	return 1;
}

BOOL CRuOptimalTerrain_Chunk::GetMesh(INT32 meshIndex, IRuMesh **meshOut, IRuMaterial **materialOut)
{
	*meshOut = m_lodMeshes[m_currentLOD];
	*materialOut = m_material;

	return TRUE;
}

BOOL CRuOptimalTerrain_Chunk::QueryCollision(CRuCollisionQuery *colQuery)
{
	if(m_colStructure)
	{
		m_colStructure->QueryCollision(colQuery);
	}

	for(INT32 i = 0; i < m_waterLayers.Count(); ++i)
	{
		m_waterLayers[i]->QueryCollision(colQuery);
	}

	return TRUE;
}

void CRuOptimalTerrain_Chunk::SetCamera(CRuCamera *viewCamera)
{
	float sqrFarPlaneDistance = viewCamera->GetFarPlaneDistance();
	float sqrDistanceToChunk = SquaredDistance(viewCamera->GetCameraPosition(), m_bounds_AABB);
	sqrFarPlaneDistance *= sqrFarPlaneDistance;

	// Calculate LOD to use based on a linear scale
	float t = sqrDistanceToChunk / sqrFarPlaneDistance;
	m_currentLOD = min((INT32) (t * m_lodLevels), m_lodLevels - 1);
}

void CRuOptimalTerrain_Chunk::ReleaseSplatIDMap()
{
	ruSAFE_RELEASE(m_splatIDMap);
}

INT32 CRuOptimalTerrain_Chunk::GetChunkIteration() const
{
	return m_chunkIteration;
}

const CRuAABB &CRuOptimalTerrain_Chunk::GetBoundingBox() const
{
	return m_bounds_AABB;
}

CRuSplatIDMap *CRuOptimalTerrain_Chunk::GetSplatIDMap()
{
	return m_splatIDMap;
}

INT32 CRuOptimalTerrain_Chunk::GetNumEntityDescriptorGUIDs()
{
	return m_entityDescriptorGUIDs.Count();
}

const CRuGUID &CRuOptimalTerrain_Chunk::GetEntityDescriptorGUID(INT32 index)
{
	return m_entityDescriptorGUIDs[index];
}

const CRuArrayList<CRuParticle_Grass> &CRuOptimalTerrain_Chunk::GetGrassParticles() const
{
	return m_grassParticles;
}

INT32 CRuOptimalTerrain_Chunk::GetNumDoodadClusters() const
{
	return m_doodadLists.Count();
}

CRuWorld_DoodadCluster *CRuOptimalTerrain_Chunk::GetDoodadCluster(INT32 index)
{
	return m_doodadLists[index];
}

INT32 CRuOptimalTerrain_Chunk::GetNumWaterLayers() const
{
	return m_waterLayers.Count();
}

CRuWorld_Water *CRuOptimalTerrain_Chunk::GetWaterLayerByIndex(INT32 index)
{
	return m_waterLayers[index];
}

INT32 CRuOptimalTerrain_Chunk::GetMeshDataSize() const
{
	return (m_numVertices * 16) + (m_numIndices * 2);
}

INT32 CRuOptimalTerrain_Chunk::GetSplatDataSize() const
{
	if(m_splatIDMap)
	{
		return m_splatIDMap->GetMemoryFootprint();
	}

	return 0;
}

void CRuOptimalTerrain_Chunk::SetAlphaTextureLOD(INT32 alphaTextureLOD)
{
	if(m_alphaTextureLOD != alphaTextureLOD)
	{
		// Store current alpha texture LOD
		m_alphaTextureLOD = alphaTextureLOD;

		// If the splat map exists, set the new LOD into the textures
		if(m_splatIDMap)
		{
			for(INT32 i = 0; i < m_splatIDMap->GetNumAlphaMaps(); ++i)
			{
				m_splatIDMap->GetAlphaMap(i)->SetLOD(m_alphaTextureLOD);
			}
		}
	}
}

void CRuOptimalTerrain_Chunk::BuildChunkTopology(BOOL ignoreCollision, BOOL ignoreSkirt)
{
	// Clear all batches in the mesh (we will rebuild all of the batches)
	m_mesh->ClearBatches();

	// Start building the LOD mesh at the base level
	UINT16 *indices = m_mesh->GetIndices();
	INT32 indexCount = 0;
	INT32 numMainBodyIndices;

	// Build LOD mesh array
	m_lodMeshes = ruNEW CRuProxyMesh * [m_lodLevels];

	for(INT32 lod = 0, vertexGap = 1, gridDimension = m_dimension; lod < m_lodLevels; ++lod, vertexGap *= 2, gridDimension /= 2)
	{
		INT32 batchNumIndices = RuTerrainUtility_BuildGridIndices(m_dimension, gridDimension, vertexGap, m_dimension + 1, m_vertexFlags, &indices[indexCount], &numMainBodyIndices, ignoreSkirt);
		INT32 batchOffset = indexCount;
		INT32 batchIndex = m_mesh->AddBatch(batchOffset / 3, batchNumIndices / 3);

		// Setup the LOD proxy mesh
		m_lodMeshes[lod] = ruNEW CRuProxyMesh();
		m_lodMeshes[lod]->SetProxiedMesh(m_mesh, batchIndex);

		// If the LOD is 0, add a separate batch for the collision mesh
		if(lod == 0 && ignoreCollision == FALSE)
		{
			batchIndex = m_mesh->AddBatch(batchOffset / 3, numMainBodyIndices / 3);

			m_collisionMesh = ruNEW CRuProxyMesh();
			m_collisionMesh->SetProxiedMesh(m_mesh, batchIndex);
		}

		indexCount += batchNumIndices;
	}
}

void CRuOptimalTerrain_Chunk::UpdateSkirtVertices()
{
	CRuVector3 *vertices = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetPosition();

	INT32 topSkirtVertexArrayOffset = (m_dimension + 1) * (m_dimension + 1);
	INT32 bottomSkirtVertexArrayOffset = topSkirtVertexArrayOffset + (m_dimension + 1);
	INT32 leftSkirtVertexArrayOffset = bottomSkirtVertexArrayOffset + (m_dimension + 1);
	INT32 rightSkirtVertexArrayOffset = leftSkirtVertexArrayOffset + (m_dimension + 1);

	// Duplicate skirt vertices (top row)
	for(INT32 i = 0, destOffset = topSkirtVertexArrayOffset, srcOffset = 0; i <= m_dimension; ++i, ++destOffset, ++srcOffset)
	{
		vertices[destOffset].Set(vertices[srcOffset].x, vertices[srcOffset].y - 20.0f, vertices[srcOffset].z);
	}

	// Duplicate skirt vertices (bottom row)
	for(INT32 i = 0, destOffset = bottomSkirtVertexArrayOffset, srcOffset = m_dimension * (m_dimension + 1); i <= m_dimension; ++i, ++destOffset, ++srcOffset)
	{
		vertices[destOffset].Set(vertices[srcOffset].x, vertices[srcOffset].y - 20.0f, vertices[srcOffset].z);
	}

	// Duplicate skirt vertices (left row)
	for(INT32 i = 0, destOffset = leftSkirtVertexArrayOffset, srcOffset = 0; i <= m_dimension; ++i, ++destOffset, srcOffset += (m_dimension + 1))
	{
		vertices[destOffset].Set(vertices[srcOffset].x, vertices[srcOffset].y - 20.0f, vertices[srcOffset].z);
	}

	// Duplicate skirt vertices (right row)
	for(INT32 i = 0, destOffset = rightSkirtVertexArrayOffset, srcOffset = m_dimension; i <= m_dimension; ++i, ++destOffset, srcOffset += (m_dimension + 1))
	{
		vertices[destOffset].Set(vertices[srcOffset].x, vertices[srcOffset].y - 20.0f, vertices[srcOffset].z);
	}
}

// ************************************************************************************************************************************************************

CRuOptimalTerrain_DataSource_DS::CRuOptimalTerrain_DataSource_DS()
:	m_dataStore(NULL),
	m_collisionOnlyMode(FALSE),
	m_chunkQueue(NULL),
	m_textureList(NULL),
	m_grassDatabase(NULL)
{
	m_chunkQueue = ruNEW CRuLRUQueue<DWORD, ChunkDescriptor *>(192, 2048);
	m_minimalChunkQueue = ruNEW CRuLRUQueue<DWORD, ChunkDescriptor *>(512, 4096);

	m_streamCompressor = ruNEW CRuStreamCompressor();
	m_compressedChunkStream = ruNEW CRuMemoryStream();
	m_decompressedChunkStream = ruNEW CRuMemoryStream();

	m_objectCS = ruNEW CRuThread_CriticalSection();
}

CRuOptimalTerrain_DataSource_DS::~CRuOptimalTerrain_DataSource_DS()
{
	// Purge chunk LRU queues
	ClearChunkQueue();
	ClearMinimalChunkQueue();

	// Purge the recycled chunk descriptors list
	while(m_recycledChunkDescriptors.Count() > 0)
	{
		delete m_recycledChunkDescriptors.Pop();
	}

	delete m_chunkQueue;
	delete m_minimalChunkQueue;

	delete m_streamCompressor;
	delete m_compressedChunkStream;
	delete m_decompressedChunkStream;

	ruSAFE_RELEASE(m_textureList);
	ruSAFE_RELEASE(m_grassDatabase);

	ruSAFE_RELEASE(m_dataStore);

	delete m_objectCS;
}

BOOL CRuOptimalTerrain_DataSource_DS::SetCollisionOnlyMode(BOOL collisionOnlyMode)
{
	m_collisionOnlyMode = collisionOnlyMode;

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::SetChunkQueueSize(INT32 chunkQueueSize)
{
	// Flush chunk queue
	ClearChunkQueue();

	// Destroy and re-create chunk queue
	delete m_chunkQueue;
	m_chunkQueue = ruNEW CRuLRUQueue<DWORD, ChunkDescriptor *>(chunkQueueSize, chunkQueueSize * 4);

	return TRUE;
}

CRuOptimalTerrain_Chunk *CRuOptimalTerrain_DataSource_DS::LoadChunk(INT32 x, INT32 z, RuOptimalTerrainChunkLoadMode loadMode)
{
	// Build chunk key and its hash
	DWORD chunkKey = (x << 16) | z;
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	ChunkDescriptor *chunkDescriptor;

	// If collision-only mode is enabled, force a change to the load mode
	if(m_collisionOnlyMode)
		loadMode = ruOPTIMALTERRAINCHUNKLOADMODE_COLLISION;

	m_objectCS->Enter();

	// Attempt to locate chunk in the chunk queue
	if(m_chunkQueue->Get(chunkKey, chunkKeyHash, chunkDescriptor))
	{
		if(chunkDescriptor->m_chunk->MakeDataConformant(loadMode) == FALSE)
		{
			// Retrieve the chunk
			IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Chunk, reinterpret_cast<BYTE *>(&chunkKey));

			if(stream)
			{
				// Decompress chunk data block
				m_streamCompressor->Decompress(stream, 0, stream->GetStreamSize(), m_decompressedChunkStream);
				delete stream;

				// Construct new chunk and read from decompressed data stream
				m_decompressedChunkStream->Seek(0, ruSSM_Begin);
				chunkDescriptor->m_chunk->SerializeFrom(m_decompressedChunkStream, loadMode);
			}
		}

		m_objectCS->Leave();

		return chunkDescriptor->m_chunk;
	}
/*
	// Time-slicing mechanism
	DWORD loadTime = timeGetTime();

	if(m_slice > 100 || m_slice < 0)
	{
		m_slice = 0;
		m_loadedChunks = 0;
	}

	if(loadTime < m_lastLoadTime)
	{
		m_slice = 0;
		m_loadedChunks = 0;
	}
	else
	{
		DWORD elapsedTime = loadTime - m_lastLoadTime;
		m_slice += elapsedTime;
	}

	m_lastLoadTime = loadTime;

	if(m_loadedChunks >= 1)
	{
//			return NULL;
	}

	++m_loadedChunks;
*/

	// Retrieve the chunk
	IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Chunk, reinterpret_cast<BYTE *>(&chunkKey));

	if(stream)
	{
		// Decompress chunk data block
		m_streamCompressor->Decompress(stream, 0, stream->GetStreamSize(), m_decompressedChunkStream);
		delete stream;

		// Construct new chunk and read from decompressed data stream
		m_decompressedChunkStream->Seek(0, ruSSM_Begin);
		CRuOptimalTerrain_Chunk *chunk = ruNEW CRuOptimalTerrain_Chunk();
		chunk->SerializeFrom(m_decompressedChunkStream, loadMode);

		// If we cannot create the object, abort
		if(chunk == NULL)
		{
			m_objectCS->Leave();

			return NULL;
		}

		// Get a chunk descriptor
		if(m_recycledChunkDescriptors.IsEmpty())
			chunkDescriptor = ruNEW ChunkDescriptor();
		else
			chunkDescriptor = m_recycledChunkDescriptors.Pop();

		// Fill out chunk descriptor
		chunkDescriptor->m_key = chunkKey;
		chunkDescriptor->m_chunk = chunk;

		// Try to free up space in the LRU queue for the new chunk, if necessary
		ChunkDescriptor *reclaimedDescriptor;
		while(m_chunkQueue->Reclaim(chunkKey, chunkKeyHash, 1, reclaimedDescriptor))
		{
			// Release the chunk
			reclaimedDescriptor->m_chunk->Release();

			// Put descriptor into recycle bin
			m_recycledChunkDescriptors.Push(reclaimedDescriptor);
		}

		// Put chunk into the chunk queue
		m_chunkQueue->Add(chunkKey, chunkKeyHash, chunkDescriptor, 1);

		m_objectCS->Leave();

		// Return the chunk
		return chunkDescriptor->m_chunk;
	}

	m_objectCS->Leave();

	return NULL;
}

CRuOptimalTerrain_Chunk *CRuOptimalTerrain_DataSource_DS::LoadMinimalChunk(INT32 x, INT32 z)
{
	// Build chunk key and its hash
	DWORD chunkKey = (x << 16) | z;
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	ChunkDescriptor *chunkDescriptor;

	m_objectCS->Enter();

	// Attempt to locate chunk in the chunk queue
	if(m_minimalChunkQueue->Get(chunkKey, chunkKeyHash, chunkDescriptor))
	{
		m_objectCS->Leave();

		return chunkDescriptor->m_chunk;
	}
	else
	{
		// Retrieve the chunk
		IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Chunk, reinterpret_cast<BYTE *>(&chunkKey));

		if(stream)
		{
			// Decompress chunk data block
			m_streamCompressor->Decompress(stream, 0, stream->GetStreamSize(), m_decompressedChunkStream);
			delete stream;

			// Construct new chunk and read from decompressed data stream
			m_decompressedChunkStream->Seek(0, ruSSM_Begin);
			CRuOptimalTerrain_Chunk *chunk = ruNEW CRuOptimalTerrain_Chunk();

			// Load chunk as minimal chunk
			chunk->SerializeFrom(m_decompressedChunkStream, ruOPTIMALTERRAINCHUNKLOADMODE_MINIMAL);

			// If we cannot create the object, abort
			if(chunk == NULL)
			{
				m_objectCS->Leave();

				return NULL;
			}

			// Get a chunk descriptor
			if(m_recycledChunkDescriptors.IsEmpty())
				chunkDescriptor = ruNEW ChunkDescriptor();
			else
				chunkDescriptor = m_recycledChunkDescriptors.Pop();

			// Fill out chunk descriptor
			chunkDescriptor->m_key = chunkKey;
			chunkDescriptor->m_chunk = chunk;

			// Try to free up space in the LRU queue for the new chunk, if necessary
			ChunkDescriptor *reclaimedDescriptor;
			while(m_minimalChunkQueue->Reclaim(chunkKey, chunkKeyHash, 1, reclaimedDescriptor))
			{
				// Release the chunk
				reclaimedDescriptor->m_chunk->Release();

				// Put descriptor into recycle bin
				m_recycledChunkDescriptors.Push(reclaimedDescriptor);
			}

			// Put chunk into the chunk queue
			m_minimalChunkQueue->Add(chunkKey, chunkKeyHash, chunkDescriptor, 1);

			m_objectCS->Leave();

			// Return the chunk
			return chunkDescriptor->m_chunk;
		}
	}

	m_objectCS->Leave();

	return NULL;
}

BOOL CRuOptimalTerrain_DataSource_DS::WarmChunk(INT32 x, INT32 z)
{
	// Build chunk key and its hash
	DWORD chunkKey = (x << 16) | z;
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	ChunkDescriptor *chunkDescriptor;

	m_objectCS->Enter();

	// Attempt to locate chunk in the chunk queue
	if(m_chunkQueue->Get(chunkKey, chunkKeyHash, chunkDescriptor))
	{
		m_objectCS->Leave();

		return TRUE;
	}

	m_objectCS->Leave();

	return FALSE;
}

BOOL CRuOptimalTerrain_DataSource_DS::ChunkExistsInCache(INT32 x, INT32 z)
{
	// Build chunk key and its hash
	DWORD chunkKey = (x << 16) | z;
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	ChunkDescriptor *chunkDescriptor;

	m_objectCS->Enter();

	// Attempt to locate chunk in the chunk queue
	if(m_chunkQueue->Get(chunkKey, chunkKeyHash, chunkDescriptor))
	{
		m_objectCS->Leave();

		return TRUE;
	}

	m_objectCS->Leave();

	return FALSE;
}

BOOL CRuOptimalTerrain_DataSource_DS::ChunkExistsInMinimalCache(INT32 x, INT32 z)
{
	// Build chunk key and its hash
	DWORD chunkKey = (x << 16) | z;
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	ChunkDescriptor *chunkDescriptor;

	m_objectCS->Enter();

	// Attempt to locate chunk in the chunk queue
	if(m_minimalChunkQueue->Get(chunkKey, chunkKeyHash, chunkDescriptor))
	{
		m_objectCS->Leave();

		return TRUE;
	}

	m_objectCS->Leave();

	return FALSE;
}

CRuTextureList *CRuOptimalTerrain_DataSource_DS::GetTextureList()
{
	return m_textureList;
}

CRuGrassDatabase *CRuOptimalTerrain_DataSource_DS::GetGrassDatabase()
{
	return m_grassDatabase;
}

INT32 CRuOptimalTerrain_DataSource_DS::GetNumWaterBodyDescriptors()
{
	return m_dataStore->GetNumRecords(m_tableHandle_WaterDescriptor);
}

CRuWaterBodyDescriptor *CRuOptimalTerrain_DataSource_DS::LoadWaterBodyDescriptorByIndex(INT32 index)
{
	CRuWaterBodyDescriptor *waterBodyDescriptor = NULL;
	IRuStream *stream = m_dataStore->RetrieveByIndex(m_tableHandle_WaterDescriptor, index);

	if(stream)
	{
		waterBodyDescriptor = ruNEW CRuWaterBodyDescriptor();

		if(waterBodyDescriptor->SerializeFrom(stream) == FALSE)
		{
			delete waterBodyDescriptor;
			waterBodyDescriptor = NULL;
		}

		delete stream;
	}

	return waterBodyDescriptor;
}

INT32 CRuOptimalTerrain_DataSource_DS::GetChunkDimension() const
{
	return m_chunkDimension;
}

INT32 CRuOptimalTerrain_DataSource_DS::GetNumXChunks() const
{
	return m_numXChunks;
}

INT32 CRuOptimalTerrain_DataSource_DS::GetNumZChunks() const
{
	return m_numZChunks;
}

INT32 CRuOptimalTerrain_DataSource_DS::GetSplatResolution() const
{
	return m_splatResolution;
}

CRuAABB CRuOptimalTerrain_DataSource_DS::GetBounds(INT32 x, INT32 z) const
{
	CRuAABB chunkBounds;
	REAL verticalLimit = 20000.0f;
	REAL chunkWorldSpaceSize = m_chunkDimension * m_gridSize;

	// Calculate chunk bounds based on integer (X, Z) coordinates
	chunkBounds.Minima().Set(x * chunkWorldSpaceSize + m_origin.m_x, -verticalLimit, z * chunkWorldSpaceSize + m_origin.m_z);
	chunkBounds.Maxima().Set(chunkBounds.Minima().m_x + chunkWorldSpaceSize, verticalLimit, chunkBounds.Minima().m_z + chunkWorldSpaceSize);

	return chunkBounds;
}

INT32 CRuOptimalTerrain_DataSource_DS::GetChunkIteration(INT32 x, INT32 z)
{
	CRuOptimalTerrain_Chunk *chunk = this->LoadMinimalChunk(x, z);

	return chunk->GetChunkIteration();
}

CRuDataStore_Base *CRuOptimalTerrain_DataSource_DS::GetDataStore()
{
	return m_dataStore;
}

BOOL CRuOptimalTerrain_DataSource_DS::Open(CRuDataStore_Base *dataStore)
{
	// Store reference to data store
	ruSAFE_RELEASE(m_dataStore);
	m_dataStore = dataStore;
	m_dataStore->AddRef();

	// Get table handles
	m_tableHandle_Header = m_dataStore->GetTableHandle("header");
	m_tableHandle_Chunk = m_dataStore->GetTableHandle("chunks");
	m_tableHandle_TextureList = m_dataStore->GetTableHandle("texture_list");
	m_tableHandle_GrassDB = m_dataStore->GetTableHandle("grass_database");
	m_tableHandle_WaterDescriptor = m_dataStore->GetTableHandle("water_descriptor");

	// Load header information
	LoadHeader();

	// Construct texture list
	m_textureList = new CRuTextureList();

	// Prepend 2048 NULL textures into the texture list
	for(INT32 i = 0; i < 2048; ++i)
	{
		m_textureList->AddTexture(NULL);
	}

	// Load texture list
	LoadTextureList(m_textureList);

	// Load grass database
	m_grassDatabase = LoadGrassDatabase();

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::LoadHeader()
{
	INT32 dummyKey = 0;
	IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Header, reinterpret_cast<BYTE *>(&dummyKey));

	if(stream)
	{
		// Read header information
		stream->Read(&m_origin.m_x, sizeof(REAL));
		stream->Read(&m_origin.m_y, sizeof(REAL));
		stream->Read(&m_origin.m_z, sizeof(REAL));
		stream->Read(&m_chunkDimension, sizeof(INT32));
		stream->Read(&m_numXChunks, sizeof(INT32));
		stream->Read(&m_numZChunks, sizeof(INT32));
		stream->Read(&m_splatResolution, sizeof(INT32));
		stream->Read(&m_gridSize, sizeof(REAL));
		stream->Read(&m_lodLevels, sizeof(INT32));

		delete stream;

		return TRUE;
	}

	return FALSE;
}

BOOL CRuOptimalTerrain_DataSource_DS::LoadTextureList(CRuTextureList *textureList)
{
	if(textureList)
	{
		if(m_collisionOnlyMode)
		{
			for(INT32 i = 0; i < 2048; ++i)
			{
				textureList->SetTexture(i, NULL);
			}
		}
		else
		{
			for(INT32 i = 0; i < 2048; ++i)
			{
				// Get texture name using the index as the key
				IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_TextureList, reinterpret_cast<BYTE *>(&i));

				char textureName[1024] = "";

				if(stream)
				{
					stream->Read(textureName, stream->GetStreamSize());
					delete stream;
				}

				if(strlen(textureName) > 0)
				{
					// Load the texture and set it into the texture list
					IRuBaseTexture *texture = g_ruResourceManager->LoadTexture(textureName);
					textureList->SetTexture(i, texture);
					ruSAFE_RELEASE(texture);
				}
				else
				{
					textureList->SetTexture(i, NULL);
				}
			}
		}

		return TRUE;
	}

	return FALSE;
}

CRuGrassDatabase *CRuOptimalTerrain_DataSource_DS::LoadGrassDatabase()
{
	CRuGrassDatabase *grassDB = ruNEW CRuGrassDatabase();

	INT32 dummyKey = 0;
	IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_GrassDB, reinterpret_cast<BYTE *>(&dummyKey));

	if(stream)
	{
		grassDB->SerializeFrom(stream);
		grassDB->SetDirty(FALSE);
		delete stream;
	}

	return grassDB;
}

BOOL CRuOptimalTerrain_DataSource_DS::ClearChunkQueue()
{
	if(m_chunkQueue)
	{
		m_chunkQueue->ForAllElements(ClearChunkQueueCallback, this);
		m_chunkQueue->Clear();
	}

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::ClearChunkQueueCallback(const void *key, const void *data, void *userData)
{
	ChunkDescriptor *cd = *((ChunkDescriptor **) data);

	cd->m_chunk->Release();
	delete cd;

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::ClearMinimalChunkQueue()
{
	if(m_chunkQueue)
	{
		m_minimalChunkQueue->ForAllElements(ClearChunkQueueCallback, this);
		m_minimalChunkQueue->Clear();
	}

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::ClearMinimalChunkQueueCallback(const void *key, const void *data, void *userData)
{
	ChunkDescriptor *cd = *((ChunkDescriptor **) data);

	cd->m_chunk->Release();
	delete cd;

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::EnumerateChunkQueue(CRuArrayList<ChunkDescriptor *> *chunkList)
{
	if(m_chunkQueue)
	{
		m_chunkQueue->ForAllElements(EnumerateChunkQueueCallback, chunkList);
	}

	return TRUE;
}

BOOL CRuOptimalTerrain_DataSource_DS::EnumerateChunkQueueCallback(const void *key, const void *data, void *userData)
{
	ChunkDescriptor *cd = *((ChunkDescriptor **) data);

	((CRuArrayList<ChunkDescriptor *> *) userData)->Add(cd);

	return TRUE;
}

// ************************************************************************************************************************************************************

CRuOptimalTerrain::CRuOptimalTerrain()
:	m_dataSource(NULL),
	m_chunkTree(NULL),
	m_objectContainer(NULL),
	m_objectManager(NULL),
	m_doodadContainer(NULL),
	m_doodadManager(NULL),
	m_textureList(NULL),
	m_gaiaObject(NULL),
	m_terrainDetailLevel(2),
	m_renderTerrain(TRUE),
	m_renderTerrainTexture(TRUE),
	m_renderBackdropTerrain(TRUE),
	m_renderObjects(TRUE),
	m_renderGrass(TRUE),
	m_renderWater(TRUE),
	m_maxPreloadsPerSecond(3.0f),
	m_preloadsPerSecond(0.0f),
	m_maxBackdropPreloadsPerSecond(6.0f),
	m_backdropPreloadsPerSecond(0.0f),
	m_metric_NumCacheMisses(0),
	m_metric_NumPreloadedChunks(0),
	m_metric_NumPreloadedBackdropChunks(0),
	m_metric_NumEnumeratedEntities(0)
{
	m_viewVolumeMarginSize = 120.0f;
	m_viewVolume.Set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);

	// Setup identity transform
	m_identityTransform.SetToIdentity();

	m_backdropQueue = ruNEW CRuLRUQueue<DWORD, CRuWorld_Backdrop *>(128, 256);

	m_chunkContainer = ruNEW CRuEntity();
	this->AddChild(m_chunkContainer);

	m_backdropChunkContainer = ruNEW CRuEntity();
	this->AddChild(m_backdropChunkContainer);

	m_grassContainer = ruNEW CRuEntity();
	this->AddChild(m_grassContainer);

	m_waterContainer = ruNEW CRuEntity();
	this->AddChild(m_waterContainer);

	// Create a hash map for water body descriptors
	m_waterBodyDescriptorMap = ruNEW CRuHashMap<CRuGUID, CRuWaterBodyDescriptor *>(1024);

	// Construct grass queue
	m_grassQueue = ruNEW CRuLRUQueue<DWORD, CRuEntity_Particle_Grass_Optimized *>(16, 128);

	// Create workspace hash
	m_entityDescriptorGUIDHash = ruNEW CRuKeyHash<CRuGUID>(2048);

	// Set self as decal target
	this->SetOption(ruENTITYOPTION_DECALTARGET, TRUE);
}

CRuOptimalTerrain::~CRuOptimalTerrain()
{
	ClearBackdropQueue();
	delete m_backdropQueue;

	ClearGrassQueue();

	ruSAFE_RELEASE(m_chunkContainer);
	ruSAFE_RELEASE(m_backdropChunkContainer);
	ruSAFE_RELEASE(m_grassContainer);
	ruSAFE_RELEASE(m_waterContainer);

	// Release entity container
	ruSAFE_RELEASE(m_objectContainer);
	ruSAFE_RELEASE(m_objectManager);

	// Release doodad container and manager
	ruSAFE_RELEASE(m_doodadContainer);
	ruSAFE_RELEASE(m_doodadManager);

	ruSAFE_RELEASE(m_gaiaObject);

	ruSAFE_RELEASE(m_dataSource);

	// Release chunk tree
	ruSAFE_RELEASE(m_chunkTree);

	for(INT32 i = 0; i < m_waterBodyDescriptors.Count(); ++i)
	{
		ruSAFE_RELEASE(m_waterBodyDescriptors[i]);
	}

	delete m_waterBodyDescriptorMap;

	delete m_grassQueue;

	delete m_entityDescriptorGUIDHash;
}

BOOL CRuOptimalTerrain::SerializeFrom(IRuStream *stream)
{
	return TRUE;
}

BOOL CRuOptimalTerrain::SerializeTo(IRuStream *stream)
{
	return TRUE;
}

BOOL CRuOptimalTerrain::ResetStates()
{
	return TRUE;
}

BOOL CRuOptimalTerrain::Update(float elapsedTime)
{
	m_preloadsPerSecond -= m_maxPreloadsPerSecond * elapsedTime;
	if(m_preloadsPerSecond < 0.0f)
		m_preloadsPerSecond = 0.0f;

	m_backdropPreloadsPerSecond -= m_maxBackdropPreloadsPerSecond * elapsedTime;
	if(m_backdropPreloadsPerSecond < 0.0f)
		m_backdropPreloadsPerSecond = 0.0f;

	// Update active water descriptors
	for(INT32 i = 0; i < m_waterBodyDescriptors.Count(); ++i)
	{
		m_waterBodyDescriptors[i]->Update(elapsedTime);
	}

	return IRuEntity_Container::Update(elapsedTime);
}

BOOL CRuOptimalTerrain::QueryCollision(CRuCollisionQuery *colQuery)
{
	// Store the inverse of the current world transform into the collision query
	colQuery->m_worldTransform = const_cast<CRuMatrix4x4 *>(&this->GetWorldTransform());
	colQuery->m_invWorldTransform = const_cast<CRuMatrix4x4 *>(&this->GetInverseWorldTransform());

	if(m_chunkTree)
	{
		switch(colQuery->m_queryType)
		{
			case ruCOLQUERY_RAY:
			case ruCOLQUERY_RAY_HITTEST:
			case ruCOLQUERY_RAY_NEAREST:
			case ruCOLQUERY_RAY_PICK:
			case ruCOLQUERY_RAY_TRIPICK:
			case ruCOLQUERY_RAY_SUBOBJECT:
				{
					CRuCollisionQuery_Ray_Base *rayQuery = static_cast<CRuCollisionQuery_Ray_Base *>(colQuery);

					// Get a list intersecting chunks
					CRuArrayList<DWORD> chunkKeyList;
					m_chunkTree->EnumerateChunks(rayQuery->m_colRay, chunkKeyList);

					for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
					{
						// Decode the chunk key
						INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
						INT32 z = chunkKeyList[i] & 0x0000FFFF;

						// Load the chunk
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_ANY);
						if(chunk)
						{
							// Query ray collision with the chunk
							chunk->QueryCollision(rayQuery);
						}
					}

					// Query collision against instanced entities
//					m_dataSource->GetEntityContainer()->QueryCollision(rayQuery);
				}

				return TRUE;

			case ruCOLQUERY_SEGMENT:
			case ruCOLQUERY_SEGMENT_HITTEST:
			case ruCOLQUERY_SEGMENT_NEAREST:
				{
					CRuCollisionQuery_Segment_Base *segQuery = static_cast<CRuCollisionQuery_Segment_Base *>(colQuery);

					// Get a list intersecting chunks
					CRuArrayList<DWORD> chunkKeyList;
					m_chunkTree->EnumerateChunks(segQuery->m_colSegment, chunkKeyList);

					for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
					{
						// Decode the chunk key
						INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
						INT32 z = chunkKeyList[i] & 0x0000FFFF;

						// Load the chunk
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_ANY);
						if(chunk)
						{
							// Query ray collision with the chunk
							chunk->QueryCollision(segQuery);
						}
					}

					// Query collision against instanced entities
					for(INT32 i = 0; i < m_enumeratedEntityGUIDs.Count(); ++i)
					{
						// Retrieve descriptor from entity container
						CRuWorld_EntityDescriptor *descriptor = m_objectContainer->GetEntityDescriptor(m_enumeratedEntityGUIDs[i]);
						if(descriptor)
						{
							CRuEntity *instancedEntity = m_objectContainer->GetEntityInstance(m_enumeratedEntityGUIDs[i]);
							if(instancedEntity)
							{
								instancedEntity->QueryCollision(segQuery);
							}
						}
					}
				}

				return TRUE;

			case ruCOLQUERY_AABB_HITTEST:
			case ruCOLQUERY_AABB_SUBOBJECT:
			case ruCOLQUERY_AABB_TRIENUM:
				{
					CRuCollisionQuery_AABB_TriEnum *boxQuery = static_cast<CRuCollisionQuery_AABB_TriEnum *>(colQuery);

					// Get a list intersecting chunks
					CRuArrayList<DWORD> chunkKeyList;
					m_chunkTree->EnumerateChunks(boxQuery->m_colAABB, chunkKeyList);

					for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
					{
						// Decode the chunk key
						INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
						INT32 z = chunkKeyList[i] & 0x0000FFFF;

						// Load the chunk
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_ANY);
						if(chunk)
						{
							INT32 startTriangleIndex = boxQuery->m_colTriFlags->Count();

							// Query ray collision with the chunk
							chunk->QueryCollision(colQuery);

							CRuVector3 upVector(0.0f, 1.0f, 0.0f);

							// Iterate through each enumerated triangle and determine whether it is walkable
							for(INT32 j = startTriangleIndex; j < boxQuery->m_colTriFlags->Count(); ++j)
							{
								const CRuVector3 &v0 = (*boxQuery->m_colTriVerts)[j * 3];
								const CRuVector3 &v1 = (*boxQuery->m_colTriVerts)[j * 3 + 1];
								const CRuVector3 &v2 = (*boxQuery->m_colTriVerts)[j * 3 + 2];

								// Perform a backface check
								CRuVector3 e0(v1 - v0);
								CRuVector3 e1(v2 - v0);
								CRuVector3 n = CrossProduct(e0, e1);
								n.Normalize();

								if(DotProduct(upVector, n) > 0.65f)
								{
									(*boxQuery->m_colTriFlags)[j] |= 0x00100000;
								}

								// TODO: Temporarily set zone ID to 1 by default
								(*boxQuery->m_colTriFlags)[j] |= 1;
							}
						}
					}

					// Query collision against instanced entities
//					m_dataSource->GetEntityContainer()->QueryCollision(colQuery);

					// Query collision against water bodies
				}

				return TRUE;

			case ruCOLQUERY_SPHERE_SLIDETEST:
			case ruCOLQUERY_SPHERE_ANCHOR:
			case ruCOLQUERY_SPHERE_TRIENUM:
				{
					CRuCollisionQuery_Sphere_Base *sphereQuery = static_cast<CRuCollisionQuery_Sphere_Base *>(colQuery);

					// Get a list intersecting chunks
					CRuArrayList<DWORD> chunkKeyList;
					m_chunkTree->EnumerateChunks(sphereQuery->m_colSphere, chunkKeyList);

					for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
					{
						// Decode the chunk key
						INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
						INT32 z = chunkKeyList[i] & 0x0000FFFF;

						// Load the chunk
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_ANY);
						if(chunk)
						{
							// Query ray collision with the chunk
							chunk->QueryCollision(sphereQuery);
						}
					}

					// Query collision against instanced entities
					for(INT32 i = 0; i < m_enumeratedEntityGUIDs.Count(); ++i)
					{
						// Retrieve descriptor from entity container
						CRuWorld_EntityDescriptor *descriptor = m_objectContainer->GetEntityDescriptor(m_enumeratedEntityGUIDs[i]);
						if(descriptor)
						{
							CRuEntity *instancedEntity = m_objectContainer->GetEntityInstance(m_enumeratedEntityGUIDs[i]);
							if(instancedEntity)
							{
								instancedEntity->QueryCollision(sphereQuery);
							}
						}
					}

					// Query collision against instanced entities
//					m_dataSource->GetEntityContainer()->QueryCollision(sphereQuery);
				}

				return TRUE;

			case ruCOLQUERY_GAIA_CAMTEST:
				{
					CRuCollisionQuery_Gaia_CamTest *camQuery = static_cast<CRuCollisionQuery_Gaia_CamTest *>(colQuery);

					// Get a list intersecting chunks
					CRuArrayList<DWORD> chunkKeyList;
					m_chunkTree->EnumerateChunks(camQuery->m_colSphere, chunkKeyList);

					for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
					{
						// Decode the chunk key
						INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
						INT32 z = chunkKeyList[i] & 0x0000FFFF;

						// Load the chunk
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_ANY);
						if(chunk)
						{
							// Query ray collision with the chunk
							chunk->QueryCollision(camQuery);
						}
					}

					// Query collision against instanced entities
//					m_dataSource->GetEntityContainer()->QueryCollision(camQuery);
				}

				return TRUE;

			case ruCOLQUERY_USER_FLAG_GET_SEGMENT:
				{
					CRuCollisionQuery_User_Flag_Get_Segment *flagQuery = static_cast<CRuCollisionQuery_User_Flag_Get_Segment *>(colQuery);

					// Get a list intersecting chunks
					CRuArrayList<DWORD> chunkKeyList;
					m_chunkTree->EnumerateChunks(flagQuery->m_colSegment, chunkKeyList);

					for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
					{
						// Decode the chunk key
						INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
						INT32 z = chunkKeyList[i] & 0x0000FFFF;

						// Load the chunk
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_ANY);
						if(chunk)
						{
							// Query ray collision with the chunk
							chunk->QueryCollision(flagQuery);
						}
					}

					// Query collision against instanced entities
//					m_dataSource->GetEntityContainer()->QueryCollision(flagQuery);
				}

				break;
		}
	}

	return FALSE;
}

BOOL CRuOptimalTerrain::Initialize()
{
	return TRUE;
}

BOOL CRuOptimalTerrain::AttachDataSource(IRuOptimalTerrain_DataSource *dataSource, BOOL enableCollisionData)
{
	// Release previous data source
	ruSAFE_RELEASE(m_dataSource);

	// Release previously allocated data



	// Save options
	m_enableCollisionData = enableCollisionData;

	// Attach to new data source
	m_dataSource = dataSource;
	m_dataSource->AddRef();

	// Get terrain dimensions
	INT32 numXChunks = m_dataSource->GetNumXChunks();
	INT32 numZChunks = m_dataSource->GetNumZChunks();
	INT32 totalChunks = numXChunks * numZChunks;

	// Build the maximal bounding box
	CRuAABB spatialBounds(m_dataSource->GetBounds(0, 0));
	for(INT32 z = 0; z < numZChunks; ++z)
	{
		for(INT32 x = 0; x < numXChunks; ++x)
		{
			spatialBounds.Merge(m_dataSource->GetBounds(x, z));
		}
	}

	m_terrainBounds = spatialBounds;

	// Force total chunks to be a power of 2
	totalChunks = RoundUpToPow2(totalChunks);

	// Build partitioning structure
	m_chunkTree = ruNEW CRuWorld_ChunkTree();
	m_chunkTree->Initialize(spatialBounds, 32);

	// Insert chunk descriptors into partitioning structure
	for(INT32 z = 0; z < numZChunks; ++z)
	{
		for(INT32 x = 0; x < numXChunks; ++x)
		{
			m_chunkTree->InsertChunk(x, z, m_dataSource->GetBounds(x, z));
		}
	}

	// Get texture list from data source
	m_textureList = dataSource->GetTextureList();

	// Get grass database from data source
	m_grassDatabase = dataSource->GetGrassDatabase();

	// Load water body descriptors
	INT32 numWaterBodyDescriptors = m_dataSource->GetNumWaterBodyDescriptors();
	for(INT32 i = 0; i < numWaterBodyDescriptors; ++i)
	{
		CRuWaterBodyDescriptor *waterBodyDescriptor = m_dataSource->LoadWaterBodyDescriptorByIndex(i);

		if(waterBodyDescriptor)
		{
			// Append descriptor to list
			m_waterBodyDescriptors.Add(waterBodyDescriptor);

			// Map water body descriptor
			m_waterBodyDescriptorMap->Add(waterBodyDescriptor->m_guid, waterBodyDescriptor->m_guid.HashCode(), waterBodyDescriptor);
		}
	}

	// Open GAIA
	m_gaiaObject = ruNEW CRuGaia_DataStore();
	m_gaiaObject->Open(static_cast<CRuOptimalTerrain_DataSource_DS *>(m_dataSource)->GetDataStore());

	// Construct entity container
	m_objectContainer = ruNEW CRuWorld_ObjectContainer();
	m_objectContainer->Attach(static_cast<CRuOptimalTerrain_DataSource_DS *>(m_dataSource)->GetDataStore());

	// Construct entity manager
	m_objectManager = ruNEW CRuWorld_ObjectManager();
	m_objectManager->Attach(m_objectContainer);
	m_objectManager->SetEnumerator(boost::bind(&CRuOptimalTerrain::GetEntityGUIDEnumeration, this));

	// Construct doodad container
	m_doodadContainer = ruNEW CRuWorld_DoodadContainer();
	m_doodadContainer->Attach(static_cast<CRuOptimalTerrain_DataSource_DS *>(m_dataSource)->GetDataStore(), FALSE);

	// Construct doodad manager
	m_doodadManager = ruNEW CRuWorld_DoodadManager();
	m_doodadManager->Attach(m_doodadContainer);
	m_doodadManager->SetEnumerator(boost::bind(&CRuOptimalTerrain::GetDoodadEnumeration, this, _1, _2));

	return TRUE;
}

BOOL CRuOptimalTerrain::SetChunkQueueSize(INT32 chunkQueueSize)
{
	return m_dataSource->SetChunkQueueSize(chunkQueueSize);
}

void CRuOptimalTerrain::SetMovementDirection(const CRuVector3 &direction)
{
	m_movementDirection = direction;
}

void CRuOptimalTerrain::SetCamera(CRuCamera *viewCamera)
{
	if(m_chunkTree == NULL)
		return;

	while(m_preloadList.Count() > 0 && m_preloadsPerSecond < m_maxPreloadsPerSecond)
	{
		UINT32 preloadChunkKey = m_preloadList.Pop();
		INT32 x = (preloadChunkKey >> 16) & 0x0000FFFF;
		INT32 z = preloadChunkKey & 0x0000FFFF;

		if(m_dataSource->ChunkExistsInCache(x, z) == FALSE)
		{
			// Load chunk with no preference for splats
			CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_FULL);

			m_preloadsPerSecond += 1.0f;
			++m_metric_NumPreloadedChunks;
		}
	}

	while(m_backdropPreloadList.Count() > 0 && m_backdropPreloadsPerSecond < m_maxBackdropPreloadsPerSecond)
	{
		UINT32 preloadChunkKey = m_backdropPreloadList.Pop();
		INT32 x = (preloadChunkKey >> 16) & 0x0000FFFF;
		INT32 z = preloadChunkKey & 0x0000FFFF;

		if(m_dataSource->ChunkExistsInMinimalCache(x, z) == FALSE)
		{
			// Load chunk with no preference for splats
			CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadMinimalChunk(x, z);

			m_backdropPreloadsPerSecond += 1.0f;
			++m_metric_NumPreloadedBackdropChunks;
		}
	}

	CRuAABB normalEnumerationBounds = viewCamera->GetSweepBounds();
	CRuAABB backdropEnumerationBounds;

	m_standardViewVolume = normalEnumerationBounds;

	if(viewCamera->IsBackdropEnabled())
	{
		backdropEnumerationBounds = viewCamera->GetBackdropSweepBounds();
	}
	else
	{
		backdropEnumerationBounds = viewCamera->GetSweepBounds();
	}

	REAL grassThreshold = 300.0f * 300.0f;

	INT32 chunkDimension = m_dataSource->GetChunkDimension();

	// Enumerate only if the view volume has changed significantly
	if(m_viewVolume.Contains(backdropEnumerationBounds) == FALSE)
	{
		// Clear temporary GUID hash
		m_entityDescriptorGUIDHash->Clear();

		// Clear enumeration cache
		this->ClearEnumerationCache();

		// Set view volume to the camera sweep bounds plus margin
		m_viewVolume = backdropEnumerationBounds;
		m_viewVolume.Minima().m_x -= m_viewVolumeMarginSize;
		m_viewVolume.Minima().m_y -= m_viewVolumeMarginSize;
		m_viewVolume.Minima().m_z -= m_viewVolumeMarginSize;
		m_viewVolume.Maxima().m_x += m_viewVolumeMarginSize;
		m_viewVolume.Maxima().m_y += m_viewVolumeMarginSize;
		m_viewVolume.Maxima().m_z += m_viewVolumeMarginSize;

		normalEnumerationBounds.Minima().m_x -= m_viewVolumeMarginSize;
		normalEnumerationBounds.Minima().m_y -= m_viewVolumeMarginSize;
		normalEnumerationBounds.Minima().m_z -= m_viewVolumeMarginSize;
		normalEnumerationBounds.Maxima().m_x += m_viewVolumeMarginSize;
		normalEnumerationBounds.Maxima().m_y += m_viewVolumeMarginSize;
		normalEnumerationBounds.Maxima().m_z += m_viewVolumeMarginSize;

		m_movementDirection.m_y = 0.0f;
		if(m_movementDirection.Magnitude() > ruEPSILON)
			m_movementDirection.Normalize();

		CRuAABB xPaddingVolume(normalEnumerationBounds);
		CRuAABB zPaddingVolume(normalEnumerationBounds);

		CRuAABB xBackdropPaddingVolume(m_viewVolume);
		CRuAABB zBackdropPaddingVolume(m_viewVolume);

		if(m_movementDirection.m_x < 0.0f)
		{
			xPaddingVolume.Maxima().m_x = xPaddingVolume.Minima().m_x;
			xPaddingVolume.Minima().m_x -= 960.0f;

			zPaddingVolume.Minima().m_x -= 960.0f;

			xBackdropPaddingVolume.Maxima().m_x = xBackdropPaddingVolume.Minima().m_x;
			xBackdropPaddingVolume.Minima().m_x -= 960.0f;

			zBackdropPaddingVolume.Minima().m_x -= 960.0f;
		}
		else
		{
			xPaddingVolume.Minima().m_x = xPaddingVolume.Maxima().m_x;
			xPaddingVolume.Maxima().m_x += 960.0f;

			zPaddingVolume.Minima().m_x += 960.0f;

			xBackdropPaddingVolume.Minima().m_x = xBackdropPaddingVolume.Maxima().m_x;
			xBackdropPaddingVolume.Maxima().m_x += 960.0f;

			zBackdropPaddingVolume.Minima().m_x += 960.0f;
		}

		if(m_movementDirection.m_z < 0.0f)
		{
			xPaddingVolume.Minima().m_z -= 960.0f;

			zPaddingVolume.Maxima().m_z = zPaddingVolume.Minima().m_z;
			zPaddingVolume.Minima().m_z -= 960.0f;

			xBackdropPaddingVolume.Minima().m_z -= 960.0f;

			zBackdropPaddingVolume.Maxima().m_z = zBackdropPaddingVolume.Minima().m_z;
			zBackdropPaddingVolume.Minima().m_z -= 960.0f;
		}
		else
		{
			xPaddingVolume.Minima().m_z += 960.0f;

			zPaddingVolume.Minima().m_z = zPaddingVolume.Maxima().m_z;
			zPaddingVolume.Maxima().m_z += 960.0f;

			xBackdropPaddingVolume.Minima().m_z += 960.0f;

			zBackdropPaddingVolume.Minima().m_z = zBackdropPaddingVolume.Maxima().m_z;
			zBackdropPaddingVolume.Maxima().m_z += 960.0f;
		}

		{
			m_preloadList.Clear();

			// Get a list of keys to visible chunks
			CRuArrayList<DWORD> chunkKeyList;
			m_chunkTree->EnumerateChunks(xPaddingVolume, chunkKeyList);
			m_chunkTree->EnumerateChunks(zPaddingVolume, chunkKeyList);

			for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
			{
				m_preloadList.Push(chunkKeyList[i]);
			}
		}

		{
			m_backdropPreloadList.Clear();

			// Get a list of keys to visible chunks
			CRuArrayList<DWORD> chunkKeyList;
			m_chunkTree->EnumerateChunks(xBackdropPaddingVolume, chunkKeyList);
			m_chunkTree->EnumerateChunks(zBackdropPaddingVolume, chunkKeyList);

			for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
			{
				m_backdropPreloadList.Push(chunkKeyList[i]);
			}
		}

		// Remove all children
		m_chunkContainer->RemoveAllChildren();
		m_backdropChunkContainer->RemoveAllChildren();

		if(m_chunkTree)
		{
			// Get a list of keys to visible chunks
			CRuArrayList<DWORD> chunkKeyList;
			m_chunkTree->EnumerateChunks(m_viewVolume, chunkKeyList);

			for(INT32 i = 0; i < chunkKeyList.Count(); ++i)
			{
				// Decode the chunk key
				INT32 x = (chunkKeyList[i] >> 16) & 0x0000FFFF;
				INT32 z = chunkKeyList[i] & 0x0000FFFF;

				// Get chunk bounds
				const CRuAABB &chunkBounds = m_dataSource->GetBounds(x, z);

				BOOL isNormalChunk = Intersects(normalEnumerationBounds, chunkBounds);
				BOOL isBackdropChunk = TRUE;

				// If the chunk intersects the normal view sweep bounds, process it as a normal chunk
				if(isNormalChunk)
				{
					CRuOptimalTerrain_Chunk *chunk = NULL;
					IRuMesh *mesh = NULL;
					IRuMaterial *material = NULL;

					if(m_dataSource->ChunkExistsInCache(x, z) == FALSE)
						++m_metric_NumCacheMisses;

					// Load chunk with full splat data
					chunk = m_dataSource->LoadChunk(x, z, ruOPTIMALTERRAINCHUNKLOADMODE_FULL);

					if(chunk == NULL)
						continue;

					// Set detail level
					switch(m_terrainDetailLevel)
					{
						case 0:
							chunk->SetAlphaTextureLOD(3);
							break;

						case 1:
							chunk->SetAlphaTextureLOD(2);
							break;

						case 2:
							chunk->SetAlphaTextureLOD(1);
							break;

						case 3:
						default:
							chunk->SetAlphaTextureLOD(0);
							break;
					}

					chunk->GetMesh(0, &mesh, &material);

					// Build alpha maps
					CRuSplatIDMap *idMap = chunk->GetSplatIDMap();

					if(idMap)
					{
						// Setup data references
						INT32 numAlphaMaps = idMap->GetNumAlphaMaps();
						INT32 numLayers = idMap->GetNumLayers();

						// Set number of layers
						material->GetParameterBlock()->SetParameter(ruPARAMTYPE_SPLAT_LAYERCOUNT, numLayers);

						// Set alpha maps
						for(INT32 j = 0; j < numAlphaMaps; ++j)
						{
							material->SetTexture((RuTextureChannel) (ruTEXCHANNEL_TERRAIN_ALPHA_0 + j), idMap->GetAlphaMap(j));
						}

						// Set layer textures
						for(INT32 j = 0; j < numLayers; ++j)
						{
							material->SetTexture((RuTextureChannel) (ruTEXCHANNEL_TERRAIN_LAYER_0 + j), m_textureList->GetTexture(idMap->GetLayerID(j)));
						}
					}
					else
					{
						// Set number of layers
						material->GetParameterBlock()->SetParameter(ruPARAMTYPE_SPLAT_LAYERCOUNT, 0);
					}

					if(m_renderTerrainTexture == FALSE)
					{
						material->GetParameterBlock()->SetParameter(ruPARAMTYPE_SPLAT_LAYERCOUNT, 0);
					}

					if(chunk)
					{
						m_chunkContainer->AddChild(chunk);

						// Add to chunk list
						m_enumeratedChunks.Add(chunk);

						// Iterate through entity descriptors if the chunk is within entity enumeration bounds
						for(INT32 j = 0; j < chunk->GetNumEntityDescriptorGUIDs(); ++j)
						{
							const CRuGUID &descriptorGUID = chunk->GetEntityDescriptorGUID(j);

							// Ensure that the entity descriptor is unique
							if(m_entityDescriptorGUIDHash->ContainsKey(descriptorGUID, descriptorGUID.HashCode()) == FALSE)
							{
								m_enumeratedEntityGUIDs.Add(descriptorGUID);
								m_entityDescriptorGUIDHash->Add(descriptorGUID, descriptorGUID.HashCode());
							}
						}

						// Iterate through doodad lists
						for(INT32 j = 0; j < chunk->GetNumDoodadClusters(); ++j)
						{
							m_enumeratedDoodadKeys.Add(chunkKeyList[i]);
							m_enumeratedDoodadClusters.Add(chunk->GetDoodadCluster(j));
						}
					}
				}

				if(isBackdropChunk)
				{
					// If the chunk is in the backdrop band, process it as a backdrop object
					if(m_renderBackdropTerrain)
					{
						INT32 m_backdropGranularity = 4;

						INT32 backdropX = x / m_backdropGranularity;
						INT32 backdropZ = z / m_backdropGranularity;
						DWORD backdropKey = (backdropX << 16) | backdropZ;
						DWORD backdropKeyHash = RuGlobalHashGen().GetHashCode(backdropKey);

						CRuWorld_Backdrop *backdrop = NULL;
						if(m_backdropQueue->Get(backdropKey,backdropKeyHash, backdrop) == FALSE)
						{
							backdrop = ruNEW CRuWorld_Backdrop(117 * m_backdropGranularity * m_backdropGranularity, 192 * m_backdropGranularity * m_backdropGranularity);

							CRuWorld_Backdrop *reclaimedBackdrop;
							while(m_backdropQueue->Reclaim(backdropKey, backdropKeyHash, 1, reclaimedBackdrop))
							{
								ruSAFE_RELEASE(reclaimedBackdrop);
							}

							m_backdropQueue->Add(backdropKey, backdropKeyHash, backdrop, 1);
						}

						if(backdrop->ContainsChunk(chunkKeyList[i]) == FALSE)
						{
							CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadMinimalChunk(x, z);

							if(chunk)
							{
								backdrop->InsertChunk(chunkKeyList[i], chunk);
							}
						}

						RuEntity_SetOption(backdrop, ruENTITYOPTION_BACKDROP, TRUE);

						CRuVector3 center = backdrop->GetWorldBounds().Center();

						if(backdrop->GetParent() == NULL && (center - viewCamera->GetCameraPosition()).SquaredMagnitude() > (960.0f * 960.0f))
						{
							m_backdropChunkContainer->AddChild(backdrop);
						}

/*
						CRuOptimalTerrain_Chunk *chunk = m_dataSource->LoadMinimalChunk(x, z);

						if(chunk)
						{
							// Add to backdrop chunk list
							m_enumeratedBackdropChunks.Add(chunk);

							// Set option
							RuEntity_SetOption(chunk, ruENTITYOPTION_BACKDROP, TRUE);

							m_backdropChunkContainer->AddChild(chunk);
						}
*/
					}
				}
			}
		}
	}

	m_grassContainer->RemoveAllChildren();
	m_waterContainer->RemoveAllChildren();

	// Clear visible entities list
	m_visibleChunks.Clear();
	m_visibleGrass.Clear();
	m_visibleEntities.Clear();
	m_visibleWaterEntities.Clear();

	for(INT32 i = 0; i < m_enumeratedChunks.Count(); ++i)
	{
		CRuOptimalTerrain_Chunk *chunk = m_enumeratedChunks[i];
		if(chunk)
		{
			REAL sqrDistanceToChunk = SquaredDistance(viewCamera->GetCameraPosition(), chunk->GetBoundingBox());

			// Force LOD selection
			chunk->SetCamera(viewCamera);

			// Display grass system
			if(sqrDistanceToChunk < grassThreshold && chunk->GetGrassParticles().Count() > 0)
			{
				DWORD key = (DWORD) chunk;
				DWORD keyHash = RuGlobalHashGen().GetHashCode(key);
				if(m_grassQueue->ContainsKey(key, keyHash) == FALSE)
				{
					// Create new grass particle system
					CRuEntity_Particle_Grass_Optimized *grassSystem = ruNEW CRuEntity_Particle_Grass_Optimized();
					grassSystem->SetGrassDatabase(m_grassDatabase);
					grassSystem->SetParticleList(chunk->GetGrassParticles());
					grassSystem->SetLocalEntityBounds(chunk->GetLocalEntityBounds());

					CRuEntity_Particle_Grass_Optimized *reclaimedGrassSystem;
					while(m_grassQueue->Reclaim(key, keyHash, 1, reclaimedGrassSystem))
					{
						// Release the chunk
						ruSAFE_RELEASE(reclaimedGrassSystem);
					}

					m_grassQueue->Add(key, keyHash, grassSystem, 1);
				}

				m_grassContainer->AddChild(m_grassQueue->Get(key, keyHash));

				m_visibleGrass.Add(m_grassQueue->Get(key, keyHash));
			}

			// Add to visible entities list
			m_visibleChunks.Add(chunk);
		}

		// Display water layers
		for(INT32 j = 0; j < chunk->GetNumWaterLayers(); ++j)
		{
			CRuWorld_Water *waterLayer = chunk->GetWaterLayerByIndex(j);

			// Override material
			CRuWaterBodyDescriptor *descriptor = NULL;
			if(m_waterBodyDescriptorMap->Get(waterLayer->GetWaterBodyGUID(), waterLayer->GetWaterBodyGUID().HashCode(), descriptor))
			{
				waterLayer->SetWaterLayerMaterial(descriptor->m_waterMaterial);
			}

			m_waterContainer->AddChild(waterLayer);

			// Add water layer to list of visible water entities
			m_visibleWaterEntities.Add(waterLayer);
		}
	}

	// Update entity enumeration
	m_objectManager->UpdateEnumeration(viewCamera->GetSweepBounds());

	// Update doodad enumeration
	m_doodadManager->UpdateEnumeration(viewCamera->GetSweepBounds());

	// Attach/detach sub entities based on visibility options
	m_renderTerrain ? this->AddChild(m_chunkContainer) : m_chunkContainer->DetachFromParent();
	m_renderBackdropTerrain ? this->AddChild(m_backdropChunkContainer) : m_backdropChunkContainer->DetachFromParent();
	m_renderGrass ? this->AddChild(m_grassContainer) : m_grassContainer->DetachFromParent();
	m_renderObjects ? this->AddChild(m_objectManager) : m_objectManager->DetachFromParent();
	m_renderObjects ? this->AddChild(m_doodadManager) : m_doodadManager->DetachFromParent();
	m_renderWater ? this->AddChild(m_waterContainer) : m_waterContainer->DetachFromParent();
}

const CRuAABB &CRuOptimalTerrain::GetTerrainBounds() const
{
	return m_terrainBounds;
}

INT32 CRuOptimalTerrain::GetNumXChunks() const
{
	return m_dataSource->GetNumXChunks();
}

INT32 CRuOptimalTerrain::GetNumZChunks() const
{
	return m_dataSource->GetNumZChunks();
}

INT32 CRuOptimalTerrain::GetChunkIteration(INT32 x, INT32 z)
{
	return m_dataSource->GetChunkIteration(x, z);
}

BOOL CRuOptimalTerrain::ClearEnumerationCache()
{
	// Reset view volume
	m_viewVolume.Set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);

	// Clear internal enumerations
	m_enumeratedChunks.Clear();
	m_enumeratedBackdropChunks.Clear();
	m_enumeratedEntityGUIDs.Clear();
	m_enumeratedDoodadKeys.Clear();
	m_enumeratedDoodadClusters.Clear();

	// Clear external enumerations
	m_objectManager->ClearEnumerationCache();
	m_doodadManager->ClearEnumerationCache();

	return TRUE;
}

INT32 CRuOptimalTerrain::GetZoneID(const CRuSegment3 &segment)
{
	REAL nearestDistance = FLT_MAX;
	CRuVector3 nearestPoint;
	CRuWorld_Trigger *nearestTrigger = NULL;

	// Build collision query
	CRuCollisionQuery_Segment_Nearest segQuery;
	CRuMatrix4x4 identityMatrix;
	segQuery.m_worldTransform = &identityMatrix;
	segQuery.m_invWorldTransform = &identityMatrix;
	segQuery.m_ignoreCollisionFlag = TRUE;
	segQuery.m_colSegment = segment;

	// Iterate through potential colliding entity descriptors
	for(INT32 i = 0; i < m_enumeratedEntityGUIDs.Count(); ++i)
	{
		IRuObject *object = m_objectContainer->GetObject(m_enumeratedEntityGUIDs[i]);

		if(object && object->GetType() == CRuWorld_Trigger::Type)
		{
			CRuWorld_Trigger *trigger = static_cast<CRuWorld_Trigger *>(object);

			segQuery.m_positiveHit = FALSE;
			segQuery.m_collisionT = FLT_MAX;
			trigger->QueryCollision(&segQuery);

			if(segQuery.m_positiveHit)
			{
				if(segQuery.m_collisionT < nearestDistance)
				{
					nearestDistance = segQuery.m_collisionT;
					nearestPoint = segQuery.m_collisionPoint;
					nearestTrigger = trigger;
				}
			}
		}
	}

	// Return query results
	if(nearestTrigger)
	{
		return nearestTrigger->GetZoneID();
	}

	return 0;
}

void CRuOptimalTerrain::SetTerrainDetailLevel(INT32 detailLevel)
{
	m_terrainDetailLevel = detailLevel;
}

void CRuOptimalTerrain::ToggleOption_RenderTerrain()
{
	m_renderTerrain = !m_renderTerrain;
}

void CRuOptimalTerrain::ToggleOption_RenderTerrainTexture()
{
	m_renderTerrainTexture = !m_renderTerrainTexture;
}

void CRuOptimalTerrain::ToggleOption_RenderBackdropTerrain()
{
	m_renderBackdropTerrain = !m_renderBackdropTerrain;
}

void CRuOptimalTerrain::ToggleOption_RenderObjects()
{
	m_renderObjects = !m_renderObjects;
}

void CRuOptimalTerrain::ToggleOption_RenderGrass()
{
	m_renderGrass = !m_renderGrass;
}

void CRuOptimalTerrain::ToggleOption_RenderWater()
{
	m_renderWater = !m_renderWater;
}

INT32 CRuOptimalTerrain::GetNumMetricStrings()
{
	return 2;
}

const char *CRuOptimalTerrain::GetMetricString(INT32 stringIndex)
{
	switch(stringIndex)
	{
		case 0:
			sprintf(m_metric_String, "[Terrain Manager] [Chunk Misses : %d] [Preloaded Chunks : %d] [Preloaded Backdrops : %d]", m_metric_NumCacheMisses, m_metric_NumPreloadedChunks, m_metric_NumPreloadedBackdropChunks);
			return m_metric_String;

		case 1:
			sprintf(m_metric_String, "[Terrain Manager] [Enumerated Entities : %d] [(%f, %f) - (%f, %f)]", m_metric_NumEnumeratedEntities, m_standardViewVolume.Minima().m_x / 480.0f, m_standardViewVolume.Minima().m_z / 480.0f, m_standardViewVolume.Maxima().m_x / 480.0f, m_standardViewVolume.Maxima().m_z / 480.0f);
			return m_metric_String;
	}

	sprintf(m_metric_String, "[Terrain Manager] [Chunk Misses : %d] [Preloaded Chunks : %d] [Preloaded Backdrops : %d]", m_metric_NumCacheMisses, m_metric_NumPreloadedChunks, m_metric_NumPreloadedBackdropChunks);
	return m_metric_String;
}

void CRuOptimalTerrain::DumpDebugInfo()
{
	// Dump debug info for the data source
	m_dataSource->DumpDebugInfo();

	// Dump debug info for the terrain object
	char outputString[512];

	sprintf(outputString, "Number of enumerated entities: %d\n", m_metric_NumEnumeratedEntities);
	OutputDebugString(outputString);
}

BOOL CRuOptimalTerrain::ClearBackdropQueue()
{
	if(m_backdropQueue)
	{
		m_backdropQueue->ForAllElements(ClearBackdropQueueCallback, this);
		m_backdropQueue->Clear();
	}

	return TRUE;
}

BOOL CRuOptimalTerrain::ClearBackdropQueueCallback(const void *key, const void *data, void *userData)
{
	CRuWorld_Backdrop *backdrop = *((CRuWorld_Backdrop **) data);

	ruSAFE_RELEASE(backdrop);

	return TRUE;
}

BOOL CRuOptimalTerrain::ClearGrassQueue()
{
	if(m_grassQueue)
	{
		m_grassQueue->ForAllElements(ClearGrassQueueCallback, this);
		m_grassQueue->Clear();
	}

	return TRUE;
}

BOOL CRuOptimalTerrain::ClearGrassQueueCallback(const void *key, const void *data, void *userData)
{
	CRuEntity_Particle_Grass_Optimized *grassSys = *((CRuEntity_Particle_Grass_Optimized **) data);

	ruSAFE_RELEASE(grassSys);

	return TRUE;
}

// ************************************************************************************************************************************************************

#pragma managed
