#include "../../Scene/Paperdoll/RuEntityCompositeModel.h"

#include "../../Direct3D/Engine/RuEngineD3D.h"

#include "../../ImageFX/RuImageFX_Blitter.h"
#include "../../../Tools/dxtLib/dxtutility.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuCompositeModelTemplate, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuCompositeModelTemplate, "CRuCompositeModelTemplate", "IRuObject")
ruCLASSGUID_DEFINE(CRuCompositeModelTemplate, 0x2BBCDBB6, 0xAA3A481a, 0x9ADA9A1B, 0x6ABC1F31)

ruRTTI_DEFINE_SUBCLASS(CRuCompositeModel, IRuEntity_Renderable)
ruHEAP_DEFINE_SUBCLASS(CRuCompositeModel, "CRuCompositeModel", "IRuEntity_Renderable")
ruCLASSGUID_DEFINE(CRuCompositeModel, 0xDDE985A6, 0x1B694655, 0x963D291A, 0xB0E22E38)

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

CRuCompositeModelTemplate::CRuCompositeModelTemplate()
{
}

CRuCompositeModelTemplate::~CRuCompositeModelTemplate()
{
	// Delete all part descriptors
	for(INT32 i = 0; i < m_descriptors.Count(); ++i)
	{
		delete m_descriptors[i];
	}
}

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

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

	// Read length of descriptor string including NULL
	INT32 descriptorLength;
	stream->Read(&descriptorLength, sizeof(descriptorLength));

	// Read descriptor string including NULL
	char *descriptorString = ruNEW char [descriptorLength];
	stream->Read(descriptorString, descriptorLength);

	// Set descriptor string
	SetDescriptorString(descriptorString);
	delete[] descriptorString;

	return TRUE;
}

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

	// Write length of descriptor string including NULL
	INT32 descriptorLength = m_descriptorString.GetLength() + 1;
	stream->Write(&descriptorLength, sizeof(descriptorLength));

	// Write descriptor string including NULL
	stream->Write((char *) m_descriptorString, descriptorLength);

	return TRUE;
}

void CRuCompositeModelTemplate::SetDescriptorString(const char *descriptorString)
{
	// Delete all existing part descriptors
	for(INT32 i = 0; i < m_descriptors.Count(); ++i)
	{
		delete m_descriptors[i];
	}
	m_descriptors.Clear();

	// Copy descriptor string
	m_descriptorString = descriptorString;

	// Parse descriptor
	CRuStringTokenizer strTokenizer;
	strTokenizer.Tokenize(m_descriptorString, CRuString("\n\r"));

	// Parse each line in the descriptor
	for(INT32 i = 0; i < strTokenizer.GetNumTokens(); ++i)
	{
		CRuStringTokenizer lineTokenizer;
		lineTokenizer.Tokenize(strTokenizer.GetToken(i), CRuString(" "));

		// Descriptor format: [Part ID] [Part name] [X] [Y] [Width] [Height]
		if(lineTokenizer.GetNumTokens() == 6)
		{
			// Create new part descriptor
			PartDescriptor *partDescriptor = ruNEW PartDescriptor();

			// Parse part descriptor information
			partDescriptor->m_partID = lineTokenizer.GetTokenAsInteger(0);
			partDescriptor->m_partName = lineTokenizer.GetToken(1);
			partDescriptor->m_partTexureX = lineTokenizer.GetTokenAsInteger(2);
			partDescriptor->m_partTexureY = lineTokenizer.GetTokenAsInteger(3);
			partDescriptor->m_partTexureWidth = lineTokenizer.GetTokenAsInteger(4);
			partDescriptor->m_partTexureHeight = lineTokenizer.GetTokenAsInteger(5);

			// Append new part descriptor to the descriptor list
			m_descriptors.Add(partDescriptor);
		}
	}
}

const char *CRuCompositeModelTemplate::GetDescriptorString() const
{
	return (char *) m_descriptorString;
}

INT32 CRuCompositeModelTemplate::GetNumParts() const
{
	return m_descriptors.Count();
}

INT32 CRuCompositeModelTemplate::GetPartIndex(INT32 partID) const
{
	// Find a descriptor with part ID matching the specified part ID
	for(INT32 i = 0; i < m_descriptors.Count(); ++i)
	{
		if(m_descriptors[i]->m_partID == partID)
		{
			return i;
		}
	}

	// Return -1 if the specified part ID was not found
	return -1;
}

INT32 CRuCompositeModelTemplate::GetPartID(INT32 partIndex) const
{
	// Ensure the part index is within range
	if(partIndex >= 0 && partIndex < m_descriptors.Count())
	{
		// Return part ID
		return m_descriptors[partIndex]->m_partID;
	}

	return -1;
}

const char *CRuCompositeModelTemplate::GetPartName(INT32 partIndex) const
{
	// Ensure the part index is within range
	if(partIndex >= 0 && partIndex < m_descriptors.Count())
	{
		// Return part name
		return (char *) m_descriptors[partIndex]->m_partName;
	}

	return "";
}

BOOL CRuCompositeModelTemplate::GetPartTextureQuad(INT32 partIndex, INT32 &xOut, INT32 &yOut, INT32 &widthOut, INT32 &heightOut) const
{
	// Ensure the part index is within range
	if(partIndex >= 0 && partIndex < m_descriptors.Count())
	{
		// Return the texture quad parameters
		xOut = m_descriptors[partIndex]->m_partTexureX;
		yOut = m_descriptors[partIndex]->m_partTexureY;
		widthOut = m_descriptors[partIndex]->m_partTexureWidth;
		heightOut = m_descriptors[partIndex]->m_partTexureHeight;

		return TRUE;
	}

	return FALSE;
}

CRuCompositeModel *CRuCompositeModelTemplate::Construct()
{
	CRuCompositeModel *compositeModel = ruNEW CRuCompositeModel();

	// Set template into the composite model and increment the template's reference count
	compositeModel->m_modelTemplate = this;
	this->AddRef();

	return compositeModel;
}

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

CRuCompositeModel::CRuCompositeModel()
:	m_modelTemplate(NULL),
	m_compositeMesh(NULL),
	m_compositeMaterial(NULL)
{
	// Gotta move this into an option or something
	m_fullCompositionSize = 1024;			// This is the size of the original intended, completely composited texture size
	m_compositeTextureSize = 1024;			// This is the actual requested size of the composited diffuse texture size
	m_maskTextureSize = 256;				// This is the actual requested size of the composited mask texture size
	m_colorMapTextureSize = 32;
	m_glossTextureSize = 256;				// This is the actual requested size of the composited gloss texture size

	// Construct the composite material
	if(m_compositeMaterial == NULL)
	{
		m_compositeMaterial = ruNEW CRuMaterial();

		m_compositeMaterial->SetShader(g_ruResourceManager->GetShader("ruSHADER_PAPERDOLL_SH9_GLOSS"));
		m_compositeMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_LIGHT_SELF, RuCastFloatToDWORD(0.1f));

		IRuBaseTexture *texture = g_ruResourceManager->CreateTexture("ChangeThisToAGUID", m_compositeTextureSize, m_compositeTextureSize, ruSURFACEFMT_DXT1);
		if(texture)
		{
			m_compositeMaterial->SetTexture(ruTEXCHANNEL_DIFFUSEMAP, texture);
			texture->Release();
		}
		else
		{
		}

		IRuBaseTexture *maskTexture = g_ruResourceManager->CreateTexture("ChangeThisToAGUID", m_glossTextureSize, m_glossTextureSize, ruSURFACEFMT_A4R4G4B4);
		if(maskTexture)
		{
			m_compositeMaterial->SetTexture(ruTEXCHANNEL_PAPERDOLL_MASK_COMPOSITE, maskTexture);
			maskTexture->Release();
		}
		else
		{
		}

		IRuVolumeTexture *colorMapTexture = g_ruResourceManager->CreateVolumeTexture("ChangeThisToAGUID", m_colorMapTextureSize, m_colorMapTextureSize, 4, 1, ruSURFACEFMT_A8R8G8B8);
		if(colorMapTexture)
		{
			m_compositeMaterial->SetTexture(ruTEXCHANNEL_PAPERDOLL_COLORMAP, colorMapTexture);
			colorMapTexture->Release();
		}
		else
		{
		}

		IRuBaseTexture *glossTexture = g_ruResourceManager->CreateTexture("ChangeThisToAGUID", m_glossTextureSize, m_glossTextureSize, ruSURFACEFMT_DXT1);
		if(glossTexture)
		{
			m_compositeMaterial->SetTexture(ruTEXCHANNEL_GLOSSMAP, glossTexture);
			glossTexture->Release();
		}
		else
		{
		}
	}

}

CRuCompositeModel::~CRuCompositeModel()
{
	// Release references to submodels
	for(INT32 i = 0; i < m_modelParts.Count(); ++i)
	{
		if(m_modelParts[i].m_mesh)
			m_modelParts[i].m_mesh->Release();

		if(m_modelParts[i].m_material)
			m_modelParts[i].m_material->Release();
	}

	// Release reference to model template
	if(m_modelTemplate)
	{
		m_modelTemplate->Release();
	}

	// Release reference to composite mesh
	if(m_compositeMesh)
	{
		m_compositeMesh->Release();
	}

	// Release reference to composite material
	if(m_compositeMaterial)
	{
		m_compositeMaterial->Release();
	}
}

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

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

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

BOOL CRuCompositeModel::Update(REAL elapsedTime)
{
	return TRUE;
}

BOOL CRuCompositeModel::QueryCollision(CRuCollisionQuery *colQuery)
{
	// If collision query is disabled, return immediately
	if(colQuery->m_ignoreCollisionFlag == FALSE && GetOption(ruENTITYOPTION_COLLISION) == 0)
		return TRUE;

	if(m_compositeMesh)
	{
		// Save previous collision transform
		CRuMatrix4x4 *originalWorldTransform = colQuery->m_worldTransform;
		CRuMatrix4x4 *originalInvWorldTransform = colQuery->m_invWorldTransform;

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

		if(m_compositeMesh->QueryCollision(colQuery) == FALSE)
			return FALSE;

		// Restore original collision transform
		colQuery->m_worldTransform = originalWorldTransform;
		colQuery->m_invWorldTransform = originalInvWorldTransform;
	}

	return TRUE;
}

BOOL CRuCompositeModel::SetPart(INT32 partID, IRuEntity_Renderable *partModel)
{
	// Setup the model parts array
	while(m_modelParts.Count() < m_modelTemplate->GetNumParts())
	{
		m_modelParts.Add(ModelPart());
	}

	// Search for part ID
	INT32 partIndex = m_modelTemplate->GetPartIndex(partID);

	if(partIndex >= 0)
	{
		if(partModel == NULL || partModel->GetNumMeshes() != 1)
		{
			// Decrement reference count on the old mesh
			if(m_modelParts[partIndex].m_mesh)
				m_modelParts[partIndex].m_mesh->Release();

			// Decrement reference count on the old material
			if(m_modelParts[partIndex].m_material)
				m_modelParts[partIndex].m_material->Release();

			// Set part model into the model parts array
			m_modelParts[partIndex].m_mesh = NULL;
			m_modelParts[partIndex].m_material = NULL;
		}
		else
		{
			IRuMesh *mesh;
			IRuMaterial *material;

			// Get mesh and material specified by the new part model
			partModel->GetMesh(0, &mesh, &material);

			if(mesh && material)
			{
				// Increment reference count on the new mesh and material
				mesh->AddRef();
				material->AddRef();

				// Decrement reference count on the old mesh
				if(m_modelParts[partIndex].m_mesh)
					m_modelParts[partIndex].m_mesh->Release();

				// Decrement reference count on the old material
				if(m_modelParts[partIndex].m_material)
					m_modelParts[partIndex].m_material->Release();

				// Set new mesh and material into parts
				m_modelParts[partIndex].m_mesh = mesh;
				m_modelParts[partIndex].m_material = material;
			}
			else
			{
				// Decrement reference count on the old mesh
				if(m_modelParts[partIndex].m_mesh)
					m_modelParts[partIndex].m_mesh->Release();

				// Decrement reference count on the old material
				if(m_modelParts[partIndex].m_material)
					m_modelParts[partIndex].m_material->Release();

				// Set part model into the model parts array
				m_modelParts[partIndex].m_mesh = NULL;
				m_modelParts[partIndex].m_material = NULL;
			}
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuCompositeModel::SetPartColors(INT32 partID, const BOOL *layerActivated, const RUCOLOR *layerColor)
{
	// Setup the model parts array
	while(m_modelParts.Count() < m_modelTemplate->GetNumParts())
	{
		m_modelParts.Add(ModelPart());
	}

	// Search for part ID
	INT32 partIndex = m_modelTemplate->GetPartIndex(partID);

	if(partIndex >= 0)
	{
		for(INT32 i = 0; i < 4; ++i)
		{
			m_modelParts[partIndex].m_layerActivated[i] = layerActivated[i];
			m_modelParts[partIndex].m_layerColor[i] = layerColor[i] | 0XFF000000;
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuCompositeModel::BuildModel()
{
	// Build the color map
	BuildColorMap();

	// Make sure the model parts array has been properly initialized
	if(m_modelParts.Count() == m_modelTemplate->GetNumParts())
	{
		// Release old composite mesh
		if(m_compositeMesh)
		{
			m_compositeMesh->Release();
		}

		INT32 totalVertices = 0, totalIndices = 0;
		for(INT32 i = 0; i < m_modelParts.Count(); ++i)
		{
			if(m_modelParts[i].m_mesh == NULL || m_modelParts[i].m_material == NULL)
			{
				continue;
			}

			totalVertices += m_modelParts[i].m_mesh->GetNumVertices();
			totalIndices += m_modelParts[i].m_mesh->GetNumIndices();
		}

		// Abort if there is nothing in the model to build
		if(totalVertices == 0 || totalIndices == 0)
		{
			return FALSE;
		}

		// Initialize new composite mesh
		m_compositeMesh = ruNEW CRuMesh();
		m_compositeMesh->Initialize(ruPRIMTYPE_TRIANGLELIST, totalVertices, totalIndices / 3, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_NORMAL | ruVERTEXFORMAT_DIFFUSE | ruVERTEXFORMAT_BLENDINDICES | ruVERTEXFORMAT_BLENDWEIGHT, 1);

		// Combine each mesh
		CRuMorphTarget *destMorphTarget = static_cast<CRuMorphTarget *>(m_compositeMesh->GetMorphTarget(0));

		UINT16 *destIndices = m_compositeMesh->GetIndices();

		CRuVector3 *destPosition = destMorphTarget->GetPosition();
		CRuVector3 *destNormal = destMorphTarget->GetNormal();
		DWORD *destDiffuse = destMorphTarget->GetDiffuse();
		UINT16 *destBlendIndex = destMorphTarget->GetBlendIndex();
		REAL *destBlendWeight = destMorphTarget->GetBlendWeight();

		if(destMorphTarget->GetTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP) == NULL)
			m_compositeMesh->AddTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);
		REAL *destDiffuseChannel = destMorphTarget->GetTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);

		IRuTexture *destTexture = (IRuTexture *) m_compositeMaterial->GetTexture(ruTEXCHANNEL_DIFFUSEMAP);
		IRuTexture *destMaskTexture = (IRuTexture *) m_compositeMaterial->GetTexture(ruTEXCHANNEL_PAPERDOLL_MASK_COMPOSITE);
		IRuTexture *destGlossTexture = (IRuTexture *) m_compositeMaterial->GetTexture(ruTEXCHANNEL_GLOSSMAP);


		// Clear textures
		{
			UINT32 numLevels = destMaskTexture->GetLevelCount();
			for(UINT32 j = 0; j < numLevels; ++j)
			{
				UINT16 *destBits;
				INT32 destPitch;

				// Lock destination and source
				destMaskTexture->Lock(j, (void **) &destBits, &destPitch);

				RuImageFX_Clear_16(destBits, 0, 0, destMaskTexture->GetLevelWidth(j), destMaskTexture->GetLevelHeight(j), destPitch);

				// Unlock destination and source
				destMaskTexture->Unlock(j);
			}
		}

		// Iterate through each model part
		for(INT32 i = 0, curIndex = 0, curVertIndex = 0; i < m_modelParts.Count(); ++i)
		{
			if(m_modelParts[i].m_mesh == NULL || m_modelParts[i].m_material == NULL)
			{
				continue;
			}

			IRuMorphTarget *srcMorphTarget = static_cast<IRuMorphTarget *>(m_modelParts[i].m_mesh->GetMorphTarget(0));

			const UINT16 *srcIndices = m_modelParts[i].m_mesh->GetIndices();

			const CRuVector3 *srcPosition = srcMorphTarget->GetPosition();
			const CRuVector3 *srcNormal = srcMorphTarget->GetNormal();
			const DWORD *srcDiffuse = srcMorphTarget->GetDiffuse();
			const UINT16 *srcBlendIndex = srcMorphTarget->GetBlendIndex();
			const REAL *srcBlendWeight = srcMorphTarget->GetBlendWeight();

			// Copy index data
			for(INT32 j = 0; j < m_modelParts[i].m_mesh->GetNumIndices(); ++j, ++curIndex)
			{
				// Modify indices using a constant offset
				destIndices[curIndex] = srcIndices[j] + curVertIndex;
			}

			// Copy position, normal, and diffuse
			for(INT32 j = 0, k = curVertIndex; j < m_modelParts[i].m_mesh->GetNumVertices(); ++j, ++k)
			{
				destPosition[k] = srcPosition[j];
				destNormal[k] = srcNormal[j];
				destDiffuse[k] = srcDiffuse[j];
			}

			if(srcBlendIndex)
			{
				// Copy blend indices
				for(INT32 j = 0, k = curVertIndex; j < m_modelParts[i].m_mesh->GetNumVertices(); ++j, ++k)
				{
					destBlendIndex[k * 4] = srcBlendIndex[j * 4];
					destBlendIndex[k * 4 + 1] = srcBlendIndex[j * 4 + 1];
					destBlendIndex[k * 4 + 2] = srcBlendIndex[j * 4 + 2];
					destBlendIndex[k * 4 + 3] = srcBlendIndex[j * 4 + 3];
				}
			}

			if(srcBlendWeight)
			{
				// Copy blend weights
				for(INT32 j = 0, k = curVertIndex; j < m_modelParts[i].m_mesh->GetNumVertices(); ++j, ++k)
				{
					destBlendWeight[k * 4] = srcBlendWeight[j * 4];
					destBlendWeight[k * 4 + 1] = srcBlendWeight[j * 4 + 1];
					destBlendWeight[k * 4 + 2] = srcBlendWeight[j * 4 + 2];
					destBlendWeight[k * 4 + 3] = srcBlendWeight[j * 4 + 3];
				}
			}

			// Calculate texture coordinate offsets
			INT32 texX = 0, texY = 0, srcTexWidth = 0, srcTexHeight = 0;
			m_modelTemplate->GetPartTextureQuad(i, texX, texY, srcTexWidth, srcTexHeight);

			REAL uOffset = (REAL) texX / (REAL) m_fullCompositionSize;
			REAL vOffset = (REAL) texY / (REAL) m_fullCompositionSize;
			REAL uScale = (REAL) srcTexWidth / (REAL) m_fullCompositionSize;
			REAL vScale = (REAL) srcTexHeight / (REAL) m_fullCompositionSize;

			// Support only diffuse map channel
			const REAL *srcDiffuseChannel = srcMorphTarget->GetTextureCoordinate(ruTEXCHANNEL_DIFFUSEMAP);
			if(srcDiffuseChannel)
			{
				for(INT32 j = 0, k = curVertIndex * 2, l = 0; j < m_modelParts[i].m_mesh->GetNumVertices(); ++j)
				{
					destDiffuseChannel[k++] = srcDiffuseChannel[l++] * uScale + uOffset;
					destDiffuseChannel[k++] = srcDiffuseChannel[l++] * vScale + vOffset;
				}
			}

			curVertIndex += m_modelParts[i].m_mesh->GetNumVertices();

			INT32 numAggregateTextureChannels = 4;
			RuTextureChannel aggTextureChannels[] =
				{
					ruTEXCHANNEL_DIFFUSEMAP,
					ruTEXCHANNEL_PAPERDOLL_BASE,
					ruTEXCHANNEL_PAPERDOLL_LAYER_0,
					ruTEXCHANNEL_PAPERDOLL_LAYER_1
				};

			// *** this composites the diffuse texture

			for(INT32 k = 0; k < numAggregateTextureChannels; ++k)
			{
				IRuTexture *srcTexture = (IRuTexture *) m_modelParts[i].m_material->GetTexture(aggTextureChannels[k]);
				if(srcTexture)
				{
					// Calculate texture size ratios for source and destination textures
					REAL srcTextureScale = (REAL) srcTexture->GetLevelWidth(0) / srcTexWidth;
					REAL destTextureScale = (REAL) m_compositeTextureSize / m_fullCompositionSize;

					// While the source texture scale is too small, scale the coordinates
					INT32 srcShrink = 0;
					while(srcTextureScale < 1.0f - ruEPSILON)
					{
						++srcShrink;
						srcTextureScale *= 2.0f;
					}

					// While the destination texture scale is too small, scale the coordinates
					UINT32 destTexX = texX;
					UINT32 destTexY = texY;
					INT32 destShrink = 0;
					while(destTextureScale < 1.0f - ruEPSILON)
					{
						destTexX /= 2;
						destTexY /= 2;
						++destShrink;
						destTextureScale *= 2.0f;
					}

					if(srcShrink > destShrink)
					{
						// In this case, we are losing one or more mipmap levels -- probably prompt a critical error here
						MessageBox(NULL, "Source layers not enough to cover destination", "CRITICAL", MB_OK);
					}

					INT32 srcLevel = 0;
					INT32 destLevel = 0;

					// If we have more source levels than needed for the destination, figure out which level to start copying from
					while(destShrink > srcShrink)
					{
						++srcLevel;
						--destShrink;
					}

					UINT32 numLevels = min(srcTexture->GetLevelCount() - srcLevel, destTexture->GetLevelCount() - destLevel);
					for(UINT32 j = 0, curTexX = destTexX, curTexY = destTexY; j < numLevels; ++j, ++srcLevel, ++destLevel, curTexX /= 2, curTexY /= 2)
					{
						if(srcTexture->GetLevelWidth(srcLevel) < 4 || srcTexture->GetLevelHeight(srcLevel) < 4)
							break;

						void *srcBits, *destBits;
						INT32 srcPitch, destPitch;

						// Lock destination and source
						srcTexture->Lock(srcLevel, &srcBits, &srcPitch);
						destTexture->Lock(destLevel, &destBits, &destPitch);

						// Blit
						switch(aggTextureChannels[k])
						{
							case ruTEXCHANNEL_DIFFUSEMAP:
							case ruTEXCHANNEL_PAPERDOLL_BASE:
								BltDXT(destBits, kDXT1a, curTexX, curTexY, destTexture->GetLevelWidth(destLevel), destTexture->GetLevelHeight(destLevel), srcBits, kDXT1a, srcTexture->GetLevelWidth(srcLevel), srcTexture->GetLevelHeight(srcLevel), 0xFFFFFFFF, DXT_ROP_SELECTSRC);
								break;
							case ruTEXCHANNEL_PAPERDOLL_LAYER_0:
							case ruTEXCHANNEL_PAPERDOLL_LAYER_1:
								BltDXT(destBits, kDXT1a, curTexX, curTexY, destTexture->GetLevelWidth(destLevel), destTexture->GetLevelHeight(destLevel), srcBits, kDXT1a, srcTexture->GetLevelWidth(srcLevel), srcTexture->GetLevelHeight(srcLevel), 0xFF00FF00, DXT_ROP_OVERLAY);
								break;
							case ruTEXCHANNEL_PAPERDOLL_LAYER_0_FIXED:
							case ruTEXCHANNEL_PAPERDOLL_LAYER_1_FIXED:
								BltDXT(destBits, kDXT1a, curTexX, curTexY, destTexture->GetLevelWidth(destLevel), destTexture->GetLevelHeight(destLevel), srcBits, kDXT1a, srcTexture->GetLevelWidth(srcLevel), srcTexture->GetLevelHeight(srcLevel), 0xFFFFFFFF, DXT_ROP_OVERLAY);
								break;
						}

						// Unlock destination and source
						srcTexture->Unlock(srcLevel);
						destTexture->Unlock(destLevel);
					}
				}
			}

//			D3DXSaveTextureToFile("D:\\outputexture.dds", D3DXIFF_DDS, ((CRuTextureD3D *) destTexture)->GetIDirect3DTexture(), NULL);

			// *** this composites the masking texture
			{
				for(INT32 layer = 0; layer < 4; ++layer)
				{
					// Only apply the masking texture if the layer is active
					if(m_modelParts[i].m_layerActivated[layer])
					{
						IRuTexture *srcTexture = (IRuTexture *) m_modelParts[i].m_material->GetTexture(static_cast<RuTextureChannel>(ruTEXCHANNEL_PAPERDOLL_MASK_0 + layer));

						if(srcTexture)
						{
							// Calculate texture size ratios for source and destination textures
							REAL srcTextureScale = (REAL) srcTexture->GetLevelWidth(0) / srcTexWidth;
							REAL destTextureScale = (REAL) m_glossTextureSize / m_fullCompositionSize;

							// While the source texture scale is too small, scale the coordinates
							INT32 srcShrink = 0;
							while(srcTextureScale < 1.0f - ruEPSILON)
							{
								++srcShrink;
								srcTextureScale *= 2.0f;
							}

							// While the destination texture scale is too small, scale the coordinates
							UINT32 destTexX = texX;
							UINT32 destTexY = texY;
							INT32 destShrink = 0;
							while(destTextureScale < 1.0f - ruEPSILON)
							{
								destTexX /= 2;
								destTexY /= 2;
								++destShrink;
								destTextureScale *= 2.0f;
							}

							if(srcShrink > destShrink)
							{
								// In this case, we are losing one or more mipmap levels -- probably prompt a critical error here
								MessageBox(NULL, "Source layers not enough to cover destination", "CRITICAL", MB_OK);
							}

							INT32 srcLevel = 0;
							INT32 destLevel = 0;

							// If we have more source levels than needed for the destination, figure out which level to start copying from
							while(destShrink > srcShrink)
							{
								++srcLevel;
								--destShrink;
							}

							UINT32 numLevels = min(srcTexture->GetLevelCount() - srcLevel, destGlossTexture->GetLevelCount() - destLevel);
							for(UINT32 j = 0, curTexX = destTexX, curTexY = destTexY; j < numLevels; ++j, ++srcLevel, ++destLevel, curTexX /= 2, curTexY /= 2)
							{
								if(srcTexture->GetLevelWidth(srcLevel) < 4 || srcTexture->GetLevelHeight(srcLevel) < 4)
									break;

								UINT32 *srcBits;
								UINT16 *destBits;
								INT32 srcPitch, destPitch;

								// Lock destination and source
								srcTexture->Lock(srcLevel, (void **) &srcBits, &srcPitch);
								destMaskTexture->Lock(destLevel, (void **) &destBits, &destPitch);

								// Blit
								RuImageFX_Blit_A8R8G8B8ToA4R4G4B4(destBits, curTexX, curTexY, destPitch, layer, srcBits, srcTexture->GetLevelWidth(srcLevel), srcTexture->GetLevelHeight(srcLevel), srcPitch, 0);

								// Unlock destination and source
								srcTexture->Unlock(srcLevel);
								destMaskTexture->Unlock(destLevel);
							}
						}
					}
				}

//				D3DXSaveTextureToFile("D:\\outputexture.dds", D3DXIFF_DDS, ((CRuTextureD3D *) destMaskTexture)->GetIDirect3DTexture(), NULL);
			}

			// *** this composites the gloss texture
			{
				// NOTE: Assumption is made that both the source and the destination are NEVER larger than the expected value

				IRuTexture *srcTexture = (IRuTexture *) m_modelParts[i].m_material->GetTexture(ruTEXCHANNEL_GLOSSMAP);
				if(srcTexture)
				{
					// Calculate texture size ratios for source and destination textures
					REAL srcTextureScale = (REAL) srcTexture->GetLevelWidth(0) / srcTexWidth;
					REAL destTextureScale = (REAL) m_glossTextureSize / m_fullCompositionSize;

					// While the source texture scale is too small, scale the coordinates
					INT32 srcShrink = 0;
					while(srcTextureScale < 1.0f - ruEPSILON)
					{
						++srcShrink;
						srcTextureScale *= 2.0f;
					}

					// While the destination texture scale is too small, scale the coordinates
					UINT32 destTexX = texX;
					UINT32 destTexY = texY;
					INT32 destShrink = 0;
					while(destTextureScale < 1.0f - ruEPSILON)
					{
						destTexX /= 2;
						destTexY /= 2;
						++destShrink;
						destTextureScale *= 2.0f;
					}

					if(srcShrink > destShrink)
					{
						// In this case, we are losing one or more mipmap levels -- probably prompt a critical error here
						MessageBox(NULL, "Source layers not enough to cover destination", "CRITICAL", MB_OK);
					}

					INT32 srcLevel = 0;
					INT32 destLevel = 0;

					// If we have more source levels than needed for the destination, figure out which level to start copying from
					while(destShrink > srcShrink)
					{
						++srcLevel;
						--destShrink;
					}

					UINT32 numLevels = min(srcTexture->GetLevelCount() - srcLevel, destGlossTexture->GetLevelCount() - destLevel);
					for(UINT32 j = 0, curTexX = destTexX, curTexY = destTexY; j < numLevels; ++j, ++srcLevel, ++destLevel, curTexX /= 2, curTexY /= 2)
					{
						if(srcTexture->GetLevelWidth(srcLevel) < 4 || srcTexture->GetLevelHeight(srcLevel) < 4)
							break;

						void *srcBits, *destBits;
						INT32 srcPitch, destPitch;

						// Lock destination and source
						srcTexture->Lock(srcLevel, &srcBits, &srcPitch);
						destGlossTexture->Lock(destLevel, &destBits, &destPitch);

						// Blit
						BltDXT(destBits, kDXT1a, curTexX, curTexY, destGlossTexture->GetLevelWidth(destLevel), destGlossTexture->GetLevelHeight(destLevel), srcBits, kDXT1a, srcTexture->GetLevelWidth(srcLevel), srcTexture->GetLevelHeight(srcLevel), 0xFFFFFFFF, DXT_ROP_SELECTSRC);

						// Unlock destination and source
						srcTexture->Unlock(srcLevel);
						destGlossTexture->Unlock(destLevel);
					}
				}
			}

//			D3DXSaveTextureToFile("D:\\outputexture.dds", D3DXIFF_DDS, ((CRuTextureD3D *) destTexture)->m_texture, NULL);
		}

		// Recalculate bounds for the composite mesh
		m_compositeMesh->CalculateBounds();
		this->SetLocalEntityBounds(m_compositeMesh->GetBounds());

		return TRUE;
	}

	return FALSE;
}

INT32 CRuCompositeModel::GetNumMeshes() const
{
	if(m_compositeMesh)
	{
		return 1;
	}

	return 0;
}

BOOL CRuCompositeModel::GetMesh(INT32 subModelIndex, IRuMesh **meshOut, IRuMaterial **materialOut)
{
	if(subModelIndex == 0 && m_compositeMesh && m_compositeMaterial)
	{
		*meshOut = m_compositeMesh;
		*materialOut = m_compositeMaterial;

		return TRUE;
	}

	return FALSE;
}

void CRuCompositeModel::BuildColorMap()
{
	IRuVolumeTexture *destColorMapTexture = (IRuVolumeTexture *) m_compositeMaterial->GetTexture(ruTEXCHANNEL_PAPERDOLL_COLORMAP);

	// Iterate through each model part
	for(INT32 i = 0, curIndex = 0, curVertIndex = 0; i < m_modelParts.Count(); ++i)
	{
		// Calculate texture coordinate offsets
		INT32 texX = 0, texY = 0, partTextureWidth = 0, partTextureHeight = 0;
		m_modelTemplate->GetPartTextureQuad(i, texX, texY, partTextureWidth, partTextureHeight);

		// Scale the quad down to the resolution of the color map
		UINT32 quadScale = m_fullCompositionSize / m_colorMapTextureSize;

		texX = texX / quadScale;
		texY = texY / quadScale;
		partTextureWidth = partTextureWidth / quadScale;
		partTextureHeight = partTextureHeight / quadScale;

		UINT32 *destBits;
		INT32 destRowPitch, destSlicePitch;

		// Lock destination and source
		destColorMapTexture->Lock(0, (void **) &destBits, &destRowPitch, &destSlicePitch);

		for(INT32 j = 0; j < 4; ++j)
		{
			RuImageFX_Clear_32(static_cast<UINT32 *>(destBits + j * (destSlicePitch / 4)), texX, texY, partTextureWidth, partTextureHeight, destRowPitch, m_modelParts[i].m_layerColor[j]);
		}

		// Unlock destination and source
		destColorMapTexture->Unlock(0);
	}

//	D3DXSaveTextureToFile("D:\\outputexture.dds", D3DXIFF_DDS, ((CRuVolumeTextureD3D *) destColorMapTexture)->GetIDirect3DVolumeTexture(), NULL);
}

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

#pragma managed
