/*!
	@file RuEntity_Decal.cpp

	Copyright (c) 2004-2006 All rights reserved
*/

#include "../../Scene/FX/RuEntity_Decal.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuEntity_Decal, IRuEntity_Renderable)
ruHEAP_DEFINE_SUBCLASS(CRuEntity_Decal, "CRuEntity_Decal", "IRuEntity_Renderable")
ruCLASSGUID_DEFINE(CRuEntity_Decal, 0x7D3E8CA6, 0x94BE4aa3, 0x8CD5E352, 0x19E86E59)

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

CRuEntity_Decal::CRuEntity_Decal()
:	m_maximumDecalFaces(2000),
	m_uvBillboardType(ruBILLBOARDTYPE_NONE),
	m_decalRadius(1.0f),
	m_mesh(NULL),
	m_material(NULL)
{
	m_decalTransform.SetToIdentity();

	m_mesh = ruNEW CRuDynamicMesh();
	m_mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, m_maximumDecalFaces * 3, m_maximumDecalFaces, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE, 1);
	m_mesh->AddTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);
	m_mesh->SetNumPrimitives(0);
	m_mesh->SetNumVertices(0);

	m_material = ruNEW CRuMaterial();
	m_material->SetShader(g_ruResourceManager->GetShader("ruSHADER_FX_DECAL"));

	// Register self as an event listener
	this->RegisterListener(this, NULL);
}

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

void CRuEntity_Decal::SetDecalRadius(REAL decalRadius)
{
	m_decalRadius = decalRadius;
}

void CRuEntity_Decal::SetDecalTexture(IRuTexture *texture)
{
	m_material->SetTexture(ruTEXCHANNEL_DIFFUSEMAP, texture);
}

void CRuEntity_Decal::SetDecalTransform(const CRuMatrix4x4 &decalTransform)
{
	m_decalTransform = decalTransform;
}

void CRuEntity_Decal::SetDecalUVBasis(const CRuVector3 basisVectors[3], REAL uOffset, REAL vOffset)
{
	m_decalUVBasis[0] = basisVectors[0];
	m_decalUVBasis[1] = basisVectors[1];
	m_decalUVBasis[2] = basisVectors[2];

	m_decalUOffset = uOffset;
	m_decalVOffset = vOffset;
}

void CRuEntity_Decal::BuildDecalMesh(const CRuArrayList<CRuEntity *> &decalTargets)
{
	m_decalBounds.Set(CRuVector3(this->GetWorldTransform()._41, this->GetWorldTransform()._42, this->GetWorldTransform()._43), m_decalRadius);

	const CRuSphere &decalBounds = m_decalBounds;

	CRuMorphTarget *morphTarget = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0));
	CRuVector3 *position = morphTarget->GetPosition();
	DWORD *diffuse = morphTarget->GetDiffuse();
	//	REAL *uv = morphTarget->GetTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);
	UINT16 *indices = m_mesh->GetIndices();

	INT32 vertexIndex = 0;
	INT32 iIndex = 0;

	for(INT32 i = 0; i < decalTargets.Count(); ++i)
	{
		// Build triangle enumeration query
		CRuCollisionQuery_Sphere_TriEnum triEnumQuery;
		triEnumQuery.m_exclusionMask = 0x00A00000;		// Exclude water and foliage surfaces
		triEnumQuery.m_colSphere = decalBounds;

		// Perform collision query on the current decal target
		decalTargets[i]->QueryCollision(&triEnumQuery);

		INT32 maxVertsToProcess = min(m_maximumDecalFaces * 3 - vertexIndex, triEnumQuery.m_colTriVerts->Count());
		if(maxVertsToProcess == 0)
			continue;

		// Iterate through colliding triangles and add them to the decal mesh
		for(INT32 j = 0; j < maxVertsToProcess; )
		{
			position[vertexIndex] = triEnumQuery.m_colTriVerts->Get(j);
			position[vertexIndex + 1] = triEnumQuery.m_colTriVerts->Get(j + 1);
			position[vertexIndex + 2] = triEnumQuery.m_colTriVerts->Get(j + 2);

			diffuse[vertexIndex] = 0xFFFFFFFF;
			diffuse[vertexIndex + 1] = 0xFFFFFFFF;
			diffuse[vertexIndex + 2] = 0xFFFFFFFF;

			indices[iIndex++] = vertexIndex;
			indices[iIndex++] = vertexIndex + 1;
			indices[iIndex++] = vertexIndex + 2;

			vertexIndex += 3;
			j += 3;
		}
	}

	m_mesh->SetNumPrimitives(iIndex / 3);
	m_mesh->SetNumVertices(vertexIndex);

	this->SetLocalEntityBounds(CRuSphere(CRuVector3(0.0f, 0.0f, 0.0f), m_decalRadius));
}

RuBillboardType CRuEntity_Decal::GetBillboardType()
{
	return m_uvBillboardType;
}

void CRuEntity_Decal::SetBillboardType(RuBillboardType billboardType)
{
	m_uvBillboardType = billboardType;
}

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

	return clonedObject;
}

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

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

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

	// Chain serialization to base class
	IRuEntity_Renderable::SerializeFrom(stream);

	return TRUE;
}

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

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

	// Chain serialization to base class
	IRuEntity_Renderable::SerializeTo(stream);

	return TRUE;
}

BOOL CRuEntity_Decal::Update(REAL elapsedTime)
{
	// Chain update to base class
	IRuEntity_Renderable::Update(elapsedTime);

	// Update UV transform
	switch(m_uvBillboardType)
	{
		default:
		case ruBILLBOARDTYPE_NONE:
			{
				m_uvTransform = RuUVTransform_Projection(CRuVector3(this->GetWorldTransform()._41, this->GetWorldTransform()._42, this->GetWorldTransform()._43), m_decalTransform, m_decalUVBasis, m_decalRadius, m_decalUOffset, m_decalVOffset);
			}

			break;

		case ruBILLBOARDTYPE_AXIAL:
			{
				CRuMatrix4x4 aggregateMatrix;
				aggregateMatrix.SetToAxialBillboard(m_aggregateMatrix, CRuVector3(0.0f, 0.0f, 1.0f), CRuVector3(0.0f, 1.0f, 0.0f));

				// NOTE: I am not sure why, but the axial billboard rotates points in the OPPOSITE direction. Is the UV transform interfering
				//       with the billboard in this case? Need to investigate. For now, it works.
//				aggregateMatrix = m_decalTransform * aggregateMatrix.GetInverse();
				aggregateMatrix = aggregateMatrix.GetInverse();

				m_uvTransform = RuUVTransform_Projection(CRuVector3(this->GetWorldTransform()._41, this->GetWorldTransform()._42, this->GetWorldTransform()._43), aggregateMatrix, m_decalUVBasis, m_decalRadius, m_decalUOffset, m_decalVOffset);
			}

			break;
	}

	// Set UV transform into material
	m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_TEXTURE_TRANSFORM_0, reinterpret_cast<DWORD>(&m_uvTransform));

	return TRUE;
}

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

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

		return TRUE;
	}

	return FALSE;
}

BOOL CRuEntity_Decal::HandleEvent(CRuEntity *entity, INT32 eventID, INT32 eventParameter0, INT32 eventParameter1, const char *eventString, REAL eventDuration, void *userData)
{
	// Handle event
	switch(eventID)
	{
		case ruENTITYEVENT_BROADCAST_CAMERA:
			{
				// Cast parameter 0 as the primary camera
				CRuCamera *primaryCamera = reinterpret_cast<CRuCamera *>(eventParameter0);

				// Store aggregated transform matrix
				m_aggregateMatrix = GetWorldTransform() * primaryCamera->GetCameraTransform();
			}

			break;

		case ruENTITYEVENT_BROADCAST_DECALTARGET:
			{
				// Cast parameter 0 as the decal target list
				CRuArrayList<CRuEntity *> *validDecalTargets = reinterpret_cast<CRuArrayList<CRuEntity *> *>(eventParameter0);

				// Build decal mesh
				BuildDecalMesh(*validDecalTargets);
			}

			break;
	}

	return FALSE;
}

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

#pragma managed
