/*********************************************************************
*	3D Models Library
*	Knihovna 3D modelu se skeletalni animaci. Pracuje s knihovnou Cal3D,
*	ktera se stara o vsechny vypocty behem animace. Cal3D ale neimplementuje
*	zadne vykreslovani, protoze se snazi byt nezavisla na grafickem API, 
*	proto bylo nutne vytvorit vlastni vykreslovani. Tato operace byla
*	prevzata z ukazkoveho programu, ktery prezentuje knihovnu Cal3D.
*	Ve vysledku to probiha tak, ze Cal3D vygeneruje pixely, normaly
*	a texturovaci souradnice do pole, ktere se pomoci vertex arrays pote
*	vykresli. Krome vykreslovani je zde podle vzoroveho prikladu i nacitani
*	modelu. Hlavni funkci teto knihovny je ale sdileni modelu i kdyz jen
*	v omezene mire. Pro kazdy model existuje tzv. CoreModel, ktery obsahuje
*	staticke informace o modelu. Prave tento objekt je v knihovne vzdy pouze
*	jeden pro kazdy pouzivany model. Z neho se pak vytvareji konkretni instance,
*	jez uz nelze sdilet, protoze je kazdy naprosto jiny dle prave provadene animace.
*	I tak se ale usetri hodne pameti kvuli texturam, ktere se nactou vzdy jen jednou.
*	Knihovna umoznuje vkladat zaznamy (klice) modelu, vytvaret instance modelu,
*	vykreslovat a samozrejme i mazat.
*	
*	Krome modelu typu Cal3D umoznuje knihovna pracovat se statickymi modely typu
*	MS3D (Milkshape 3D) a animovanymi typu *.mdl, ktery pouziva Half-life 1 + vsechny jeho
*	modifikace. U obou typu je implementovana velice efektivni sdileni dat.
*
*	author: Michal Jirous
*	date: 4.11.2008
*	file: modelslib.cpp
**********************************************************************/

#include "modelslib.h"
#include "textureslib.h"
#include <queue>

#ifdef PHOBIA_CONSOLE
#include "sys_console.h"
using namespace systemConsole;
#define PRINT_ERROR(error)	console << error << endline
#else
#include <iostream>
#define PRINT_ERROR(error)	cerr << error << endl
#endif

using namespace std;
using namespace modelLib;


std::string ModelElement::getFileName()
{
	if( m_pCoreModel )
		return m_pCoreModel->getFileName();
	return "";
}






modelLib::ModelsLibrary modelLib::modelLibrary;

/*! Funkce vytvari novy model dle zakladniho objektu CalCoreModel tridy Cal3D, kde jsou obsazeny vsechny dulezite informace
*	o modelu. Krome techto dat je jeste potreba ulozit informace o indexech a poctu animaci. Po vytvoreni
*	konkretni instance CalModel z objektu CalCoreModel se jeste nastavi vsechny meshe a materialy take z tohot objektu.
*	Pote uz je model pripraven k pouziti.
*
*	@param model Objekt s informacemi o modelu.
*	@param pNumAnimations Pocet animaci tohoto modelu.
*	@param pAnimations Identifikacni cisla animaci.
*/
Cal3DModelElement::Cal3DModelElement( CCal3DCoreModel *model, unsigned int *pNumAnimations, int *pAnimations )
{
	m_pCoreModel = model;
	if( model && model->m_calCoreModel )
	{
		m_calModel = new CalModel( model->m_calCoreModel );


		// attach all meshes to the model
		int meshId;
		for(meshId = 0; meshId < model->m_calCoreModel->getCoreMeshCount(); meshId++)
		{
			m_calModel->attachMesh( meshId );
		}

		// set the material set of the whole model
		m_calModel->setMaterialSet(0);
	}
	
	m_fLODLevel = 1.0f;
	m_uiCurrentAnimation = -1;
	m_pTotalAnimations = pNumAnimations;
	m_pAnimations = pAnimations;
}



/*!	@return Ukazatel na data modelu. */
CalModel* Cal3DModelElement::getCalModel()
{ 
	return m_calModel; 
}	


/*! 	Hodnota urovne detailu smi byt mezi 0 a 1, kde 1 znamena zakladni model a s klesajici hodnotou
*	se snizuje pocet polygonu. Operace, ktera to provadi je ale casove narocna a neni vhodne ji 
*	provadet casto.
*
*	@param lod Uroven detailu, ktera se ma nastavit.
*/
void Cal3DModelElement::setLOD( float lod )
{
	if( lod < 0.0f || lod > 1.0f )
		return;
	m_fLODLevel = lod;
}

/*!	Aktualni cyklicka animace muze byt dle teto funkce pouze jedna.
*
*	@param animation Animace, ktera se ma nastavit jako aktivni.
*	@param delay Trvani prechodu na novou animaci.
*	@param motion_blend Vlastnost animace.
*/
void Cal3DModelElement::setAnimation( unsigned int animation, float delay, float motion_blend )
{
	if( !m_calModel )
		return;

	if( animation < *m_pTotalAnimations)
	{
		//m_calModel->getMixer()->clearCycle( m_uiCurrentAnimation, delay );
		m_calModel->getMixer()->blendCycle( animation, motion_blend, delay );
	}
}

/*!	
*	@param animation Animace, ktera se ma odstranit.
*	@param delay Trvani prechodu.
*/
void Cal3DModelElement::removeAnimation( unsigned int animation, float delay )
{
	m_calModel->getMixer()->clearCycle( animation, delay );
}

void Cal3DModelElement::removeExecutedAnimation( unsigned int animation )
{
	m_calModel->getMixer()->removeAction( animation );
}



/*! Pri nastaveni teto animace nedojde ke zruseni cyklicke animace, ale dojde k jejich promichani.
*
*	@param animation Animace, ktera se ma spustit.
*	@param delayin Trvani prechodu na novou animaci.
*	@param delayout Trvani prechodu pri ukonceni animace.
*/
void Cal3DModelElement::executeAnimation( unsigned int animation, float delayin, float delayout )
{
	if( m_calModel && animation < *m_pTotalAnimations)
		m_calModel->getMixer()->executeAction( animation, delayin, delayout);
}

/*!	@param elapsetseconds Pocet sekund, ktere ubehly od posledni aktualizace
*/
void Cal3DModelElement::update( float elapsetseconds )
{
	if( m_calModel )
		m_calModel->update( elapsetseconds );
}

Cal3DModelElement::~Cal3DModelElement()
{
	delete m_calModel;
}


/*! @param scale Modifikator velikosti pri vykreslovani.
*/
void Cal3DModelElement::setRenderScale( float scale )
{
	m_fRenderScale = scale;
}


void Cal3DModelElement::draw()
{
	glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT );
	glPushMatrix();

	glScalef( m_fRenderScale, m_fRenderScale, m_fRenderScale);

	render();

	glPopMatrix();
	glPopAttrib();
}


void Cal3DModelElement::render()
{
// get the renderer of the model
  CalRenderer *pCalRenderer;
  pCalRenderer = m_calModel->getRenderer();

  // begin the rendering loop
  if(!pCalRenderer->beginRendering()) 
	  return;

  // we will use vertex arrays, so enable them
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);

  // get the number of meshes
  int meshCount;
  meshCount = pCalRenderer->getMeshCount();

  // render all meshes of the model
  int meshId;
  for(meshId = 0; meshId < meshCount; meshId++)
  {
    // get the number of submeshes
    int submeshCount;
    submeshCount = pCalRenderer->getSubmeshCount(meshId);

    // render all submeshes of the mesh
    int submeshId;
    for(submeshId = 0; submeshId < submeshCount; submeshId++)
    {
      // select mesh and submesh for further data access
      if(pCalRenderer->selectMeshSubmesh(meshId, submeshId))
      {
        unsigned char meshColor[4];
        GLfloat materialColor[4];

        // set the material ambient color
        pCalRenderer->getAmbientColor(&meshColor[0]);
        materialColor[0] = meshColor[0] / 255.0f;  materialColor[1] = meshColor[1] / 255.0f; materialColor[2] = meshColor[2] / 255.0f; materialColor[3] = meshColor[3] / 255.0f;
        glMaterialfv(GL_FRONT, GL_AMBIENT, materialColor);

        // set the material diffuse color
        pCalRenderer->getDiffuseColor(&meshColor[0]);
        materialColor[0] = meshColor[0] / 255.0f;  materialColor[1] = meshColor[1] / 255.0f; materialColor[2] = meshColor[2] / 255.0f; materialColor[3] = meshColor[3] / 255.0f;
        glMaterialfv(GL_FRONT, GL_DIFFUSE, materialColor);

        // set the vertex color if we have no lights

          glColor4fv(materialColor);


        // set the material specular color
        pCalRenderer->getSpecularColor(&meshColor[0]);
        materialColor[0] = meshColor[0] / 255.0f;  materialColor[1] = meshColor[1] / 255.0f; materialColor[2] = meshColor[2] / 255.0f; materialColor[3] = meshColor[3] / 255.0f;
        glMaterialfv(GL_FRONT, GL_SPECULAR, materialColor);

        // set the material shininess factor
        float shininess;
        shininess = 50.0f; //TODO: pCalRenderer->getShininess();
        glMaterialfv(GL_FRONT, GL_SHININESS, &shininess);

        // get the transformed vertices of the submesh
        static float meshVertices[30000][3];
        int vertexCount;
        vertexCount = pCalRenderer->getVertices(&meshVertices[0][0]);

        // get the transformed normals of the submesh
        static float meshNormals[30000][3];
        pCalRenderer->getNormals(&meshNormals[0][0]);

        // get the texture coordinates of the submesh
        static float meshTextureCoordinates[30000][2];
        int textureCoordinateCount;
        textureCoordinateCount = pCalRenderer->getTextureCoordinates(0, &meshTextureCoordinates[0][0]);

        // get the faces of the submesh
        static CalIndex meshFaces[50000][3];
        int faceCount;
        faceCount = pCalRenderer->getFaces(&meshFaces[0][0]);

        // set the vertex and normal buffers
        glVertexPointer(3, GL_FLOAT, 0, &meshVertices[0][0]);
        glNormalPointer(GL_FLOAT, 0, &meshNormals[0][0]);

        // set the texture coordinate buffer and state if necessary
        if((pCalRenderer->getMapCount() > 0) && (textureCoordinateCount > 0))
        {
          glEnable(GL_TEXTURE_2D);
          glEnableClientState(GL_TEXTURE_COORD_ARRAY);
          glEnable(GL_COLOR_MATERIAL);

		  long long id = (long long)pCalRenderer->getMapUserData(0);
          // set the texture id we stored in the map user data
          glBindTexture(GL_TEXTURE_2D, (GLuint)id);

          // set the texture coordinate buffer
          glTexCoordPointer(2, GL_FLOAT, 0, &meshTextureCoordinates[0][0]);
          glColor3f(1.0f, 1.0f, 1.0f);
        }

        // draw the submesh
        
        if(sizeof(CalIndex)==2)
			  glDrawElements(GL_TRIANGLES, faceCount * 3, GL_UNSIGNED_SHORT, &meshFaces[0][0]);
		  else
			  glDrawElements(GL_TRIANGLES, faceCount * 3, GL_UNSIGNED_INT, &meshFaces[0][0]);

        // disable the texture coordinate state if necessary
        if((pCalRenderer->getMapCount() > 0) && (textureCoordinateCount > 0))
        {
          glDisable(GL_COLOR_MATERIAL);
          glDisableClientState(GL_TEXTURE_COORD_ARRAY);
          glDisable(GL_TEXTURE_2D);
        }
      }
    }
  }

  // clear vertex array state
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);


  // end the rendering
  pCalRenderer->endRendering();
}



/***************************************************************************
*		HALF-LIFE MODEL ELEMENT
***************************************************************************/

/** @param pModel Jadro modele HalfLife mdl. */
HLModelElement::HLModelElement( CCoreHLmdl *pModel )
{
	m_pCoreModel = pModel;
	m_fFrame = 0.0f;
	m_iSequence = 0;
	m_fAnimTime = 0.0f;
	m_bPlaying = false;
	m_bLooping = false;
	m_iSkin = 0;
	m_iGroup = 0;
	m_iPart = 0;

	if( m_pCoreModel )
	{
		HLCORE(m_pCoreModel)->getStudioModel()//safe reference due to static creating of studiomodel object
			->SetSequence( m_iSequence );
		m_fFrameRate = HLCORE(m_pCoreModel)->getStudioModel()->GetFps();
	}
}

/** @param fFrame Cislo snimku, ktery se ma prehravat, pri vlozeni realneho cisla se provadi interpolace modelu pri vykreslovani. */
void HLModelElement::setFrame( float fFrame )
{
	m_fFrame = (float)fFrame;
	if( m_pCoreModel )
		HLCORE(m_pCoreModel)->getStudioModel()->SetFrame( m_fFrame );
}

/** @param iSequence Cislo sekvence, ktera se ma prehravat. 
*	@param loop true = opakovani sekvence, false = bez opakovani.
*/
void HLModelElement::playSequence( unsigned int iSequence, bool loop )
{
	m_iSequence = iSequence;
	if( m_pCoreModel )
	{
		HLCORE(m_pCoreModel)->getStudioModel()->SetSequence( m_iSequence );
		m_fFrameRate = HLCORE(m_pCoreModel)->getStudioModel()->GetFps();
		m_bLooping = loop;
		m_bPlaying = true;
		m_fFrame = 0.0f;
	}
}

void HLModelElement::setSkin( int skin )
{
	m_iSkin = skin;
}

void HLModelElement::getSequenceInfo( float &frameRate, float &groundSpeed )
{
	if( !m_pCoreModel )
		return;

	StudioModel *pModel = HLCORE(m_pCoreModel)->getStudioModel();
	if( pModel )
	{
		pModel->SetBodygroup( m_iGroup, m_iPart );
		pModel->SetSkin( m_iSkin );
		pModel->SetSequence( m_iSequence );	//sequence always first
		pModel->GetSequenceInfo( &frameRate, &groundSpeed );
	}
}

int HLModelElement::getGroupsCount()
{
	if( !m_pCoreModel || !HLCORE(m_pCoreModel)->getStudioModel() )
		return 0;

	return HLCORE(m_pCoreModel)->getStudioModel()->GetGroupsCount();
}

int HLModelElement::getGroupPartsCount( int group )
{
	if( !m_pCoreModel || !HLCORE(m_pCoreModel)->getStudioModel() )
		return 0;

	return HLCORE(m_pCoreModel)->getStudioModel()->GetGroupModelsCount( group );
}

int HLModelElement::getSkinsCount()
{
	if( !m_pCoreModel || !HLCORE(m_pCoreModel)->getStudioModel() )
		return 0;

	return HLCORE(m_pCoreModel)->getStudioModel()->GetSkinsCount();

}
void HLModelElement::setGroupAndPart( int group, int part )
{
	m_iGroup = group;
	m_iPart = part;
}




void HLModelElement::draw()
{
	if( m_pCoreModel )
	{
		StudioModel *pModel = HLCORE(m_pCoreModel)->getStudioModel();
		if( pModel )
		{
			//always set sequence and frame
			pModel->SetBodygroup( m_iGroup, m_iPart );
			pModel->SetSkin( m_iSkin );
			pModel->SetSequence( m_iSequence );	//sequence always first
			pModel->SetFrame( m_fFrame );
			glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT);

			glEnable( GL_TEXTURE_2D );
			pModel->DrawModel();
			glPopAttrib();

		}
	}
}

void HLModelElement::getBoundingBox( float *mins, float *maxs )
{
	if( m_pCoreModel )
	{
		StudioModel *pStudio = HLCORE(m_pCoreModel)->getStudioModel();
		if( pStudio )
			pStudio->ExtractBbox( mins, maxs );
	}
}


/** @param elapsetseconds Pocet sekund, ktere uplynuly od posledni aktualizace. */
void HLModelElement::update( float elapsetseconds )
{
	if( !m_bPlaying )
		return;
	
	if( m_pCoreModel )
	{
		StudioModel *pModel = HLCORE(m_pCoreModel)->getStudioModel();
		if( pModel )
		{
			pModel->SetBodygroup( m_iGroup, m_iPart );
			pModel->SetSkin( m_iSkin );
			pModel->SetSequence( m_iSequence );
			float addFrame = elapsetseconds * m_fFrameRate;	//pocet pridanych snimku
			float fNumFrames = (float)pModel->GetNumFrames();	//celkovy pocet snimku

		
			if( m_fFrame + addFrame > fNumFrames-1.0f )
			{
				
				if( m_bLooping )
					m_fFrame = m_fFrame - (fNumFrames-1) + addFrame;	//zmenseni poctu snimku o celou periodu
				else
				{
					m_bPlaying = false;
					return;
				}
			}
			else
				m_fFrame += addFrame;
			
		}
	}
}

/** @return true, pokud se sekvence prehrava, false, pokud ne. */
bool HLModelElement::isPlaying()
{
	return m_bPlaying;
}

/** @return Cislo aktualni sekvence. */
unsigned int HLModelElement::getCurrentSequence()
{
	return m_iSequence;
}


/****************************************************
*	MILKSHAPE MODEL ELEMENT
****************************************************/

/** @dlist_index Display list index modelu. */
MS3DModelElement::MS3DModelElement( unsigned int dlist_index, CMS3DCoreModel *pCore )
{
	m_pCoreModel = pCore;
	m_DListIndex = dlist_index;
}

void MS3DModelElement::draw()
{
	glPushMatrix();
	glRotatef(90.0f, 1.0f,0.0f,0.0f);	//prohozeni os Y a Z
	glPushAttrib( GL_POLYGON_BIT );
	glFrontFace( GL_CCW );
	glEnable( GL_CULL_FACE );
	glCullFace(GL_BACK);
	glCallList( m_DListIndex );
	glPopAttrib();
	glPopMatrix();
}

void MS3DModelElement::getBoundingBox( float *mins, float *maxs )
{
	if( m_pCoreModel )
	{
		MS3DCORE(m_pCoreModel)->getBoundingBox( mins, maxs );
	}
}


/***************************************************************************
*		ABSTRACT CORE MODEL CLASS
***************************************************************************/

/*	Funkce zkontroluje pocet uziti tohoto modelu
*	a pokud je rovna nule, tak smaze data modelu.
*/
void CAbstractCoreModel::checkUsage()
{
	if( m_uiShared_index == 0 )
		clearData();
}

/*!	Snizuje pocet pouziti tohoto modelu o jedna a jestlize hodnota klesne na nulu, tak pokud probehne kontrola uziti, dojde ke smazani dat modelu.
*/
void CAbstractCoreModel::unloadModel()
{
	if( m_uiShared_index > 0 )
		m_uiShared_index--;
}

std::string CAbstractCoreModel::getFileName()
{
	return m_sFilename;
}

/*! @param filename identifikacni retezec a cesta k souboru modelu.
*/
CAbstractCoreModel::CAbstractCoreModel( std::string filename )
{ 
	m_sFilename = filename;
	m_uiShared_index = 0; m_iType = -1;
}

/*! @return Typ modelu.
*/
int CAbstractCoreModel::getType()
{ 
	return m_iType;
}

/***************************************************************************
*		CORE MODEL CLASS
***************************************************************************/

/*! @param filename Cesta k souboru modelu. Je zaroveni i klicem tohoto modelu.
*/
CCal3DCoreModel::CCal3DCoreModel(std::string filename ) : CAbstractCoreModel( filename )
{
	m_uiNumAnimations = 0;
	m_pAnimations = NULL;
	m_calCoreModel = NULL;
	m_fRenderScale = 1.0f;
	m_iType = CAL3DMODEL;	//specifikace typu modelu
}

CCal3DCoreModel::~CCal3DCoreModel()
{
	clear();
}



void CCal3DCoreModel::clearData()
{
	if( m_pAnimations )
	{
		delete [] m_pAnimations;
		m_pAnimations = NULL;
	}

	if( m_calCoreModel )
	{
		//musim projit vsechny materialy a smazat alokovane textury
		int h = m_calCoreModel->getCoreMaterialCount();
		int materialId;
		for(materialId = 0; materialId < m_calCoreModel->getCoreMaterialCount(); materialId++)
		{
			// get the core material
			CalCoreMaterial *pCoreMaterial;
			pCoreMaterial = m_calCoreModel->getCoreMaterial(materialId);

			// loop through all maps of the core material
			int m = pCoreMaterial->getMapCount();
			int mapId;
			for(mapId = 0; mapId < pCoreMaterial->getMapCount(); mapId++)
			{
				long long id = (long long)pCoreMaterial->getMapUserData( mapId );

				if( id )
					glDeleteTextures( 1, (GLuint*)&id );

			}
		}

		delete m_calCoreModel;
		m_calCoreModel = NULL;
	}
}





/*!	Kontroluje se, zda je model nacteny. Pokud ne,
*	tak se vytvori novy s tim, ze se museji data nacist ze souboru.
*	a nakonec se novemu modelu nastavi modifikator velikosti vykreslovani.
*	@return Novy model.
*/
Cal3DModelElement *CCal3DCoreModel::applyModel()
{
	//pokud neni objekt vytvoren, tak se musi vygenerovat
	if( !m_calCoreModel && !create() )
	{
		delete m_calCoreModel;
		m_calCoreModel = NULL;
	}

	Cal3DModelElement *newOne = NULL;

	//ale i potom je nutne ho zkontrolovat
	if( m_calCoreModel )
	{
		m_uiShared_index++;

		newOne = new Cal3DModelElement( this, &m_uiNumAnimations, m_pAnimations );
		if( newOne )
			newOne->setRenderScale( m_fRenderScale );
	}
	return newOne;
}


/* CODE FROM CAL3D EXAMPLE */
bool CCal3DCoreModel::create()
{
	m_calCoreModel = new CalCoreModel("dummy");

	if( !m_calCoreModel )
	{
		PRINT_ERROR( "Error: Memory allocation failed '" << m_sFilename << "'." );
		return false;
	}
	std::string sFilename = MODEL_DIRECTORY + m_sFilename;
	std::ifstream file;
	file.open( sFilename.c_str(), std::ios::in | std::ios::binary );
	if(!file)
	{
		PRINT_ERROR( "Failed to open model configuration file '" << m_sFilename << "'." );
		return false;
	}

	

	// initialize the data path
	std::string strPath;

	// initialize the animation count
	m_uiNumAnimations = 0;
	queue<int> animationQueue;

	// parse all lines from the model configuration file
	int line;
	for(line = 1; ; line++)
	{
		// read the next model configuration line
		std::string strBuffer;
		std::getline(file, strBuffer);

		// stop if we reached the end of file
		if(file.eof())
			break;

		// check if an error happend while reading from the file
		if(!file)
		{
			PRINT_ERROR( "Error while reading from the model configuration file '" << m_sFilename << "'." );
			file.close();
			return false;
		}

		// find the first non-whitespace character
		std::string::size_type pos;
		pos = strBuffer.find_first_not_of(" \t");

		// check for empty lines
		if((pos == std::string::npos) || (strBuffer[pos] == '\n') || (strBuffer[pos] == '\r') || (strBuffer[pos] == 0)) 
			continue;

		// check for comment lines
		if(strBuffer[pos] == '#') 
			continue;

		// get the key
		std::string strKey;
		strKey = strBuffer.substr(pos, strBuffer.find_first_of(" =\t\n\r", pos) - pos);
		pos += strKey.size();

		// get the '=' character
		pos = strBuffer.find_first_not_of(" \t", pos);
		if((pos == std::string::npos) || (strBuffer[pos] != '='))
		{
		  PRINT_ERROR( m_sFilename << "(" << line << "): Invalid syntax." );
		}

		// find the first non-whitespace character after the '=' character
		pos = strBuffer.find_first_not_of(" \t", pos + 1);

		// get the data
		std::string strData;
		strData = strBuffer.substr(pos, strBuffer.find_first_of("\n\r", pos) - pos);

		// handle the model creation
		if(strKey == "scale")
		{
		  // set rendering scale factor
		  m_fRenderScale = (float)atof(strData.c_str());
		}
		else if(strKey == "path")
		{
			//set the new path for the data files if one hasn't been set already
			strPath = MODEL_DIRECTORY + strData;
		}
		else if(strKey == "skeleton")
		{
			/*******************
			* load core SKELETON
			*******************/
		#ifdef _DEBUG
			std::cout << "Loading skeleton '" << strData << "'..." << std::endl;
		#endif
			if( !m_calCoreModel->loadCoreSkeleton(strPath + strData) )
			{
				PRINT_ERROR( "Error while loading skeleton " << strData << " for model " << m_sFilename );
				CalError::printLastError();
				file.close();
				return false;
			}
		}
		else if( strKey == "animation" )
		{
			/*******************
			* load core ANIMATION
			*******************/
		#ifdef _DEBUG
			std::cout << "Loading animation '" << strData << "'..." << std::endl;
		#endif
			int iAnimation = m_calCoreModel->loadCoreAnimation(strPath + strData);
		  
			if(iAnimation == -1)
			{
				PRINT_ERROR( "Error while loading animation " << strData << " for model " << m_sFilename );
				CalError::printLastError();
				file.close();
				return false;
			}
			animationQueue.push( iAnimation );
			m_uiNumAnimations++;
		}
		else if(strKey == "mesh")
		{
		  /*******************
			* load core MESH
			*******************/
		#ifdef _DEBUG
		  std::cout << "Loading mesh '" << strData << "'..." << std::endl;
		#endif
			if(m_calCoreModel->loadCoreMesh(strPath + strData) == -1)
			{
				PRINT_ERROR( "Error while loading mesh " << strData << " for model " << m_sFilename);
				CalError::printLastError();
				file.close();
				return false;
			}
		}
		else if(strKey == "material")
		{
			/*******************
			* load core MATERIAL
			*******************/
		#ifdef _DEBUG
		  std::cout << "Loading material '" << strData << "'..." << std::endl;
		#endif
			if(m_calCoreModel->loadCoreMaterial(strPath + strData) == -1)
			{
				PRINT_ERROR( "Error while loading material " << strData << " for model " << m_sFilename);
				CalError::printLastError();
				file.close();
				return false;
			}
		}
		else
		{
			PRINT_ERROR( m_sFilename << "(" << line << "): Invalid syntax." );
			file.close();
			return false;
		}
	}

	// explicitely close the file
	file.close();

	

	// load all textures and store the opengl texture id in the corresponding map in the material
	int h = m_calCoreModel->getCoreMaterialCount();
	int materialId;
	for(materialId = 0; materialId < m_calCoreModel->getCoreMaterialCount(); materialId++)
	{
		// get the core material
		CalCoreMaterial *pCoreMaterial;
		pCoreMaterial = m_calCoreModel->getCoreMaterial(materialId);

		// loop through all maps of the core material
		int m = pCoreMaterial->getMapCount();
		int mapId;
		for(mapId = 0; mapId < pCoreMaterial->getMapCount(); mapId++)
		{
			// get the filename of the texture
			std::string strFilename;
			strFilename = pCoreMaterial->getMapFilename(mapId);

			// load the texture from the file
			TextureElement texture;
			texture.image.setFilename( strPath + strFilename);
			if( textureLibrary.applyTexture( &texture ) )
			{
				// store the opengl texture id in the user data of the map
				long long textureID = texture.texture_id;
				pCoreMaterial->setMapUserData(mapId, (Cal::UserData)(textureID));
			}
		}
	}


  // make one material thread for each material
  // NOTE: this is not the right way to do it, but this viewer can't do the right
  // mapping without further information on the model etc.
  for(materialId = 0; materialId < m_calCoreModel->getCoreMaterialCount(); materialId++)
  {
    // create the a material thread
    m_calCoreModel->createCoreMaterialThread(materialId);

    // initialize the material thread
    m_calCoreModel->setCoreMaterialId(materialId, 0, materialId);
  }

  // Calculate Bounding Boxes

	m_calCoreModel->getCoreSkeleton()->calculateBoundingBoxes(m_calCoreModel);


	m_pAnimations = new int[m_uiNumAnimations];
	for( unsigned int i = 0; i < m_uiNumAnimations && !animationQueue.empty(); i++  )
	{
		m_pAnimations[i] = animationQueue.front();
		animationQueue.pop();
	}
	return true;
}



/****************************************************
*	MILKSHAPE 3D CORE MODEL
****************************************************/

#include "MilkshapeModel.h"

void CMS3DCoreModel::clearData()
{
	if( m_uiDListIndex )
	{
		glDeleteLists( m_uiDListIndex, 1 );
		m_uiDListIndex = 0;
	}

	for( vector<unsigned int>::iterator iter = m_Textures.begin(); iter != m_Textures.end(); iter++ )
		glDeleteTextures( 1, &(*iter) );
	m_Textures.clear();
}

/*! @param filename Identifikacni retezec a cesta k souboru modelu.
*/
CMS3DCoreModel::CMS3DCoreModel( std::string filename ) : CAbstractCoreModel( filename )
{
	m_iType = MS3DMODEL;
	m_uiDListIndex = 0;
}

/*!	@return Index OpenGL display listu.
*/
MS3DModelElement *CMS3DCoreModel::applyModel()
{
	if( m_uiDListIndex == 0 && !create() )
		return 0;

	m_uiShared_index++;
	return new MS3DModelElement( m_uiDListIndex, this );
}

CMS3DCoreModel::~CMS3DCoreModel()
{
	clear();
}

bool CMS3DCoreModel::create()
{
	MilkshapeModel newModel;
	if( newModel.loadModelData( (MODEL_DIRECTORY + m_sFilename).c_str() ) )
	{
		newModel.getTextureIDs( m_Textures );
		m_uiDListIndex = glGenLists( 1);
		if( m_uiDListIndex )
		{
			mins[0] = newModel.mins[0];
			mins[1] = newModel.mins[2];
			mins[2] = newModel.mins[1];

			maxs[0] = newModel.maxs[0];
			maxs[1] = newModel.maxs[2];
			maxs[2] = newModel.maxs[1];


			glNewList( m_uiDListIndex, GL_COMPILE );
				newModel.draw();
			glEndList();
			return true;
		}


	}
	return false;
}

void CMS3DCoreModel::getBoundingBox( float *mins, float *maxs )
{
	memcpy( mins, this->mins, sizeof( this->mins ) );
	memcpy( maxs, this->maxs, sizeof( this->maxs ) );
}

/****************************************************
*	HALF-LIFE MDL CORE MODEL
****************************************************/

void CCoreHLmdl::clearData()
{
	if( m_bReady )
		m_Model.FreeModel();
	m_bReady = false;

}

/*! @param filename Identifikacni retezec a cesta k souboru modelu.
*/
CCoreHLmdl::CCoreHLmdl( std::string filename ) : CAbstractCoreModel( filename )
{
	m_iType = HLMDL;
	m_bReady = false;
	m_Model.Init();
}

/*!	@return HLModel Objekt ovladani modelu.
*/
HLModelElement *CCoreHLmdl::applyModel()
{
	if( !m_bReady && !create() )
		return 0;

	m_uiShared_index++;
	HLModelElement *newModel = new HLModelElement( this );
	return newModel;
}

CCoreHLmdl::~CCoreHLmdl()
{
	clear();
}

bool CCoreHLmdl::create()
{
	m_Model.FreeModel ();
	if (m_Model.LoadModel ( (MODEL_DIRECTORY + m_sFilename).c_str() ) )
	{
		if (m_Model.PostLoadModel ( (MODEL_DIRECTORY + m_sFilename).c_str() ) )
		{
			m_Model.SetSequence( 0 );
			m_Model.SetFrame( 0 );
			
			m_bReady = true;
			return true;
		}
	}
	m_bReady = false;
	return false;
}

/** @return Ukazatel na vlastni data modelu. */
StudioModel *CCoreHLmdl::getStudioModel()
{
	return &m_Model;
}



/****************************************************
*	MODEL LIBRARY CLASS
****************************************************/

/*!	@param filename Klic modelu, ktery chci pouzivat.
*	@return Novy model pripraveny k pouziti.
*/
Cal3DModelElement *ModelsLibrary::applyCal3DModel( std::string filename )
{
	CAbstractCoreModel *tmpModel = getCoreModel( filename );
	if( !tmpModel )	//pokud takovy soubor neni v databaze, tak musime vytvorit nove jadro modelu
	{
		tmpModel = new CCal3DCoreModel( filename );
		std::pair< modelsmap_t::iterator, bool> result;
		result = m_ModelsMap.insert( make_pair( filename, tmpModel ) );
		if( !result.second )
		{
			delete tmpModel;
			return NULL;
		}
	}

	if( tmpModel->getType() != CAL3DMODEL )
		return NULL;

	CCal3DCoreModel *pCal3DCoreModel = reinterpret_cast<CCal3DCoreModel*>( tmpModel );
	return pCal3DCoreModel->applyModel();
}

CAbstractCoreModel *ModelsLibrary::getCoreModel( std::string filename )
{
	modelsmap_t::iterator iter = m_ModelsMap.find( filename );
	if( iter != m_ModelsMap.end() )
		return iter->second;
	return NULL;
}

/*! @param filename Klic modelu, ktery se uz nechci pouzivat z pameti.
*/
void ModelsLibrary::unloadModel( std::string filename )
{
	CAbstractCoreModel *pModel = getCoreModel( filename );
	if( pModel )
		pModel->unloadModel();
}


void ModelsLibrary::checkModelsUsage()
{
	for( modelsmap_t::iterator iter = m_ModelsMap.begin(); iter != m_ModelsMap.end(); iter++ )
		iter->second->checkUsage();
}

void ModelsLibrary::destroy()
{
	for( modelsmap_t::iterator iter = m_ModelsMap.begin(); iter != m_ModelsMap.end(); iter++ )
		delete iter->second;
	m_ModelsMap.clear();
}

void ModelsLibrary::unloadAndFreeModelElement( ModelElement *&element )
{
	CAbstractCoreModel *pModel = getCoreModel( element->getFileName() );
	if( pModel )
		pModel->unloadModel();

	freeModelElement( element );
}




/*! @param element ModelElement, ktery se ma smazat.
*/
void ModelsLibrary::freeModelElement( ModelElement *&element )
{
	if( element )
	{
		delete element;
		element = NULL;
	}
}

/*! @param filename Soubor modelu ms3d.
*	@return Index OpenGL display listu.
*/
MS3DModelElement *ModelsLibrary::applyStaticMS3DModel( std::string filename )
{
	CAbstractCoreModel *tmpModel = getCoreModel( filename );
	if( !tmpModel )	//pokud takovy soubor neni v databaze, tak musime vytvorit nove jadro modelu
	{
		tmpModel = new CMS3DCoreModel( filename );
		std::pair< modelsmap_t::iterator, bool> result;
		result = m_ModelsMap.insert( make_pair( filename, tmpModel ) );
		if( !result.second )
		{
			delete tmpModel;
			return 0;
		}
	}

	if( tmpModel->getType() != MS3DMODEL )
	{
		delete tmpModel;
		return NULL;
	}

	CMS3DCoreModel *pMS3DCoreModel = reinterpret_cast<CMS3DCoreModel*>( tmpModel );
	return pMS3DCoreModel->applyModel();
}

/** @param filename Cesta k souboru modelu.
*	@return Novy model element.
*/
HLModelElement *ModelsLibrary::applyHalfLifeMDL( std::string filename )
{
	CAbstractCoreModel *tmpModel = getCoreModel( filename );
	if( !tmpModel )	//pokud takovy soubor neni v databaze, tak musime vytvorit nove jadro modelu
	{
		tmpModel = new CCoreHLmdl( filename );
		std::pair< modelsmap_t::iterator, bool> result;
		result = m_ModelsMap.insert( make_pair( filename, tmpModel ) );
		if( !result.second )
		{
			delete tmpModel;
			return NULL;
		}
	}

	if( tmpModel->getType() != HLMDL )
	{
		delete tmpModel;
		return NULL;
	}

	CCoreHLmdl *pHLCoreModel = reinterpret_cast<CCoreHLmdl*>( tmpModel );
	return pHLCoreModel->applyModel();
}

