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

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuEntity_Water, IRuEntity_Container)
ruHEAP_DEFINE_SUBCLASS(CRuEntity_Water, "CRuEntity_Water", "IRuEntity_Container")
ruCLASSGUID_DEFINE(CRuEntity_Water, 0x86E05615, 0x112943cb, 0xB2C0385B, 0xD5441604)

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

CRuEntity_Water::CRuEntity_Water()
:	m_effectType(ruWATERFXTYPE_DIFFUSE),
	m_reflectionFactor(0.5f),
	m_refractionFactor(0.5f),
	m_alphaFactor(1.0f),
	m_baseFactor(1.0f),
	m_specularStrength(0.0f),
	m_uvScale(1.0f),
	m_baseColor(0xFFFFFFFF),
	m_baseMapFrames(0),
	m_baseMapName(NULL),
	m_normalMapFrames(0),
	m_normalMapName(NULL),
	m_cubeMapFrames(0),
	m_cubeMapName(NULL),
	m_waterPolys(NULL),
	m_waterMaterial(NULL),
	m_baseController(NULL),
	m_causticsController(NULL)
{
}

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

	ruSAFE_RELEASE(m_waterPolys);
	ruSAFE_RELEASE(m_waterMaterial);

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

	ruSAFE_RELEASE(m_baseController);
	ruSAFE_RELEASE(m_causticsController);
}

IRuObject *CRuEntity_Water::Clone(IRuObject *clonedObject)
{
	return NULL;
}

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

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

	INT32 length;

	// Read water data type
	INT32 waterDataType;
	stream->Read(&waterDataType, sizeof(INT32));

	if(waterDataType == 0)
	{
		// Water is editable
		m_isOptimizedEntity = FALSE;

		// 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;
		}

		// Read water quad size
		stream->Read(&m_waterQuadSize, sizeof(float));

		// Read water origin
		stream->Read(&m_waterOrigin.x, sizeof(float));
		stream->Read(&m_waterOrigin.y, sizeof(float));
		stream->Read(&m_waterOrigin.z, sizeof(float));

		// Set local transform based on origin
		this->ResetLocalTransform();
		this->Translate(m_waterOrigin);

		// Read water plane
		stream->Read(&m_waterPlane.m_a, sizeof(float));
		stream->Read(&m_waterPlane.m_b, sizeof(float));
		stream->Read(&m_waterPlane.m_c, sizeof(float));
		stream->Read(&m_waterPlane.m_d, sizeof(float));

		// Serialize poly soup
		IRuObject *polyObject = g_ruObjectFactory.CreateObject(stream);

		if(polyObject)
		{
			if(polyObject->GetType() == CRuPolySoup_Quadtree::Type)
			{
				m_waterPolys = static_cast<CRuPolySoup_Quadtree *>(polyObject);

				// Build water model
				BuildWaterModel(TRUE);

				// Calculate bounds
				CalculateBounds();

				return TRUE;
			}
		}
	}
	else
	{
		// Water is client-optimized and not editable
		m_isOptimizedEntity = TRUE;

		// 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;
		}

		// Read water plane
		stream->Read(&m_waterPlane.m_a, sizeof(float));
		stream->Read(&m_waterPlane.m_b, sizeof(float));
		stream->Read(&m_waterPlane.m_c, sizeof(float));
		stream->Read(&m_waterPlane.m_d, sizeof(float));
/*
		// Read water bounding sphere
		stream->Read(&m_entityBounds.Center().x, sizeof(float));
		stream->Read(&m_entityBounds.Center().y, sizeof(float));
		stream->Read(&m_entityBounds.Center().z, sizeof(float));
		stream->Read(&m_entityBounds.Radius(), sizeof(float));
*/
		// Read number of models
		INT32 numWaterModels;
		stream->Read(&numWaterModels, sizeof(INT32));

		// Create new water material
		ruSAFE_RELEASE(m_waterMaterial);
		m_waterMaterial = ruNEW CRuMaterial();
		m_waterMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_WATER_PS20_DIFFUSE"));

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

		for(INT32 i = 0; i < numWaterModels; ++i)
		{
			// Serialize model
			IRuObject *modelObject = g_ruObjectFactory.CreateObject(stream);

			if(modelObject)
			{
				if(modelObject->GetType() == CRuModel::Type)
				{
					CRuModel *model = static_cast<CRuModel *>(modelObject);

					// Create new water model using the serialized mesh information
					CRuModel *waterModel = ruNEW CRuModel();

					for(INT32 i = 0; i < model->GetNumMeshes(); ++i)
					{
						IRuMesh *mesh;
						IRuMaterial *material;
						if(model->GetMesh(i, &mesh, &material))
						{
							waterModel->AddSubModel(mesh, m_waterMaterial);
						}
					}

					// Add new water model to water model list
					m_waterModels.Add(waterModel);

					// Release original model
					ruSAFE_RELEASE(model);
				}
			}
		}

		// Build texture controllers
		BuildTextureControllers();

		return TRUE;
	}

	return FALSE;
}

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

	// Write water data type (0 = editable, 1 = client non-editable)
	INT32 waterDataType = 0;
	stream->Write(&waterDataType, sizeof(INT32));

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

	// Write water quad size
	stream->Write(&m_waterQuadSize, sizeof(float));

	// Write water origin
	stream->Write(&m_waterOrigin.x, sizeof(float));
	stream->Write(&m_waterOrigin.y, sizeof(float));
	stream->Write(&m_waterOrigin.z, sizeof(float));

	// Write water plane
	stream->Write(&m_waterPlane.m_a, sizeof(float));
	stream->Write(&m_waterPlane.m_b, sizeof(float));
	stream->Write(&m_waterPlane.m_c, sizeof(float));
	stream->Write(&m_waterPlane.m_d, sizeof(float));

	// Serialize poly soup
	return m_waterPolys->SerializeTo(stream);
}

BOOL CRuEntity_Water::SerializeToOptimizedWater(IRuStream *stream)
{
	// Build model
	if(BuildWaterModel(FALSE))
	{
		// Recalculate bounds
		CalculateBounds();

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

		// Write water data type (0 = editable, 1 = client non-editable)
		INT32 waterDataType = 1;
		stream->Write(&waterDataType, sizeof(INT32));

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

		// Write water plane
		stream->Write(&m_waterPlane.m_a, sizeof(float));
		stream->Write(&m_waterPlane.m_b, sizeof(float));
		stream->Write(&m_waterPlane.m_c, sizeof(float));
		stream->Write(&m_waterPlane.m_d, sizeof(float));
/*
		// Write water bounding sphere
		stream->Write(&m_entityBounds.Center().x, sizeof(float));
		stream->Write(&m_entityBounds.Center().y, sizeof(float));
		stream->Write(&m_entityBounds.Center().z, sizeof(float));
		stream->Write(&m_entityBounds.Radius(), sizeof(float));
*/
		// Write number of water models
		INT32 numWaterModels = m_waterModels.Count();
		stream->Write(&numWaterModels, sizeof(INT32));

		// Write models
		for(INT32 i = 0; i < numWaterModels; ++i)
		{
			m_waterModels[i]->SerializeTo(stream);
		}

		return TRUE;
	}

	return FALSE;
}

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

BOOL CRuEntity_Water::Update(float elapsedTime)
{
	IRuEntity_Container::Update(elapsedTime);

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

	// Update shader (change this later.. can't be searching for shader every damn update)
	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;

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

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

	// Update base controller if available
	if(m_baseController)
	{
		m_baseController->Update(elapsedTime);
	}

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

	return TRUE;
}

INT32 CRuEntity_Water::GetNumEntities()
{
	return m_waterModels.Count();
}

CRuEntity *CRuEntity_Water::GetEntity(INT32 index)
{
	return m_waterModels[index];
}

BOOL CRuEntity_Water::QueryCollision(CRuCollisionQuery *colQuery)
{
	if(m_waterPolys)
	{
		return m_waterPolys->QueryCollision(colQuery);
	}
	else
	{
		CRuCollisionQuery_AABB_TriEnum *boxQuery = NULL;
		INT32 firstFaceIndex = 0;

		if(colQuery->m_queryType == ruCOLQUERY_AABB_TRIENUM)
		{
			boxQuery = static_cast<CRuCollisionQuery_AABB_TriEnum *>(colQuery);
			firstFaceIndex = boxQuery->m_colTriFlags->Count();
		}

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

		if(boxQuery)
		{
			if(boxQuery->m_colTriVerts->Count() / 3 == boxQuery->m_colTriFlags->Count())
			{
				for(INT32 i = firstFaceIndex; i < boxQuery->m_colTriFlags->Count(); ++i)
				{
					(*boxQuery->m_colTriFlags)[i] = 0x00600000;
				}
			}
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuEntity_Water::HasNoGeometry() const
{
	if(m_waterPolys)
	{
		if(m_waterPolys->GetNumTriangles() == 0)
		{
			return TRUE;
		}

		return FALSE;
	}

	return TRUE;
}

const CRuVector3 &CRuEntity_Water::GetWaterBodyOrigin() const
{
	return m_waterOrigin;
}

const CRuAABB &CRuEntity_Water::GetAABB() const
{
	return m_bounds;
}

BOOL CRuEntity_Water::InitializeWaterBody(const CRuVector3 &initPt, float quadSize)
{
	m_waterQuadSize = quadSize;
	float halfQuadSize = m_waterQuadSize / 2.0f;

	// Set water origin and plane
	m_waterOrigin = initPt;
	m_waterPlane.SetPlane(CRuVector3(0.0f, 1.0f, 0.0f), m_waterOrigin);

	// Set local transform based on origin
	this->ResetLocalTransform();
	this->Translate(m_waterOrigin);

	// Initialize a new quad using the initialization point as the origin
	CRuVector3 v[4];

	v[0].Set(0.0f, 0.0f, 0.0f);
	v[1].Set(m_waterQuadSize, 0.0f, 0.0f);
	v[2].Set(0.0f, 0.0f, m_waterQuadSize);
	v[3].Set(m_waterQuadSize, 0.0f, m_waterQuadSize);

	for(INT32 i = 0; i < 4; ++i)
	{
		v[i] += initPt;
	}

	// Allocate new water poly soup
	ruSAFE_RELEASE(m_waterPolys);
	m_waterPolys = ruNEW CRuPolySoup_Quadtree();

	// Add triangles for the initial quad
	m_waterPolys->AddTriangle(v[0], v[2], v[1]);
	m_waterPolys->AddTriangle(v[2], v[3], v[1]);

	// Build water model
	BuildWaterModel(TRUE);

	// Calculate bounds
	CalculateBounds();

	return TRUE;
}

BOOL CRuEntity_Water::ExtendWaterBody(const CRuRay3 &expansionRay, float expansionRadius, INT32 tessellationLevel)
{
	REAL heightRange = 10000.0f;
	REAL epsilon = m_waterQuadSize / 100.0f;

	CRuVector3 expansionCenter;
	float intT;

	// Make sure tessellation level is at least 1
	tessellationLevel = max(1, tessellationLevel);

	// Find intersection point with the water plane
	if(FindIntersection(expansionRay, m_waterPlane, expansionCenter, intT))
	{
		// Build a grid of points centered at the expansion point and check to see if they are already occupied
		INT32 integerRadius = static_cast<INT32>(ceil(expansionRadius / m_waterQuadSize));

		// Modify the expansion center to fit on the grid
		CRuVector3 relativeOrigin;
		relativeOrigin.x = floor((expansionCenter.x - m_waterOrigin.x) / m_waterQuadSize) * m_waterQuadSize + m_waterOrigin.x;
		relativeOrigin.y = expansionCenter.y;
		relativeOrigin.z = floor((expansionCenter.z - m_waterOrigin.z) / m_waterQuadSize) * m_waterQuadSize + m_waterOrigin.z;

		for(INT32 z = -integerRadius; z <= integerRadius; ++z)
		{
			for(INT32 x = -integerRadius; x <= integerRadius; ++x)
			{
				CRuVector3 quadOrigin(relativeOrigin.x + x * m_waterQuadSize, relativeOrigin.y + 10.0f, relativeOrigin.z + z * m_waterQuadSize);
				CRuVector3 quadCenter(quadOrigin.x + m_waterQuadSize / 2.0f, quadOrigin.y, quadOrigin.z + m_waterQuadSize / 2.0f);

				if((quadCenter - expansionCenter).Magnitude() <= expansionRadius)
				{
					CRuCollisionQuery_AABB_HitTest boxQuery;
					boxQuery.m_colAABB.Set(quadOrigin.x + epsilon, -heightRange, quadOrigin.z + epsilon, quadOrigin.x + m_waterQuadSize - epsilon, heightRange, quadOrigin.z + m_waterQuadSize - epsilon);
					QueryCollision(&boxQuery);

					if(boxQuery.m_positiveHit == FALSE)
					{
						REAL tesQuadSize = m_waterQuadSize / tessellationLevel;

						for(INT32 tesZ = 0; tesZ < tessellationLevel; ++tesZ)
						{
							for(INT32 tesX = 0; tesX < tessellationLevel; ++tesX)
							{
								// Initialize a new quad using the initialization point as the midpoint
								CRuVector3 v[4];

								v[0].Set(quadOrigin.x + tesX * tesQuadSize, relativeOrigin.y, quadOrigin.z + tesZ * tesQuadSize);
								v[1].Set(quadOrigin.x + (tesX + 1) * tesQuadSize, relativeOrigin.y, quadOrigin.z + tesZ * tesQuadSize);
								v[2].Set(quadOrigin.x + tesX * tesQuadSize, relativeOrigin.y, quadOrigin.z + (tesZ + 1) * tesQuadSize);
								v[3].Set(quadOrigin.x + (tesX + 1)* tesQuadSize, relativeOrigin.y, quadOrigin.z + (tesZ + 1) * tesQuadSize);

								// Add triangles for the initial quad
								m_waterPolys->AddTriangle(v[0], v[2], v[1]);
								m_waterPolys->AddTriangle(v[2], v[3], v[1]);
							}
						}
					}
				}
			}
		}

		// Rebuild water model
		BuildWaterModel(TRUE);

		// Calculate bounds
		CalculateBounds();

		return TRUE;
	}

	return FALSE;
}

BOOL CRuEntity_Water::ShrinkWaterBody(const CRuRay3 &shrinkRay, float shrinkRadius)
{
	REAL heightRange = 10000.0f;
	REAL epsilon = m_waterQuadSize / 100.0f;

	CRuVector3 shrinkCenter;
	float intT;

	// Find intersection point with the water plane
	if(FindIntersection(shrinkRay, m_waterPlane, shrinkCenter, intT))
	{
		// Build a grid of points centered at the shrink point and check to see if they are already occupied
		INT32 integerRadius = static_cast<INT32>(ceil(shrinkRadius / m_waterQuadSize));

		// Modify the shrink center to fit on the grid
		CRuVector3 relativeOrigin;
		relativeOrigin.x = floor((shrinkCenter.x - m_waterOrigin.x) / m_waterQuadSize) * m_waterQuadSize + m_waterOrigin.x;
		relativeOrigin.y = shrinkCenter.y;
		relativeOrigin.z = floor((shrinkCenter.z - m_waterOrigin.z) / m_waterQuadSize) * m_waterQuadSize + m_waterOrigin.z;

		for(INT32 z = -integerRadius; z <= integerRadius; ++z)
		{
			for(INT32 x = -integerRadius; x <= integerRadius; ++x)
			{
				// Form a vertical ray directed at the quad's center
				CRuVector3 quadOrigin(relativeOrigin.x + x * m_waterQuadSize, relativeOrigin.y + 10.0f, relativeOrigin.z + z * m_waterQuadSize);
				CRuVector3 quadCenter(quadOrigin.x + m_waterQuadSize / 2.0f, quadOrigin.y, quadOrigin.z + m_waterQuadSize / 2.0f);
				if((quadCenter - shrinkCenter).Magnitude() <= shrinkRadius)
				{
					CRuCollisionQuery_AABB_HitTest boxQuery;
					boxQuery.m_colAABB.Set(quadOrigin.x + epsilon, -heightRange, quadOrigin.z + epsilon, quadOrigin.x + m_waterQuadSize - epsilon, heightRange, quadOrigin.z + m_waterQuadSize - epsilon);

					m_waterPolys->RemoveTriangle(boxQuery.m_colAABB);
				}
			}
		}

		// Rebuild water model
		BuildWaterModel(TRUE);

		// Calculate bounds
		CalculateBounds();

		return TRUE;
	}

	return FALSE;
}

BOOL CRuEntity_Water::UpdateWaterOrigin()
{
/*
	if(m_waterOrigin.x != m_localTransform._41 || m_waterOrigin.y != m_localTransform._42 || m_waterOrigin.z != m_localTransform._43)
	{
		// Calculate offset
		CRuVector3 offset(m_localTransform._41 - m_waterOrigin.x, m_localTransform._42 - m_waterOrigin.y, m_localTransform._43 - m_waterOrigin.z);

		// Offset poly soup
		m_waterPolys->TranslatePolySoup(offset);

		// Update water origin
		m_waterOrigin.x = m_localTransform._41;
		m_waterOrigin.y = m_localTransform._42;
		m_waterOrigin.z = m_localTransform._43;

		// Update water plane
		m_waterPlane.SetPlane(CRuVector3(0.0f, 1.0f, 0.0f), m_waterOrigin);

		// Rebuild water model
		BuildWaterModel(TRUE);

		// Calculate bounds
		CalculateBounds();
	}
*/
	return TRUE;
}

RuWaterEffectType CRuEntity_Water::GetEffectType() const
{
	return m_effectType;
}

float CRuEntity_Water::GetReflectionFactor() const
{
	return m_reflectionFactor;
}

float CRuEntity_Water::GetRefractionFactor() const
{
	return m_refractionFactor;
}

float CRuEntity_Water::GetAlphaFactor() const
{
	return m_alphaFactor;
}

float CRuEntity_Water::GetBaseFactor() const
{
	return m_baseFactor;
}

float CRuEntity_Water::GetSpecularStrength() const
{
	return m_specularStrength;
}

float CRuEntity_Water::GetUVScale() const
{
	return m_uvScale;
}

RUCOLOR CRuEntity_Water::GetBaseColor() const
{
	return m_baseColor;
}

INT32 CRuEntity_Water::GetBaseMapFrames() const
{
	return m_baseMapFrames;
}

const char *CRuEntity_Water::GetBaseMapName() const
{
	return m_baseMapName;
}

INT32 CRuEntity_Water::GetNormalMapFrames() const
{
	return m_normalMapFrames;
}

const char *CRuEntity_Water::GetNormalMapName() const
{
	return m_normalMapName;
}

INT32 CRuEntity_Water::GetCubeMapFrames() const
{
	return m_cubeMapFrames;
}

const char *CRuEntity_Water::GetCubeMapName() const
{
	return m_cubeMapName;
}

void CRuEntity_Water::SetEffectType(RuWaterEffectType effectType)
{
	m_effectType = effectType;
}

void CRuEntity_Water::SetReflectionFactor(float refFactor)
{
	m_reflectionFactor = refFactor;
}

void CRuEntity_Water::SetRefractionFactor(float refFactor)
{
	m_refractionFactor = refFactor;
}

void CRuEntity_Water::SetAlphaFactor(float alphaFactor)
{
	m_alphaFactor = alphaFactor;
}

void CRuEntity_Water::SetBaseFactor(float baseFactor)
{
	m_baseFactor = baseFactor;
}

void CRuEntity_Water::SetSpecularStrength(float specularStrength)
{
	m_specularStrength = specularStrength;
}

void CRuEntity_Water::SetUVScale(float uvScale)
{
	m_uvScale = uvScale;
}

void CRuEntity_Water::SetBaseColor(RUCOLOR color)
{
	m_baseColor = color;
}

void CRuEntity_Water::SetBaseMapFrames(INT32 frames)
{
	m_baseMapFrames = frames;
}

void CRuEntity_Water::SetBaseMapName(const char *name)
{
	delete[] m_baseMapName;

	m_baseMapName = ruNEW char [strlen(name) + 1];
	strcpy(m_baseMapName, name);
}

void CRuEntity_Water::SetNormalMapFrames(INT32 frames)
{
	m_normalMapFrames = frames;
}

void CRuEntity_Water::SetNormalMapName(const char *name)
{
	delete[] m_normalMapName;

	m_normalMapName = ruNEW char [strlen(name) + 1];
	strcpy(m_normalMapName, name);
}

void CRuEntity_Water::SetCubeMapFrames(INT32 frames)
{
	m_cubeMapFrames = frames;
}

void CRuEntity_Water::SetCubeMapName(const char *name)
{
	delete[] m_cubeMapName;

	m_cubeMapName = ruNEW char [strlen(name) + 1];
	strcpy(m_cubeMapName, name);
}

void CRuEntity_Water::CalculateBounds()
{
	// Update bounding box
	m_bounds = m_waterPolys->GetBounds();

	// Update entity bounding sphere
	CRuVector3 center = (m_bounds.Minima() + m_bounds.Maxima()) / 2.0f;
	REAL radius = (m_bounds.Maxima() - m_bounds.Minima()).Magnitude();
//	m_entityBounds.Set(center, radius);
}

BOOL CRuEntity_Water::BuildWaterModel(BOOL fastBuild)
{
	// Release old water models
	for(INT32 i = 0; i < m_waterModels.Count(); ++i)
	{
		ruSAFE_RELEASE(m_waterModels[i]);
	}
	m_waterModels.Clear();

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

	// Clear workspace
	faceCountList.Clear();
	faceIndexList.Clear();

	// Generate partitions list from the water body polygons
	INT32 trisPerPartition = 1000;
	m_waterPolys->GetUniquePartitions(trisPerPartition, faceCountList, faceIndexList);

	for(INT32 i = 0, j = 0; i < faceCountList.Count(); )
	{
		// Clear vertex and index lists
		vertices.Clear();
		indices.Clear();

		// Iterate through each face list for the current partition - partitions are separated by a -1 value in faceCountList
		for(; faceCountList[i] >= 0; ++i, ++j)
		{
			// Iterate through each face in the current face group
			INT32 faceCount = faceCountList[i];
			for(INT32 k = 0; k < faceCount; ++k)
			{
				INT32 faceIndices[3];
				
				// Figure out face indices
				faceIndices[0] = m_waterPolys->m_indices[faceIndexList[j][k] * 3];
				faceIndices[1] = m_waterPolys->m_indices[faceIndexList[j][k] * 3 + 1];
				faceIndices[2] = m_waterPolys->m_indices[faceIndexList[j][k] * 3 + 2];

				// Re-index the face
				UINT16 remappedFaceIndices[3];
				for(INT32 l = 0; l < 3; ++l)
				{
					remappedFaceIndices[l] = 0xFFFF;

					// If fast build is off, search through the current mesh's vertex list for one that matches the current vertex
					if(fastBuild == FALSE)
					{
						for(INT32 m = 0; m < vertices.Count(); ++m)
						{
							if(vertices[m] == m_waterPolys->m_vertices[faceIndices[l]])
							{
								remappedFaceIndices[l] = (UINT16) m;
								break;
							}
						}
					}

					// If no existing vertex matches the current vertex, add the current vertex to the vertex list
					if(remappedFaceIndices[l] == 0xFFFF)
					{
						remappedFaceIndices[l] = (UINT16) vertices.Count();
						vertices.Add(m_waterPolys->m_vertices[faceIndices[l]]);
					}

					// Add vertex index to the index list
					indices.Add(remappedFaceIndices[l]);
				}
			}
		}

		if(vertices.Count() > 0 && indices.Count() > 0)
		{
			// Build a mesh for the current partition
			CRuMesh *waterMesh = ruNEW CRuMesh();
			waterMesh->Initialize(ruPRIMTYPE_TRIANGLELIST, vertices.Count(), indices.Count() / 3, ruVERTEXFORMAT_POSITION, 1);

			CRuVector3 *position = static_cast<CRuMorphTarget *>(waterMesh->GetMorphTarget(0))->GetPosition();
			for(INT32 a = 0; a < vertices.Count(); ++a)
			{
				position[a] = vertices[a];
			}

			UINT16 *index = waterMesh->GetIndices();
			for(INT32 a = 0; a < indices.Count(); ++a)
			{
				index[a] = indices[a];
			}

			// Calculate bounding sphere for the current partition
			waterMesh->CalculateBounds();

			// Add water mesh to the model object
			CRuModel *waterModel = ruNEW CRuModel();
			waterModel->AddSubModel(waterMesh, m_waterMaterial);
			m_waterModels.Add(waterModel);

			// Release local reference of water mesh
			ruSAFE_RELEASE(waterMesh);
		}

		// Next iteration
		++i;
	}
/*
	for(INT32 i = 0; i < m_waterModels.Count(); ++i)
	{
		// Invoke bounds update on child entity
		m_waterModels[i]->UpdateEntityBounds();
	}
*/
	// Build texture controllers
	BuildTextureControllers();

	return TRUE;
}

BOOL CRuEntity_Water::BuildTextureControllers()
{
	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);

	return TRUE;
}

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

#pragma managed
