//--------------------------------------------------------------------------------------
// Copyright (C) 2008 http://shift.delblanco.es
//--------------------------------------------------------------------------------------
// SHIFT Videogame is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//--------------------------------------------------------------------------------------
// 3DMesh.cpp
//--------------------------------------------------------------------------------------
#include "dxstdafx.h"
#include "DXUT.h"
#include <map>
#include <string>
#include <vector>

#include "3DMesh.h"
#include "3DObject.h"

//--------------------------------------------------------------------------------------
// Espacio de nombres
//--------------------------------------------------------------------------------------
using namespace std;

//--------------------------------------------------------------------------------------
// Variables Globales
//--------------------------------------------------------------------------------------
extern bool								g_bCamPer;		// Flag para indicar si la cámara es o no persecutoria (3DCamera.cpp)
vector<C3DMesh*>						g_aAlphaYes;    // Vector de geometrías transparentes siempre
vector<C3DMesh*>						g_aAlphaCam;    // Vector de geometrías transparentes ante cámaras persecutorias
map<basic_string<wchar_t>,SMeshEntry*>	g_aMeshes;		// Estructura para almacenar geometrías ya abiertas, con el fin de poder reutilizarlas

//--------------------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------------------
C3DMesh::C3DMesh()
{
    m_bInit = false;
    m_pObject    = NULL;

    m_pMesh      = NULL;
    m_aMtrls     = NULL;
    m_aTextures  = NULL;
    m_awMeshPath = NULL;
}

//--------------------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------------------
C3DMesh::~C3DMesh()
{
    Release();
}

//--------------------------------------------------------------------------------------
// Inicializa el modelo gráfico
//--------------------------------------------------------------------------------------
bool C3DMesh::initialize(C3DObject *dObject, LPCWSTR MeshPath, int dAlpha)
{
    // Salimos si ya se ha inicializado
    if(m_bInit) return false;
    m_bInit = true;

    // Tomamos el dispositivo
    m_pDevice = DXUTGetD3DDevice();
    assert(m_pDevice != NULL);
	m_pDevice->AddRef();

    // Almacenamos el objeto asociado
    m_pObject = dObject;

	// Almacenamos el tipo de transparencia
	m_iAlpha = dAlpha;

	//--------------------------------------------------------------------------------------
    // Si ya tenemos el objeto cargado en memoria lo reutilizamos
    // de esta forma cada modelo sólo estará abierto una única vez en memoria
    map<basic_string<wchar_t>,SMeshEntry*>::iterator itr = g_aMeshes.find(MeshPath);
    if( (itr != g_aMeshes.end()) && (itr->second != NULL) )
    {
        // Tomamos el objeto ya cargado
        m_awMeshPath = itr->second->MeshPath;
        m_pMesh      = itr->second->Mesh;
        m_aMtrls     = itr->second->Mtrls;
        m_aTextures  = itr->second->Textures;

        // Incrementamos su contador de referencias
        itr->second->num = itr->second->num + 1;
    }

	//--------------------------------------------------------------------------------------
    // En otro caso tenemos que cargar el modelo en memoria
    // Creamos las estructuras de datos para materiales y texturas
	else
	{
		m_awMeshPath = _wcsdup(MeshPath);
		m_aMtrls = new std::vector<D3DMATERIAL9>;
		if(!m_aMtrls) return false;
		m_aTextures = new std::vector<IDirect3DTexture9*>;
		if(!m_aTextures) return false;

		LPD3DXBUFFER	pAdjacency;
		LPD3DXBUFFER	pMaterials;
		DWORD			NumMaterials;

		HRESULT hr = D3DXLoadMeshFromX(
			MeshPath,			// Nombre del fichero
			D3DXMESH_MANAGED,	// Opciones de la malla, lo ponemos para que lo maneje directx
			m_pDevice,			// Puntero al dispositivo
			&pAdjacency,		// Puntero a buffer de adyacencias
			&pMaterials,		// Puntero a buffer
			0,
			&NumMaterials,
			&m_pMesh);
		if (FAILED(hr)) return false;

		hr=m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT|D3DXMESHOPT_ATTRSORT|D3DXMESHOPT_VERTEXCACHE, (unsigned long *)pAdjacency->GetBufferPointer(), NULL, NULL, NULL) ;
		if(FAILED(hr))
			return false;

		if( pMaterials != 0 && NumMaterials != 0 )
		{
			D3DXMATERIAL* mtrls=(D3DXMATERIAL*)pMaterials->GetBufferPointer();
			for(unsigned int i = 0; i < NumMaterials; i++)
			{
				// La propiedad MatD3D no carga componente de ambiente
				// asi que asignamos el mismo valor que el difuso
				mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse;
				m_aMtrls->push_back( mtrls[i].MatD3D );

				// HAY TEXTURA?
				if( mtrls[i].pTextureFilename != 0 )
				{
					// (SI)--> la cargamos
					size_t k, max = strlen(mtrls[i].pTextureFilename);
					WCHAR *Tfile = new WCHAR [max+1];
					if(!Tfile) return false;
					for(k=0 ; k < max ; k++)
					Tfile[k] = mtrls[i].pTextureFilename[k];
					Tfile[k]=NULL;

					IDirect3DTexture9* tex = 0;
					D3DXCreateTextureFromFile(
						m_pDevice,
						Tfile,
						&tex);

					SAFE_DELETE_ARRAY(Tfile);

					// La añadimos al vector de texturas
					m_aTextures->push_back(tex);
				}
				else
				{
					// (NO)--> aniadimos una textura nula --> NULL --> 0
					m_aTextures->push_back(0);
				}
			}
		}
		if(pMaterials) pMaterials->Release();

		// Almacenamos el objeto como ya cargado en memoria
		// Además, inicialicamos el contador de referencias a 1
		SMeshEntry *MyEntry = new SMeshEntry;
		if(!MyEntry) return false;
		MyEntry->MeshPath = m_awMeshPath;
		MyEntry->Mesh     = m_pMesh;
		MyEntry->Mtrls    = m_aMtrls;
		MyEntry->Textures = m_aTextures;
		MyEntry->num = 1;
		g_aMeshes.operator [](m_awMeshPath) = MyEntry;
	}

	// Insertamos la nueva instancia de geometría en la lista de alpha correspondiente
	switch(m_iAlpha)
	{
	case MESH_ALPHA_NON: break;
	case MESH_ALPHA_CAM: g_aAlphaCam.push_back(this); break;
	case MESH_ALPHA_YES: g_aAlphaYes.push_back(this); break;
	}

    // El modelo ya está preparado
    return true;
}

//--------------------------------------------------------------------------------------
// Renderizar el modelo gráfico
//--------------------------------------------------------------------------------------
void C3DMesh::render(bool force)
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

	// Renderizamos ahora o al final del proceso dependiento del alpha y la cámara
	if(!force)
	switch(m_iAlpha)
	{
	case MESH_ALPHA_NON: break;
	case MESH_ALPHA_CAM: if (g_bCamPer) return; else break;
	case MESH_ALPHA_YES: return;
	}

	// Indicamos la transformación
    m_pDevice->SetTransform(D3DTS_WORLD, getObject()->getTransformation());

	// Renderizamos materiales y texturas
    for(unsigned int i = 0; i < m_aMtrls->size(); i++)
    {
        m_pDevice->SetMaterial( &(m_aMtrls->operator[](i)));
        m_pDevice->SetTexture(0, m_aTextures->operator[](i));
        m_pMesh->DrawSubset(i);
    }
}

//--------------------------------------------------------------------------------------
// Libera el modelo gráfico
//--------------------------------------------------------------------------------------
void C3DMesh::Release()
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;
    m_bInit = false;

	// Extraemos la geometría de la lista correspondiente
	switch(m_iAlpha)
	{
	case MESH_ALPHA_NON:
		break;
	case MESH_ALPHA_CAM:
		for (vector<C3DMesh*>::iterator itr = g_aAlphaCam.begin(); itr != g_aAlphaCam.end() ; itr++)
			if (*(itr) == this)
			{
				g_aAlphaCam.erase(itr);
				break;
			}
		break;
	case MESH_ALPHA_YES:
		for (vector<C3DMesh*>::iterator itr = g_aAlphaYes.begin(); itr != g_aAlphaYes.end() ; itr++)
			if (*(itr) == this)
			{
				g_aAlphaYes.erase(itr);
				break;
			}
		break;
	}

    // Liberamos el dispositivo
    SAFE_RELEASE(m_pDevice);

	// Buscamos los componentes compartidos del objeto
    map<basic_string<wchar_t>,SMeshEntry*>::iterator itr = g_aMeshes.find(m_awMeshPath);
    if( (itr != g_aMeshes.end()) && (itr->second != NULL) )
    {
		// Decrementamos su contador de referencias
		itr->second->num = itr->second->num - 1;

		// Si es la última referencia liberamos la memoria asociada
		if(itr->second->num <= 0)
		{
			// Lo extraemos de la estructura global, liberando su entrada
			SAFE_DELETE(itr->second);
			g_aMeshes.erase(itr);

			// Liberamos estructuras originales
			for(unsigned i=0;i<m_aTextures->size();i++)
				SAFE_RELEASE(m_aTextures->operator[](i));
			SAFE_DELETE_ARRAY(m_awMeshPath);
			SAFE_DELETE(m_aMtrls);
			SAFE_DELETE(m_aTextures);
			SAFE_RELEASE(m_pMesh);
		}
	}

}
