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

#include "../../Engine/Geometry/RuPrimitiveToolkit.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuSkyDome, CRuEntity)
ruHEAP_DEFINE_SUBCLASS(CRuSkyDome, "CRuSkyDome", "CRuEntity")
ruCLASSGUID_DEFINE(CRuSkyDome, 0xE4D6FD45, 0x705B4bcf, 0x9F602069, 0x672CBFDD)

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

CRuSkyDome::CRuSkyDome()
:	m_domeGradientTDelta(0.0f),
	m_domeGradientTexture0(NULL),
	m_domeGradientTexture1(NULL),
	m_domeModel(NULL),
	m_domeMesh(NULL),
	m_domeMaterial(NULL),
	m_horizonCloudTransitionMode(ctNONE),
	m_horizonCloudCount(0),
	m_horizonCloudTexture0(NULL),
	m_horizonCloudTexture1(NULL),
	m_horizonCloudMesh(NULL),
	m_horizonCloudModel(NULL),
	m_overheadCloudTransitionMode(ctNONE),
	m_overheadCloudCount(0),
	m_overheadCloudTexture0(NULL),
	m_overheadCloudTexture1(NULL),
	m_overheadCloudMesh(NULL),
	m_overheadCloudModel(NULL)
{
}

CRuSkyDome::~CRuSkyDome()
{
	ruSAFE_RELEASE(m_domeGradientTexture0);
	ruSAFE_RELEASE(m_domeGradientTexture1);

	ruENTITY_SAFE_RELEASE_CHILD(m_domeModel);
	ruSAFE_RELEASE(m_domeMesh);
	ruSAFE_RELEASE(m_domeMaterial);

	ruSAFE_RELEASE(m_horizonCloudTexture0);
	ruSAFE_RELEASE(m_horizonCloudTexture1);
	ruSAFE_RELEASE(m_horizonCloudMesh);
	ruENTITY_SAFE_RELEASE_CHILD(m_horizonCloudModel);

	ruSAFE_RELEASE(m_overheadCloudTexture0);
	ruSAFE_RELEASE(m_overheadCloudTexture1);
	ruSAFE_RELEASE(m_overheadCloudMesh);
	ruENTITY_SAFE_RELEASE_CHILD(m_overheadCloudModel);
}

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

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

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

BOOL CRuSkyDome::Update(float elapsedTime)
{
	// Chain update call to parent class
	if(CRuEntity::Update(elapsedTime) == FALSE)
	{
		return FALSE;
	}

	if(m_domeGradientTDelta > 0.0f)
	{
		m_domeGradientT -= (m_domeGradientTDelta * elapsedTime);

		if(m_domeGradientT <= 0.0f)
		{
			ruSAFE_RELEASE(m_domeGradientTexture0);

			// Reset gradient t to 1.0
			m_domeGradientT = 1.0f;

			m_domeGradientTDelta = 0.0f;

			m_domeGradientTexture0 = m_domeGradientTexture1;
			m_domeGradientTexture1 = NULL;
		}

		// Set gradient t
		m_domeMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_SKYDOME_GRADIENT_T, RuCastFloatToDWORD(m_domeGradientT));

		// Set gradient texture
		m_domeMaterial->SetTexture(ruTEXCHANNEL_SKYDOME_GRADIENT_0, m_domeGradientTexture0);
		m_domeMaterial->SetTexture(ruTEXCHANNEL_SKYDOME_GRADIENT_1, m_domeGradientTexture1);
	}


	// Update dome model
	m_domeModel->Update(elapsedTime);

	UpdateHorizonClouds(elapsedTime);
	UpdateOverheadClouds(elapsedTime);

	return TRUE;
}

void CRuSkyDome::UpdateHorizonClouds(float elapsedTime)
{
	// Generate horizon clouds if necessary
	REAL m_sizeChangeGradientT = 1.0f / 10.0f;
	REAL m_maxHorizonCloudThetaGradient = 0.005f;
	REAL m_minHorizonCloudLifetime = 20.0f;
	REAL m_maxHorizonCloudLifetime = 600.0f;
	REAL m_horizonCloudFadeInTime = 5.0f;
	REAL m_horizonCloudFadeOutTime = 15.0f;

	REAL m_horizonCloudSizeChangeProbability = 0.5f;
	
	m_horizonCloudTypeCount = 4;
	m_horizonCloudSizeCount = 4;

	while(m_horizonClouds.Count() < m_horizonCloudCount)
	{
		INT32 cloudIndex = m_horizonClouds.Count();

		m_horizonClouds.Add(HorizonCloudDesc());

		m_horizonClouds[cloudIndex].m_age = 0.0f;
		m_horizonClouds[cloudIndex].m_fadeStartTime = -1.0f;
		m_horizonClouds[cloudIndex].m_theta = g_mtRand.GenerateFloat() * ruPI * 2.0f;
		m_horizonClouds[cloudIndex].m_thetaGradient = (g_mtRand.GenerateFloat() - 0.5f) * m_maxHorizonCloudThetaGradient;

		m_horizonClouds[cloudIndex].m_material = ruNEW CRuMaterial();
		m_horizonClouds[cloudIndex].m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_CLOUDPLANE"));

		m_horizonClouds[cloudIndex].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_horizonCloudTexture0);
		m_horizonClouds[cloudIndex].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_horizonCloudTexture0);

		m_horizonClouds[cloudIndex].m_model = ruNEW CRuModel();
		m_horizonClouds[cloudIndex].m_model->AddSubModel(m_horizonCloudMesh, m_horizonClouds[cloudIndex].m_material);

		m_horizonClouds[cloudIndex].m_typeIndex = g_mtRand.GenerateUINT32() % m_horizonCloudTypeCount;
		m_horizonClouds[cloudIndex].m_sizeIndex0 = g_mtRand.GenerateUINT32() % m_horizonCloudSizeCount;
		m_horizonClouds[cloudIndex].m_sizeIndex1 = m_horizonClouds[cloudIndex].m_sizeIndex0;
		m_horizonClouds[cloudIndex].m_sizeT = 1.0f;
		m_horizonClouds[cloudIndex].m_sizeTGradient = 0.0f;
	}

	// Determine number of active horizon clouds
	INT32 activeHorizonClouds = 0;
	for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
	{
		if(m_horizonClouds[i].m_fadeStartTime < 0.0f)
		{
			++activeHorizonClouds;
		}
	}

	// Deactivate clouds until we hit the threshold count
	while(activeHorizonClouds > m_horizonCloudCount)
	{
		for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
		{
			if(m_horizonClouds[i].m_fadeStartTime < 0.0f)
			{
				m_horizonClouds[i].m_fadeStartTime = m_horizonClouds[i].m_age;
				--activeHorizonClouds;
			}
		}
	}

	REAL uStep = 1.0f / m_horizonCloudSizeCount;
	REAL vStep = 1.0f / m_horizonCloudTypeCount;

	// Calculate cloud diffuse color
	CRuColor4 blendedCloudColor;
	if(m_horizonCloudColorTDelta > 0.0f)
	{
		m_horizonCloudColorT -= (m_horizonCloudColorTDelta * elapsedTime);

		if(m_horizonCloudColorT <= 0.0f)
		{
			m_horizonCloudColorT = 1.0f;
			m_horizonCloudColorTDelta = 0.0f;

			m_horizonCloudColor0 = m_horizonCloudColor1;
		}
	}

	blendedCloudColor = m_horizonCloudColor0 * m_horizonCloudColorT + m_horizonCloudColor1 * (1.0f - m_horizonCloudColorT);

	// Update horizon cloud transition time
	for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
	{
		m_horizonClouds[i].m_age += elapsedTime;
		m_horizonClouds[i].m_theta += m_horizonClouds[i].m_thetaGradient * elapsedTime;

		m_horizonClouds[i].m_sizeT = max(0.0f, m_horizonClouds[i].m_sizeT - m_horizonClouds[i].m_sizeTGradient * elapsedTime);
	}

	// Process horizon cloud texture change
	switch(m_horizonCloudTransitionMode)
	{
		case ctNONE:
			{
				for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
				{
					if(m_horizonClouds[i].m_sizeT >= 1.0f)
					{
						if(g_mtRand.GenerateFloat() < m_horizonCloudSizeChangeProbability)
						{
							if(m_horizonClouds[i].m_sizeIndex0 == 0)
							{
								m_horizonClouds[i].m_sizeIndex1 = m_horizonClouds[i].m_sizeIndex0 + 1;
							}
							else if(m_horizonClouds[i].m_sizeIndex0 == m_horizonCloudSizeCount - 1)
							{
								m_horizonClouds[i].m_sizeIndex1 = m_horizonClouds[i].m_sizeIndex0 - 1;
							}
							else
							{
								if(g_mtRand.GenerateFloat() < 0.5f)
								{
									m_horizonClouds[i].m_sizeIndex1 = m_horizonClouds[i].m_sizeIndex0 + 1;
								}
								else
								{
									m_horizonClouds[i].m_sizeIndex1 = m_horizonClouds[i].m_sizeIndex0 - 1;
								}
							}

							m_horizonClouds[i].m_sizeTGradient = m_sizeChangeGradientT;
						}
					}

					if(m_horizonClouds[i].m_sizeT <= 0.0f)
					{
						m_horizonClouds[i].m_sizeT = 1.0f;
						m_horizonClouds[i].m_sizeTGradient = 0.0f;
						m_horizonClouds[i].m_sizeIndex0 = m_horizonClouds[i].m_sizeIndex1;
					}
				}
			}

			break;

		case ctPENDING:
			{
				BOOL texTransitionOK = TRUE;

				// Make sure no clouds are undergoing transition
				for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
				{
					if(m_horizonClouds[i].m_sizeT < 1.0f)
					{
						texTransitionOK = FALSE;
						break;
					}
				}

				// If we can transition, execute the transition
				if(texTransitionOK)
				{
					m_horizonCloudTransitionMode = ctEXECUTING;

					for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
					{
						m_horizonClouds[i].m_sizeT = 1.0f;
						m_horizonClouds[i].m_sizeTGradient = 1.0f / 10.0f;
						m_horizonClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_horizonCloudTexture0);
						m_horizonClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_horizonCloudTexture1);
					}
				}
				else
				{
					for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
					{
						if(m_horizonClouds[i].m_sizeT <= 0.0f)
						{
							m_horizonClouds[i].m_sizeT = 1.0f;
							m_horizonClouds[i].m_sizeTGradient = 0.0f;
							m_horizonClouds[i].m_sizeIndex0 = m_horizonClouds[i].m_sizeIndex1;
						}
					}
				}
			}

			break;

		case ctEXECUTING:
			{
				BOOL texTransitionDone = TRUE;

				// Make sure no clouds are undergoing transition
				for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
				{
					if(m_horizonClouds[i].m_sizeT > 0.0f)
					{
						texTransitionDone = FALSE;
						break;
					}
				}

				// If the transition is done....
				if(texTransitionDone)
				{
					m_horizonCloudTransitionMode = ctNONE;

					ruSAFE_RELEASE(m_horizonCloudTexture0);

					m_horizonCloudTexture0 = m_horizonCloudTexture1;
					m_horizonCloudTexture1 = NULL;

					for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
					{
						m_horizonClouds[i].m_sizeT = 1.0f;
						m_horizonClouds[i].m_sizeTGradient = 0.0f;
						m_horizonClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_horizonCloudTexture0);
						m_horizonClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_horizonCloudTexture0);
					}
				}
			}

			break;
	}

	// Update horizon clouds
	m_horizonCloudModel->ClearEntities();
	for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
	{
		// Calculate alpha & color
		REAL alpha = 1.0f;
		if(m_horizonClouds[i].m_age <= m_horizonCloudFadeInTime)
		{
			alpha = min(1.0f, max(0.0f, m_horizonClouds[i].m_age / m_horizonCloudFadeInTime));
		}

		if(m_horizonClouds[i].m_fadeStartTime >= 0.0f)
		{
			alpha = (1.0f - min(1.0f, max(0.0f, (m_horizonClouds[i].m_age - m_horizonClouds[i].m_fadeStartTime) / m_horizonCloudFadeOutTime))) * alpha;

			// If the cloud has expired, remove it
			if(m_horizonClouds[i].m_age - m_horizonClouds[i].m_fadeStartTime > m_horizonCloudFadeOutTime)
			{
				ruSAFE_RELEASE(m_horizonClouds[i].m_model);
				ruSAFE_RELEASE(m_horizonClouds[i].m_material);
				m_horizonClouds.RemoveAt(i);
				--i;
				continue;
			}
		}

		m_horizonClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GLOBAL_DIFFUSE, (blendedCloudColor.GetARGB() & 0x00FFFFFF) | ((BYTE) (alpha * 255.0f) << 24));

		// Setup texture selection parameters
		m_horizonClouds[i].m_vsConstants[0] = uStep;
		m_horizonClouds[i].m_vsConstants[1] = vStep;
		m_horizonClouds[i].m_vsConstants[2] = m_horizonClouds[i].m_sizeT;
		m_horizonClouds[i].m_vsConstants[4] = static_cast<REAL>(m_horizonClouds[i].m_sizeIndex0);
		m_horizonClouds[i].m_vsConstants[5] = static_cast<REAL>(m_horizonClouds[i].m_typeIndex);
		m_horizonClouds[i].m_vsConstants[6] = static_cast<REAL>(m_horizonClouds[i].m_sizeIndex1);
		m_horizonClouds[i].m_vsConstants[7] = static_cast<REAL>(m_horizonClouds[i].m_typeIndex);

		m_horizonClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_RAW_OFFSET, 27);
		m_horizonClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_RAW_COUNT, 2);
		m_horizonClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_RAW_DATA, reinterpret_cast<DWORD>(m_horizonClouds[i].m_vsConstants));

		// Calculate cloud transform
		CRuQuaternion rotQuat;
		rotQuat.FromAngleAxis(CRuVector3(0.0f, 1.0f, 0.0f), m_horizonClouds[i].m_theta);

		m_horizonClouds[i].m_model->ResetLocalTransform();
		m_horizonClouds[i].m_model->Rotate(rotQuat);

		// Add cloud to visible list
		m_horizonCloudModel->AddEntity(m_horizonClouds[i].m_model);
	}

	m_horizonCloudModel->Update(elapsedTime);
}

void CRuSkyDome::UpdateOverheadClouds(float elapsedTime)
{
	// Generate overhead clouds if necessary
	REAL m_sizeChangeGradientT = 1.0f / 10.0f;
	REAL m_maxOverheadCloudThetaGradient = 0.005f;
	REAL m_minOverheadCloudLifetime = 20.0f;
	REAL m_maxOverheadCloudLifetime = 600.0f;
	REAL m_overheadCloudFadeInTime = 5.0f;
	REAL m_overheadCloudFadeOutTime = 15.0f;

	REAL m_overheadCloudSizeChangeProbability = 0.5f;
	
	m_overheadCloudTypeCount = 4;
	m_overheadCloudSizeCount = 4;

	while(m_overheadClouds.Count() < m_overheadCloudCount)
	{
		INT32 cloudIndex = m_overheadClouds.Count();

		m_overheadClouds.Add(OverheadCloudDesc());

		m_overheadClouds[cloudIndex].m_age = 0.0f;
		m_overheadClouds[cloudIndex].m_fadeStartTime = -1.0f;
		m_overheadClouds[cloudIndex].m_omega = g_mtRand.GenerateFloat() * ruPI * 2.0f;
		m_overheadClouds[cloudIndex].m_omegaGradient = (g_mtRand.GenerateFloat() - 0.5f) * m_maxOverheadCloudThetaGradient;
		m_overheadClouds[cloudIndex].m_rho = g_mtRand.GenerateFloat() * 1000.0f;
		m_overheadClouds[cloudIndex].m_rhoGradient = (g_mtRand.GenerateFloat() - 0.5f) * m_maxOverheadCloudThetaGradient;
		m_overheadClouds[cloudIndex].m_theta = g_mtRand.GenerateFloat() * ruPI * 2.0f;
		m_overheadClouds[cloudIndex].m_thetaGradient = (g_mtRand.GenerateFloat() - 0.5f) * m_maxOverheadCloudThetaGradient;

		m_overheadClouds[cloudIndex].m_material = ruNEW CRuMaterial();
		m_overheadClouds[cloudIndex].m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_CLOUDPLANE"));

		m_overheadClouds[cloudIndex].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_overheadCloudTexture0);
		m_overheadClouds[cloudIndex].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_overheadCloudTexture0);

		m_overheadClouds[cloudIndex].m_model = ruNEW CRuModel();
		m_overheadClouds[cloudIndex].m_model->AddSubModel(m_overheadCloudMesh, m_overheadClouds[cloudIndex].m_material);

		m_overheadClouds[cloudIndex].m_typeIndex = g_mtRand.GenerateUINT32() % m_overheadCloudTypeCount;
		m_overheadClouds[cloudIndex].m_sizeIndex0 = g_mtRand.GenerateUINT32() % m_overheadCloudSizeCount;
		m_overheadClouds[cloudIndex].m_sizeIndex1 = m_overheadClouds[cloudIndex].m_sizeIndex0;
		m_overheadClouds[cloudIndex].m_sizeT = 1.0f;
		m_overheadClouds[cloudIndex].m_sizeTGradient = 0.0f;
	}

	// Determine number of active overhead clouds
	INT32 activeOverheadClouds = 0;
	for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
	{
		if(m_overheadClouds[i].m_fadeStartTime < 0.0f)
		{
			++activeOverheadClouds;
		}
	}

	// Deactivate clouds until we hit the threshold count
	while(activeOverheadClouds > m_overheadCloudCount)
	{
		for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
		{
			if(m_overheadClouds[i].m_fadeStartTime < 0.0f)
			{
				m_overheadClouds[i].m_fadeStartTime = m_overheadClouds[i].m_age;
				--activeOverheadClouds;
			}
		}
	}

	REAL uStep = 1.0f / m_overheadCloudSizeCount;
	REAL vStep = 1.0f / m_overheadCloudTypeCount;

	// Calculate cloud diffuse color
	CRuColor4 blendedCloudColor;
	if(m_overheadCloudColorTDelta > 0.0f)
	{
		m_overheadCloudColorT -= (m_overheadCloudColorTDelta * elapsedTime);

		if(m_overheadCloudColorT <= 0.0f)
		{
			m_overheadCloudColorT = 1.0f;
			m_overheadCloudColorTDelta = 0.0f;

			m_overheadCloudColor0 = m_overheadCloudColor1;
		}
	}

	blendedCloudColor = m_overheadCloudColor0 * m_overheadCloudColorT + m_overheadCloudColor1 * (1.0f - m_overheadCloudColorT);

	// Update overhead cloud transition time
	for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
	{
		m_overheadClouds[i].m_age += elapsedTime;
		m_overheadClouds[i].m_omega += m_overheadClouds[i].m_omegaGradient * elapsedTime;
		m_overheadClouds[i].m_rho += m_overheadClouds[i].m_rhoGradient * elapsedTime;
		m_overheadClouds[i].m_theta += m_overheadClouds[i].m_thetaGradient * elapsedTime;

		m_overheadClouds[i].m_sizeT = max(0.0f, m_overheadClouds[i].m_sizeT - m_overheadClouds[i].m_sizeTGradient * elapsedTime);
	}

	// Process overhead cloud texture change
	switch(m_overheadCloudTransitionMode)
	{
		case ctNONE:
			{
				for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
				{
					if(m_overheadClouds[i].m_sizeT >= 1.0f)
					{
						if(g_mtRand.GenerateFloat() < m_overheadCloudSizeChangeProbability)
						{
							if(m_overheadClouds[i].m_sizeIndex0 == 0)
							{
								m_overheadClouds[i].m_sizeIndex1 = m_overheadClouds[i].m_sizeIndex0 + 1;
							}
							else if(m_overheadClouds[i].m_sizeIndex0 == m_overheadCloudSizeCount - 1)
							{
								m_overheadClouds[i].m_sizeIndex1 = m_overheadClouds[i].m_sizeIndex0 - 1;
							}
							else
							{
								if(g_mtRand.GenerateFloat() < 0.5f)
								{
									m_overheadClouds[i].m_sizeIndex1 = m_overheadClouds[i].m_sizeIndex0 + 1;
								}
								else
								{
									m_overheadClouds[i].m_sizeIndex1 = m_overheadClouds[i].m_sizeIndex0 - 1;
								}
							}

							m_overheadClouds[i].m_sizeTGradient = m_sizeChangeGradientT;
						}
					}

					if(m_overheadClouds[i].m_sizeT <= 0.0f)
					{
						m_overheadClouds[i].m_sizeT = 1.0f;
						m_overheadClouds[i].m_sizeTGradient = 0.0f;
						m_overheadClouds[i].m_sizeIndex0 = m_overheadClouds[i].m_sizeIndex1;
					}
				}
			}

			break;

		case ctPENDING:
			{
				BOOL texTransitionOK = TRUE;

				// Make sure no clouds are undergoing transition
				for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
				{
					if(m_overheadClouds[i].m_sizeT < 1.0f)
					{
						texTransitionOK = FALSE;
						break;
					}
				}

				// If we can transition, execute the transition
				if(texTransitionOK)
				{
					m_overheadCloudTransitionMode = ctEXECUTING;

					for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
					{
						m_overheadClouds[i].m_sizeT = 1.0f;
						m_overheadClouds[i].m_sizeTGradient = 1.0f / 10.0f;
						m_overheadClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_overheadCloudTexture0);
						m_overheadClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_overheadCloudTexture1);
					}
				}
				else
				{
					for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
					{
						if(m_overheadClouds[i].m_sizeT <= 0.0f)
						{
							m_overheadClouds[i].m_sizeT = 1.0f;
							m_overheadClouds[i].m_sizeTGradient = 0.0f;
							m_overheadClouds[i].m_sizeIndex0 = m_overheadClouds[i].m_sizeIndex1;
						}
					}
				}
			}

			break;

		case ctEXECUTING:
			{
				BOOL texTransitionDone = TRUE;

				// Make sure no clouds are undergoing transition
				for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
				{
					if(m_overheadClouds[i].m_sizeT > 0.0f)
					{
						texTransitionDone = FALSE;
						break;
					}
				}

				// If the transition is done....
				if(texTransitionDone)
				{
					m_overheadCloudTransitionMode = ctNONE;

					ruSAFE_RELEASE(m_overheadCloudTexture0);

					m_overheadCloudTexture0 = m_overheadCloudTexture1;
					m_overheadCloudTexture1 = NULL;

					for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
					{
						m_overheadClouds[i].m_sizeT = 1.0f;
						m_overheadClouds[i].m_sizeTGradient = 0.0f;
						m_overheadClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_overheadCloudTexture0);
						m_overheadClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_overheadCloudTexture0);
					}
				}
			}

			break;
	}

	// Update overhead clouds
	m_overheadCloudModel->ClearEntities();
	for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
	{
		// Calculate alpha & color
		REAL alpha = 1.0f;
		if(m_overheadClouds[i].m_age <= m_overheadCloudFadeInTime)
		{
			alpha = min(1.0f, max(0.0f, m_overheadClouds[i].m_age / m_overheadCloudFadeInTime));
		}

		if(m_overheadClouds[i].m_fadeStartTime >= 0.0f)
		{
			alpha = (1.0f - min(1.0f, max(0.0f, (m_overheadClouds[i].m_age - m_overheadClouds[i].m_fadeStartTime) / m_overheadCloudFadeOutTime))) * alpha;

			// If the cloud has expired, remove it
			if(m_overheadClouds[i].m_age - m_overheadClouds[i].m_fadeStartTime > m_overheadCloudFadeOutTime)
			{
				ruSAFE_RELEASE(m_overheadClouds[i].m_model);
				ruSAFE_RELEASE(m_overheadClouds[i].m_material);
				m_overheadClouds.RemoveAt(i);
				--i;
				continue;
			}
		}

		m_overheadClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GLOBAL_DIFFUSE, (blendedCloudColor.GetARGB() & 0x00FFFFFF) | ((BYTE) (alpha * 255.0f) << 24));

		// Setup texture selection parameters
		m_overheadClouds[i].m_vsConstants[0] = uStep;
		m_overheadClouds[i].m_vsConstants[1] = vStep;
		m_overheadClouds[i].m_vsConstants[2] = m_overheadClouds[i].m_sizeT;
		m_overheadClouds[i].m_vsConstants[4] = static_cast<REAL>(m_overheadClouds[i].m_sizeIndex0);
		m_overheadClouds[i].m_vsConstants[5] = static_cast<REAL>(m_overheadClouds[i].m_typeIndex);
		m_overheadClouds[i].m_vsConstants[6] = static_cast<REAL>(m_overheadClouds[i].m_sizeIndex1);
		m_overheadClouds[i].m_vsConstants[7] = static_cast<REAL>(m_overheadClouds[i].m_typeIndex);

		m_overheadClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_RAW_OFFSET, 27);
		m_overheadClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_RAW_COUNT, 2);
		m_overheadClouds[i].m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_RAW_DATA, reinterpret_cast<DWORD>(m_overheadClouds[i].m_vsConstants));

		// Calculate cloud transform
		CRuQuaternion rotQuatOmega, rotQuatTheta;
		rotQuatOmega.FromAngleAxis(CRuVector3(0.0f, 1.0f, 0.0f), m_overheadClouds[i].m_omega);
		rotQuatTheta.FromAngleAxis(CRuVector3(0.0f, 1.0f, 0.0f), m_overheadClouds[i].m_theta);

		m_overheadClouds[i].m_model->ResetLocalTransform();
		m_overheadClouds[i].m_model->Rotate(rotQuatOmega);
		m_overheadClouds[i].m_model->Translate(CRuVector3(m_overheadClouds[i].m_rho, 0.0f, 0.0f));
		m_overheadClouds[i].m_model->Rotate(rotQuatTheta);

		// Add cloud to visible list
		m_overheadCloudModel->AddEntity(m_overheadClouds[i].m_model);
	}

	m_overheadCloudModel->Update(elapsedTime);
}
/*
void CRuSkyDome::UpdateEntityTransform()
{
	// Chain transform update call to base class
	CRuEntity::UpdateEntityTransform();

	// Invoke update call on model
	if(m_domeModel)
	{
		m_domeModel->UpdateEntityTransform();
	}

	if(m_horizonCloudModel)
	{
		m_horizonCloudModel->UpdateEntityTransform();
	}

	if(m_overheadCloudModel)
	{
		m_overheadCloudModel->UpdateEntityTransform();
	}
}

void CRuSkyDome::UpdateEntityBounds()
{
	// Chain bounds update call to base class
	CRuEntity::UpdateEntityBounds();

	if(m_domeModel)
	{
		m_domeModel->UpdateEntityBounds();
		m_entityBounds = m_domeModel->GetBounds();
	}

	if(m_horizonCloudModel)
	{
		m_horizonCloudModel->UpdateEntityBounds();
		m_entityBounds.Merge(m_horizonCloudModel->GetBounds());
	}

	if(m_overheadCloudModel)
	{
		m_overheadCloudModel->UpdateEntityBounds();
		m_entityBounds.Merge(m_overheadCloudModel->GetBounds());
	}
}
*/
BOOL CRuSkyDome::Initialize(INT32 horzSegments, INT32 vertSegments, float vertSweep, float radius, float vertScale, float horzCompression, INT32 cloudPlaneSegments)
{
	m_horizontalSegments = horzSegments;
	m_verticalSegments = vertSegments;

	// Construct the dome mesh
	m_domeMesh = ruNEW CRuMesh();
	m_domeMesh->Initialize(ruPRIMTYPE_TRIANGLELIST, CRuPrimitiveToolkit::Shell_VertexCount(horzSegments, vertSegments), CRuPrimitiveToolkit::Shell_PrimitiveCount(horzSegments, vertSegments), ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE, 1);

	CRuVector3 *positions = static_cast<CRuMorphTarget *>(m_domeMesh->GetMorphTarget(0))->GetPosition();
	DWORD *diffuse = static_cast<CRuMorphTarget *>(m_domeMesh->GetMorphTarget(0))->GetDiffuse();
	UINT16 *indices = m_domeMesh->GetIndices();

	INT32 vertexOffset = 0;
	INT32 indexOffset = 0;
	CRuPrimitiveToolkit::Shell_Build(horzSegments, vertSegments, vertSweep, ruPI * 2.0f, radius, vertScale, 1.0f, vertexOffset, indexOffset, positions, indices, diffuse);

	// Calculate mesh bounds
	m_domeMesh->CalculateBounds();

	// Construct dome material
	m_domeMaterial = ruNEW CRuMaterial();
	m_domeMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_SKYDOME"));

	// Construct dome model
	m_domeModel = ruNEW CRuModel();
	m_domeModel->AddSubModel(m_domeMesh, m_domeMaterial);
	this->AddChild(m_domeModel);

	// Construct the cloud mesh
	{
		m_horizonCloudMesh = ruNEW CRuMesh();
		m_horizonCloudMesh->Initialize(ruPRIMTYPE_TRIANGLELIST, CRuPrimitiveToolkit::Shell_VertexCount(horzSegments, vertSegments), CRuPrimitiveToolkit::Shell_PrimitiveCount(horzSegments, vertSegments), ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE, 1);

		CRuVector3 *positions = static_cast<CRuMorphTarget *>(m_horizonCloudMesh->GetMorphTarget(0))->GetPosition();
		DWORD *diffuse = static_cast<CRuMorphTarget *>(m_horizonCloudMesh->GetMorphTarget(0))->GetDiffuse();
		UINT16 *indices = m_horizonCloudMesh->GetIndices();

		INT32 vertexOffset = 0;
		INT32 indexOffset = 0;
		CRuPrimitiveToolkit::Shell_Build(horzSegments, vertSegments, vertSweep, ruPI / 3.0f, radius, vertScale, 1.0f, vertexOffset, indexOffset, positions, indices, diffuse);
	}

	// Calculate mesh bounds
	m_horizonCloudMesh->CalculateBounds();

	// Construct horizon cloud model container
	m_horizonCloudModel = ruNEW CRuFrame();
	this->AddChild(m_horizonCloudModel);

	// Construct the cloud mesh
	{
		// Initialize cloud plane
		float ratio = 0.1f;
		float size = 300.0f;
		INT32 cp_NumVertices = (cloudPlaneSegments + 1) * (cloudPlaneSegments + 1);
		INT32 cp_NumPrimitives = cloudPlaneSegments * cloudPlaneSegments * 2;

		m_overheadCloudMesh = ruNEW CRuMesh();
		m_overheadCloudMesh->Initialize(ruPRIMTYPE_TRIANGLELIST, cp_NumVertices, cp_NumPrimitives, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE, 1);

		positions = static_cast<CRuMorphTarget *>(m_overheadCloudMesh->GetMorphTarget(0))->GetPosition();
		diffuse = static_cast<CRuMorphTarget *>(m_overheadCloudMesh->GetMorphTarget(0))->GetDiffuse();

		float segmentInterval = (size * 2.0f) / cloudPlaneSegments;
		for(INT32 z = 0, i = 0, j = 0; z <= cloudPlaneSegments; ++z)
		{
			float u = (float) z / (float) cloudPlaneSegments * ruPI;

			for(INT32 x = 0; x <= cloudPlaneSegments; ++x, ++i)
			{
				float v = (float) x / (float) cloudPlaneSegments * ruPI;

//				float h = (sin(u) + sin(v)) * 100.0f + 1000.0f;
				float h = radius / 3.0f;

				positions[i].Set(-size + x * segmentInterval, h, -size + z * segmentInterval);
				diffuse[i] = RUCOLOR_ARGB(0xFF, (BYTE) (((float) z / cloudPlaneSegments) * 255.0f), (BYTE) (((float) x / cloudPlaneSegments) * 255.0f), 0xFF);
			}
		}

		indices = m_overheadCloudMesh->GetIndices();

		// Build indices for the mesh body
		for(INT32 z = 0, i = 0; z < cloudPlaneSegments; ++z)
		{
			// Build indices for four faces at a time
			for(INT32 x = 0; x < cloudPlaneSegments; ++x)
			{
				UINT16 v0Idx = z * (cloudPlaneSegments + 1) + x;

				indices[i++] = v0Idx;
				indices[i++] = v0Idx + 1;
				indices[i++] = v0Idx + (cloudPlaneSegments + 1);

				indices[i++] = v0Idx + 1;
				indices[i++] = v0Idx + 1 + (cloudPlaneSegments + 1);
				indices[i++] = v0Idx + (cloudPlaneSegments + 1);
			}
		}
	}

	// Calculate mesh bounds
	m_overheadCloudMesh->CalculateBounds();

	// Construct overhead cloud model container
	m_overheadCloudModel = ruNEW CRuFrame();
	this->AddChild(m_overheadCloudModel);

	return TRUE;
}

void CRuSkyDome::SetCamera(CRuCamera *viewCamera)
{
	m_domeCenterY = -200.0f;

	// Update local transform
	this->ResetLocalTransform();
	this->SetTranslation(CRuVector3(viewCamera->GetCameraPosition().x, viewCamera->GetCameraPosition().y + m_domeCenterY, viewCamera->GetCameraPosition().z));
}

BOOL CRuSkyDome::IsInTransition() const
{
	if(m_domeGradientTDelta > 0.0f)
	{
		return TRUE;
	}

	switch(m_horizonCloudTransitionMode)
	{
		case ctPENDING:
		case ctEXECUTING:
			return TRUE;
	}

	switch(m_overheadCloudTransitionMode)
	{
		case ctPENDING:
		case ctEXECUTING:
			return TRUE;
	}

	return FALSE;
}

void CRuSkyDome::SetSkyDomeGradientTexture(IRuTexture *gradientTexture)
{
	ruSAFE_RELEASE(m_domeGradientTexture0);

	m_domeGradientT = 1.0f;
	m_domeGradientTDelta = 0.0f;

	m_domeGradientTexture0 = gradientTexture;
	m_domeGradientTexture0->AddRef();

	m_domeMaterial->SetTexture(ruTEXCHANNEL_SKYDOME_GRADIENT_0, m_domeGradientTexture0);
	m_domeMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_SKYDOME_GRADIENT_T, RuCastFloatToDWORD(m_domeGradientT));
}

void CRuSkyDome::TransitionSkyDomeGradientTexture(IRuTexture *gradientTexture, float transitionTime)
{
	ruSAFE_RELEASE(m_domeGradientTexture1);

	m_domeGradientT = 1.0f;
	m_domeGradientTDelta = 1.0f / transitionTime;

	m_domeGradientTexture1 = gradientTexture;
	m_domeGradientTexture1->AddRef();

	m_domeMaterial->SetTexture(ruTEXCHANNEL_SKYDOME_GRADIENT_1, m_domeGradientTexture0);
	m_domeMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_SKYDOME_GRADIENT_T, RuCastFloatToDWORD(m_domeGradientT));
}

void CRuSkyDome::SetHorizonCloudTexture(IRuTexture *cloudPlaneTexture)
{
	ruSAFE_RELEASE(m_horizonCloudTexture0);

	m_horizonCloudTexture0 = cloudPlaneTexture;
	m_horizonCloudTexture0->AddRef();

	// HACK
	for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
	{
		m_horizonClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_horizonCloudTexture0);
		m_horizonClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_horizonCloudTexture0);
	}
}

void CRuSkyDome::TransitionHorizonCloudTexture(IRuTexture *cloudPlaneTexture, float transitionTime)
{
	if(m_horizonCloudTransitionMode == ctNONE)
	{
		ruSAFE_RELEASE(m_horizonCloudTexture1);

		m_horizonCloudTexture1 = cloudPlaneTexture;
		m_horizonCloudTexture1->AddRef();

		m_horizonCloudTransitionMode = ctPENDING;

		// Speed up existing transitions
		for(INT32 i = 0; i < m_horizonClouds.Count(); ++i)
		{
			m_horizonClouds[i].m_sizeTGradient *= 5.0f;
		}
	}
}

void CRuSkyDome::SetHorizonCloudColor(const CRuColor4 &cloudColor)
{
	m_horizonCloudColorT = 1.0f;
	m_horizonCloudColorTDelta = 0.0f;

	m_horizonCloudColor0 = cloudColor;
	m_horizonCloudColor1 = cloudColor;
}

void CRuSkyDome::TransitionHorizonCloudColor(const CRuColor4 &cloudColor, REAL transitionTime)
{
	m_horizonCloudColorT = 1.0f;
	m_horizonCloudColorTDelta = 1.0f / transitionTime;

	m_horizonCloudColor1 = cloudColor;
}

void CRuSkyDome::SetHorizonCloudCount(INT32 cloudCount)
{
	m_horizonCloudCount = cloudCount;
}

void CRuSkyDome::SetOverheadCloudTexture(IRuTexture *cloudPlaneTexture)
{
	ruSAFE_RELEASE(m_overheadCloudTexture0);

	m_overheadCloudTexture0 = cloudPlaneTexture;
	m_overheadCloudTexture0->AddRef();

	// HACK
	for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
	{
		m_overheadClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_0, m_overheadCloudTexture0);
		m_overheadClouds[i].m_material->SetTexture(ruTEXCHANNEL_SKYDOME_CLOUD_1, m_overheadCloudTexture0);
	}
}

void CRuSkyDome::TransitionOverheadCloudTexture(IRuTexture *cloudPlaneTexture, float transitionTime)
{
	if(m_overheadCloudTransitionMode == ctNONE)
	{
		ruSAFE_RELEASE(m_overheadCloudTexture1);

		m_overheadCloudTexture1 = cloudPlaneTexture;
		m_overheadCloudTexture1->AddRef();

		m_overheadCloudTransitionMode = ctPENDING;

		// Speed up existing transitions
		for(INT32 i = 0; i < m_overheadClouds.Count(); ++i)
		{
			m_overheadClouds[i].m_sizeTGradient *= 5.0f;
		}
	}
}

void CRuSkyDome::SetOverheadCloudColor(const CRuColor4 &cloudColor)
{
	m_overheadCloudColorT = 1.0f;
	m_overheadCloudColorTDelta = 0.0f;

	m_overheadCloudColor0 = cloudColor;
	m_overheadCloudColor1 = cloudColor;
}

void CRuSkyDome::TransitionOverheadCloudColor(const CRuColor4 &cloudColor, REAL transitionTime)
{
	m_overheadCloudColorT = 1.0f;
	m_overheadCloudColorTDelta = 1.0f / transitionTime;

	m_overheadCloudColor1 = cloudColor;
}

void CRuSkyDome::SetOverheadCloudCount(INT32 cloudCount)
{
	m_overheadCloudCount = cloudCount;
}

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

#pragma managed
