#include "Model3D.h"
#include <stdio.h>

#include <cmath>

Model3D::Model3D(const char* strPath)
{
	//se importa la escena
	_pScene = aiImportFile(strPath,aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_Triangulate);

	//si se cargo correctamente el modelo
	if (_pScene)
	{
		getBoundingBox(&oSceneMin,&oSceneMax);
		oSceneCenter.x = (oSceneMin.x + oSceneMax.x) / 2.0f;
		oSceneCenter.y = (oSceneMin.y + oSceneMax.y) / 2.0f;
		oSceneCenter.z = (oSceneMin.z + oSceneMax.z) / 2.0f;

		oUpdatedSceneMin.x = oSceneMin.x;oUpdatedSceneMin.y = oSceneMin.y;oUpdatedSceneMin.z = oSceneMin.z;
		oUpdatedSceneMax.x = oSceneMax.x;oUpdatedSceneMax.y = oSceneMax.y;oUpdatedSceneMax.z = oSceneMax.z;
		oUpdatedSceneCenter.x = (oUpdatedSceneMin.x + oUpdatedSceneMax.x) / 2.0f;
		oUpdatedSceneCenter.y = (oUpdatedSceneMin.y + oUpdatedSceneMax.y) / 2.0f;
		oUpdatedSceneCenter.z = (oUpdatedSceneMin.z + oUpdatedSceneMax.z) / 2.0f;

		//si tiene animaciones creo estructuras auxiliares
		if (_pScene->HasAnimations())
		{
			maxMeshVertices = 0;
			for(unsigned int i = 0; i < _pScene->mNumMeshes; i++)
			{
				if (_pScene->mMeshes[i]->mNumVertices > maxMeshVertices)
					maxMeshVertices = _pScene->mMeshes[i]->mNumVertices;
			}

			//estructuras auxiliares para animar
			_aVertices = new aiVector3D[maxMeshVertices];
			_aVertexModified = new bool[maxMeshVertices];
		}
		else
		{
			_aVertices = NULL;
			_aVertexModified = NULL;
		}
	}
	else
	{
		printf("cargo mal modelo: %s\n", strPath);
	}

	_nActualAnimation = -1;
	_pSceneAnimator = NULL;
}

void Model3D::getBoundingBox (Vector3* pMin, Vector3* pMax)
{
	struct aiMatrix4x4 trafo;
	aiIdentityMatrix4(&trafo);

	pMin->x = pMin->y = pMin->z =  1e10f;
	pMax->x = pMax->y = pMax->z = -1e10f;
	getBoundingBoxForNode(_pScene->mRootNode,pMin,pMax,&trafo);
}

void Model3D::getBoundingBoxForNode (const struct aiNode* pNd, Vector3* pMin, Vector3* pMax, struct aiMatrix4x4* pTrafo)
{
	struct aiMatrix4x4 prev;
	unsigned int n = 0, t;

	prev = *pTrafo;
	aiMultiplyMatrix4(pTrafo,&pNd->mTransformation);

	for (; n < pNd->mNumMeshes; ++n)
	{
		const struct aiMesh* mesh = _pScene->mMeshes[pNd->mMeshes[n]];
		for (t = 0; t < mesh->mNumVertices; ++t)
		{

			struct aiVector3D tmp = mesh->mVertices[t];
			aiTransformVecByMatrix4(&tmp,pTrafo);

			pMin->x = pMin->x < tmp.x? pMin->x : tmp.x;
			pMin->y = pMin->y < tmp.y? pMin->y : tmp.y;
			pMin->z = pMin->z < tmp.z? pMin->z : tmp.z;

			pMax->x = tmp.x > pMax->x? tmp.x : pMax->x;
			pMax->y = tmp.y > pMax->y? tmp.y : pMax->y;
			pMax->z = pMax->z > tmp.z? pMax->z : tmp.z;
		}
	}

	for (n = 0; n < pNd->mNumChildren; ++n)
	{
		getBoundingBoxForNode(pNd->mChildren[n],pMin,pMax,pTrafo);
	}
	*pTrafo = prev;
}

bool Model3D::isModelLoaded()
{
	return _pScene != NULL;
}

void Model3D::process(bool bUpdateBBox)
{
	if (_pScene != NULL)
	{
		//llamo antes de empezar a procesar el modelo
		preProcess();

		if (bUpdateBBox)
		{
			_bFirstBBoxUpdate = true;
		}
		
		if (bUpdateBBox && _pSceneAnimator == NULL)
		{
			oUpdatedSceneMin.x = oSceneMin.x;oUpdatedSceneMin.y = oSceneMin.y;oUpdatedSceneMin.z = oSceneMin.z;
			oUpdatedSceneMax.x = oSceneMax.x;oUpdatedSceneMax.y = oSceneMax.y;oUpdatedSceneMax.z = oSceneMax.z;
		}

		//proceso el modelo desde el nodo raiz
		recursiveProcess(_pScene->mRootNode, bUpdateBBox);

		oUpdatedSceneCenter.x = (oUpdatedSceneMin.x + oUpdatedSceneMax.x) / 2.0f;
		oUpdatedSceneCenter.y = (oUpdatedSceneMin.y + oUpdatedSceneMax.y) / 2.0f;
		oUpdatedSceneCenter.z = (oUpdatedSceneMin.z + oUpdatedSceneMax.z) / 2.0f;

		//llamo despues de procesar el modelo
		postProcess();
	}
}

void Model3D::recursiveProcess(const struct aiNode* pNd, bool bUpdateBBox)
{
	unsigned int n;
	struct aiMatrix4x4 m = pNd->mTransformation;

	
	aiTransposeMatrix4(&m);

	// aplicar transformacion para este nodo
	applyTransformation((float*)&m);

	// procesar todos las mesh asignadas a este nodo
	for (n = 0; n < pNd->mNumMeshes; ++n)
	{

		const struct aiMesh* mesh = _pScene->mMeshes[pNd->mMeshes[n]];
		
		// si esta activada una animacion, el modelo tiene esqueleto y esta mesh tiene huesos asignados, entonces calculo como deforma el modelo
		if( _pSceneAnimator != NULL && mesh->HasBones())
		{
			const std::vector<aiMatrix4x4>& boneMats = _pSceneAnimator->GetBoneMatrices( pNd, n);

			//inicializo en false el arreglo _aVertexModified
			memset(_aVertexModified, 0, maxMeshVertices*sizeof(bool));

			for( unsigned int a = 0; a < mesh->mNumBones; a++)
			{
				for (unsigned int b = 0; b < mesh->mBones[a]->mNumWeights; b ++)
				{
					aiVector3D temp = mesh->mVertices[mesh->mBones[a]->mWeights[b].mVertexId];
					temp *= boneMats[a];

					if (_aVertexModified[mesh->mBones[a]->mWeights[b].mVertexId])
					{
						_aVertices[mesh->mBones[a]->mWeights[b].mVertexId].x += temp.x*mesh->mBones[a]->mWeights[b].mWeight;
						_aVertices[mesh->mBones[a]->mWeights[b].mVertexId].y += temp.y*mesh->mBones[a]->mWeights[b].mWeight;
						_aVertices[mesh->mBones[a]->mWeights[b].mVertexId].z += temp.z*mesh->mBones[a]->mWeights[b].mWeight;
					}
					else
					{
						_aVertices[mesh->mBones[a]->mWeights[b].mVertexId].x = temp.x*mesh->mBones[a]->mWeights[b].mWeight;
						_aVertices[mesh->mBones[a]->mWeights[b].mVertexId].y = temp.y*mesh->mBones[a]->mWeights[b].mWeight;
						_aVertices[mesh->mBones[a]->mWeights[b].mVertexId].z = temp.z*mesh->mBones[a]->mWeights[b].mWeight;
						_aVertexModified[mesh->mBones[a]->mWeights[b].mVertexId] = true;
					}
					
				}
			}
			
		}

		// proceso los datos de esta mesh
		processMesh(_pScene->mMaterials[mesh->mMaterialIndex], mesh->mFaces, mesh->mNumFaces, mesh->mVertices, mesh->mColors[0], mesh->mNormals, mesh->mTextureCoords[0]);

		//se hace el update del boundig box si es necesario
		if (bUpdateBBox && _pSceneAnimator != NULL)
		{
			for (unsigned int q = 0; q < mesh->mNumVertices; ++q)
			{
				if (_aVertexModified[q])
				{	
					if (_bFirstBBoxUpdate)
					{
						oUpdatedSceneMin.x = _aVertices[q].x;oUpdatedSceneMin.y = _aVertices[q].y;oUpdatedSceneMin.z = _aVertices[q].z;
						oUpdatedSceneMax.x = _aVertices[q].x;oUpdatedSceneMax.y = _aVertices[q].y;oUpdatedSceneMax.z = _aVertices[q].z;
						_bFirstBBoxUpdate = false;
					}
					else
					{
						//comparar
						if (oUpdatedSceneMin.x > _aVertices[q].x) oUpdatedSceneMin.x = _aVertices[q].x;
						if (oUpdatedSceneMin.y > _aVertices[q].y) oUpdatedSceneMin.y = _aVertices[q].y;
						if (oUpdatedSceneMin.z > _aVertices[q].z) oUpdatedSceneMin.z = _aVertices[q].z;

						if (oUpdatedSceneMax.x < _aVertices[q].x) oUpdatedSceneMax.x = _aVertices[q].x;
						if (oUpdatedSceneMax.y < _aVertices[q].y) oUpdatedSceneMax.y = _aVertices[q].y;
						if (oUpdatedSceneMax.z < _aVertices[q].z) oUpdatedSceneMax.z = _aVertices[q].z;
					}
				}
				else
				{
					if (_bFirstBBoxUpdate)
					{
						oUpdatedSceneMin.x = mesh->mVertices[q].x;oUpdatedSceneMin.y = mesh->mVertices[q].y;oUpdatedSceneMin.z = mesh->mVertices[q].z;
						oUpdatedSceneMax.x = mesh->mVertices[q].x;oUpdatedSceneMax.y = mesh->mVertices[q].y;oUpdatedSceneMax.z = mesh->mVertices[q].z;
						_bFirstBBoxUpdate = false;
					}
					else
					{
						//comparar
						if (oUpdatedSceneMin.x > mesh->mVertices[q].x) oUpdatedSceneMin.x = mesh->mVertices[q].x;
						if (oUpdatedSceneMin.y > mesh->mVertices[q].y) oUpdatedSceneMin.y = mesh->mVertices[q].y;
						if (oUpdatedSceneMin.z > mesh->mVertices[q].z) oUpdatedSceneMin.z = mesh->mVertices[q].z;

						if (oUpdatedSceneMax.x < mesh->mVertices[q].x) oUpdatedSceneMax.x = mesh->mVertices[q].x;
						if (oUpdatedSceneMax.y < mesh->mVertices[q].y) oUpdatedSceneMax.y = mesh->mVertices[q].y;
						if (oUpdatedSceneMax.z < mesh->mVertices[q].z) oUpdatedSceneMax.z = mesh->mVertices[q].z;
					}
				}
			}
		}


	}

	// proceso los nodos hijos
	for (n = 0; n < pNd->mNumChildren; ++n) {
		recursiveProcess(pNd->mChildren[n], bUpdateBBox);
	}

	//deshago la transformacion de este nodo
	unApplyLastTransformation();
}

bool Model3D::hasAnimations()
{
	return _pScene->HasAnimations();
}

int Model3D::numberOfAnimations()
{
	return _pScene->mNumAnimations;
}

char* Model3D::animationName(unsigned int n)
{
	if (n < _pScene->mNumAnimations)
		return _pScene->mAnimations[n]->mName.data;
	
	return NULL;
}

bool Model3D::setAnimation(unsigned int n, float fSecondsIni, float fSecondsEnd)
{
	_fSecondsCount = 0.0f;
	_fSecondsCountIni = fSecondsIni;
	_fSecondsCountEnd = fSecondsEnd;

	if (n < _pScene->mNumAnimations)
	{
		if(_pSceneAnimator != NULL)
			delete _pSceneAnimator;
		_pSceneAnimator = new SceneAnimator(_pScene, n);
		_nActualAnimation = n;
		return true;
	}

	_nActualAnimation = -1;

	if(_pSceneAnimator != NULL)
			delete _pSceneAnimator;

	_pSceneAnimator = NULL;
	return false;
}

int Model3D::getActualAnimation()
{
	return _nActualAnimation;
}

void Model3D::disableAnimation()
{
	setAnimation(_pScene->mNumAnimations);
}

void Model3D::advance(float fSeconds)
{
	if (_fSecondsCountIni >= 0.0f && _fSecondsCountEnd >= 0.0f && _fSecondsCountIni < _fSecondsCountEnd)
		_fSecondsCount = _fSecondsCountIni + fmod((_fSecondsCount + fSeconds - _fSecondsCountIni), (_fSecondsCountEnd - _fSecondsCountIni));
	else
		_fSecondsCount += fSeconds;

	if (_pSceneAnimator != NULL)
		_pSceneAnimator->Calculate(_fSecondsCount);
}

void Model3D::setAnimationTime(float fSeconds)
{
	if (_pSceneAnimator != NULL)
		_pSceneAnimator->Calculate(fSeconds);
}

Model3D::~Model3D(void)
{
	if (_aVertices != NULL)
		delete [] _aVertices;
	if (_aVertexModified != NULL)
		delete [] _aVertexModified;

	//se libera memoria usada por aiScene
	aiReleaseImport(_pScene);

}
