//----------------------------------------------------------------------------
//	file: mesh.cpp
//	desc: implementation of gem_Mesh methods
//----------------------------------------------------------------------------
#include "directgem.h"
#include "gemmath.h"
#include "enum.h"
#include "loader.h"

//constructor initialize all members
gem_Mesh::gem_Mesh( gem_Scene* scene ) : gem_Object( scene )
{
	dwVertexCount = 0;	

	pMeshVertexTab = NULL;
	pTransformVertexTab = NULL;	
		
	bHide = FALSE;	

	mtxTransform = IdentMtx();
}

//deleting vertex tables
gem_Mesh::~gem_Mesh()
{
	delete pMeshVertexTab;
	delete pTransformVertexTab;
}


//render a object
VOID gem_Mesh::Render()
{
	LPDIRECT3DDEVICE7		lpd3dDevice = pScene->lpd3dDevice;
	gem_Group*				group;
	DWORD					flags = pScene->dwFlags;
	D3DMATRIX				mat = (D3DMATRIX)IdentMtx();

	if( bHide )
		return;

	if( !(flags&GEMFLAGS_SOFTWAREGEOMETRY) )	
	{
		mat = (D3DMATRIX) mtxTransform;		
	}

	lpd3dDevice->SetTransform( D3DTRANSFORMSTATE_WORLD, &mat );

	for( int i = 0 ; group=GroupList.GetItem(i) ; i++ )
	{
		if( group->pMaterial )
			group->pMaterial->SetMaterial( lpd3dDevice );

		lpd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, D3DFVF_VERTEX,
		 								   pTransformVertexTab, dwVertexCount,
										   group->pIndices, group->dwIndicesCount, 0);
	}
	
}

DWORD gem_Mesh::Type()
{
	return GEMOBJECTTYPE_MESH;
}

//nothing to init, all mebers ar initialized by constructor
VOID gem_Mesh::Init()
{
}

//pretransformation, pivot translation and loocal coordinatin system matrix
VOID gem_Mesh::PreTransform( gem_Matrix mtx )
{
	for( DWORD i = 0 ;  i<dwVertexCount ; i++ )
	{
		pMeshVertexTab[i] = pMeshVertexTab[i]*mtx;
	}
}


VOID gem_Mesh::Morph( gem_MorphInfo& info )
{

	for( DWORD i = 0 ; i<dwVertexCount ; i++ )
	{
		pTransformVertexTab[i] = vLERP( info.MorphSource->pMeshVertexTab[i],
										info.MorphTarget->pMeshVertexTab[i],	
										info.MorphFactor );

		pTransformVertexTab[i].tu = pMeshVertexTab[i].tu;
		pTransformVertexTab[i].tv = pMeshVertexTab[i].tv;
	}

}


DWORD gem_Mesh::ReadVerticesList( FILE *inFile )
{
	DWORD				size;
	DWORD				pos;	
	FLOAT				x, y, z;

	size = ReadDword( inFile ) - IDLENGTH;
	pos = ftell( inFile );
	dwVertexCount = ReadWord( inFile );
	
	pMeshVertexTab = new D3DVERTEX[dwVertexCount];	
	pTransformVertexTab = new D3DVERTEX[dwVertexCount];

	for( DWORD i = 0 ; ( !feof( inFile ) ) && i<dwVertexCount ; i++ )
	{
		x = ReadFloat( inFile );
		y = ReadFloat( inFile );
		z = ReadFloat( inFile );
		pMeshVertexTab[i].x = x;
		pMeshVertexTab[i].y = z;	//swap vector
		pMeshVertexTab[i].z = y;
	 }


	fseek( inFile , pos+size, SEEK_SET );

	if ( feof( inFile ) )
		return GEMERROR_LOAD;

	return GEMERROR_OK;
}


DWORD gem_Mesh::ReadFacesList( FILE* inFile )
{
	struct Face
	{
		DWORD		indices[3];
		BOOL		used;
	} *faces;

	LONG				size;
	LONG				pos;
	WORD				number;
	WORD				a, b, c;
	WORD				sign;
	TCHAR				matname[80];	
	WORD				usednbr;
	WORD				allnbr;
	DWORD				i;

	gem_Group*			faceGroup;
		
	size = ReadDword( inFile ) - IDLENGTH;
	pos = ftell( inFile );
	number= ReadWord( inFile );

	faces = new Face[number];
			

	for( i = 0 ; ( !feof( inFile ) ) && i<number ; i++ )
	{
		a = ReadWord( inFile );
		b = ReadWord( inFile );
		c = ReadWord( inFile );

		faces[i].indices[0] = a;
		faces[i].indices[1] = c;
		faces[i].indices[2] = b;	
		faces[i].used = FALSE; //just a additional info used when mesh is divided
							   //into group	

		ReadWord( inFile );
	}

	if( feof( inFile ) )
	{
		delete faces;
		return GEMERROR_LOAD;
	}

	//vertex normal calculation comes here
	gem_Vector			normal;
	gem_Vector			v1;
	gem_Vector			v2;
	
	for( i = 0 ; i<dwVertexCount ; i++ )
	{
		normal = gem_Vector( 0, 0, 0 );

		for( DWORD j = 0 ; j<number ; j++ )		
			if( i == faces[j].indices[0] || i == faces[j].indices[1] || i == faces[j].indices[2] )
			{				
				v1.x = pMeshVertexTab[faces[j].indices[1]].x -
					   pMeshVertexTab[faces[j].indices[0]].x;
				v1.y = pMeshVertexTab[faces[j].indices[1]].y -
					   pMeshVertexTab[faces[j].indices[0]].y;
				v1.z = pMeshVertexTab[faces[j].indices[1]].z - 
					   pMeshVertexTab[faces[j].indices[0]].z;

				v2.x = pMeshVertexTab[faces[j].indices[2]].x -
					   pMeshVertexTab[faces[j].indices[0]].x;
				v2.y = pMeshVertexTab[faces[j].indices[2]].y -
					   pMeshVertexTab[faces[j].indices[0]].y;
				v2.z = pMeshVertexTab[faces[j].indices[2]].z -
					   pMeshVertexTab[faces[j].indices[0]].z;

				normal = normal + Cross( v1, v2 );
			}

		normal = Normalize( normal );

		pMeshVertexTab[i].nx = normal.x;
		pMeshVertexTab[i].ny = normal.z;
		pMeshVertexTab[i].nz = normal.y;
	}


	//group creation comes here, group is a subset of mesh faces with the same
	//material
	allnbr = number;
	usednbr = 0;

	while( ( !feof( inFile ) ) && ftell( inFile )<size+pos )
	{
		sign = ReadWord( inFile );

		if( sign==GEMCHUNK_FACEMATERIAL )
		{
			faceGroup = new gem_Group;
			ReadDword( inFile );
			ReadASCIIZ( inFile, matname );
			
			faceGroup->pMaterial = pScene->FindMaterial( matname );

			number = ReadWord( inFile );
			faceGroup->dwIndicesCount = number*3;
			faceGroup->pIndices = new WORD[number*3];
			
			usednbr += number;			

			for( i = 0 ; i<number ; i++ )
			{
				a = ReadWord( inFile );
				
				faceGroup->pIndices[i*3]   = faces[a].indices[0];
				faceGroup->pIndices[i*3+1] = faces[a].indices[1];
				faceGroup->pIndices[i*3+2] = faces[a].indices[2];

				faces[a].used = TRUE;
			}

			GroupList.AddItem( faceGroup );
		}
		else
			if( JumpThroughtChunk( inFile )!=GEMERROR_OK )
			{
				delete faces;
				return GEMERROR_LOAD;
			}
	}
	
	if( allnbr > usednbr )
	{
		faceGroup = new gem_Group;

		faceGroup->dwIndicesCount = 3*(allnbr - usednbr);
		faceGroup->pIndices = new WORD[faceGroup->dwIndicesCount];
		faceGroup->pMaterial = NULL;

		DWORD index = 0;

		for( i = 0; i<allnbr; i++ )
		{
			if( !faces[i].used )
			{
				faceGroup->pIndices[index++] = faces[i].indices[0];
				faceGroup->pIndices[index++] = faces[i].indices[1];
				faceGroup->pIndices[index++] = faces[i].indices[2];
			}
		}

		GroupList.AddItem( faceGroup );
	}

	delete faces;

	if( feof( inFile ) )
		return GEMERROR_LOAD;

	return GEMERROR_OK;
}



DWORD gem_Mesh::ReadMappingList( FILE* inFile )
{
	LONG				size;
	LONG				pos;
	WORD				number;
	FLOAT				u, v;

	size = ReadDword( inFile ) - IDLENGTH;
	pos = ftell( inFile );
	number = ReadWord( inFile );

	for( DWORD i = 0 ; ( !feof( inFile ) ) && i<number ; i++ )
	{
		u = ReadFloat( inFile );
		v = ReadFloat( inFile );

		pMeshVertexTab[i].tu = u;
		pMeshVertexTab[i].tv = v;
	}

	fseek( inFile, pos+size, SEEK_SET );

	if( feof( inFile ) )
		return GEMERROR_LOAD;

	return GEMERROR_OK;
}


/*
 *  odczytuje lokalny uklad wspolzednych i przeksztalca wspolzedne
 *  wierzcholkow obiektu tak aby byly w nim wyrazone (translacja i odpowiedni
 *  obrot poniewaz poczatkowe wspolzedne obiektu sa zapisane wzgledem
 *  globalego ukladu swiata)
 */
DWORD gem_Mesh::ReadLocalCoords( FILE* inFile )
{
	DWORD				size;
	DWORD				pos;
	FLOAT				a,b,c;

	gem_Matrix			mtx=IdentMtx();

	size = ReadDword( inFile ) - IDLENGTH;
	pos = ftell( inFile );


	for( DWORD i = 1 ; i<=3 ; i++ )
		for( DWORD j = 1 ; j<=3 ; j++ )
			mtx(j,i) = ReadFloat( inFile );

	SwapMatrix( mtx );

	a = ReadFloat( inFile );
	b = ReadFloat( inFile );
	c = ReadFloat( inFile );

	mtx = TranslationMtx( gem_Vector( -a, -c, -b ) )*mtx;
	
	PreTransform( mtx );

	fseek( inFile, pos+size, SEEK_SET );

	if( feof( inFile ) )
		return GEMERROR_LOAD;

	return GEMERROR_OK;
}


/*
 *  odczytuje caly chunk obiektu
 */
DWORD gem_Mesh::Load( FILE* inFile )
{
	LONG				size;
	LONG				pos;
	WORD				sign;	
	
	size = ReadDword( inFile ) - IDLENGTH;
	pos = ftell( inFile );


	while( ( !feof( inFile ) ) && ftell( inFile )<size+pos )
	{
		sign = ReadWord( inFile );
		switch( sign )
		{
			case GEMCHUNK_VERTICES:
				if( ReadVerticesList( inFile )!=GEMERROR_OK )
					return GEMERROR_LOAD;
			break;

			case GEMCHUNK_FACES:
				if( ReadFacesList( inFile )!=GEMERROR_OK )
					return GEMERROR_LOAD;
			break;

			case GEMCHUNK_MAPPINGCOORDS:
				if( ReadMappingList( inFile )!=GEMERROR_OK )
					return GEMERROR_LOAD;
			break;

			case GEMCHUNK_LOCALCOORDS:
				if( ReadLocalCoords( inFile )!=GEMERROR_OK )
					return GEMERROR_LOAD;
			break;

			default:
				if( JumpThroughtChunk( inFile )!=GEMERROR_OK )
					return GEMERROR_LOAD;
			break;

		}
	}

	if( feof( inFile ) )
		return GEMERROR_LOAD;

	return GEMERROR_OK;
}

VOID gem_Mesh::SetMaterial( gem_Material* mat )
{
	gem_Group*		group;

	for( int i = 0 ; group=GroupList.GetItem(i) ; i++ )
	{
		if( group->pMaterial )
			delete group->pMaterial;

		group->pMaterial = mat;
	}
}



