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

#include "../../Scene/Base/RuSceneBase.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuSplatIDMap, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuSplatIDMap, "CRuSplatIDMap", "IRuObject")
ruCLASSGUID_DEFINE(CRuSplatIDMap, 0x7CD84C24, 0x7D9841cb, 0xB3B04ECB, 0xEA72A60F)

ruRTTI_DEFINE_SUBCLASS(CRuSplatMapBuilder, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuSplatMapBuilder, "CRuSplatMapBuilder", "IRuObject")
ruCLASSGUID_DEFINE(CRuSplatMapBuilder, 0xB2566073, 0x8BB94243, 0xABD39B2A, 0xF218782E)

ruRTTI_DEFINE_SUBCLASS(CRuSplatMapManager, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuSplatMapManager, "CRuSplatMapManager", "IRuObject")
ruCLASSGUID_DEFINE(CRuSplatMapManager, 0x23C2B172, 0xBA764342, 0xA6D41AA5, 0x4409ACD0)

ruRTTI_DEFINE_SUBCLASS(CRuTextureList, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuTextureList, "CRuTextureList", "IRuObject")
ruCLASSGUID_DEFINE(CRuTextureList, 0x2AE2F39B, 0x53D74887, 0x8A59457A, 0x5475DC5F)

ruRTTI_DEFINE_SUBCLASS(CRuWaterBodyDescriptor, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuWaterBodyDescriptor, "CRuWaterBodyDescriptor", "IRuObject")
ruCLASSGUID_DEFINE(CRuWaterBodyDescriptor, 0xF7BB4692, 0x215443a8, 0xAAD2AF46, 0x20D6F03B)

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

CRuTerrainUtility_ChunkInfo::CRuTerrainUtility_ChunkInfo(CRuVector3 *normalField, BYTE *autoPaintField, CRuVector3 &origin, INT32 fieldWidth, REAL gridSize)
:	m_normalField(normalField),
	m_autoPaintField(autoPaintField),
	m_origin(origin),
	m_fieldWidth(fieldWidth),
	m_gridSize(gridSize)
{
}

REAL CRuTerrainUtility_ChunkInfo::EvaluateSlope(const CRuVector3 &position, REAL range)
{
	CRuVector3 upVec(0.0f, 1.0f, 0.0f);
	CRuVector3 relPos = position - m_origin;

	INT32 iX0 = min(m_fieldWidth, max(0, static_cast<INT32>(relPos.x / m_gridSize)));
	INT32 iZ0 = min(m_fieldWidth, max(0, static_cast<INT32>(relPos.z / m_gridSize)));
	INT32 iX1 = (iX0 == m_fieldWidth) ? m_fieldWidth : iX0 + 1;
	INT32 iZ1 = (iZ0 == m_fieldWidth) ? m_fieldWidth : iZ0 + 1;

	REAL p0 = 1.0f - DotProduct(upVec, m_normalField[iX0 + iZ0 * (m_fieldWidth + 1)]);
	REAL p1 = 1.0f - DotProduct(upVec, m_normalField[iX1 + iZ0 * (m_fieldWidth + 1)]);
	REAL p2 = 1.0f - DotProduct(upVec, m_normalField[iX0 + iZ1 * (m_fieldWidth + 1)]);
	REAL p3 = 1.0f - DotProduct(upVec, m_normalField[iX1 + iZ1 * (m_fieldWidth + 1)]);

	REAL p0Weight = m_gridSize - max(fabs(iX0 * m_gridSize - relPos.x), fabs(iZ0 * m_gridSize - relPos.z));
	REAL p1Weight = m_gridSize - max(fabs(iX1 * m_gridSize - relPos.x), fabs(iZ0 * m_gridSize - relPos.z));
	REAL p2Weight = m_gridSize - max(fabs(iX0 * m_gridSize - relPos.x), fabs(iZ1 * m_gridSize - relPos.z));
	REAL p3Weight = m_gridSize - max(fabs(iX1 * m_gridSize - relPos.x), fabs(iZ1 * m_gridSize - relPos.z));

	return (p0 * p0Weight + p1 * p1Weight + p2 * p2Weight + p3 * p3Weight) / (p0Weight + p1Weight + p2Weight + p3Weight);
}

BOOL CRuTerrainUtility_ChunkInfo::PaintOK(const CRuVector3 &position)
{
	CRuVector3 relPos = position - m_origin;

	relPos.x += m_gridSize;
	relPos.z += m_gridSize;

	INT32 iX0 = min(m_fieldWidth + 2, max(0, static_cast<INT32>(relPos.x / m_gridSize)));
	INT32 iZ0 = min(m_fieldWidth + 2, max(0, static_cast<INT32>(relPos.z / m_gridSize)));
	INT32 iX1 = (iX0 == m_fieldWidth + 2) ? m_fieldWidth + 2 : iX0 + 1;
	INT32 iZ1 = (iZ0 == m_fieldWidth + 2) ? m_fieldWidth + 2 : iZ0 + 1;

	INT32 changeCount = 0;

	if(m_autoPaintField[iX0 + iZ0 * (m_fieldWidth + 3)])
	{
		++changeCount;
	}

	if(m_autoPaintField[iX1 + iZ0 * (m_fieldWidth + 3)])
	{
		++changeCount;
	}

	if(m_autoPaintField[iX0 + iZ1 * (m_fieldWidth + 3)])
	{
		++changeCount;
	}

	if(m_autoPaintField[iX1 + iZ1 * (m_fieldWidth + 3)])
	{
		++changeCount;
	}

	if(changeCount > 0)
	{
		return TRUE;
	}

	return FALSE;
}

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

CRuImageConvolver::CRuImageConvolver(INT32 kernelSize, float *kernel, INT32 imageSize)
{
	// Store kernel
	m_kernelSize = kernelSize;
	m_kernel = ruNEW float [m_kernelSize];
	memcpy(m_kernel, kernel, sizeof(float) * m_kernelSize);

	// Initialize image
	m_dimension = imageSize;
	m_paddingSize = m_kernelSize / 2;
	m_realDimension = m_dimension + m_paddingSize * 2;
	m_image = ruNEW UINT8 [m_realDimension * m_realDimension];
	m_workBuffer = ruNEW UINT8 [m_realDimension];

	// Clear memory
	memset(m_image, 0, sizeof(UINT8) * m_realDimension * m_realDimension);
	memset(m_workBuffer, 0, sizeof(UINT8) * m_realDimension);
}

CRuImageConvolver::~CRuImageConvolver()
{
	delete[] m_kernel;
	delete[] m_image;
	delete[] m_workBuffer;
}

void CRuImageConvolver::ClearImageData()
{
	if(m_image)
	{
		memset(m_image, 0, sizeof(UINT8) * m_realDimension * m_realDimension);
	}
}

UINT8 *CRuImageConvolver::GetImageData()
{
	return &m_image[m_paddingSize * m_realDimension + m_paddingSize];
}

UINT8 *CRuImageConvolver::GetRawImageData()
{
	return m_image;
}

INT32 CRuImageConvolver::GetImagePitch()
{
	return m_realDimension;
}

BOOL CRuImageConvolver::Convolve()
{
	// Convolution along X
	for(INT32 y = 0; y < m_dimension; ++y)
	{
		INT32 srcOffset = (m_paddingSize + y) * m_realDimension;
		INT32 destOffset = m_paddingSize;

		for(INT32 x = 0; x < m_dimension; ++x, ++srcOffset, ++ destOffset)
		{
			// Convolve and accumulate
			float sum = 0.0f;

			for(INT32 i = 0; i < m_kernelSize; ++i)
			{
				sum += m_kernel[i] * m_image[srcOffset + i];
			}

			m_workBuffer[destOffset] = (UINT8) sum;
		}

		// Write-back
		memcpy(&m_image[(m_paddingSize + y) * m_realDimension], m_workBuffer, m_realDimension);
	}

	// Convolution along Y
	for(INT32 x = 0; x < m_dimension; ++x)
	{
		INT32 srcOffset = m_paddingSize + x;
		INT32 destOffset = m_paddingSize;

		for(INT32 y = 0; y < m_dimension; ++y, srcOffset += m_realDimension, ++destOffset)
		{
			// Convolve and accumulate
			float sum = 0.0f;

			for(INT32 i = 0; i < m_kernelSize; ++i)
			{
				sum += m_kernel[i] * m_image[srcOffset + i * m_realDimension];
			}

			m_workBuffer[destOffset] = (UINT8) sum;
		}

		// Write-back
		srcOffset = m_paddingSize;
		destOffset = m_paddingSize * m_realDimension + m_paddingSize + x;
		for(INT32 y = 0; y < m_dimension; ++y, ++srcOffset, destOffset += m_realDimension)
		{
			m_image[destOffset] = m_workBuffer[srcOffset];
		}
	}

	return TRUE;
}

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

CRuSplatIDMap::CRuSplatIDMap()
:	m_dimension(72),
	m_idMap(NULL),
	m_isFiltered(FALSE),
	m_isDirty(TRUE),
	m_numAlphaMaps(0),
	m_alphaMaps(NULL)
{
}

CRuSplatIDMap::~CRuSplatIDMap()
{
	delete[] m_idMap;

	// Release alpha maps
	if(m_alphaMaps)
	{
		for(INT32 i = 0; i < m_numAlphaMaps; ++i)
		{
			m_alphaMaps[i]->Release();
		}
		delete[] m_alphaMaps;
	}

	// Reset variables
	m_dimension = 0;
	m_idMap = NULL;
	m_numAlphaMaps = 0;
	m_alphaMaps = NULL;
}

IRuObject *CRuSplatIDMap::Clone(IRuObject *clonedObject)
{
	// Create a new cloned entity if necessary
	clonedObject = clonedObject ? clonedObject : ruNEW CRuSplatIDMap();

	// Chain cloning call to parent class
	IRuObject::Clone(clonedObject);

	CRuSplatIDMap *splatIDMap = static_cast<CRuSplatIDMap *>(clonedObject);

	splatIDMap->Initialize(m_dimension, m_splatRect);

	memcpy(splatIDMap->m_idMap, m_idMap, sizeof(UINT16) * m_dimension * m_dimension);

	return clonedObject;
}

BOOL CRuSplatIDMap::SerializeFrom(IRuStream *stream)
{
	CRuGUID classGUID;

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

	// Delete old map data
	delete[] m_idMap;

	// Read map type (0 = normal, 1 = alpha textures only)
	INT32 idMapType = 0;
	stream->Read(&idMapType, sizeof(INT32));

	if(idMapType == 0)
	{
		// Read map dimension
		stream->Read(&m_dimension, sizeof(INT32));

		// Read splat rect
		stream->Read(&m_splatRect.m_left, sizeof(float));
		stream->Read(&m_splatRect.m_top, sizeof(float));
		stream->Read(&m_splatRect.m_right, sizeof(float));
		stream->Read(&m_splatRect.m_bottom, sizeof(float));

		// Allocate map data array and read map data
		m_idMap = ruNEW UINT16 [m_dimension * m_dimension];
		stream->Read(m_idMap, sizeof(UINT16) * m_dimension * m_dimension);
	}
	else
	{
		// Read signature and version
		INT32 signature;
		INT32 version = 1;
		stream->Read(&signature, sizeof(INT32));

		if(signature == 0xFFFFFFFF)
		{
			// Signature found, read version 
			stream->Read(&version, sizeof(INT32));
		}
		else
		{
			// No signature, this is an old model stream, so rewind
			stream->Seek(-4, ruSSM_Current);
		}

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

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

		// Read layer IDs
		for(INT32 i = 0; i < numLayers; ++i)
		{
			UINT16 layerID;
			stream->Read(&layerID, sizeof(UINT16));
			m_layerIDs.Add(layerID);
		}

		// Read number of alpha maps
		stream->Read(&m_numAlphaMaps, sizeof(INT32));
		m_alphaMaps = ruNEW IRuTexture * [m_numAlphaMaps];

		for(INT32 i = 0; i < m_numAlphaMaps; ++i)
		{
			// Read number of mip levels
			INT32 numMipLevels = 1;
			if(version > 1)
			{
				stream->Read(&numMipLevels, sizeof(INT32));
			}

			// Read alpha map dimension
			UINT32 alphaMapWidth, alphaMapHeight;
			stream->Read(&alphaMapWidth, sizeof(UINT32));
			stream->Read(&alphaMapHeight, sizeof(UINT32));

			// Create alpha map texture
			if(version == 1)
			{
				m_alphaMaps[i] = g_ruEngine->CreateTexture(alphaMapWidth, alphaMapHeight, 0, ruSURFACEFMT_X1R5G5B5, ruRESPOOL_MANAGED);
			}
			else
			{
				m_alphaMaps[i] = g_ruEngine->CreateTexture(alphaMapWidth, alphaMapHeight, numMipLevels, ruSURFACEFMT_X1R5G5B5, ruRESPOOL_MANAGED);
			}

			// Read alpha map
			assert(m_alphaMaps[i]);

			// Read alpha map mip levels
			for(INT32 j = 0; j < numMipLevels; ++j)
			{
				// Lock the alpha map surface
				BYTE *alphaBits;
				INT32 alphaPitch;
				m_alphaMaps[i]->Lock(j, (void **) &alphaBits, &alphaPitch);

				for(UINT32 y = 0; y < alphaMapHeight; ++y)
				{
					UINT32 offset = (y * alphaPitch);

					stream->Read(&alphaBits[offset], alphaMapWidth * 2);
				}

				// Unlock alpha map surface
				m_alphaMaps[i]->Unlock(j);
			}

			// If the splat map is version 1, generate mip levels
			if(version == 1)
			{
				m_alphaMaps[i]->GenerateMipLevels();
			}
		}

		m_isFiltered = TRUE;
		m_isDirty = FALSE;
		m_idMap = NULL;
	}

	return TRUE;
}

BOOL CRuSplatIDMap::SerializeTo(IRuStream *stream)
{
	if(m_idMap == NULL)
		return FALSE;

	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write map type (0 = normal, 1 = alpha textures only)
	INT32 idMapType = 0;
	stream->Write(&idMapType, sizeof(INT32));

	// Write map dimension
	stream->Write(&m_dimension, sizeof(INT32));

	// Write splat rect
	stream->Write(&m_splatRect.m_left, sizeof(float));
	stream->Write(&m_splatRect.m_top, sizeof(float));
	stream->Write(&m_splatRect.m_right, sizeof(float));
	stream->Write(&m_splatRect.m_bottom, sizeof(float));
	
	// Write map data
	stream->Write(m_idMap, sizeof(UINT16) * m_dimension * m_dimension);

	return TRUE;
}

BOOL CRuSplatIDMap::SerializeToAlphaMaps(IRuStream *stream)
{
	if(m_idMap == NULL)
		return FALSE;

	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write map type (0 = normal, 1 = alpha textures only)
	INT32 idMapType = 1;
	stream->Write(&idMapType, sizeof(INT32));

	// Write id map dimension
	stream->Write(&m_dimension, sizeof(INT32));

	// Write number of layers
	INT32 numLayers = m_layerIDs.Count();
	stream->Write(&numLayers, sizeof(INT32));

	// Write layer IDs
	for(INT32 i = 0; i < m_layerIDs.Count(); ++i)
	{
		stream->Write(&m_layerIDs[i], sizeof(UINT16));
	}

	// Write number of alpha maps
	stream->Write(&m_numAlphaMaps, sizeof(INT32));

	for(INT32 i = 0; i < m_numAlphaMaps; ++i)
	{
		// Write alpha map dimension
		UINT32 alphaMapWidth = m_alphaMaps[i]->GetLevelWidth(0);
		UINT32 alphaMapHeight = m_alphaMaps[i]->GetLevelHeight(0);
		stream->Write(&alphaMapWidth, sizeof(UINT32));
		stream->Write(&alphaMapHeight, sizeof(UINT32));

		// Write alpha map

		// Lock the alpha map surface
		BYTE *alphaBits;
		INT32 alphaPitch;
		m_alphaMaps[i]->Lock(0, (void **) &alphaBits, &alphaPitch);

		for(UINT32 y = 0; y < alphaMapHeight; ++y)
		{
			UINT32 offset = (y * alphaPitch);

			stream->Write(&alphaBits[offset], alphaMapWidth * 2);
		}

		// Unlock alpha map surface
		m_alphaMaps[i]->Unlock(0);
	}

	return TRUE;
/*
	// NOTE: This is the new export routine, but we use the old one for now, since on-the-fly mip generation seems fast enough
	if(m_idMap == NULL)
		return FALSE;

	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write map type (0 = normal, 1 = alpha textures only)
	INT32 idMapType = 1;
	stream->Write(&idMapType, sizeof(INT32));

	// Write signature
	INT32 signature = 0xFFFFFFFF;
	stream->Write(&signature, sizeof(INT32));

	// Write version
	INT32 version = 2;
	stream->Write(&version, sizeof(INT32));

	// Write id map dimension
	stream->Write(&m_dimension, sizeof(INT32));

	// Write number of layers
	INT32 numLayers = m_layerIDs.Count();
	stream->Write(&numLayers, sizeof(INT32));

	// Write layer IDs
	for(INT32 i = 0; i < m_layerIDs.Count(); ++i)
	{
		stream->Write(&m_layerIDs[i], sizeof(UINT16));
	}

	// Write number of alpha maps
	stream->Write(&m_numAlphaMaps, sizeof(INT32));

	if(m_numAlphaMaps > 0)
	{
		// Create temporary alpha map
		IRuTexture *currentAlphaMap = g_ruEngine->CreateTexture(m_alphaMaps[0]->GetLevelWidth(0), m_alphaMaps[0]->GetLevelHeight(0), 0, ruSURFACEFMT_X1R5G5B5, ruRESPOOL_MANAGED);
		INT32 numMipLevels = currentAlphaMap->GetLevelCount();

		for(INT32 i = 0; i < m_numAlphaMaps; ++i)
		{
			// Get alpha map dimensions for level 0
			UINT32 alphaMapWidth = m_alphaMaps[i]->GetLevelWidth(0);
			UINT32 alphaMapHeight = m_alphaMaps[i]->GetLevelHeight(0);

			// Copy level 0 to temporary alpha map texture
			BYTE *srcAlphaBits, *destAlphaBits;
			INT32 srcAlphaPitch, destAlphaPitch;
			m_alphaMaps[i]->Lock(0, (void **) &srcAlphaBits, &srcAlphaPitch);
			currentAlphaMap->Lock(0, (void **) &destAlphaBits, &destAlphaPitch);

			for(UINT32 y = 0; y < alphaMapHeight; ++y)
			{
				UINT32 srcOffset = (y * srcAlphaPitch);
				UINT32 destOffset = (y * destAlphaPitch);
				memcpy(&destAlphaBits[destOffset], &srcAlphaBits[srcOffset], alphaMapWidth * 2);
			}

			currentAlphaMap->Unlock(0);
			m_alphaMaps[i]->Unlock(0);

			// Generate mip levels for the temporary alpha map
			currentAlphaMap->GenerateMipLevels();

			// Write number of mip levels
			stream->Write(&numMipLevels, sizeof(INT32));

			// Write alpha map dimension
			stream->Write(&alphaMapWidth, sizeof(UINT32));
			stream->Write(&alphaMapHeight, sizeof(UINT32));

			// Write alpha map mip levels
			for(INT32 j = 0; j < numMipLevels; ++j)
			{
				// Lock the alpha map surface
				currentAlphaMap->Lock(j, (void **) &srcAlphaBits, &srcAlphaPitch);

				for(UINT32 y = 0; y < alphaMapHeight; ++y)
				{
					UINT32 offset = (y * srcAlphaPitch);

					stream->Write(&srcAlphaBits[offset], alphaMapWidth * 2);
				}

				// Unlock alpha map surface
				currentAlphaMap->Unlock(j);
			}
		}

		// Release temporary alpha map
		currentAlphaMap->Release();
	}

	return TRUE;
*/
}

BOOL CRuSplatIDMap::SkipStream(IRuStream *stream)
{
	CRuGUID classGUID;

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

	// Read map type (0 = normal, 1 = alpha textures only)
	INT32 idMapType = 0;
	stream->Read(&idMapType, sizeof(INT32));

	if(idMapType == 0)
	{
		// Read map dimension
		INT32 dimension;
		stream->Read(&dimension, sizeof(INT32));

		// Skip the rest of the data
		stream->Seek(16 + 2 * dimension * dimension, ruSSM_Current);
	}
	else
	{
		// Read map dimension
		INT32 dimension;
		stream->Read(&dimension, sizeof(INT32));

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

		// Read layer IDs
		for(INT32 i = 0; i < numLayers; ++i)
		{
			UINT16 layerID;
			stream->Read(&layerID, sizeof(UINT16));
		}

		// Read number of alpha maps
		INT32 numAlphaMaps;
		stream->Read(&numAlphaMaps, sizeof(INT32));

		for(INT32 i = 0; i < numAlphaMaps; ++i)
		{
			// Read alpha map dimension
			UINT32 alphaMapWidth, alphaMapHeight;
			stream->Read(&alphaMapWidth, sizeof(UINT32));
			stream->Read(&alphaMapHeight, sizeof(UINT32));

			// Skip alpha map data
			stream->Seek(alphaMapHeight * alphaMapWidth * 2, ruSSM_Current);
		}
	}

	return TRUE;
}

BOOL CRuSplatIDMap::Initialize(INT32 dimension, const CRuRectangle &splatRect)
{
	if(dimension > 0)
	{
		// Save specified dimension
		m_dimension = dimension;

		// Save specified splat rect
		m_splatRect = splatRect;

		// Allocate map and initialize data to zero
		m_idMap = ruNEW UINT16 [m_dimension * m_dimension];
		memset(m_idMap, 0, sizeof(UINT16) * m_dimension * m_dimension);

		return TRUE;
	}

	return FALSE;
}

INT32 CRuSplatIDMap::GetNumLayers() const
{
	return m_layerIDs.Count();
}

void CRuSplatIDMap::GetIDListAsString(char *splatIDListOut)
{
	if(splatIDListOut)
	{
		splatIDListOut[0] = NULL;

		for(INT32 i = 0; i < m_layerIDs.Count(); ++i)
		{
			char temp[16];
			sprintf(temp, "%d ", m_layerIDs[i]);
			strcat(splatIDListOut, temp);
		}
	}
}

UINT16 CRuSplatIDMap::GetLayerID(INT32 layerIndex) const
{
	if(layerIndex >= 0 && layerIndex < m_layerIDs.Count())
	{
		return m_layerIDs[layerIndex];
	}

	return 0;
}

INT32 CRuSplatIDMap::GetNumAlphaMaps() const
{
	return m_numAlphaMaps;
}

IRuTexture *CRuSplatIDMap::GetAlphaMap(INT32 mapIndex)
{
	if(mapIndex >= 0 && mapIndex < m_numAlphaMaps)
		return m_alphaMaps[mapIndex];

	return NULL;
}

INT32 CRuSplatIDMap::GetDimension() const
{
	return m_dimension;
}

BOOL CRuSplatIDMap::PaintSplat(const CRuVector3 &center, float radius, UINT16 splatID)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);
	
	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = max(0, (INT32) (effectRect.m_left / xGridSize));
	INT32 sZ = max(0, (INT32) (effectRect.m_top / zGridSize));
	INT32 eX = min(m_dimension, (INT32) (effectRect.m_right / xGridSize) + 1);
	INT32 eZ = min(m_dimension, (INT32) (effectRect.m_bottom / zGridSize) + 1);

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);

			float distance = (gridPoint - relativeCenter).Magnitude();
			if(distance <= radius)
			{
				m_idMap[x + z * m_dimension] = splatID;
				m_isFiltered = FALSE;
				m_isDirty = TRUE;
			}
		}
	}

	return m_isDirty;
}

BOOL CRuSplatIDMap::SprayPaintSplat(const CRuVector3 &center, float radius, UINT16 splatID, float sprayProbability)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = max(0, (INT32) (effectRect.m_left / xGridSize));
	INT32 sZ = max(0, (INT32) (effectRect.m_top / zGridSize));
	INT32 eX = min(m_dimension, (INT32) (effectRect.m_right / xGridSize) + 1);
	INT32 eZ = min(m_dimension, (INT32) (effectRect.m_bottom / zGridSize) + 1);

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);

			float probability = g_mtRand.GenerateFloat();

			if(probability <= sprayProbability)
			{
				float distance = (gridPoint - relativeCenter).Magnitude();
				if(distance <= radius)
				{
					m_idMap[x + z * m_dimension] = splatID;
					m_isFiltered = FALSE;
					m_isDirty = TRUE;
				}
			}
		}
	}

	return m_isDirty;
}

BOOL CRuSplatIDMap::BlockFillSplat(const CRuVector3 &center, float radius, UINT16 splatID)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = 0;
	INT32 sZ = 0;
	INT32 eX = m_dimension;
	INT32 eZ = m_dimension;

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			m_idMap[x + z * m_dimension] = splatID;
		}
	}

	m_isFiltered = FALSE;
	m_isDirty = TRUE;

	return m_isDirty;
}

BOOL CRuSplatIDMap::FloodFillSplat(const CRuVector3 &center, float radius, UINT16 splatID, INT32 chunkKey, RuWorld::GetNeighborSplat getSplatCallback)
{
	INT32 maxFillBlocks = 4;

	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	REAL xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	REAL zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = min(max(0, (INT32) (relativeCenter.m_x / xGridSize)), m_dimension - 1);
	INT32 sZ = min(max(0, (INT32) (relativeCenter.m_z / zGridSize)), m_dimension - 1);

	UINT16 fillID = m_idMap[sX + sZ * m_dimension];

	FloodFillDesc *fillDesc = ruNEW FloodFillDesc();

	fillDesc->m_chunkKey = chunkKey;
	fillDesc->m_splatIDMap = this;
	fillDesc->m_texelStack.Push((sX << 16) | sZ);

	CRuStack<FloodFillDesc *> fillDescStack;
	fillDescStack.Push(fillDesc);

	while(fillDescStack.IsEmpty() == FALSE)
	{
		// Pop next fill descriptor off the stack
		FloodFillDesc *curFillDesc = fillDescStack.Pop();

		CRuSplatIDMap *splatIDMap = curFillDesc->m_splatIDMap;
		CRuStack<INT32> &texelStack = curFillDesc->m_texelStack;
		UINT16 *idMap = splatIDMap->m_idMap;

		INT32 cx = ruWORLD_CHUNKKEYX(curFillDesc->m_chunkKey);
		INT32 cz = ruWORLD_CHUNKKEYZ(curFillDesc->m_chunkKey);

		INT32 distanceToChunk = max(abs(cx - ruWORLD_CHUNKKEYX(chunkKey)), abs(cz - ruWORLD_CHUNKKEYZ(chunkKey)));
		if(distanceToChunk > maxFillBlocks)
		{
			delete curFillDesc;
			continue;
		}

		while(texelStack.IsEmpty() == FALSE)
		{
			INT32 texelKey = texelStack.Pop();
			INT32 x = (texelKey >> 16) & 0x0000FFFF;
			INT32 z = texelKey & 0x0000FFFF;
			INT32 texelIndex = x + z * m_dimension;

			if(idMap[texelIndex] == fillID && idMap[texelIndex] != splatID)
			{
				// Replace splat
				idMap[texelIndex] = splatID;

				// Push neighbors onto stack
				for(INT32 i = 0, k = 0; i < 3; ++i)
				{
					for(INT32 j = 0; j < 3; ++j, ++k)
					{
						INT32 nx = x + j - 1;
						INT32 nz = z + i - 1;

						if(nx < 0)
						{
							if(nz < 0)
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 0);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 xPadding = static_cast<INT32>(fabs(((neighborSplatIDMap->m_splatRect.m_right - splatIDMap->m_splatRect.m_left) / xGridSize) / 2));
									INT32 zPadding = static_cast<INT32>(fabs(((neighborSplatIDMap->m_splatRect.m_bottom - splatIDMap->m_splatRect.m_top) / zGridSize) / 2));

									// Adjust texel coordinates
									nx = m_dimension - xPadding;
									nz = m_dimension - zPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx - 1, cz - 1);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
							else if(nz >= m_dimension)
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 5);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 xPadding = static_cast<INT32>(fabs(((neighborSplatIDMap->m_splatRect.m_right - splatIDMap->m_splatRect.m_left) / xGridSize) / 2));
									INT32 zPadding = static_cast<INT32>(fabs(((splatIDMap->m_splatRect.m_bottom - neighborSplatIDMap->m_splatRect.m_top) / zGridSize) / 2));

									// Adjust texel coordinates
									nx = m_dimension - xPadding;
									nz = zPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx - 1, cz + 1);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
							else
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 3);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 xPadding = static_cast<INT32>(fabs(((neighborSplatIDMap->m_splatRect.m_right - splatIDMap->m_splatRect.m_left) / xGridSize) / 2));

									// Adjust texel coordinates
									nx = m_dimension - xPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx - 1, cz);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
						}
						else if(nx >= m_dimension)
						{
							if(nz < 0)
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 2);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 xPadding = static_cast<INT32>(fabs(((splatIDMap->m_splatRect.m_right - neighborSplatIDMap->m_splatRect.m_left) / xGridSize) / 2));
									INT32 zPadding = static_cast<INT32>(fabs(((neighborSplatIDMap->m_splatRect.m_bottom - splatIDMap->m_splatRect.m_top) / zGridSize) / 2));

									// Adjust texel coordinates
									nx = xPadding;
									nz = m_dimension - zPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx + 1, cz - 1);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
							else if(nz >= m_dimension)
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 7);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 xPadding = static_cast<INT32>(fabs(((splatIDMap->m_splatRect.m_right - neighborSplatIDMap->m_splatRect.m_left) / xGridSize) / 2));
									INT32 zPadding = static_cast<INT32>(fabs(((splatIDMap->m_splatRect.m_bottom - neighborSplatIDMap->m_splatRect.m_top) / zGridSize) / 2));

									// Adjust texel coordinates
									nx = xPadding;
									nz = zPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx + 1, cz + 1);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
							else
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 4);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 xPadding = static_cast<INT32>(fabs(((splatIDMap->m_splatRect.m_right - neighborSplatIDMap->m_splatRect.m_left) / xGridSize) / 2));

									// Adjust texel coordinates
									nx = xPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx + 1, cz);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
						}
						else
						{
							if(nz < 0)
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 1);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 zPadding = static_cast<INT32>(fabs(((neighborSplatIDMap->m_splatRect.m_bottom - splatIDMap->m_splatRect.m_top) / zGridSize) / 2));

									// Adjust texel coordinates
									nz = m_dimension - zPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx, cz - 1);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);
								}
							}
							else if(nz >= m_dimension)
							{
								CRuSplatIDMap *neighborSplatIDMap = getSplatCallback(curFillDesc->m_chunkKey, 6);
								if(neighborSplatIDMap)
								{
									// Find padding along x and z
									INT32 zPadding = static_cast<INT32>(fabs(((splatIDMap->m_splatRect.m_bottom - neighborSplatIDMap->m_splatRect.m_top) / zGridSize) / 2));

									// Adjust texel coordinates
									nz = zPadding;

									FloodFillDesc *newFillDesc = ruNEW FloodFillDesc();
									newFillDesc->m_chunkKey = ruWORLD_MAKECHUNKKEY(cx, cz + 1);
									newFillDesc->m_splatIDMap = neighborSplatIDMap;
									newFillDesc->m_texelStack.Push((nx << 16) | nz);
									fillDescStack.Push(newFillDesc);

								}
							}
							else
							{
								texelStack.Push((nx << 16) | nz);
							}
						}
					}
				}
			}
		}

		splatIDMap->m_isFiltered = FALSE;
		splatIDMap->m_isDirty = TRUE;

		delete curFillDesc;
	}

	return m_isDirty;
}

void swap(UINT16 &obj0, UINT16 &obj1)
{
	UINT16 temp = obj0;
	obj0 = obj1;
	obj1 = temp;
}

BOOL CRuSplatIDMap::SmudgeSplat(const CRuVector3 &center, float radius, float smudgeProbability)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = max(0, (INT32) (effectRect.m_left / xGridSize));
	INT32 sZ = max(0, (INT32) (effectRect.m_top / zGridSize));
	INT32 eX = min(m_dimension, (INT32) (effectRect.m_right / xGridSize) + 1);
	INT32 eZ = min(m_dimension, (INT32) (effectRect.m_bottom / zGridSize) + 1);

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);

			float probability = g_mtRand.GenerateFloat();

			if(probability <= smudgeProbability)
			{
				float distance = (gridPoint - relativeCenter).Magnitude();
				if(distance <= radius)
				{
					float directionProbability = g_mtRand.GenerateFloat();

					if(directionProbability <= 0.250f)
					{
						if(z - 1 >= 0)
						{
							swap(m_idMap[x + z * m_dimension], m_idMap[x + (z - 1) * m_dimension]);
							m_isFiltered = FALSE;
							m_isDirty = TRUE;
						}
					}
					else if(directionProbability <= 0.500f)
					{
						if(z + 1 < m_dimension)
						{
							swap(m_idMap[x + z * m_dimension], m_idMap[x + (z + 1) * m_dimension]);
							m_isFiltered = FALSE;
							m_isDirty = TRUE;
						}
					}
					else if(directionProbability <= 0.750f)
					{
						if(x - 1 >= 0)
						{
							swap(m_idMap[x + z * m_dimension], m_idMap[x - 1 + z * m_dimension]);
							m_isFiltered = FALSE;
							m_isDirty = TRUE;
						}
					}
					else
					{
						if(x + 1 < m_dimension)
						{
							swap(m_idMap[x + z * m_dimension], m_idMap[x + 1 + z * m_dimension]);
							m_isFiltered = FALSE;
							m_isDirty = TRUE;
						}
					}
				}
			}
		}
	}

	return m_isDirty;
}

BOOL CRuSplatIDMap::ReplaceSplat(const CRuVector3 &center, float radius, UINT16 splatID)
{
	UINT16 oldSplatID = PickSplatID(center);

	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = 0;
	INT32 sZ = 0;
	INT32 eX = m_dimension;
	INT32 eZ = m_dimension;

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			if(m_idMap[x + z * m_dimension] == oldSplatID)
			{
				m_idMap[x + z * m_dimension] = splatID;
			}
		}
	}

	m_isFiltered = FALSE;
	m_isDirty = TRUE;

	return m_isDirty;
}

BOOL CRuSplatIDMap::AttractSplat(const CRuVector3 &center, float radius, UINT16 attractedSplatID, float attractProbability)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = max(0, (INT32) (effectRect.m_left / xGridSize));
	INT32 sZ = max(0, (INT32) (effectRect.m_top / zGridSize));
	INT32 eX = min(m_dimension, (INT32) (effectRect.m_right / xGridSize) + 1);
	INT32 eZ = min(m_dimension, (INT32) (effectRect.m_bottom / zGridSize) + 1);

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);

			float probability = g_mtRand.GenerateFloat();

			if(m_idMap[x + z * m_dimension] == attractedSplatID && probability <= attractProbability)
			{
				float distance = (gridPoint - relativeCenter).Magnitude();
				if(distance <= radius)
				{
					if(gridPoint.x < relativeCenter.x)
					{
						if(gridPoint.z < relativeCenter.z)
						{
							if(z + 1 < m_dimension && x + 1 < m_dimension)
							{
								swap(m_idMap[x + z * m_dimension], m_idMap[(x + 1) + (z + 1) * m_dimension]);
								m_isFiltered = FALSE;
								m_isDirty = TRUE;
							}
						}
						else if(gridPoint.z > relativeCenter.z)
						{
							if(z - 1 >= 0 && x + 1 < m_dimension)
							{
								swap(m_idMap[x + z * m_dimension], m_idMap[(x + 1) + (z - 1) * m_dimension]);
								m_isFiltered = FALSE;
								m_isDirty = TRUE;
							}
						}
						else
						{
							if(x + 1 < m_dimension)
							{
								swap(m_idMap[x + z * m_dimension], m_idMap[(x + 1) + z * m_dimension]);
								m_isFiltered = FALSE;
								m_isDirty = TRUE;
							}
						}
					}
					else if(gridPoint.x > relativeCenter.x)
					{
						if(gridPoint.z < relativeCenter.z)
						{
							if(z + 1 < m_dimension && x - 1 >= 0)
							{
								swap(m_idMap[x + z * m_dimension], m_idMap[(x - 1) + (z + 1) * m_dimension]);
								m_isFiltered = FALSE;
								m_isDirty = TRUE;
							}
						}
						else if(gridPoint.z > relativeCenter.z)
						{
							if(z - 1 >= 0 && x - 1 >= 0)
							{
								swap(m_idMap[x + z * m_dimension], m_idMap[(x - 1) + (z - 1) * m_dimension]);
								m_isFiltered = FALSE;
								m_isDirty = TRUE;
							}
						}
						else
						{
							if(x - 1 >= 0)
							{
								swap(m_idMap[x + z * m_dimension], m_idMap[(x - 1) + z * m_dimension]);
								m_isFiltered = FALSE;
								m_isDirty = TRUE;
							}
						}
					}
				}
			}
		}
	}

	return m_isDirty;
}

UINT16 CRuSplatIDMap::PickSplatID(const CRuVector3 &center)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 x = max(0, (INT32) (relativeCenter.x / xGridSize));
	INT32 z = max(0, (INT32) (relativeCenter.z / zGridSize));

	return m_idMap[x + z * m_dimension];
}

BOOL CRuSplatIDMap::MojoPaint(const CRuVector3 &center, float radius, CRuMojo_AttributeParcel<REAL> *refHF, CRuMojo_AttributeParcel<REAL> *chunkHF, CRuFractal_Base *srcFractal, const CRuSplatIDMap_PaintSetting &paintSetting)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = max(0, (INT32) (effectRect.m_left / xGridSize));
	INT32 sZ = max(0, (INT32) (effectRect.m_top / zGridSize));
	INT32 eX = min(m_dimension, (INT32) (effectRect.m_right / xGridSize) + 1);
	INT32 eZ = min(m_dimension, (INT32) (effectRect.m_bottom / zGridSize) + 1);

	REAL coordScale = paintSetting.m_brushLimit / radius;

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);
			CRuVector3 worldPoint(gridPoint.x + m_splatRect.m_left, 1000.0f, gridPoint.z + m_splatRect.m_top);
			CRuVector3 relativePos = worldPoint - center;

			float distance = (gridPoint - relativeCenter).Magnitude();
			if(distance <= radius)
			{
				REAL refHeight = Mojo_GetHeightAtPoint(refHF, worldPoint);
				REAL chunkHeight = Mojo_GetHeightAtPoint(chunkHF, worldPoint);
				REAL relativeHeight = chunkHeight - refHeight;

				if(fabs(relativeHeight) > ruEPSILON)
				{
					REAL noiseLevel = relativeHeight;
					REAL slope = Mojo_GetSlopeAtPoint(chunkHF, worldPoint);

					if(noiseLevel >= paintSetting.m_threshold_0 && slope >= paintSetting.m_slope_0 && paintSetting.m_splat_0 >= 0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_0;
					}
					else if(noiseLevel >= paintSetting.m_threshold_1 && slope >= paintSetting.m_slope_1 && paintSetting.m_splat_1 >= 0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_1;
					}
					else if(noiseLevel >= paintSetting.m_threshold_2 && slope >= paintSetting.m_slope_2 && paintSetting.m_splat_2 >= 0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_2;
					}
					else if(noiseLevel >= paintSetting.m_threshold_3 && slope >= paintSetting.m_slope_3 && paintSetting.m_splat_3 >= 0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_3;
					}
					else if(noiseLevel >= paintSetting.m_threshold_4 && slope >= paintSetting.m_slope_4 && paintSetting.m_splat_4 >= 0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_4;
					}
					else if(noiseLevel >= paintSetting.m_threshold_5 && slope >= paintSetting.m_slope_5 && paintSetting.m_splat_5 >= 0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_5;
					}

					m_isFiltered = FALSE;
					m_isDirty = TRUE;
				}
			}
		}
	}

	return m_isDirty;
}

BOOL CRuSplatIDMap::AutoPaintSplat(CRuMojo_AttributeParcel<REAL> *refHF, CRuMojo_AttributeParcel<REAL> *chunkHF, CRuFractal_Base *srcFractal, const CRuSplatIDMap_PaintSetting &paintSetting)
{
	REAL xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	REAL zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = 0;
	INT32 sZ = 0;
	INT32 eX = m_dimension;
	INT32 eZ = m_dimension;

//	REAL coordScale = paintSetting.m_brushLimit / radius;

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);
			CRuVector3 worldPoint(gridPoint.x + m_splatRect.m_left, 1000.0f, gridPoint.z + m_splatRect.m_top);

			REAL refHeight = Mojo_GetHeightAtPoint(refHF, worldPoint);
			REAL chunkHeight = Mojo_GetHeightAtPoint(chunkHF, worldPoint);
			REAL relativeHeight = chunkHeight - refHeight;

			if(fabs(relativeHeight) > ruEPSILON)
			{
				REAL noiseLevel = relativeHeight;
				REAL slope = Mojo_GetSlopeAtPoint(chunkHF, worldPoint);
/*
				// Get basic noise level
				if(paintSetting.m_brushLimit > 0.0f)
				{
					// Offset relative position by painting clamp center
					relativePos.x *= coordScale;
					relativePos.z *= coordScale;

					relativePos.x += paintSetting.m_brushCenterX;
					relativePos.z += paintSetting.m_brushCenterY;

					noiseLevel = max(0.0f, min(1.0f, (srcFractal->Evaluate(relativePos.x, relativePos.z, 0.0f) + 1.0f) / 2.0f));
				}
				else
				{
					noiseLevel = max(0.0f, min(1.0f, (srcFractal->Evaluate(worldPoint.x, worldPoint.z, 0.0f) + 1.0f) / 2.0f));
				}

				// Noise falloff
				noiseLevel *= paintSetting.EvaluateFalloff(paintSetting.m_noiseFalloffType, distance, radius);
*/

				if(noiseLevel >= paintSetting.m_threshold_0 && slope >= paintSetting.m_slope_0)
				{
					m_idMap[x + z * m_dimension] = paintSetting.m_splat_0;
				}
				else if(noiseLevel >= paintSetting.m_threshold_1 && slope >= paintSetting.m_slope_1)
				{
					m_idMap[x + z * m_dimension] = paintSetting.m_splat_1;
				}
				else if(noiseLevel >= paintSetting.m_threshold_2 && slope >= paintSetting.m_slope_2)
				{
					m_idMap[x + z * m_dimension] = paintSetting.m_splat_2;
				}
				else if(noiseLevel >= paintSetting.m_threshold_3 && slope >= paintSetting.m_slope_3)
				{
					m_idMap[x + z * m_dimension] = paintSetting.m_splat_3;
				}
				else if(noiseLevel >= paintSetting.m_threshold_4 && slope >= paintSetting.m_slope_4)
				{
					m_idMap[x + z * m_dimension] = paintSetting.m_splat_4;
				}
				else if(noiseLevel >= paintSetting.m_threshold_5 && slope >= paintSetting.m_slope_5)
				{
					m_idMap[x + z * m_dimension] = paintSetting.m_splat_5;
				}

				m_isFiltered = FALSE;
				m_isDirty = TRUE;
			}
		}
	}

	return m_isDirty;
}

BOOL CRuSplatIDMap::AutoPaintSplat(const CRuVector3 &center, float radius, CRuFractal_Base *srcFractal, const CRuSplatIDMap_PaintSetting &paintSetting, CRuTerrainUtility_ChunkInfo &normalField)
{
	CRuVector3 relativeCenter(center.x - m_splatRect.m_left, 0.0f, center.z - m_splatRect.m_top);

	CRuRectangle effectRect;
	effectRect.m_left = relativeCenter.x - radius;
	effectRect.m_right = relativeCenter.x + radius;
	effectRect.m_top = relativeCenter.z - radius;
	effectRect.m_bottom = relativeCenter.z + radius;

	if(	effectRect.m_right < m_splatRect.m_left - m_splatRect.m_left ||
		effectRect.m_left > m_splatRect.m_right - m_splatRect.m_left ||
		effectRect.m_bottom < m_splatRect.m_top - m_splatRect.m_top ||
		effectRect.m_top > m_splatRect.m_bottom - m_splatRect.m_top)
		return FALSE;

	float xGridSize = (m_splatRect.m_right - m_splatRect.m_left) / m_dimension;
	float zGridSize = (m_splatRect.m_bottom - m_splatRect.m_top) / m_dimension;

	INT32 sX = max(0, (INT32) (effectRect.m_left / xGridSize));
	INT32 sZ = max(0, (INT32) (effectRect.m_top / zGridSize));
	INT32 eX = min(m_dimension, (INT32) (effectRect.m_right / xGridSize) + 1);
	INT32 eZ = min(m_dimension, (INT32) (effectRect.m_bottom / zGridSize) + 1);

	REAL coordScale = paintSetting.m_brushLimit / radius;

	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 gridPoint(x * xGridSize + xGridSize / 2.0f, 0.0f, z * zGridSize + zGridSize / 2.0f);
			CRuVector3 worldPoint(gridPoint.x + m_splatRect.m_left, 1000.0f, gridPoint.z + m_splatRect.m_top);
			CRuVector3 relativePos = worldPoint - center;

			float distance = (gridPoint - relativeCenter).Magnitude();
			if(distance <= radius)
			{
				if(paintSetting.m_paintCondition == ruTUPAINTCONDITION_ALL || normalField.PaintOK(worldPoint))
				{
					REAL noiseLevel = 0.0f;
					REAL slope = normalField.EvaluateSlope(worldPoint, 1.0f);

					// Get basic noise level
					if(paintSetting.m_brushLimit > 0.0f)
					{
						// Offset relative position by painting clamp center
						relativePos.x *= coordScale;
						relativePos.z *= coordScale;

						relativePos.x += paintSetting.m_brushCenterX;
						relativePos.z += paintSetting.m_brushCenterY;

						noiseLevel = max(0.0f, min(1.0f, (srcFractal->Evaluate(relativePos.x, relativePos.z, 0.0f) + 1.0f) / 2.0f));
					}
					else
					{
						noiseLevel = max(0.0f, min(1.0f, (srcFractal->Evaluate(worldPoint.x, worldPoint.z, 0.0f) + 1.0f) / 2.0f));
					}

					// Noise falloff
					noiseLevel *= paintSetting.EvaluateFalloff(paintSetting.m_noiseFalloffType, distance, radius);
/*
					// Invert if necessary
					if(paintSetting.m_heightMultiplier < 0.0f)
					{
						noiseLevel = 1.0f - noiseLevel;
					}
*/
					if(noiseLevel >= paintSetting.m_threshold_0 && slope >= paintSetting.m_slope_0)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_0;
					}
					else if(noiseLevel >= paintSetting.m_threshold_1 && slope >= paintSetting.m_slope_1)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_1;
					}
					else if(noiseLevel >= paintSetting.m_threshold_2 && slope >= paintSetting.m_slope_2)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_2;
					}
					else if(noiseLevel >= paintSetting.m_threshold_3 && slope >= paintSetting.m_slope_3)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_3;
					}
					else if(noiseLevel >= paintSetting.m_threshold_4 && slope >= paintSetting.m_slope_4)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_4;
					}
					else if(noiseLevel >= paintSetting.m_threshold_5 && slope >= paintSetting.m_slope_5)
					{
						m_idMap[x + z * m_dimension] = paintSetting.m_splat_5;
					}

					m_isFiltered = FALSE;
					m_isDirty = TRUE;
				}
			}
		}
	}

	return m_isDirty;
}

INT32 CRuSplatIDMap::GetMemoryFootprint()
{
	INT32 footprint = 0;

	if(m_idMap)
	{
		footprint += sizeof(UINT16) * m_dimension * m_dimension;
	}

	if(m_alphaMaps)
	{
		for(INT32 i = 0; i < m_numAlphaMaps; ++i)
		{
			for(UINT32 j = 0; j < m_alphaMaps[i]->GetLevelCount(); ++j)
				footprint += (m_alphaMaps[i]->GetLevelWidth(j) * m_alphaMaps[i]->GetLevelHeight(j) * 2);
		}
	}

	return footprint;
}

INT32 CRuSplatIDMap::GetSplatIDMapSize(INT32 dimension)
{
	// NOTE: This function returns the correct size for id map type 0 (normal) only!
	//       Type 1 maps (alpha textures only) cannot have an accurate size returned
	//       because the size varies depending on the number of layers.

	INT32 idMapSize = 0;

	// GUID size
	idMapSize += 16;

	// ID map type
	idMapSize += sizeof(INT32);

	// Map dimension
	idMapSize += sizeof(INT32);

	// Splat rect
	idMapSize += sizeof(float) * 4;

	// Map data
	idMapSize += dimension * dimension * sizeof(UINT16);

	return idMapSize;
}

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

CRuSplatMapBuilder::CRuSplatMapBuilder()
:	m_idMapDimension(72),
	m_idMapPadding(4)
{
/*
	// NULL out convolvers
	for(INT32 i = 0; i < 3; ++i)
	{
		m_imageConvolvers[i] = NULL;
	}
*/
	m_imageConvolvers = NULL;
}

CRuSplatMapBuilder::~CRuSplatMapBuilder()
{
/*
	for(INT32 i = 0; i < 3; ++i)
	{
		delete m_imageConvolvers[i];
	}
*/

	if(m_imageConvolvers)
	{
		for(INT32 i = 0; i < m_maxLayers; ++i)
		{
			delete m_imageConvolvers[i];
		}
		delete[] m_imageConvolvers;
	}
}

BOOL CRuSplatMapBuilder::Initialize(INT32 idMapDimension, INT32 idMapPadding, INT32 mapSizeFactor)
{
	m_idMapDimension = idMapDimension;
	m_idMapPadding = idMapPadding;
	m_mapSizeFactor = mapSizeFactor;
	m_convolverDimension = m_idMapDimension * m_mapSizeFactor;
	m_alphaMapDimension = m_convolverDimension - (m_idMapPadding * 2 * m_mapSizeFactor);

	// Setup image convolvers
//	float gaussianKernel1D[3] = { 0.25f, 0.50f, 0.25f };
//	float gaussianKernel1D[5] = { 0.0625f, 0.2500f, 0.3750f, 0.2500f, 0.0625f };
//	float gaussianKernel1D[7] = { 0.006f, 0.061f, 0.242f, 0.383f, 0.242f, 0.061f, 0.006f };
	float gaussianKernel1D[7] = { 0.025f, 0.075f, 0.150f, 0.500f, 0.150f, 0.075f, 0.025f };
/*
	for(INT32 i = 0; i < 3; ++i)
	{
//		m_imageConvolvers[i] = ruNEW CRuImageConvolver(3, gaussianKernel1D, m_convolverDimension);
//		m_imageConvolvers[i] = ruNEW CRuImageConvolver(5, gaussianKernel1D, m_convolverDimension);
		m_imageConvolvers[i] = ruNEW CRuImageConvolver(7, gaussianKernel1D, m_convolverDimension);
	}
*/
	m_maxLayers = 12;
	m_imageConvolvers = ruNEW CRuImageConvolver * [m_maxLayers];

	for(INT32 i = 0; i < m_maxLayers; ++i)
	{
		m_imageConvolvers[i] = ruNEW CRuImageConvolver(7, gaussianKernel1D, m_convolverDimension);
	}

	return TRUE;
}

BOOL CRuSplatMapBuilder::BuildAlphaMaps(CRuSplatIDMap *splatIDMap, BOOL enableFilter)
{
	// Perform the alpha map build only if necessary
	if((splatIDMap->m_isFiltered == FALSE && enableFilter) || splatIDMap->m_isDirty || splatIDMap->m_numAlphaMaps == 0)
	{
		// Setup data references
		UINT16*&					idMap = splatIDMap->m_idMap;
		CRuArrayList<UINT16>&		layerIDs = splatIDMap->m_layerIDs;
		INT32&						numAlphaMaps = splatIDMap->m_numAlphaMaps;
		IRuTexture**&				alphaMaps = splatIDMap->m_alphaMaps;

		// Free previously allocated splat data
		layerIDs.Clear();
		if(alphaMaps)
		{
			for(INT32 i = 0; i < numAlphaMaps; ++i)
			{
				if(alphaMaps[i])
				{
					alphaMaps[i]->Release();
				}
			}

			delete[] alphaMaps;
		}

		INT32 numUniqueSplatIDs = 0;
		BOOL splatIDFound[2048];							// *** NOTE: We support only 2048 splat IDs, even though the data is 16 bits
		memset(splatIDFound, 0, sizeof(BOOL) * 2048);

		// Iterate and count the number of unique splat IDs in the splat map
		for(INT32 i = 0, numTexels = m_idMapDimension * m_idMapDimension; i < numTexels; ++i)
		{
			if(splatIDFound[idMap[i]] == FALSE)
			{
				// Increment splat ID count and mark it as found
				++numUniqueSplatIDs;
				splatIDFound[idMap[i]] = TRUE;

				// Add splat ID to the list of layers
				layerIDs.Add(idMap[i]);
			}
		}

		// Cap unique splats to m_maxLayers
		if(numUniqueSplatIDs > m_maxLayers)
		{
			numUniqueSplatIDs = m_maxLayers;
		}

		// Allocate alpha maps
		numAlphaMaps = (numUniqueSplatIDs / 3) + ((numUniqueSplatIDs % 3) ? 1 : 0);
		alphaMaps = ruNEW IRuTexture * [numAlphaMaps];
		for(INT32 i = 0; i < numAlphaMaps; ++i)
		{
			alphaMaps[i] = g_ruEngine->CreateTexture(m_alphaMapDimension, m_alphaMapDimension, 1, ruSURFACEFMT_X1R5G5B5, ruRESPOOL_MANAGED);
			if(alphaMaps[i] == NULL)
			{
				// Warn... cannot create texture
				return FALSE;
			}
		}

		UINT8 *imageDataArray[12];
		for(INT32 curSplatIndex = 0; curSplatIndex < m_maxLayers; ++curSplatIndex)
		{
			imageDataArray[curSplatIndex] = m_imageConvolvers[curSplatIndex]->GetRawImageData();
			m_imageConvolvers[curSplatIndex]->ClearImageData();
		}

		// Expand splat ID map to alpha maps
		for(INT32 curSplatIndex = 0; curSplatIndex < numUniqueSplatIDs; ++curSplatIndex)
		{
			UINT16 curSplatID = layerIDs[curSplatIndex];

			// Setup target pointers
			UINT8 *image = m_imageConvolvers[curSplatIndex]->GetImageData();
			INT32 imagePitch = m_imageConvolvers[curSplatIndex]->GetImagePitch();

			// Expand in the x direction
			for(INT32 i = 0; i < m_mapSizeFactor; ++i)
			{
				for(INT32 y = 0, offset = 0; y < m_idMapDimension; ++y)
				{
					INT32 targetOffset = imagePitch * y * m_mapSizeFactor + i;

					for(INT32 x = 0; x < m_idMapDimension; ++x, ++offset, targetOffset += m_mapSizeFactor)
					{
						if(idMap[offset] == curSplatID)
							image[targetOffset] = 255;
						else
							image[targetOffset] = 0;
					}
				}
			}

			// Expand in the y direction
			for(INT32 y = 0; y < m_idMapDimension; ++y)
			{
				for(INT32 i = 0; i < m_mapSizeFactor; ++i)
				{
					memcpy(	&image[(y * m_mapSizeFactor + i) * imagePitch],
							&image[(y * m_mapSizeFactor) * imagePitch],
							sizeof(UINT8) * m_convolverDimension);
				}
			}

			if(enableFilter)
			{
				// Blur image
				m_imageConvolvers[curSplatIndex]->Convolve();
			}
		}

		// Examine splat maps to make sure all elements add up to one
		INT32 imagePitch = m_imageConvolvers[0]->GetImagePitch();
		for(INT32 y = 0, offset = 0; y < imagePitch; ++y)
		{
			for(INT32 x = 0; x < imagePitch; ++x, ++offset)
			{
				INT32 totalValue = 0, maxValue = 255;

				// Truncate values
				for(INT32 curSplatIndex = 0; curSplatIndex < numUniqueSplatIDs; ++curSplatIndex)
				{
					imageDataArray[curSplatIndex][offset] = imageDataArray[curSplatIndex][offset] & 0xF8;
				}

				// Calculate truncation error
				for(INT32 curSplatIndex = 0; curSplatIndex < numUniqueSplatIDs; ++curSplatIndex)
				{
					totalValue += imageDataArray[curSplatIndex][offset];
				}
				INT32 deltaError = maxValue - totalValue;

				// Attempt to fix-up the error caused by bit truncation
				for(INT32 curSplatIndex = 0; curSplatIndex < numUniqueSplatIDs && deltaError >= 0x08; ++curSplatIndex, deltaError = deltaError - 0x08)
				{
					if(imageDataArray[curSplatIndex][offset] < 0xF8)
					{
						imageDataArray[curSplatIndex][offset] += 0x08;
					}
				}
			}
		}

		// Copy splat maps to alpha maps
		for(INT32 curSplatIndex = 0, alphaMapIndex = 0; curSplatIndex < numUniqueSplatIDs; ++alphaMapIndex)
		{
			// Retrieve pointers and pitch for the source data
			UINT8 *sourceChannels[3];
			for(INT32 i = 0; i < 3; ++i, ++curSplatIndex)
				sourceChannels[i] = m_imageConvolvers[curSplatIndex]->GetImageData();
			INT32 sourcePitch = m_imageConvolvers[0]->GetImagePitch();

			// Lock the alpha map surface
			UINT16 *alphaBits;
			INT32 alphaPitch;
			alphaMaps[alphaMapIndex]->Lock(0, (void **) &alphaBits, &alphaPitch);

			// Copy to output channel
			INT32 pixelPadding = m_idMapPadding * m_mapSizeFactor;
			for(INT32 y = 0; y < m_alphaMapDimension; ++y)
			{
				INT32 srcOffset = (y + pixelPadding) * sourcePitch + pixelPadding;
				INT32 destOffset = (y * alphaPitch) / 2;

				for(INT32 x = 0; x < m_alphaMapDimension; ++x, ++destOffset, ++srcOffset)
				{
					UINT32 c0 = (sourceChannels[0][srcOffset] & 0xF8) >> 3;
					UINT32 c1 = (sourceChannels[1][srcOffset] & 0xF8) >> 3;
					UINT32 c2 = (sourceChannels[2][srcOffset] & 0xF8) >> 3;

					alphaBits[destOffset] = (c0 << 10) | (c1 << 5) | c2;
				}
			}

			// Unlock alpha map surface
			alphaMaps[alphaMapIndex]->Unlock(0);
		}

		splatIDMap->m_isFiltered = enableFilter;
		splatIDMap->m_isDirty = FALSE;
	}

	return TRUE;
}

void CRuSplatMapBuilder::BeginBuildSplatMap(IRuTexture *targetTexture)
{
	// Select target texture and clear its contents
	for(INT32 i = 0, levelCount = targetTexture->GetLevelCount(); i < levelCount; ++i)
	{
		targetTexture->SelectRenderTarget(i);
		g_ruEngine->Clear(RUCLEAR_TARGET, 0x00000000, 1.0f, 0);
	}
}

BOOL CRuSplatMapBuilder::BuildSplatMap(CRuSplatIDMap *splatIDMap, IRuTexture *targetTexture, IRuTexture **sourceTextures)
{
	CRuRectangle destRect;

	destRect.m_left = 0;
	destRect.m_top = 0;
	destRect.m_right = static_cast<float>(targetTexture->GetLevelWidth(0));
	destRect.m_bottom = static_cast<float>(targetTexture->GetLevelHeight(0));

	return BuildSplatMap(splatIDMap, &destRect, targetTexture, sourceTextures);
}

BOOL CRuSplatMapBuilder::BuildSplatMap(CRuSplatIDMap *splatIDMap, const CRuRectangle *targetRect, IRuTexture *targetTexture, IRuTexture **sourceTextures, float alphaFalloff)
{
	return BuildSplatMap(1, &splatIDMap, targetRect, targetTexture, sourceTextures, alphaFalloff);
}

BOOL CRuSplatMapBuilder::BuildSplatMap(INT32 numSplatIDMaps, CRuSplatIDMap **splatIDMaps, const CRuRectangle *targetRects, IRuTexture *targetTexture, IRuTexture **sourceTextures, float alphaFalloff)
{
	CRuRectangle srcRect, destRect;

	// Path for PS2.0 hardware

	float curTextureScale = 1.0f;
	float curAlphaScale = 1.0f;

	for(INT32 curLevel = 0, levelCount = targetTexture->GetLevelCount(); curLevel < levelCount; ++curLevel)
	{
		// Select current mip level as the render target
		targetTexture->SelectRenderTarget(curLevel);

		// Iterate through all the splat maps
		for(INT32 mapIndex = 0; mapIndex < numSplatIDMaps; ++mapIndex)
		{
			// Setup data references
			CRuArrayList<UINT16>&		layerIDs = splatIDMaps[mapIndex]->m_layerIDs;
			INT32&						numAlphaMaps = splatIDMaps[mapIndex]->m_numAlphaMaps;
			IRuTexture**&				alphaMaps = splatIDMaps[mapIndex]->m_alphaMaps;

			// Setup source and destination rectangles
			srcRect.Set(0.0f, 0.0f, (float) alphaMaps[0]->GetLevelWidth(0), (float) alphaMaps[0]->GetLevelHeight(0));
			destRect = targetRects[mapIndex];

			// Scale rectangles by the current mip level scale
			destRect *= curTextureScale;

			// Start 2D rendering mode
			g_ruSceneRenderer->Ru2D_Begin();

			INT32 numPasses = (numAlphaMaps / 2) + ((numAlphaMaps % 2) ? 1 : 0);

			// Iterate through all the layers
			for(INT32 i = 0, curLayer = 0; i < numPasses; ++i)
			{
				// If there are four or more layers use the PS2.0 path
				if((layerIDs.Count() - curLayer) >= 4)
				{
					g_ruSceneRenderer->Ru2D_SetEffectType(ru2DFXTYPE_SPLAT_6);
					g_ruSceneRenderer->Ru2D_SetShaderParameter(ruPARAMTYPE_MIPMAP_ALPHASCALE, (DWORD) &curAlphaScale);

					// Set alpha maps
					g_ruSceneRenderer->Ru2D_SetTexture(alphaMaps[i * 2], ruTEXCHANNEL_TERRAIN_ALPHA_0);
					g_ruSceneRenderer->Ru2D_SetTexture(alphaMaps[i * 2 + 1], ruTEXCHANNEL_TERRAIN_ALPHA_1);

					// Set source textures
					INT32 j = 0;
					for( ; j < 6 && curLayer < layerIDs.Count(); ++j, ++curLayer)
					{
						g_ruSceneRenderer->Ru2D_SetTexture(sourceTextures[layerIDs[curLayer]], (RuTextureChannel) (ruTEXCHANNEL_TERRAIN_LAYER_0 + j));
					}
					for( ; j < 6; ++j)
					{
						g_ruSceneRenderer->Ru2D_SetTexture(NULL, (RuTextureChannel) (ruTEXCHANNEL_TERRAIN_LAYER_0 + j));
					}
				}
				else
				{
					g_ruSceneRenderer->Ru2D_SetEffectType(ru2DFXTYPE_SPLAT_3);
					g_ruSceneRenderer->Ru2D_SetShaderParameter(ruPARAMTYPE_MIPMAP_ALPHASCALE, (DWORD) &curAlphaScale);

					// Set alpha map
					g_ruSceneRenderer->Ru2D_SetTexture(alphaMaps[i * 2], ruTEXCHANNEL_TERRAIN_ALPHA_0);

					// Set source textures
					INT32 j = 0;
					for( ; j < 3 && curLayer < layerIDs.Count(); ++j, ++curLayer)
					{
						g_ruSceneRenderer->Ru2D_SetTexture(sourceTextures[layerIDs[curLayer]], (RuTextureChannel) (ruTEXCHANNEL_TERRAIN_LAYER_0 + j));
					}
					for( ; j < 3; ++j)
					{
						g_ruSceneRenderer->Ru2D_SetTexture(NULL, (RuTextureChannel) (ruTEXCHANNEL_TERRAIN_LAYER_0 + j));
					}
				}
				
				g_ruSceneRenderer->Ru2D_Submit(srcRect, destRect);
			}

			// End 2D rendering mode
			g_ruSceneRenderer->Ru2D_End();
		}

		// Alter the alpha scale
		curAlphaScale *= alphaFalloff;

		// Alter the texture scale
		curTextureScale *= 0.5f;
	}

	return TRUE;
}

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

CRuSplatMapManager::CRuSplatMapManager()
:	m_mapBuilder(NULL),
	m_baseTextureResolution(512),
	m_detailTextureResolution(1024)
{
	m_baseTextureQueue = ruNEW CRuLRUQueue<DWORD, IRuTexture *>(32, 256);
	m_detailTextureQueue = ruNEW CRuLRUQueueEx<DWORD, DWORD, IRuTexture *>(32, 128, 128);
}

CRuSplatMapManager::~CRuSplatMapManager()
{
	// Release map builder
	ruSAFE_RELEASE(m_mapBuilder);

	// Return all base textures to the recycling bin
	ClearBaseTextureCache();

	// Return all detail textures to the recycling bin
	ClearDetailTextureCache();

	// Release all base textures
	while(m_recycledBaseTextures.Count() > 0)
	{
		m_recycledBaseTextures.Pop()->Release();
	}

	// Release all detail textures
	while(m_recycledDetailTextures.Count() > 0)
	{
		m_recycledDetailTextures.Pop()->Release();
	}

	// Delete texture queues
	delete m_baseTextureQueue;
	delete m_detailTextureQueue;
}

BOOL CRuSplatMapManager::Initialize(INT32 idMapDimension, INT32 numBaseTextures, INT32 numDetailTextures, INT32 baseTextureResolution, INT32 detailTextureResolution)
{
	// Release previous map builder
	ruSAFE_RELEASE(m_mapBuilder);

	m_idMapDimension = idMapDimension;

	// Initialize new map builder
	m_mapBuilder = ruNEW CRuSplatMapBuilder();
	m_mapBuilder->Initialize(idMapDimension, 4, 2);

	// Save parameters
	m_baseTextureResolution = baseTextureResolution;
	m_detailTextureResolution = detailTextureResolution;

	// Initialize base texture queue
	for(INT32 i = 0; i < numBaseTextures; ++i)
	{
		IRuTexture *texture = g_ruEngine->CreateRenderTarget(m_baseTextureResolution, m_baseTextureResolution, 3, ruSURFACEFMT_R5G6G5);
		if(texture)
		{
			m_recycledBaseTextures.Push(texture);
		}
	}

	// Initialize detail texture queue
	for(INT32 i = 0; i < numDetailTextures; ++i)
	{
		IRuTexture *texture = g_ruEngine->CreateRenderTarget(m_detailTextureResolution, m_detailTextureResolution, 0, ruSURFACEFMT_R5G6G5);
		if(texture)
		{
			m_recycledDetailTextures.Push(texture);
		}
	}

	return TRUE;
}

BOOL CRuSplatMapManager::BuildAlphaMaps(CRuSplatIDMap *splatIDMap)
{
	return m_mapBuilder->BuildAlphaMaps(splatIDMap);
}

IRuTexture *CRuSplatMapManager::GetBaseTexture(DWORD chunkKey, INT32 numSubChunkGrids, CRuSplatIDMap **idMaps, IRuTexture **sourceTextures)
{
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	IRuTexture *texture = NULL;

	// Attempt to locate the texture within the cache
	if(m_baseTextureQueue->Get(chunkKey, chunkKeyHash, texture))
	{
		return texture;
	}

	// Otherwise, grab a texture from somewhere
	if(m_recycledBaseTextures.IsEmpty())
	{
		if(m_baseTextureQueue->ReclaimUnconditional(texture) == FALSE)
		{
			return NULL;
		}
	}
	else
	{
		texture = m_recycledBaseTextures.Pop();
	}

	// Fill texture
	INT32 numSubChunks = numSubChunkGrids * numSubChunkGrids;

	for(INT32 i = 0; i < numSubChunks; ++i)
	{
		m_mapBuilder->BuildAlphaMaps(idMaps[i]);
	}

	INT32 targetDimension = m_baseTextureResolution / numSubChunkGrids;
	CRuRectangle *targetRects = ruNEW CRuRectangle [numSubChunks];
	for(INT32 z = 0, i = 0; z < numSubChunkGrids; ++z)
	{
		for(INT32 x = 0; x < numSubChunkGrids; ++x, ++i)
		{
			targetRects[i].m_left = (float) (x * targetDimension);
			targetRects[i].m_top = (float) (z * targetDimension);
			targetRects[i].m_right = targetRects[i].m_left + targetDimension;
			targetRects[i].m_bottom = targetRects[i].m_top + targetDimension;
		}
	}

	// Begin build splat map
	m_mapBuilder->BeginBuildSplatMap(texture);
	m_mapBuilder->BuildSplatMap(numSubChunks, idMaps, targetRects, texture, sourceTextures, 1.0f);

	delete[] targetRects;

	// Put splat map back into queue
	m_baseTextureQueue->Add(chunkKey, chunkKeyHash, texture, 1);

//	D3DXSaveTextureToFile("d:\\test output.dds", D3DXIFF_DDS, ((CRuTextureD3D *) texture)->m_texture, NULL);


	return texture;
}

IRuTexture *CRuSplatMapManager::GetDetailTexture(DWORD chunkKey, DWORD subChunkKey, CRuSplatIDMap *idMap, IRuTexture **sourceTextures)
{
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	DWORD subChunkKeyHash = RuGlobalHashGen().GetHashCode(subChunkKey);

	IRuTexture *texture = NULL;

	// Attempt to locate the texture within the cache
	if(m_detailTextureQueue->Get(chunkKey, chunkKeyHash, subChunkKey, subChunkKeyHash, texture))
	{
		return texture;
	}

	// Otherwise, grab a texture from somewhere
	if(m_recycledDetailTextures.IsEmpty())
	{
		if(m_detailTextureQueue->ReclaimUnconditional(texture) == FALSE)
		{
			return NULL;
		}
	}
	else
	{
		texture = m_recycledDetailTextures.Pop();
	}

	// Build texture
	m_mapBuilder->BuildAlphaMaps(idMap);

	CRuRectangle targetRect;

	targetRect.m_left = 0.0f;
	targetRect.m_top = 0.0f;
	targetRect.m_right = (float) m_detailTextureResolution;
	targetRect.m_bottom = (float) m_detailTextureResolution;

	// Begin build splat map
	m_mapBuilder->BeginBuildSplatMap(texture);
	m_mapBuilder->BuildSplatMap(1, &idMap, &targetRect, texture, sourceTextures, 0.75f);

	// Put splat map back into queue
	m_detailTextureQueue->Add(chunkKey, chunkKeyHash, subChunkKey, subChunkKeyHash, texture, 1);

//	D3DXSaveTextureToFile("d:\\test output.dds", D3DXIFF_DDS, ((CRuTextureD3D *) texture)->m_texture, NULL);

	return texture;
}

void CRuSplatMapManager::EvictCachedTexture(DWORD chunkKey)
{
	DWORD chunkKeyHash = RuGlobalHashGen().GetHashCode(chunkKey);
	IRuTexture *texture = NULL;

	// Evict textures matching the chunk key from the base texture queue
	if(m_baseTextureQueue->Get(chunkKey, chunkKeyHash, texture))
	{
		m_recycledBaseTextures.Push(texture);

		m_baseTextureQueue->Remove(chunkKey, chunkKeyHash);
	}

	// Evict textures matching the chunk key from the detail texture queue
	m_detailTextureQueue->ForAllElements(chunkKey, chunkKeyHash, EvictCachedTextureCallback, this);
	m_detailTextureQueue->Remove(chunkKey, chunkKeyHash);
}

void CRuSplatMapManager::ClearBaseTextureCache()
{
	if(m_baseTextureQueue)
	{
		m_baseTextureQueue->ForAllElements(ClearBaseTextureCacheCallback, this);
		m_baseTextureQueue->Clear();
	}
}

void CRuSplatMapManager::ClearDetailTextureCache()
{
	if(m_detailTextureQueue)
	{
		m_detailTextureQueue->ForAllElements(ClearDetailTextureCacheCallback, this);
		m_detailTextureQueue->Clear();
	}
}

BOOL CRuSplatMapManager::ClearBaseTextureCacheCallback(const void *key, const void *data, void *userData)
{
	// Push texture back onto the list of recycled base textures
	IRuTexture *texture = *((IRuTexture **) data);
	((CRuSplatMapManager *) userData)->m_recycledBaseTextures.Push(texture);

	return TRUE;
}

BOOL CRuSplatMapManager::ClearDetailTextureCacheCallback(const void *key, const void *data, void *userData)
{
	// Push texture back onto the list of recycled detail textures
	IRuTexture *texture = *((IRuTexture **) data);
	((CRuSplatMapManager *) userData)->m_recycledDetailTextures.Push(texture);

	return TRUE;
}

BOOL CRuSplatMapManager::EvictCachedTextureCallback(const void *key, const void *data, void *userData)
{
	// Push texture back onto the list of recycled detail textures
	IRuTexture *texture = *((IRuTexture **) data);
	((CRuSplatMapManager *) userData)->m_recycledDetailTextures.Push(texture);

	return TRUE;
}

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

CRuTextureList::CRuTextureList()
{
}

CRuTextureList::~CRuTextureList()
{
	// Release all references to textures
	for(INT32 i = 0; i < m_textureList.Count(); ++i)
	{
		if(m_textureList[i])
		{
			m_textureList[i]->Release();
		}
	}
}

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

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

INT32 CRuTextureList::AddTexture(IRuBaseTexture *texture)
{
	// Increment reference on new texture
	if(texture)
	{
		texture->AddRef();
	}

	m_textureDirtyList.Add(TRUE);
	m_textureList.Add(texture);

	return m_textureList.Count() - 1;
}

INT32 CRuTextureList::GetNumTextures() const
{
	return m_textureList.Count();
}

IRuBaseTexture *CRuTextureList::GetTexture(INT32 index)
{
	if(index >= 0 && index < m_textureList.Count())
	{
		return m_textureList[index];
	}

	return NULL;
}

BOOL CRuTextureList::SetTexture(INT32 index, IRuBaseTexture *texture)
{
	if(index >= 0 && index < m_textureList.Count())
	{
		// Increment reference on new texture
		if(texture)
		{
			texture->AddRef();
		}

		// Decrement reference on old texture
		if(m_textureList[index])
		{
			m_textureList[index]->Release();
		}

		// Replace
		m_textureDirtyList[index] = TRUE;
		m_textureList[index] = texture;

		return TRUE;
	}

	return FALSE;
}

void CRuTextureList::ClearTextureDirty()
{
	for(INT32 i = 0; i < m_textureDirtyList.Count(); ++i)
	{
		m_textureDirtyList[i] = FALSE;
	}
}

void CRuTextureList::SetTextureDirty()
{
	for(INT32 i = 0; i < m_textureDirtyList.Count(); ++i)
	{
		m_textureDirtyList[i] = TRUE;
	}
}

BOOL CRuTextureList::IsTextureDirty(INT32 index) const
{
	if(index >= 0 && index < m_textureList.Count())
	{
		return m_textureDirtyList[index];
	}

	return FALSE;
}

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

CRuWaterBodyDescriptor::CRuWaterBodyDescriptor()
{
	m_effectType = ruWATERFXTYPE_DIFFUSE;

	m_reflectionFactor = 0.0f;
	m_refractionFactor = 0.0f;
	m_alphaFactor = 0.5f;
	m_baseFactor = 1.0f;
	m_specularStrength = 0.0f;

	m_baseColor = 0xFFFFFFFF;

	m_uvScale = 1.0f;
	m_baseMapFrames = 0;
	m_baseMapName = NULL;
	m_normalMapFrames = 0;
	m_normalMapName = NULL;
	m_cubeMapFrames = 0;
	m_cubeMapName = NULL;

	m_isDirty = TRUE;
	m_waterMaterial = NULL;
	m_baseController = NULL;
	m_causticsController = NULL;

	// Build default water body material
	BuildWaterBodyMaterial();
}

CRuWaterBodyDescriptor::~CRuWaterBodyDescriptor()
{
	delete[] m_baseMapName;
	delete[] m_normalMapName;
	delete[] m_cubeMapName;

	// Release water body material
	ruSAFE_RELEASE(m_waterMaterial);
	ruSAFE_RELEASE(m_baseController);
	ruSAFE_RELEASE(m_causticsController);
}

BOOL CRuWaterBodyDescriptor::SerializeFrom(IRuStream *stream)
{
	INT32 length;

	CRuGUID classGUID;

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

	// Read version
	INT32 version;
	stream->Read(&version, sizeof(INT32));

	// Read water body GUID
	if(RuReadGUID(stream, m_guid) == FALSE)
	{
		return FALSE;
	}

	switch(version)
	{
		case 1:
			{
				// Read reflection, refraction, and alpha factors
				stream->Read(&m_effectType, sizeof(RuWaterEffectType));
				stream->Read(&m_reflectionFactor, sizeof(float));
				stream->Read(&m_refractionFactor, sizeof(float));
				stream->Read(&m_alphaFactor, sizeof(float));
				stream->Read(&m_baseFactor, sizeof(float));
				stream->Read(&m_specularStrength, sizeof(float));
				stream->Read(&m_uvScale, sizeof(float));

				stream->Read(&m_baseColor, sizeof(DWORD));

				// Read number of base map frames
				stream->Read(&m_baseMapFrames, sizeof(INT32));

				// Read number of normal map frames
				stream->Read(&m_normalMapFrames, sizeof(INT32));

				// Read number of cube map frames
				stream->Read(&m_cubeMapFrames, sizeof(INT32));

				// Read base map name length including NULL, then the base map name including NULL
				stream->Read(&length, sizeof(INT32));
				if(length > 0)
				{
					m_baseMapName = ruNEW char [length];
					stream->Read(m_baseMapName, sizeof(char) * length);
				}
				else
				{
					m_baseMapName = NULL;
				}

				// Read normal map name length including NULL, then the normal map name including NULL
				stream->Read(&length, sizeof(INT32));
				if(length > 0)
				{
					m_normalMapName = ruNEW char [length];
					stream->Read(m_normalMapName, sizeof(char) * length);
				}
				else
				{
					m_normalMapName = NULL;
				}

				// Read cube map name length including NULL, then the cube map name including NULL
				stream->Read(&length, sizeof(INT32));
				if(length > 0)
				{
					m_cubeMapName = ruNEW char [length];
					stream->Read(m_cubeMapName, sizeof(char) * length);
				}
				else
				{
					m_cubeMapName = NULL;
				}

				// Other shit
				m_isDirty = FALSE;
				BuildWaterBodyMaterial();
			}

			break;
	}
	return TRUE;
}

BOOL CRuWaterBodyDescriptor::SerializeTo(IRuStream *stream)
{
	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write version
	INT32 version = 1;
	stream->Write(&version, sizeof(INT32));

	// Write water body GUID
	stream->Write(m_guid.Data(), m_guid.Size());

	// Write reflection, refraction, and alpha factors
	stream->Write(&m_effectType, sizeof(RuWaterEffectType));
	stream->Write(&m_reflectionFactor, sizeof(float));
	stream->Write(&m_refractionFactor, sizeof(float));
	stream->Write(&m_alphaFactor, sizeof(float));
	stream->Write(&m_baseFactor, sizeof(float));
	stream->Write(&m_specularStrength, sizeof(float));
	stream->Write(&m_uvScale, sizeof(float));

	stream->Write(&m_baseColor, sizeof(DWORD));

	// Write number of base map frames
	stream->Write(&m_baseMapFrames, sizeof(INT32));

	// Write number of normal map frames
	stream->Write(&m_normalMapFrames, sizeof(INT32));

	// Write number of cube map frames
	stream->Write(&m_cubeMapFrames, sizeof(INT32));

	// Write base map name length including NULL, then the base map name including NULL
	if(m_baseMapName)
	{
		INT32 length = strlen(m_baseMapName) + 1;
		stream->Write(&length, sizeof(INT32));
		stream->Write(m_baseMapName, sizeof(char) * length);
	}
	else
	{
		INT32 length = 1;
		stream->Write(&length, sizeof(INT32));
		stream->Write("", sizeof(char) * length);
	}

	// Write normal map name length including NULL, then the normal map name including NULL
	if(m_normalMapName)
	{
		INT32 length = strlen(m_normalMapName) + 1;
		stream->Write(&length, sizeof(INT32));
		stream->Write(m_normalMapName, sizeof(char) * length);
	}
	else
	{
		INT32 length = 1;
		stream->Write(&length, sizeof(INT32));
		stream->Write("", sizeof(char) * length);
	}

	// Write cube map name length including NULL, then the cube map name including NULL
	if(m_cubeMapName)
	{
		INT32 length = strlen(m_cubeMapName) + 1;
		stream->Write(&length, sizeof(INT32));
		stream->Write(m_cubeMapName, sizeof(char) * length);
	}
	else
	{
		INT32 length = 1;
		stream->Write(&length, sizeof(INT32));
		stream->Write("", sizeof(char) * length);
	}

	return TRUE;
}

void CRuWaterBodyDescriptor::Update(REAL elapsedTime)
{
	// Update base controller if available
	if(m_baseController)
	{
		m_baseController->Update(elapsedTime);
	}

	// Update caustics controller if available
	if(m_causticsController)
	{
		m_causticsController->Update(elapsedTime);
	}
}

CRuMaterial *CRuWaterBodyDescriptor::GetWaterBodyMaterial()
{
	return m_waterMaterial;
}

void CRuWaterBodyDescriptor::BuildWaterBodyMaterial()
{
	// Create new water material
	ruSAFE_RELEASE(m_waterMaterial);
	m_waterMaterial = ruNEW CRuMaterial();

	// Set shader
	switch(m_effectType)
	{
		case ruWATERFXTYPE_FULL:
			m_waterMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_WATER_PS20_FULL"));
			break;

		case ruWATERFXTYPE_REFLECT:
			m_waterMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_WATER_PS20_REFLECT"));
			break;

		case ruWATERFXTYPE_REFRACT:
			m_waterMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_WATER_PS20_REFRACT"));
			break;

		case ruWATERFXTYPE_CUBICENV:
			m_waterMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_WATER_PS20_CUBICENV"));
			break;

		default:
		case ruWATERFXTYPE_DIFFUSE:
			m_waterMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_WATER_PS20_DIFFUSE"));
			break;
	}

	// Set water split plane into the material
//	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_SPLITPLANE, (DWORD) &m_waterPlane);

	// Setup shader parameters
	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_BASECOLOR, m_baseColor);
	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_REFLECTFACTOR, RuCastFloatToDWORD(m_reflectionFactor));
	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_REFRACTFACTOR, RuCastFloatToDWORD(m_refractionFactor));
	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_ALPHAFACTOR, RuCastFloatToDWORD(m_alphaFactor));
	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_BASEFACTOR, RuCastFloatToDWORD(m_baseFactor));
	m_waterMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_WATER_SPECULARSTRENGTH, RuCastFloatToDWORD(m_specularStrength));

	char tempStr[4096];

	ruSAFE_RELEASE(m_baseController);
	m_baseController = ruNEW CRuController_MaterialTexture();

	for(INT32 i = 0; i < m_baseMapFrames; ++i)
	{
		// Build texture name
		sprintf(tempStr, "%s_%d", m_baseMapName, i);

		// Load texture
		IRuTexture *texture = g_ruResourceManager->LoadTexture(tempStr);

		if(texture)
		{
			m_baseController->AddTexture(texture);
			ruSAFE_RELEASE(texture);
		}
	}

	m_baseController->SetTarget(m_waterMaterial);
	m_baseController->SetTargetChannel(ruTEXCHANNEL_DIFFUSEMAP);

	ruSAFE_RELEASE(m_causticsController);
	m_causticsController = ruNEW CRuController_MaterialTexture();

	for(INT32 i = 0; i < m_normalMapFrames; ++i)
	{
		// Build texture name
		sprintf(tempStr, "%s_%d", m_normalMapName, i);

		// Load texture
		IRuTexture *texture = g_ruResourceManager->LoadTexture(tempStr);

		if(texture)
		{
			m_causticsController->AddTexture(texture);
			ruSAFE_RELEASE(texture);
		}
	}

	m_causticsController->SetTarget(m_waterMaterial);
	m_causticsController->SetTargetChannel(ruTEXCHANNEL_CAUSTICMAP);
}

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

INT32 RuTerrainUtility_BuildGridIndices(INT32 chunkDimension, INT32 gridDimension, INT32 vertexGap, INT32 stride, BYTE *vertexFlags, UINT16 *indicesOut, INT32 *numMainBodyIndices, BOOL ignoreSkirt)
{
	INT32 evenFaceConstruction[]	= { 0, 3, 4, 0, 4, 1, 1, 4, 2, 2, 4, 5 };
	INT32 oddFaceConstruction[]		= { 0, 3, 1, 1, 3, 4, 1, 5, 2, 1, 4, 5 };

	INT32 curIndex = 0;
	UINT16 indexSet[6];

	INT32 zStride = stride * vertexGap;

	// Build indices for the mesh body
	for(INT32 z = 0; z < gridDimension; ++z)
	{
		// Build indices for four faces at a time
		for(INT32 x = 0; x < gridDimension; x += 2)
		{
			// Calculate the indices used in these four faces

			indexSet[0] = (z * zStride) + (x * vertexGap);
			indexSet[1] = indexSet[0] + vertexGap;
			indexSet[2] = indexSet[1] + vertexGap;
			indexSet[3] = indexSet[0] + zStride;
			indexSet[4] = indexSet[1] + zStride;
			indexSet[5] = indexSet[2] + zStride;

			// Alternate between even and odd rows
			INT32 *faceConstruction = (z % 2) ? oddFaceConstruction : evenFaceConstruction;

			BOOL visible = FALSE;
			for(INT32 j = 0; j < 12; ++j)
			{
				if(vertexFlags[indexSet[faceConstruction[j]]] != 0)
				{
					visible = TRUE;
				}
			}

			if(visible)
			{
				for(INT32 j = 0; j < 12; ++j)
				{
					indicesOut[curIndex++] = indexSet[faceConstruction[j]];
				}
			}
			else
			{
				for(INT32 j = 0; j < 12; ++j)
				{
					indicesOut[curIndex++] = 0;
				}
			}
		}
	}

	// If a pointer is specified for mainBodyIndexEnd, record the end of the main terrain chunk body indices into it
	if(numMainBodyIndices)
	{
		*numMainBodyIndices = curIndex;
	}

	if(ignoreSkirt == FALSE)
	{
		INT32 topSkirtVertexArrayOffset = (chunkDimension + 1) * (chunkDimension + 1);
		INT32 bottomSkirtVertexArrayOffset = topSkirtVertexArrayOffset + (chunkDimension + 1);
		INT32 leftSkirtVertexArrayOffset = bottomSkirtVertexArrayOffset + (chunkDimension + 1);
		INT32 rightSkirtVertexArrayOffset = leftSkirtVertexArrayOffset + (chunkDimension + 1);

		// Build indices for the mesh skirt (top)
		for(INT32 x = 0, z = 0; x < gridDimension; ++x)
		{
			if(vertexFlags[(z * zStride) + (x * vertexGap)] == 0 && vertexFlags[(z * zStride) + (x * vertexGap) + vertexGap] == 0)
			{
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;

				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
			}
			else
			{
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap);
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + vertexGap;
				indicesOut[curIndex++] = (x * vertexGap) + topSkirtVertexArrayOffset;

				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + vertexGap;
				indicesOut[curIndex++] = (x * vertexGap) + topSkirtVertexArrayOffset + vertexGap;
				indicesOut[curIndex++] = (x * vertexGap) + topSkirtVertexArrayOffset;
			}
		}

		// Build indices for the mesh skirt (bottom)
		for(INT32 x = 0, z = gridDimension; x < gridDimension; ++x)
		{
			if(vertexFlags[(z * zStride) + (x * vertexGap)] == 0 && vertexFlags[(z * zStride) + (x * vertexGap) + vertexGap] == 0)
			{
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;

				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
			}
			else
			{
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap);
				indicesOut[curIndex++] = (x * vertexGap) + bottomSkirtVertexArrayOffset;
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + vertexGap;

				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + vertexGap;
				indicesOut[curIndex++] = (x * vertexGap) + bottomSkirtVertexArrayOffset;
				indicesOut[curIndex++] = (x * vertexGap) + bottomSkirtVertexArrayOffset + vertexGap;
			}
		}

		// Build indices for the mesh skirt (left)
		for(INT32 z = 0, x = 0; z < gridDimension; ++z)
		{
			if(vertexFlags[(z * zStride) + (x * vertexGap)] == 0 && vertexFlags[(z * zStride) + (x * vertexGap) + zStride] == 0)
			{
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;

				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
			}
			else
			{
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap);
				indicesOut[curIndex++] = (z * vertexGap) + leftSkirtVertexArrayOffset;
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + zStride;

				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + zStride;
				indicesOut[curIndex++] = (z * vertexGap) + leftSkirtVertexArrayOffset;
				indicesOut[curIndex++] = (z * vertexGap) + leftSkirtVertexArrayOffset + vertexGap;
			}
		}

		// Build indices for the mesh skirt (right)
		for(INT32 z = 0, x = gridDimension; z < gridDimension; ++z)
		{
			if(vertexFlags[(z * zStride) + (x * vertexGap)] == 0 && vertexFlags[(z * zStride) + (x * vertexGap) + zStride] == 0)
			{
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;

				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
				indicesOut[curIndex++] = 0;
			}
			else
			{
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap);
				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + zStride;
				indicesOut[curIndex++] = (z * vertexGap) + rightSkirtVertexArrayOffset;

				indicesOut[curIndex++] = (z * zStride) + (x * vertexGap) + zStride;
				indicesOut[curIndex++] = (z * vertexGap) + rightSkirtVertexArrayOffset + vertexGap;
				indicesOut[curIndex++] = (z * vertexGap) + rightSkirtVertexArrayOffset;
			}
		}
	}

	return curIndex;
}

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

#pragma managed
