#include "MeshObject.h"

bool MeshObject::LoadSkinMeshFromFile(const char* filename)
{
	//If name is not defined - exit
	if (filename == NULL)
		return false;

	//This is X file
	ID3DXFile* xfile;

	//Create X file 
	if (FAILED(D3DXFileCreate(&xfile)))
		return false;

	// Register X file
	if (FAILED(xfile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES)))
	{
		xfile->Release();
		return false;
	}

	ID3DXFileEnumObject* xfile_enum;

	//Open file name and connect with xfile_enum
	if (FAILED(xfile->CreateEnumObject((LPVOID)filename, DXFILELOAD_FROMFILE, &xfile_enum)))
	{
		xfile->Release();
		return false;
	}

	SIZE_T num_child;
	xfile_enum->GetChildren(&num_child);

	D3DXFRAME_EX **frd = NULL;
	pSkinFrame = NULL;


	for (SIZE_T i = 0; i < num_child; i++)
	{
		ID3DXFileData* xfile_data; //Variable for data
		xfile_enum->GetChild(i, &xfile_data); //Get data from object


		if (ParseObject(xfile_data, NULL, frd, FALSE) == FALSE) {
			ReleaseCOM(xfile_data); //Kill data
			break;
		}
		ReleaseCOM(xfile_data); //Kill data
	}

	if (SkinInfo)
	{
		ppFrameMatrix = new D3DXMATRIX*[SkinInfo->GetNumBones()];
		pBoneMatrix = new D3DXMATRIX[SkinInfo->GetNumBones()];

		for (int i = 0; i< (int)SkinInfo->GetNumBones(); i++)
		{
			const char *BoneName = SkinInfo->GetBoneName(i);

			D3DXFRAME_EX *pFrame = FindDXFrame(BoneName, pSkinFrame);

			if (pFrame)
				ppFrameMatrix[i] = &pFrame->matCombined;
			else
				ppFrameMatrix[i] = NULL;
		}
	}

	if (SkinInfo) SkinnedMeshPositioning();

	ReleaseCOM(xfile_enum);
	ReleaseCOM(xfile);

	Map(pSkinFrame);

	return true;
}

bool MeshObject::ParseObject(ID3DXFileData *pDataObj, ID3DXFileData *pParentDataObj, D3DXFRAME_EX **frd, bool ref)
{
	GUID*  Type = new GUID;
	if (pDataObj == NULL)
		return NULL;

	if (FAILED(pDataObj->GetType(Type)))
		Type = NULL;

	if (*Type == TID_D3DRMFrameTransformMatrix && ref == FALSE && frd)
	{
		if (*frd)
		{
			// Get object data
			if (pDataObj)
			{
				void *TemplateData = NULL;
				SIZE_T TemplateSize = 0;

				//pDataObj->GetData(NULL, &TemplateSize, (PVOID*)&TemplateData);
				pDataObj->Lock(&TemplateSize, (LPCVOID*)(VOID **)&TemplateData);
				(*frd)->TransformationMatrix = *(D3DXMATRIX*)TemplateData;
				pDataObj->Unlock();
			}
		}
	}

	if (*Type == TID_D3DRMFrame && ref == FALSE)
	{
		D3DXFRAME_EX *pFrame = new D3DXFRAME_EX();
		pFrame->pFrameSibling = pFrame->pFrameFirstChild = NULL;

		// Get object name
		if (pDataObj)
		{
			pFrame->Name = NULL;
			DWORD  Size = 0;

			if (FAILED(pDataObj->GetName(NULL, &Size)))
				return NULL;
			if (Size)
			if ((pFrame->Name = new char[Size]) != NULL)
				pDataObj->GetName(pFrame->Name, &Size);
		}


		if (frd == NULL)
		{
			pFrame->pFrameSibling = pSkinFrame;
			pFrame->pFrameFirstChild = NULL;
			pSkinFrame = pFrame;
			frd = &pFrame;
		}
		else
		{
			pFrame->pFrameSibling = (*frd)->pFrameFirstChild;
			(*frd)->pFrameFirstChild = pFrame;
			frd = &pFrame;
		}
	}

	if (*Type == TID_D3DRMMesh)
	if (ref == FALSE)
		LoadMeshWithSkin(pDevice, pDataObj, D3DXMESH_SYSTEMMEM);

	// ============== Animation Set ============== //
	if (*Type == TID_D3DRMAnimationSet)
	{
		cAnimationSet *AnimSet = new cAnimationSet();
		AnimSet->m_Next = pAnimationSets;
		pAnimationSets = AnimSet;
		NumAnimationSets++;

		DWORD Size = 0;
		pDataObj->GetName(NULL, &Size);

		if (Size)
		{
			if ((AnimSet->m_Name = new char[Size]) != NULL)
				pDataObj->GetName(AnimSet->m_Name, &Size);
		}
		else
			AnimSet->m_Name = "Default";
	}

	// ============== Animation ============== //
	if (*Type == TID_D3DRMAnimation)
	{
		cAnimation *Anim = new cAnimation();
		Anim->m_Next = pAnimationSets->m_Animations;
		pAnimationSets->m_Animations = Anim;
		pAnimationSets->m_NumAnimations++;
	}

	// ============== Frame ============== //
	if (*Type == TID_D3DRMFrame && pAnimationSets)
	{
		GUID*  parentType = new GUID;
		if (pParentDataObj)
		{
			pParentDataObj->GetType(parentType);
			if (pParentDataObj && *parentType == TID_D3DRMAnimation)
			{
				DWORD Size = 0;
				pDataObj->GetName(NULL, &Size);

				if (Size)
				{
					if ((pAnimationSets->m_Animations->m_Name = new char[Size]) != NULL)
						pDataObj->GetName(pAnimationSets->m_Animations->m_Name, &Size);
				}
				else
					pAnimationSets->m_Animations->m_Name = "Default";
			}
		}
		//return ParseChild(pDataObj, frd, ref);
		//return TRUE;
	}

	// ============== Animation Key ============== //
	if (*Type == TID_D3DRMAnimationKey)
	{
		cAnimation *Anim = new cAnimation();
		Anim = pAnimationSets->m_Animations;
		DWORD *DataPtr = NULL;
		SIZE_T TemplateSize = 0;
		//pDataObj->GetData(NULL, &TemplateSize, (PVOID*)&DataPtr);
		pDataObj->Lock(&TemplateSize, (LPCVOID*)(VOID **)&DataPtr);
		pDataObj->Unlock();

		if (*DataPtr == 4)
		{
			DataPtr++;
			Anim->numKeys = *DataPtr;
			DataPtr++;
			Anim->animationKeys = new cAnimationKey[Anim->numKeys];
			for (int i = 0; i<Anim->numKeys; i++)
			{
				Anim->animationKeys[i].m_Time = *DataPtr;
				DataPtr++;
				if (Anim->animationKeys[i].m_Time > pAnimationSets->m_Length)
					pAnimationSets->m_Length = Anim->animationKeys[i].m_Time;
				DataPtr++;

				D3DXMATRIX *tempMatrix = (D3DXMATRIX *)DataPtr;
				DataPtr += 16;
				Anim->animationKeys[i].m_matKey = *tempMatrix;
			}
		}

	}

	return ParseChild(pDataObj, frd, ref);
}


HRESULT MeshObject::LoadMeshWithSkin(IDirect3DDevice9 *pDevice,
	ID3DXFileData *pDataObj, DWORD LoadFlags)
{
	ID3DXMesh *pLoadMesh = NULL;
	SkinInfo = NULL;
	HRESULT hr;

	ID3DXBuffer *matBuffer = NULL, *abjBuffer = NULL;

	if (FAILED(hr = D3DXLoadSkinMeshFromXof(pDataObj, LoadFlags, pDevice, &abjBuffer,
		&matBuffer, NULL, &dwNumMaterials, &SkinInfo, &pLoadMesh)))
		return hr;

	if (abjBuffer){ abjBuffer->Release(); abjBuffer = NULL; }

	if (SkinInfo && !SkinInfo->GetNumBones())
	{
		SkinInfo->Release(); SkinInfo = NULL;
	}

	MeshData.Type = D3DXMESHTYPE_MESH;
	MeshData.pMesh = pLoadMesh; pLoadMesh = NULL;

	if (SkinInfo)
	{
		MeshData.pMesh->CloneMeshFVF(0, MeshData.pMesh->GetFVF(), pDevice, &pObjMesh);
		MeshData.pMesh->CloneMeshFVF(0, MeshData.pMesh->GetFVF(), pDevice, &ppMesh);
	}

	pMeshMaterials = new D3DMATERIAL9[dwNumMaterials];
	pMeshTextures = new IDirect3DTexture9*[dwNumMaterials];
	if (!matBuffer)
	{
		D3DMATERIAL9 d3dm;
		ZeroMemory(&d3dm, sizeof(D3DMATERIAL9));
		d3dm.Diffuse.r = d3dm.Ambient.r = 1.0f; // red
		d3dm.Diffuse.g = d3dm.Ambient.g = 1.0f; // green
		d3dm.Diffuse.b = d3dm.Ambient.b = 0.0f; // blue
		d3dm.Diffuse.a = d3dm.Ambient.a = 1.0f; // alfa
		for (DWORD i = 0; i<dwNumMaterials; i++)
		{
			pMeshMaterials[i] = d3dm;
			pMeshMaterials[i].Ambient = pMeshMaterials[i].Diffuse;
			pMeshTextures[i] = NULL;
		}
	}
	else
	{
		D3DXMATERIAL *Materials = (D3DXMATERIAL*)matBuffer->GetBufferPointer();
		for (DWORD i = 0; i < dwNumMaterials; i++)
		{
			pMeshMaterials[i] = Materials[i].MatD3D;
			pMeshMaterials[i].Ambient = pMeshMaterials[i].Diffuse;

			if (Materials[i].pTextureFilename)
				HRESULT hr = D3DXCreateTextureFromFileW(pDevice, (LPCWSTR)Materials[i].pTextureFilename, &pMeshTextures[i]);
			else
				pMeshTextures[i] = NULL;
		}
	}

	if (matBuffer){ matBuffer->Release(); matBuffer = NULL; }

	return S_OK;
}

bool MeshObject::ParseChild(ID3DXFileData *pDataObj, D3DXFRAME_EX **frd, bool ref)
{
	ID3DXFileData        *pSubObj = NULL;
	SIZE_T num_child;
	BOOL                  ParseResult = TRUE;
	pDataObj->GetChildren(&num_child);

	for (SIZE_T j = 0; j < num_child; j++)
	{
		pDataObj->GetChild(j, &pSubObj);
		if (pSubObj->IsReference()){
			ref = true;
		}
		ParseResult = ParseObject(pSubObj, pDataObj, frd, ref);
		ReleaseCOM(pSubObj);
		// Return on parsing failure
		if (ParseResult == FALSE)  return FALSE;
	}

	return true;
}




D3DXFRAME_EX * MeshObject::FindDXFrame(const char *FrameName, D3DXFRAME_EX *Frame)
{
	D3DXFRAME_EX *tempFrame;

	if (Frame && Frame->Name && FrameName)
	if (!strcmp(FrameName, Frame->Name))
		return Frame;

	if (Frame->pFrameSibling)
	if ((tempFrame = FindDXFrame(FrameName, (D3DXFRAME_EX*)Frame->pFrameSibling)))
		return tempFrame;

	if (Frame->pFrameFirstChild)
	if ((tempFrame = FindDXFrame(FrameName, (D3DXFRAME_EX*)Frame->pFrameFirstChild)))
		return tempFrame;

	return NULL;
}


void MeshObject::SetMatrix(D3DXMATRIX *matTransformation, D3DXFRAME_EX *Frame)
{
	Frame->matCombined = (Frame->TransformationMatrix) * (*matTransformation);

	if (Frame->pFrameSibling)
		SetMatrix(matTransformation, (D3DXFRAME_EX*)Frame->pFrameSibling);

	if (Frame->pFrameFirstChild)
		SetMatrix(&Frame->matCombined, (D3DXFRAME_EX*)Frame->pFrameFirstChild);
}


HRESULT MeshObject::UpdateMesh()
{
	for (DWORD i = 0; i<SkinInfo->GetNumBones(); i++)
	{
		pBoneMatrix[i] = (*SkinInfo->GetBoneOffsetMatrix(i));
		pBoneMatrix[i] *= (*ppFrameMatrix[i]);
	}

	void *SrcPtr, *DestPtr;
	MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&SrcPtr);
	pObjMesh->LockVertexBuffer(0, (void**)&DestPtr);

	HRESULT hr = SkinInfo->UpdateSkinnedMesh(pBoneMatrix, NULL, SrcPtr, DestPtr);

	pObjMesh->UnlockVertexBuffer();
	MeshData.pMesh->UnlockVertexBuffer();
	ppMesh = pObjMesh;
	return S_OK;
}


void MeshObject::SkinnedMeshPositioning()
{
	D3DXMATRIX Rot;
	
	D3DXFRAME_EX *r = FindDXFrame("Bip01_R_UpperArm", pSkinFrame);
	
	D3DXMatrixRotationX(&Rot, -1.5f);
	if(r) r->TransformationMatrix.operator*=(Rot);
	D3DXMatrixRotationX(&Rot, 1.5f);
	
	SetMatrix(&Rot, pSkinFrame);
	UpdateMesh();
}

void MeshObject::SetWorldPos(D3DXMATRIX* matrix)
{
	matWorld = *matrix;
}

D3DXMATRIX* MeshObject::GetWorldPos()
{
	return &matWorld;
}

void MeshObject::UpdateSkinAnimation(DWORD Time)
{
	if (Time > pAnimationSets->m_Length)
	{
		Time = Time % (pAnimationSets->m_Length);
	}
	cAnimation *Anim = pAnimationSets->m_Animations;
	while (Anim)
	{
		D3DXMatrixIdentity(&Anim->m_Bone->TransformationMatrix);

		if (Anim->numKeys && Anim->animationKeys)
		{
			int key1 = 0, key2 = 0;
			for (int i = 0; i < Anim->numKeys; i++)
			{
				if (Time >= Anim->animationKeys[i].m_Time)
				{
					key1 = i;
				}
			}
			key2 = (key1 >= (Anim->numKeys - 1))? 0 : key1 + 1;

			D3DXMATRIX matrix = (Anim->animationKeys[key2].m_matKey - Anim->animationKeys[key1].m_matKey)*
				(Time - Anim->animationKeys[key1].m_Time) /
				(Anim->animationKeys[key2].m_Time - Anim->animationKeys[key1].m_Time);
			matrix += Anim->animationKeys[key1].m_matKey;

			Anim->m_Bone->TransformationMatrix *= matrix;
		}

		Anim = Anim->m_Next;
	}
}

void MeshObject::Map(D3DXFRAME_EX *RootFrame)
{
	// Go through each animation set
	cAnimationSet *AnimSet = pAnimationSets;
	while (AnimSet != NULL) {

		// Go through each animation object
		cAnimation *Anim = AnimSet->m_Animations;
		while (Anim != NULL) {

			// Go through all frames and look for match
			Anim->m_Bone = RootFrame->Find(Anim->m_Name);

			// Go to next animation object
			Anim = Anim->m_Next;
		}

		// Go to next animation set object
		AnimSet = AnimSet->m_Next;
	}
}

void MeshObject::DrawModel()
{
	//pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
	for (DWORD i = 0; i < dwNumMaterials; i++)
	{
		pDevice->SetMaterial(&pMeshMaterials[i]);
		//if (pMeshTextures != NULL) pDevice->SetTexture(0, pMeshTextures[i]);
		ppMesh->DrawSubset(i);
	}
}


MeshObject* MeshObject::CloneMeshObject(const char* clothesFileName)
{
	//Create new meshObject with equal animation
	MeshObject *meshObject = new MeshObject(pDevice);
	meshObject->LoadSkinMeshFromFile(clothesFileName);

	//Create new SkinFrame equal to this MeshObject's SkinFrame
	meshObject->pSkinFrame = CloneSkinFrame(pSkinFrame);
	
	//Set skeletal animation equal to new meshObject animation info
	meshObject->NumAnimationSets = NumAnimationSets;
	meshObject->pAnimationSets = CloneAnimationSet(pAnimationSets, meshObject->pSkinFrame);
	
	meshObject->ppFrameMatrix = ppFrameMatrix;
	meshObject->pBoneMatrix = pBoneMatrix;
	meshObject->matWorld = matWorld;
	meshObject->m_Mesh = m_Mesh;
	
	//Create new skin info
	D3DVERTEXELEMENT9 declaration[MAX_FVF_DECL_SIZE];
	HRESULT hr = SkinInfo->GetDeclaration(declaration);
	D3DXCreateSkinInfo(meshObject->MeshData.pMesh->GetNumVertices(), declaration, SkinInfo->GetNumBones(), &meshObject->SkinInfo);
	meshObject->SkinInfo->SetMinBoneInfluence(SkinInfo->GetMinBoneInfluence());
	for (int i = 0; i < SkinInfo->GetNumBones(); i++)
	{
		meshObject->ppFrameMatrix[i] = ppFrameMatrix[i];
		LPCSTR name = SkinInfo->GetBoneName(i);
		meshObject->SkinInfo->SetBoneName(i, SkinInfo->GetBoneName(i));
		meshObject->SkinInfo->SetBoneOffsetMatrix(i, SkinInfo->GetBoneOffsetMatrix(i));
	}
	
	if (meshObject->SkinInfo)
	{
		meshObject->MeshData.pMesh->CloneMeshFVF(0, meshObject->MeshData.pMesh->GetFVF(), pDevice, &meshObject->pObjMesh);
		meshObject->MeshData.pMesh->CloneMeshFVF(0, meshObject->MeshData.pMesh->GetFVF(), pDevice, &meshObject->ppMesh);
	}
	
	return meshObject;
}

D3DXFRAME_EX* MeshObject::CloneSkinFrame(D3DXFRAME_EX *originalFrame)
{
	D3DXFRAME_EX* Frame = new D3DXFRAME_EX;
	Frame->Name = originalFrame->Name;
	Frame->TransformationMatrix = originalFrame->TransformationMatrix;
	Frame->matOriginal = originalFrame->matOriginal;
	if (originalFrame->pFrameSibling)
	{
		Frame->pFrameSibling = CloneSkinFrame((D3DXFRAME_EX*)originalFrame->pFrameSibling);
	}

	if (originalFrame->pFrameFirstChild)
	{
		Frame->pFrameFirstChild = CloneSkinFrame((D3DXFRAME_EX*)originalFrame->pFrameFirstChild);
	}
	return Frame;
}

cAnimationSet* MeshObject::CloneAnimationSet(cAnimationSet *originalAnimationSet, D3DXFRAME_EX *Frame)
{
	cAnimationSet* AnimationSet = new cAnimationSet;
	AnimationSet->m_Name = originalAnimationSet->m_Name;
	AnimationSet->m_Length = originalAnimationSet->m_Length;
	AnimationSet->m_NumAnimations = originalAnimationSet->m_NumAnimations;
	AnimationSet->m_Animations = CloneAnimation(originalAnimationSet->m_Animations, NULL, Frame);
	return AnimationSet;
}

cAnimation* MeshObject::CloneAnimation(cAnimation *originalAnimation, cAnimation *previousAnimation, D3DXFRAME_EX *Frame)
{
	cAnimation* animation = new cAnimation();
	if (previousAnimation)
	{
		previousAnimation->m_Next = animation;
	}
	animation->m_Name = originalAnimation->m_Name;
	animation->m_Bone = FindDXFrame(originalAnimation->m_Bone->Name, Frame);
	animation->numKeys = originalAnimation->numKeys;
	animation->animationKeys = originalAnimation->animationKeys;
	if (originalAnimation->m_Next)
	{
		CloneAnimation(originalAnimation->m_Next, animation, Frame);
	}
	return animation;
}

D3DXVECTOR3* MeshObject::GetPointsForLegs()
{
	D3DXVECTOR3 points[6];

	LPDIRECT3DVERTEXBUFFER9 clothsVertexBuffer = NULL;
	ppMesh->GetVertexBuffer(&clothsVertexBuffer);
	D3DXVECTOR3* verticesFromClothsBuffer;
	clothsVertexBuffer->Lock(0, 0, (VOID**)&verticesFromClothsBuffer, D3DLOCK_READONLY);
	leftLegTight_v3 = verticesFromClothsBuffer[leftLegTight];
	rightLegTight_v3 = verticesFromClothsBuffer[rightLegTight];
	leftLegKnee_v3 = verticesFromClothsBuffer[leftLegKnee];
	rightLegKnee_v3 = verticesFromClothsBuffer[rightLegKnee];
	leftLegAnkle_v3 = verticesFromClothsBuffer[leftLegAnkle];
	rightLegAnkle_v3 = verticesFromClothsBuffer[rightLegAnkle];
	D3DXVECTOR3 startVertexTight = verticesFromClothsBuffer[leftLegTight];
	D3DXVECTOR3 endVertexTight = verticesFromClothsBuffer[leftLegTightEnd];
	D3DXVECTOR3 startVertexKnee = verticesFromClothsBuffer[leftLegKnee];
	D3DXVECTOR3 endVertexKnee = verticesFromClothsBuffer[leftLegKneeEnd];
	clothsVertexBuffer->Unlock();
	tightRadius = sqrt(pow(startVertexTight.x - endVertexTight.x, 2) + pow(startVertexTight.z - endVertexTight.z, 2));
	calfRadius = sqrt(pow(startVertexKnee.x - endVertexKnee.x, 2) + pow(startVertexKnee.z - endVertexKnee.z, 2));
	return points;

}


D3DXVECTOR3* MeshObject::GetPointsForArms()
{
	D3DXVECTOR3 points[6];

	LPDIRECT3DVERTEXBUFFER9 clothsVertexBuffer = NULL;
	ppMesh->GetVertexBuffer(&clothsVertexBuffer);
	D3DXVECTOR3* verticesFromClothsBuffer;
	clothsVertexBuffer->Lock(0, 0, (VOID**)&verticesFromClothsBuffer, D3DLOCK_READONLY);
	leftForearm_v3 = verticesFromClothsBuffer[leftForearm];
	rightForearm_v3 = verticesFromClothsBuffer[rightForearm];
	leftElbow_v3 = verticesFromClothsBuffer[leftElbow];
	rightElbow_v3 = verticesFromClothsBuffer[rightElbow];
	leftHand_v3 = verticesFromClothsBuffer[leftHand];
	rightHand_v3 = verticesFromClothsBuffer[rightHand];
	D3DXVECTOR3 startVertexForearm = verticesFromClothsBuffer[leftForearm];
	D3DXVECTOR3 endVertexForearm = verticesFromClothsBuffer[leftForearmEnd];
	D3DXVECTOR3 startVertexElbow = verticesFromClothsBuffer[leftElbow];
	D3DXVECTOR3 endVertexElbow = verticesFromClothsBuffer[leftElbowEnd];
	clothsVertexBuffer->Unlock();
	forearmRadius = sqrt(pow(startVertexForearm.x - endVertexForearm.x, 2) + pow(startVertexForearm.z - endVertexForearm.z, 2));
	armRadius = sqrt(pow(startVertexElbow.x - endVertexElbow.x, 2) + pow(startVertexElbow.z - endVertexElbow.z, 2));
	return points;

}

void MeshObject::SetLeftTightPoints(int* points)
{

}


void MeshObject::SetRightTightPoints(int* points)
{

}


void MeshObject::SetLeftCalfPoints(int* points)
{

}


void MeshObject::SetRightCalfPoints(int* points)
{

}

float MeshObject::GetTightRadius()
{
	return tightRadius;
}

float MeshObject::GetCalfRadius()
{
	return calfRadius;
}

float MeshObject::GetForearmRadius()
{
	return forearmRadius;
}

float MeshObject::GetArmRadius()
{
	return armRadius;
}
