// Filename:-	glm_code.cpp
//

#include "stdafx.h"
#include "includes.h"
#include "special_defines.h"
//
#include "anims.h"
#include "R_Model.h"
#include "mdx_format.h"
#include "sequence.h"
#include "parser.h"
#include "shader.h"
#include "skins.h"
#include "textures.h"	// for some stats stuff
//
#include "glm_code.h"



void *gpvDefaultGLA = NULL;
// this code is called before the rest of the app has even started, so do NOT try any other function calls!!!!...
//
static void GLMModel_BuildDefaultGLA()
{
	if (!gpvDefaultGLA)
	{
		gpvDefaultGLA = malloc(1024*1024);	// well OTT, 1MB is over 360 bytes, but WTF, it gets resized later
		byte *at = (byte*) gpvDefaultGLA;	// 'at' so I can paste code from another app

		// now create GLA file...
		//
		mdxaHeader_t *pMDXAHeader = (mdxaHeader_t *) at;
		at += sizeof(mdxaHeader_t);
		{// for brace-skipping...			

					pMDXAHeader->ident			=	MDXA_IDENT;
					pMDXAHeader->version		=	MDXA_VERSION;
			strncpy(pMDXAHeader->name,"*default",sizeof(pMDXAHeader->name));
					pMDXAHeader->name[sizeof(pMDXAHeader->name)-1]='\0';
					pMDXAHeader->fScale = 1.0f;
					pMDXAHeader->numFrames		=	1;	// inherently, when doing MD3 to G2 files
	//				pMDXAHeader->ofsFrames		=	not known yet
					pMDXAHeader->numBones		=	1;	// inherently, when doing MD3 to G2 files
	//				pMDXAHeader->ofsCompBonePool=	not known yet
	//				pMDXAHeader->ofsSkel		=	not known yet
	//				pMDXAHeader->ofsEnd			=	not known yet

			// write out bone hierarchy...
			//
			mdxaSkelOffsets_t * pSkelOffsets = (mdxaSkelOffsets_t *) at;
			at += (int)( &((mdxaSkelOffsets_t *)0)->offsets[ pMDXAHeader->numBones ] );
			
			pMDXAHeader->ofsSkel = at - (byte *) pMDXAHeader; 
			for (int iSkelIndex = 0; iSkelIndex < pMDXAHeader->numBones; iSkelIndex++)
			{
				mdxaSkel_t *pSkel = (mdxaSkel_t *) at;

				pSkelOffsets->offsets[iSkelIndex] = (byte *) pSkel - (byte *) pSkelOffsets;

				// setup flags...
				//
						pSkel->flags = 0;
				strcpy(	pSkel->name, "ModView internal default");	// doesn't matter what this is called
						pSkel->parent= -1;	// index of bone that is parent to this one, -1 = NULL/root

				static const mdxaBone_t IdentityBone = 
				{				
					1.0f, 0.0f, 0.0f, 0.0f,
					0.0f, 1.0f, 0.0f, 0.0f,
					0.0f, 0.0f, 1.0f, 0.0f
				};
				
				// base and inverse of an identity matrix are the same, so...
				//
				memcpy(pSkel->BasePoseMat.matrix,	IdentityBone.matrix, sizeof(pSkel->BasePoseMat.matrix));
				memcpy(pSkel->BasePoseMatInv.matrix,IdentityBone.matrix, sizeof(pSkel->BasePoseMatInv.matrix) );

						pSkel->numChildren	=	0;	// inherently, when doing MD3 to G2 files

				int iThisSkelSize = (int)( &((mdxaSkel_t *)0)->children[ pSkel->numChildren ] );

				at += iThisSkelSize;
			}

			// write out frames...
			//			
			pMDXAHeader->ofsFrames = at - (byte *) pMDXAHeader;			
//			int iFrameSize = (int)( &((mdxaFrame_t *)0)->boneIndexes[ pMDXAHeader->numBones ] );
			byte *pIndexes = (byte*) at;

			int iTotalBonePoolIndexBytes = pMDXAHeader->numFrames * pMDXAHeader->numBones * 3;

			while (iTotalBonePoolIndexBytes & 3) iTotalBonePoolIndexBytes++;	// align-padding
			at += iTotalBonePoolIndexBytes;

			for (int i=0; i<pMDXAHeader->numFrames; i++)
			{
				mdxaIndex_t *pIndex = (mdxaIndex_t *) &pIndexes[ (i*pMDXAHeader->numBones*3) + (0*3) ];
				pIndex->iIndex = 0;	// inherently, when doing MD3 to G2 files
			}

			// now write out compressed bone pool...
			//
			pMDXAHeader->ofsCompBonePool = at - (byte *) pMDXAHeader;
			for (int iCompBoneIndex = 0; iCompBoneIndex < 1/*CompressedBones.size()*/; iCompBoneIndex++)
			{
				mdxaCompQuatBone_t *pCompBone = (mdxaCompQuatBone_t *) at;
				at += sizeof(mdxaCompQuatBone_t);

				// a binary dump of a compressed matrix <g>...
				//
				static const byte Comp[sizeof(mdxaCompQuatBone_t)]=
				{
					0xFD, 0xBF, 0xFE, 0x7F,
					0xFE, 0x7F, 0xFE, 0x7F,
					0x00, 0x80, 0x00, 0x80,
					0x00, 0x80,
				};

				memcpy(pCompBone->Comp,Comp,sizeof(Comp));
			}
			
			// done...
			//
			pMDXAHeader->ofsEnd = at - (byte *) pMDXAHeader;		
		}

		realloc(gpvDefaultGLA, pMDXAHeader->ofsEnd);

/*		FILE *fhHandle = fopen("c:\\default.gla","wb");
		if (fhHandle)
		{
			fwrite(gpvDefaultGLA, 1, pMDXAHeader->ofsEnd, fhHandle);
			fclose(fhHandle);
		}
*/
	}
}

static void GLMModel_DestroyDefaultGLA()
{
	SAFEFREE(gpvDefaultGLA);
}

// this is just evil for the sake of it... :-)
//
struct tacky_s
{
	tacky_s()
	{
		GLMModel_BuildDefaultGLA();
	}
	~tacky_s()
	{
		GLMModel_DestroyDefaultGLA();
	}
};
tacky_s tackyness;

void* GLMModel_GetDefaultGLA(void)
{
	assert(gpvDefaultGLA);
	return gpvDefaultGLA;
}


// extern-called by main Model_Delete() code...
//
void GLMModel_DeleteExtra(void)
{
//	SAFEFREE(pvLoadedGLA);
//	pMDXMHeader = NULL;	// do NOT free this, dup ptr only
}

LPCSTR GLMModel_GetSurfaceName( ModelHandle_t hModel, int iSurfaceIndex )
{
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);

	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));

	assert( iSurfaceIndex < pMDXMHeader->numSurfaces );
	if (	iSurfaceIndex < pMDXMHeader->numSurfaces )
	{
		mdxmSurfHierarchy_t *pSurfHierarchy	= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceIndex]);
		return pSurfHierarchy->name;
	}

	return "GLMModel_GetSurfaceName(): Bad surface index";
}


LPCSTR GLMModel_GetSurfaceShaderName( ModelHandle_t hModel, int iSurfaceIndex )
{
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);

	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));

	assert( iSurfaceIndex < pMDXMHeader->numSurfaces );
	if (	iSurfaceIndex < pMDXMHeader->numSurfaces )
	{
		mdxmSurfHierarchy_t *pSurfHierarchy	= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceIndex]);
		return pSurfHierarchy->shader;
	}

	assert(0);
	return "GLMModel_GetSurfaceShaderName(): Bad surface index";
}


// interesting use of static here, this function IS called externally, but only through a ptr. 
//	This is to stop people accessing it directly.
//
// return basic info on the supplied model arg...
//
static LPCSTR GLMModel_GetBoneName( ModelHandle_t hModel, int iBoneIndex )
{
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	assert( iBoneIndex < pMDXAHeader->numBones);
	if (	iBoneIndex < pMDXAHeader->numBones)
	{
		mdxaSkelOffsets_t *pSkelOffsets = (mdxaSkelOffsets_t *) ((byte *)pMDXAHeader + sizeof(*pMDXAHeader));
		mdxaSkel_t *pSkelEntry = (mdxaSkel_t *) ((byte *) pSkelOffsets + pSkelOffsets->offsets[ iBoneIndex ] );

		return pSkelEntry->name;
	}

	return "GLMModel_GetBoneName(): Bad bone index";
}

LPCSTR GLMModel_BoneInfo( ModelHandle_t hModel, int iBoneIndex )
{
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	assert( iBoneIndex < pMDXAHeader->numBones);
	if (	iBoneIndex < pMDXAHeader->numBones)
	{
		mdxaSkelOffsets_t *pSkelOffsets = (mdxaSkelOffsets_t *) ((byte *)pMDXAHeader + sizeof(*pMDXAHeader));
		mdxaSkel_t		  *pSkelEntry	= (mdxaSkel_t *) ((byte *) pSkelOffsets + pSkelOffsets->offsets[ iBoneIndex ] );

		static string str;

		str = va("Bone %d/%d:  %s\n\n", iBoneIndex, pMDXAHeader->numBones, pSkelEntry->name );
		str+= va("    ->parentIndex:\t%d\n",pSkelEntry->parent );
		str+= va("    ->numChildren:\t%d  ",pSkelEntry->numChildren );
		//
		// list children...
		//
		if (pSkelEntry->numChildren)
		{
			str += "( ";
			for (int i=0; i<pSkelEntry->numChildren; i++)
			{
				str += va("%s%d",!i?"":", ",pSkelEntry->children[i]);
			}
			str += " )";
		}
		str += "\n";

		// flags...
		//
		unsigned int iFlags = pSkelEntry->flags;
		str += va("    ->flags:\t0x%08X%s\n",iFlags,!iFlags?"":va("\t( Breakdown follows: )"));
		if (iFlags)
		{
			str += va("\t\t--------------------------------------------------\n");
			#define BONEFLAG(bit)										\
					if (iFlags & bit)									\
					{													\
						str += va("\t\t0x%08X:\t%s\n",bit,#bit);		\
						iFlags ^= bit;									\
					}

			BONEFLAG(G2BONEFLAG_ALWAYSXFORM);

			if (iFlags)
			{
				str += va("\t\t0x%08X:\tUNKNOWN FLAG(S)!!\n",iFlags);
			}
		}

		// list surfaces that use this bone...
		//
		str += "\nSurfaces using this bone:\n";
		// 2 passes, one to count, second to build string. count logic affects string display for infobox size reasons...
		//
		int iUsingCount=0;
		string strSurfacesUsing;
		for (int iPass=0; iPass<2; iPass++)	
		{
			strSurfacesUsing="";
			for (int iLOD = 0; iLOD < pMDXMHeader->numLODs; iLOD++)
			{
				string strSurfacesUsingItThisLOD;
				
				for (int iSurface = 0; iSurface < pMDXMHeader->numSurfaces; iSurface++)
				{
					if (GLMModel_SurfaceContainsBoneReference( hModel, iLOD, iSurface, iBoneIndex))
					{
						if (!iPass)
						{
							iUsingCount++;
						}
						else
						{
							LPCSTR psSurfaceName = GLMModel_GetSurfaceName( hModel, iSurface );
	
							if (iUsingCount>40)	// arb
							{
								strSurfacesUsingItThisLOD += va("%s,    ",psSurfaceName);
							}
							else
							{
								strSurfacesUsingItThisLOD += va("    %s\n",psSurfaceName);
							}
						}
					}
				}

				if (!strSurfacesUsingItThisLOD.empty())
				{
					strSurfacesUsing += va("LOD %d:\n",iLOD);
					strSurfacesUsing += strSurfacesUsingItThisLOD;
					strSurfacesUsing += "\n";
				}
			}
		}
		if (!strSurfacesUsing.empty())
		{
			str += strSurfacesUsing;
		}
		else
		{
			str += "<none>";
		}

		// anything else...
		//
		
		return str.c_str();
	}

	return "GLMModel_BoneInfo(): Bad bone index";
}


bool GLMModel_SurfaceContainsBoneReference(ModelHandle_t hModel, int iLODNumber, int iSurfaceNumber, int iBoneNumber)
{
	mdxmHeader_t			*pMDXMHeader		= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));
//	mdxmSurfHierarchy_t		*pSurfHierarchy		= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceNumber]);

//	if (iLODNumber >= pMDXMHeader->numLODs)
//		return false;	// can't reference it if we don't have a LOD of this level of course

	mdxmLOD_t *pLOD = (mdxmLOD_t *)((byte *)pMDXMHeader + pMDXMHeader->ofsLODs);
	for (int iLOD = 0; iLOD < iLODNumber && iLOD < pMDXMHeader->numLODs-1; iLOD++)
	{
		pLOD = (mdxmLOD_t *)((byte *)pLOD + pLOD->ofsEnd);
	}	

	mdxmLODSurfOffset_t *pLODSurfOffset = (mdxmLODSurfOffset_t *) &pLOD[1];
	mdxmSurface_t *pSurface = (mdxmSurface_t *) ((byte *) pLODSurfOffset +  pLODSurfOffset->offsets[iSurfaceNumber]);

	int *pBoneRefs = (int *) ( (byte *)pSurface + pSurface->ofsBoneReferences );
	for (int iBone = 0; iBone < pSurface->numBoneReferences; iBone++)
	{
		if (pBoneRefs[iBone] == iBoneNumber)
			return true;
	}

	return false;
}

bool GLMModel_SurfaceIsTag(ModelHandle_t hModel, int iSurfaceIndex )
{
	mdxmHeader_t			*pMDXMHeader		= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));

	assert( iSurfaceIndex < pMDXMHeader->numSurfaces );
	if (	iSurfaceIndex < pMDXMHeader->numSurfaces )
	{
		mdxmSurfHierarchy_t *pSurfHierarchy	= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceIndex]);
		return (pSurfHierarchy->flags & G2SURFACEFLAG_ISBOLT);
	}
	
	return false;
}


bool GLMModel_SurfaceIsON(ModelHandle_t hModel, int iSurfaceIndex )
{
	LPCSTR psSurfaceName = GLMModel_GetSurfaceName(hModel, iSurfaceIndex);

	ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );
	if (!pContainer)
	{
		ErrorBox(va("GLMModel_SurfaceIsON(): Illegal surface index %d!",iSurfaceIndex));
		return false;
	}

	SurfaceOnOff_t sOnOff = trap_G2_IsSurfaceOff (hModel, pContainer->slist, psSurfaceName);

	return (sOnOff == SURF_ON);
}


LPCSTR vtos(vec3_t v3)
{
	return va("%.3f %.3f %.3f",v3[0],v3[1],v3[2]);
}

static LPCSTR v2tos(vec2_t v2)
{
	return va("%.3f %.3f",v2[0],v2[1]);
}


// generate info suitable for sending to Notepad (can be a BIG string)...
//
LPCSTR GLMModel_SurfaceVertInfo( ModelHandle_t hModel, int iSurfaceIndex )
{
	static CString	str;
					str = "";

	mdxmHeader_t			*pMDXMHeader		= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));
	mdxmSurfHierarchy_t		*pSurfHierarchy		= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceIndex]);
	mdxmLOD_t				*pLOD				= (mdxmLOD_t *)		((byte *)pMDXMHeader + pMDXMHeader->ofsLODs);

	str += va("Detailed vert info for surface %d/%d (\"%s\") of model \"%s\":\n\n",
											  iSurfaceIndex,
												pMDXMHeader->numSurfaces,
														pSurfHierarchy->name,
																		pMDXMHeader->name);
													

	for (int iLOD = 0; iLOD < pMDXMHeader->numLODs; iLOD++)
	{
		mdxmLODSurfOffset_t *pLODSurfOffset = (mdxmLODSurfOffset_t *) &pLOD[1];
		mdxmSurface_t		*pSurface		= (mdxmSurface_t *) ((byte*)pLODSurfOffset +  pLODSurfOffset->offsets[iSurfaceIndex]);
		int *piBoneRefTable = (int*)((byte*)pSurface + pSurface->ofsBoneReferences);

		str+= va("LOD %d/%d:\n",iLOD,	pMDXMHeader->numLODs );
		str+= va("#Verts:\t%d\n",		pSurface->numVerts );

		mdxmVertex_t			*pVert			= (mdxmVertex_t *) ((byte *)pSurface + pSurface->ofsVerts);
		mdxmVertexTexCoord_t	*pVertTexCoords	= (mdxmVertexTexCoord_t	*) &pVert[pSurface->numVerts];
		for (int iVert=0; iVert < pSurface->numVerts; iVert++)
		{
			StatusMessage(va("LOD %d/%d, Dumping Vert %d/%d",iLOD,pMDXMHeader->numLODs,iVert,pSurface->numVerts));

			const int iNumWeights = G2_GetVertWeights( pVert );

			str += va("    Vert %d/%d:\n",iVert,pSurface->numVerts);
			str += va("    ->normal:        %s\n",vtos(pVert->normal));
			str += va("    ->vertCoords:    %s\n",vtos(pVert->vertCoords));
			str += va("    ->texCoords:     %s\n",v2tos(pVertTexCoords[iVert].texCoords));
			str += va("    ->numWeights:    %d\n",iNumWeights);
				
			float fTotalWeight = 0.0f;
			for (int iWeight=0; iWeight<iNumWeights; iWeight++)
			{
				int		iBoneIndex	= G2_GetVertBoneIndex( pVert, iWeight );
				float	fBoneWeight	= G2_GetVertBoneWeight( pVert, iWeight, fTotalWeight, iNumWeights );

				iBoneIndex = piBoneRefTable[iBoneIndex];	//!!!!!!!!

				str += va("                     Bone: %d ( \"%s\" ), Weight:%3f\n",iBoneIndex, GLMModel_GetBoneName( hModel, iBoneIndex ),fBoneWeight);
			}
			
			str += "\n";

			pVert++;// = (mdxmVertex_t *)&pVert->weights[/*pVert->numWeights*/pSurface->maxVertBoneWeights];
		}

		// new bit, check every vert against every other in this surface for co-existing points with differing texture coords...
		//
		StatusMessage(va("LOD %d/%d, Analysing duplicate verts ( temp / tinkering code )",iLOD,pMDXMHeader->numLODs));
		pVert = (mdxmVertex_t *) ((byte *)pSurface + pSurface->ofsVerts);
		for (iVert=0; iVert<pSurface->numVerts; iVert++)
		{				
			mdxmVertex_t *pOtherVert = (mdxmVertex_t *) ((byte *)pSurface + pSurface->ofsVerts);
			for (int iOtherVert=0; iOtherVert<iVert; iOtherVert++)
			{
				if (iOtherVert != iVert)
				{
#define VectorEqual(a,b)		(fabs((a)[0]-(b)[0])<0.001f && fabs((a)[1]-(b)[1])<0.001f && fabs((a)[2]-(b)[2])<0.001f)

					if (VectorEqual(pVert->vertCoords,pOtherVert->vertCoords)
						&&
						VectorEqual(pVert->normal,pOtherVert->normal)
						&&
						!VectorEqual(pVertTexCoords[iVert].texCoords, pVertTexCoords[iOtherVert].texCoords)
						)
					{
						str += va("Vert %d and %d have same coords\n",iVert,iOtherVert);
						str += "... and the same normal\n";
						str += "... but different texCoords\n";
					}
				}

				pOtherVert++;// = (mdxmVertex_t *)&pOtherVert->weights[/*pOtherVert->numWeights*/pSurface->maxVertBoneWeights];
			}

			pVert++;// = (mdxmVertex_t *)&pVert->weights[/*pVert->numWeights*/pSurface->maxVertBoneWeights];
		}

		// next LOD...
		//
		pLOD = (mdxmLOD_t *)((byte *)pLOD + pLOD->ofsEnd);
	}
	
	StatusMessage(NULL);
	return (LPCSTR) str;//str.c_str();
}



LPCSTR GLMModel_SurfaceInfo( ModelHandle_t hModel, int iSurfaceIndex, bool bShortVersionForTag )
{	
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
//	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));
	mdxmSurfHierarchy_t		*pSurfHierarchy		= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceIndex]);

	assert ( iSurfaceIndex < pMDXMHeader->numSurfaces );

	static string str;

	str = va("%sSurface %d/%d:  '%s'\n\n", (pSurfHierarchy->flags & G2SURFACEFLAG_ISBOLT)?"Tag-":"",iSurfaceIndex, pMDXMHeader->numSurfaces, pSurfHierarchy->name );

if (bShortVersionForTag)
{
}
else
{
	str+= va("    ->shader:\t%s\n",		pSurfHierarchy->shader );
}


	// flags...
	//
	unsigned int iFlags = pSurfHierarchy->flags;
	str += va("    ->flags:\t0x%08X%s\n",iFlags,!iFlags?"":va("\t( Breakdown follows: )"));
	if (iFlags)
	{
		str += va("\t\t--------------------------------------------------\n");
		#define SURFFLAG(bit)										\
				if (iFlags & bit)									\
				{													\
					str += va("\t\t0x%08X:\t%s\n",bit,#bit);		\
					iFlags ^= bit;									\
				}

		SURFFLAG(G2SURFACEFLAG_ISBOLT);
		SURFFLAG(G2SURFACEFLAG_OFF);

		if (iFlags)
		{
			str += va("\t\t0x%08X:\tUNKNOWN FLAG(S)!!\n",iFlags);
		}
	}




	str+= va("    ->parentIndex:\t%d\n",pSurfHierarchy->parentIndex );
	str+= va("    ->numChildren:\t%d  ",pSurfHierarchy->numChildren );
	//
	// list children...
	//
	if (pSurfHierarchy->numChildren)
	{
		str += "( ";
		for (int i=0; i<pSurfHierarchy->numChildren; i++)
		{
			str += va("%s%d",!i?"":", ",pSurfHierarchy->childIndexes[i]);
		}
		str += " )";
	}
	str += "\n";

	// anything else...
	//

	str+= "\n";
	mdxmLOD_t *pLOD = (mdxmLOD_t *)((byte *)pMDXMHeader + pMDXMHeader->ofsLODs);
	for (int iLOD = 0; iLOD < pMDXMHeader->numLODs; iLOD++)
	{
		mdxmLODSurfOffset_t *pLODSurfOffset = (mdxmLODSurfOffset_t *) &pLOD[1];
		mdxmSurface_t		*pSurface		= (mdxmSurface_t *) ((byte*)pLODSurfOffset +  pLODSurfOffset->offsets[iSurfaceIndex]);

		str+= va("    LOD %d/%d:\n",iLOD, pMDXMHeader->numLODs );
	if (bShortVersionForTag)	// may actually be useful to know these? oh well, for now... inhibit
	{
	}
	else
	{
		str+= va("        # Verts:\t%d\n",		pSurface->numVerts );
		str+= va("        # Tris:\t%d\n",		pSurface->numTriangles );
	}
		str+= va("        # BoneRefs:\t%d\n",	pSurface->numBoneReferences );
		//
		// list bone refs...
		//
		if (pSurface->numBoneReferences)
		{
			int *pBoneRefs = (int *) ( (byte *)pSurface + pSurface->ofsBoneReferences );

//			str += "( ";
			for (int i=0; i<pSurface->numBoneReferences; i++)
			{
//				str += va("%s%d",!i?"":", ",pBoneRefs[i]);
				str += va("                   \t( %3d )  \"%s\"\n",pBoneRefs[i],GLMModel_GetBoneName(hModel, pBoneRefs[i] ));
			}
//			str += " )";
		}
//		str+= "\n";

	if (bShortVersionForTag)	// may actually be useful to know these? oh well, for now... inhibit
	{
	}
	else
	{
//		str+= va("        # MaxVertBoneWeights:\t %d\n", pSurface->maxVertBoneWeights);
	}


		str+= "\n";

		pLOD = (mdxmLOD_t *)((byte *)pLOD + pLOD->ofsEnd);
	}

	return str.c_str();
}


// provides common functionality to save duping logic...
//
static LPCSTR GLMModel_CreateSurfaceName( LPCSTR psSurfaceName, bool bOnOff)
{
	static CString string;
	
	string = psSurfaceName;	// do NOT use in constructor form since this is a static (that got me first time... :-)

	if (!bOnOff)
	{
		string.Insert(0, "//////// ");
	}

	return (LPCSTR) string;
}


static bool R_GLM_AddSurfaceToTree( ModelHandle_t hModel, HTREEITEM htiParent, int iThisSurfaceIndex, mdxmHierarchyOffsets_t *pHierarchyOffsets, bool bTagsOnly)
{
	bool bReturn = true;

	mdxmSurfHierarchy_t *pSurfHierarchy_This = (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iThisSurfaceIndex]);


	// insert me...
	//
	TreeItemData_t	TreeItemData={0};
					TreeItemData.iItemType		= bTagsOnly ? TREEITEMTYPE_GLM_TAGSURFACE : TREEITEMTYPE_GLM_SURFACE;
					TreeItemData.iModelHandle	= hModel;
					TreeItemData.iItemNumber	= iThisSurfaceIndex;					

	HTREEITEM htiThis = NULL;
	if (!bTagsOnly || (pSurfHierarchy_This->flags & G2SURFACEFLAG_ISBOLT) )
	{
		htiThis = ModelTree_InsertItem(	GLMModel_CreateSurfaceName(pSurfHierarchy_This->name, true),	// LPCTSTR psName, 
										htiParent,			// HTREEITEM hParent
										TreeItemData.uiData,// TREEITEMTYPE_GLM_SURFACE | iThisSurfaceIndex	// UINT32 uiUserData
										bTagsOnly?TVI_SORT:TVI_LAST
										);
	}

	// insert my children...
	//
	for (int iChild = 0; iChild < pSurfHierarchy_This->numChildren; iChild++)
	{
		R_GLM_AddSurfaceToTree(	hModel, bTagsOnly?htiParent:htiThis, pSurfHierarchy_This->childIndexes[iChild], pHierarchyOffsets, bTagsOnly );
	}


	return bReturn;
}



static bool R_GLM_AddBoneToTree( ModelHandle_t hModel, HTREEITEM htiParent, int iThisBoneIndex, mdxaSkelOffsets_t* pSkelOffsets)
{
	bool bReturn = true;

	mdxaSkel_t *pSkeletonEntry_This = (mdxaSkel_t *) ((byte *) pSkelOffsets + pSkelOffsets->offsets[ iThisBoneIndex ] );

	
	// insert me...
	//
	TreeItemData_t	TreeItemData={0};
					TreeItemData.iItemType		= TREEITEMTYPE_GLM_BONE;
					TreeItemData.iModelHandle	= hModel;
					TreeItemData.iItemNumber	= iThisBoneIndex;

	HTREEITEM htiThis = ModelTree_InsertItem(	pSkeletonEntry_This->name,	// LPCTSTR psName, 
												htiParent,					// HTREEITEM hParent
												TreeItemData.uiData	//	TREEITEMTYPE_GLM_BONE | iThisBoneIndex	// UINT32 uiUserData
												);

	// insert my children...
	//
	for (int iChild = 0; iChild < pSkeletonEntry_This->numChildren; iChild++)
	{
		R_GLM_AddBoneToTree( hModel, htiThis, pSkeletonEntry_This->children[iChild], pSkelOffsets);
	}

	return bReturn;
}


// Note, this function is only really supposed to be called once, to setup the Container that owns this model
//
static int GLMModel_GetNumFrames( ModelHandle_t hModel )
{
	// I should really try-catch these, but for now...
	//
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	return pMDXAHeader->numFrames;
}

// Note, this function is only really supposed to be called once, to setup the Container that owns this model
//
static int GLMModel_GetNumBones( ModelHandle_t hModel )
{
	// I should really try-catch these, but for now...
	//
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	return pMDXAHeader->numBones;
}


// Note, this function is only really supposed to be called once, to setup the Container that owns this model
//
static int GLMModel_GetNumSurfaces( ModelHandle_t hModel )
{
	// I should really try-catch these, but for now...
	//
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);	

	return pMDXMHeader->numSurfaces;
}



// Note, this function is only really supposed to be called once, to setup the Container that owns this model
//
static int GLMModel_GetNumLODs( ModelHandle_t hModel )
{
	// I should really try-catch this, but for now...
	//
	mdxmHeader_t *pMDXMHeader = (mdxmHeader_t *) RE_GetModelData(hModel);
	return pMDXMHeader->numLODs;
}

// these next 2 functions are closely related, the GetCount function fills in public data which the other reads on query
//
set <string> stringSet;
static int GLMModel_GetUniqueShaderCount( ModelHandle_t hModel )
{	
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
//	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));

	stringSet.clear();
	
	for (int iSurfaceIndex = 0; iSurfaceIndex < pMDXMHeader->numSurfaces; iSurfaceIndex++)
	{
		mdxmSurfHierarchy_t	*pSurfHierarchy = (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceIndex]);

		string strShader(pSurfHierarchy->shader);

		stringSet.insert(stringSet.end(),strShader);
	}

	return stringSet.size();
}
static LPCSTR GLMModel_GetUniqueShader(int iShader)
{
	assert(iShader < stringSet.size());
	
	for (set <string>::iterator it = stringSet.begin(); it != stringSet.end(); ++it)
	{
		if (!iShader--)
			return (*it).c_str();
	}	

	return "(Error)";	// should never get here
}

// interesting use of static here, this function IS called externally, but only through a ptr. 
//	This is to stop people accessing it directly.
//
// return basic info on the supplied model arg...
//
static LPCSTR GLMModel_Info( ModelHandle_t hModel )
{
	// I should really try-catch these, but for now...
	//
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	static string str;

	ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle( hModel );

	str = va("Model: %s%s\n\n", Model_GetFilename(hModel), !pContainer?"":!pContainer->pBoneBolt_ParentContainer?(!pContainer->pSurfaceBolt_ParentContainer?"":va("   ( Bolted to parent model via surface bolt '%s' )",Model_GetBoltName(pContainer->pSurfaceBolt_ParentContainer,pContainer->iSurfaceBolt_ParentBoltIndex,false))):va("   ( Bolted to parent model via bone bolt '%s' )",Model_GetBoltName(pContainer->pBoneBolt_ParentContainer,pContainer->iBoneBolt_ParentBoltIndex,true)));
	str+= va("GLM Info:\t\t( FileSize: %d bytes )\n\n",pMDXMHeader->ofsEnd);
	str+= va("    ->ident :\t%X\n",		pMDXMHeader->ident );	// extra space before ':' here to push tab to next column
	str+= va("    ->version:\t%d\n",	pMDXMHeader->version );
	str+= va("    ->name:\t%s\n",		pMDXMHeader->name );
	str+= va("    ->animName:\t%s\n",	pMDXMHeader->animName );
	str+= va("    ->numBones:\t%d\n",	pMDXMHeader->numBones );
	str+= va("    ->numLODs:\t%d\n",	pMDXMHeader->numLODs );

	// work out what types of surfaces we have for extra info...
	//
	int iNumTagSurfaces=0;
	int iNumOFFSurfaces=0;
	for (int i=0; i<pMDXMHeader->numSurfaces; i++)
	{
		if (GLMModel_SurfaceIsTag(hModel, i))
			iNumTagSurfaces++;

		LPCSTR psSurfaceName = GLMModel_GetSurfaceName( hModel, i);
		if (!stricmp("_off",&psSurfaceName[strlen(psSurfaceName)-4]))
			iNumOFFSurfaces++;
	}

	str+= va("    ->numSurfaces:\t%d",pMDXMHeader->numSurfaces );

	if (iNumTagSurfaces || iNumOFFSurfaces)
	{
		str+= va("  ( = %d default", (pMDXMHeader->numSurfaces - iNumTagSurfaces) - iNumOFFSurfaces);

		if (iNumTagSurfaces)
		{
			str+= va(" + %d TAG",iNumTagSurfaces);
		}
		if (iNumOFFSurfaces)
		{
			str+= va(" + %d OFF",iNumOFFSurfaces);
		}
		str+= " )";
	}
	str+= "\n";


	// show shader usage...
	//
	if (pContainer->SkinSets.size())
	{
		// model uses skin files...
		//
		str += va("\n\nSkin Shader Info:\n\nSkin File:\t\t%s\nEthnic ver:\t%s\n",
														pContainer->strCurrentSkinFile.c_str(),
																		pContainer->strCurrentSkinEthnic.c_str()
					);																		
	}
	else
	if (pContainer->OldSkinSets.size())
	{
		// model is using old EF1/ID type skins...
		//
		str += va("\n\nSkin Info:\n\nSkin File:\t\t%s\n", pContainer->strCurrentSkinFile.c_str());
	}
	else
	{
		// standard shaders...
		//
		int iUniqueShaderCount = GLMModel_GetUniqueShaderCount( hModel );
		str += va("\n\nShader Info:\t( %d unique shaders )\n\n", iUniqueShaderCount);
		for (int iUniqueShader = 0; iUniqueShader < iUniqueShaderCount; iUniqueShader++)
		{
			bool bFound = false;

			LPCSTR	psShaderName		= GLMModel_GetUniqueShader( iUniqueShader );
			LPCSTR	psLocalTexturePath	= R_FindShader( psShaderName );
		
			if (psLocalTexturePath && strlen(psLocalTexturePath))
			{
				TextureHandle_t hTexture		= TextureHandle_ForName( psLocalTexturePath );
				GLuint			uiBind			= (hTexture == -1)?0:Texture_GetGLBind( hTexture );
				bFound = (uiBind||!stricmp(psShaderName,"[NoMaterial]"));			
			}
			str += va("     %s%s\n",String_EnsureMinLength(psShaderName[0]?psShaderName:"<blank>",16/*arb*/),(!bFound)?"\t(Not Found)":"");
		}
	}

	// show anim file details...
	//
		
	str+= va("\n\nGLA Info:\t\t( FileSize: %d bytes )\n\n",pMDXAHeader->ofsEnd);

//	mdxaHeader_t *pMDXAHeader = (mdxaHeader_t *) pvLoadedGLA;

	str+= va("    ->ident :\t%X\n",		pMDXAHeader->ident );	// extra space before ':' here to push tab to next column
	str+= va("    ->version:\t%d\n",	pMDXAHeader->version );
	str+= va("    ->name:\t%s\n",		pMDXAHeader->name );
	str+= va("    ->scale:\t%g\n",		(pMDXAHeader->fScale == 0.0f)?1.0f:pMDXAHeader->fScale);
	str+= va("    ->numFrames:\t%d\n",	pMDXAHeader->numFrames );
	str+= va("    ->ofsFrames:\t%d\n",	pMDXAHeader->ofsFrames );
	str+= va("    ->numBones:\t%d\n",	pMDXAHeader->numBones );
	str+= va("    ->ofsCompBonePool:\t%d\n",	pMDXAHeader->ofsCompBonePool );
	str+= va("    ->ofsSkel:\t%d\n",	pMDXAHeader->ofsSkel );

	return str.c_str();
}



// call this to re-evaluate any part of the tree that has surfaces owned by this model, and set their text ok...
//
// hTreeItem = tree item to start from, pass NULL to start from root
//
bool R_GLMModel_Tree_ReEvalSurfaceText(ModelHandle_t hModel, HTREEITEM hTreeItem /* = NULL */, bool bDeadFromHereOn /* = false */)
{
	bool bReturn = false;

	if (!hTreeItem)
		 hTreeItem = ModelTree_GetRootItem();

	if (hTreeItem)
	{
		// process this tree item...
		//
		TreeItemData_t	TreeItemData;
						TreeItemData.uiData = ModelTree_GetItemData(hTreeItem);

		if (TreeItemData.iModelHandle == hModel)
		{
			// ok, tree item belongs to this model, so what is it?...
			//
			bool bKillMyChildren = false;

			ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
			if (pContainer)
			{
				if (TreeItemData.iItemType == TREEITEMTYPE_GLM_SURFACE)
				{
					// it's a surface, so re-eval its text...
					//
					LPCSTR psSurfaceName = GLMModel_GetSurfaceName( hModel, TreeItemData.iItemNumber );

					// a little harmless optimisation here...
					//
					SurfaceOnOff_t sOnOff = bDeadFromHereOn ? SURF_OFF : trap_G2_IsSurfaceOff (hModel, pContainer->slist, psSurfaceName);

					ModelTree_SetItemText( hTreeItem, GLMModel_CreateSurfaceName( psSurfaceName, bDeadFromHereOn?false:(sOnOff == SURF_ON)));
					
					if (sOnOff == SURF_NO_DESCENDANTS)
					{
						bKillMyChildren = true;
					}
				}
			}

			// process child...
			//
			HTREEITEM hTreeItem_Child = ModelTree_GetChildItem(hTreeItem);
			if (hTreeItem_Child)
				R_GLMModel_Tree_ReEvalSurfaceText(hModel, hTreeItem_Child, (bDeadFromHereOn || bKillMyChildren) );


			// process siblings...
			//
			HTREEITEM hTreeItem_Sibling = ModelTree_GetNextSiblingItem(hTreeItem);
			if (hTreeItem_Sibling)
				R_GLMModel_Tree_ReEvalSurfaceText(hModel, hTreeItem_Sibling, bDeadFromHereOn);
		}
	}

	return true;
}


// read an optional set of skin files (SOF2-style), and if present, add them into the model tree...
//
// return is success/fail (but it's an optional file, so return bool is just FYI really)
//    (note that partial failures still count as successes, as long as at least one file succeeds)
//
static bool GLMModel_ReadSkinFiles(HTREEITEM hParent, ModelContainer_t *pContainer, LPCSTR psLocalFilename)
{
	// check for optional .g2skin files... (SOF2-type)
	//
	if (Skins_Read(psLocalFilename, pContainer))	
	{
		return Skins_ApplyToTree(hParent, pContainer);
	}

	// check for optional .skin files... (CHC-type)
	//
	if (OldSkins_Read(psLocalFilename, pContainer))
	{
		return OldSkins_ApplyToTree(hParent, pContainer);
	}

	return false;
}
	  

// read an optional bone alias file, and if present then add into the model tree...
//  (this may become a generic model function, not just a GLMModel one, but for now...
//
// return is success/fail (but it's an optional file, so return bool is just FYI really)
//    (note that partial failures still count as successes, as long as at least one alias succeeds)
//
static bool GLMModel_ReadBoneAliasFile(HTREEITEM hParent, HTREEITEM hInsertAfter, ModelContainer_t *pContainer, LPCSTR psLocalFilename)
{	
	TreeItemData_t	TreeItemData = {0};
					TreeItemData.iModelHandle = pContainer->hModel;

	HTREEITEM hTreeItem_BoneAliases	= NULL;
	bool bReturn = false;


	// check for optional alias file...
	//
	CString strALIASFilename(va("%s%s.alias",gamedir,psLocalFilename));
	CString strErrors;

	if (FileExists(strALIASFilename))
	{
		TreeItemData.iItemType	= TREEITEMTYPE_BONEALIASHEADER;
		hTreeItem_BoneAliases	= ModelTree_InsertItem("Bone Aliases", hParent, TreeItemData.uiData, hInsertAfter);
	
		if (hTreeItem_BoneAliases)
		{	
			if (Parser_Load(strALIASFilename, pContainer->Aliases))
			{
				for (MappedString_t::iterator it = pContainer->Aliases.begin(); it != pContainer->Aliases.end(); ++it)
				{
					CString strBoneName_Real (((*it).first).c_str());
					CString strBoneName_Alias(((*it).second).c_str());

					int iRealBoneIndex = ModelContainer_BoneIndexFromName(pContainer, strBoneName_Real);

					if (iRealBoneIndex != -1)
					{
						TreeItemData.iItemType	= TREEITEMTYPE_GLM_BONEALIAS;
						TreeItemData.iItemNumber= iRealBoneIndex;

						ModelTree_InsertItem(strBoneName_Alias, hTreeItem_BoneAliases, TreeItemData.uiData );

						bReturn = true;
					}
					else
					{
						strErrors += va("Bone: \"%s\" (Alias: \"%s\")",(LPCSTR) strBoneName_Real, (LPCSTR) strBoneName_Alias);
					}
				}				
			}
			else
			{
				TreeItemData.iItemType = TREEITEMTYPE_NULL;
				ModelTree_InsertItem("Error during parse!",	hTreeItem_BoneAliases, TreeItemData.uiData);
			}
		}	
	}

	if (!strErrors.IsEmpty())
	{
		strErrors.Insert(0,va("The following bone names in the alias file: \"%s\"\n\n...had no corresponding bones in the anim file for the model: \"%s\"\n\n\n",(LPCSTR) strALIASFilename, pContainer->sLocalPathName));
		ErrorBox(strErrors);
	}

	bReturn = true;

	return bReturn;
}


// return = true if some sequences created (because of having found a valid animation file, 
//	either "<modelname>.frames" (SOF2) or "animation.cfg" (CHC)...
//
static bool GLMModel_ReadSequenceInfo(HTREEITEM hTreeItem_Root, ModelContainer_t *pContainer, LPCSTR psLocalFilename_GLA)
{
	assert(hTreeItem_Root);

	// try a CHC-style "animation.cfg" file...
	//
	bool bFromANIMATIONCFG = Anims_ReadFile_ANIMATION_CFG(pContainer, psLocalFilename_GLA);
	//
	// if no joy with that, try a SOF2 "<name>.frames" file...
	//
	if (!bFromANIMATIONCFG)
	{
		Anims_ReadFile_FRAMES(pContainer, psLocalFilename_GLA);
	}

	// now add to tree if we found something...
	//
	if (pContainer->SequenceList.size())
	{
		TreeItemData_t	TreeItemData={0};
						TreeItemData.iItemType		= TREEITEMTYPE_SEQUENCEHEADER;
						TreeItemData.iModelHandle	= pContainer->hModel;

						HTREEITEM hTreeItem_Sequences = ModelTree_InsertItem(va("Sequences %s",bFromANIMATIONCFG?"( From animation.cfg )":"( From .frames file )"), hTreeItem_Root, TreeItemData.uiData);

						ModelTree_InsertSequences(pContainer, hTreeItem_Sequences);		
	}

	return !!(pContainer->SequenceList.size());
}


// if we get this far now then we no longer need to check the model data because RE_RegisterModel has already 
//	validated it (and loaded the GLA file!). Oh well...
//
// this MUST be called after Jake's code has finished, since I read from his tables...
//
bool GLMModel_Parse(struct ModelContainer *pContainer, LPCSTR psLocalFilename, HTREEITEM hTreeItem_Parent /* = NULL */)
{
	bool bReturn = false;

	ModelHandle_t hModel = pContainer->hModel;

	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);
	
	HTREEITEM hTreeItem_Bones		= NULL;

	if (pMDXMHeader->ident == MDXM_IDENT)
	{
		if (pMDXMHeader->version == MDXM_VERSION)
		{
//			// now see if we can find the corresponding GLA file...
//			//
//			CString strGLAFilename(va("%s%s.gla",gamedir,pMDXMHeader->animName));
//
//			if (FileExists(strGLAFilename))
			{
//				assert(pvLoadedGLA == NULL);

//				int iFilelenGLA = LoadFile( strGLAFilename, &pvLoadedGLA, true );	// bReportErrors

//				if (iFilelenGLA != -1)
				{
					mdxaHeader_t *pMDXAHeader = (mdxaHeader_t *) RE_GetModelData( pMDXMHeader->animIndex );

					if (pMDXAHeader->ident == MDXA_IDENT)
					{
						if (pMDXAHeader->version == MDXA_VERSION)
						{
							// phew, all systems go...
							//
							bReturn = true;
			
							TreeItemData_t	TreeItemData={0};
											TreeItemData.iModelHandle = hModel;
									
							TreeItemData.iItemType	= TREEITEMTYPE_MODELNAME;
							pContainer->hTreeItem_ModelName = ModelTree_InsertItem(va("==>  %s  <==",Filename_WithoutPath(/*Filename_WithoutExt*/(psLocalFilename))), hTreeItem_Parent, TreeItemData.uiData);

							TreeItemData.iItemType	= TREEITEMTYPE_SURFACEHEADER;
				  HTREEITEM hTreeItem_Surfaces		= ModelTree_InsertItem("Surfaces",	pContainer->hTreeItem_ModelName, TreeItemData.uiData);

							TreeItemData.iItemType	= TREEITEMTYPE_TAGSURFACEHEADER;
				  HTREEITEM hTreeItem_TagSurfaces	= ModelTree_InsertItem("Tag Surfaces",	pContainer->hTreeItem_ModelName, TreeItemData.uiData);

							TreeItemData.iItemType	= TREEITEMTYPE_BONEHEADER;
							hTreeItem_Bones			= ModelTree_InsertItem("Bones",		pContainer->hTreeItem_ModelName, TreeItemData.uiData);

							// send surface heirarchy to tree...
							//
							mdxmHierarchyOffsets_t *pHierarchyOffsets = (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));

							R_GLM_AddSurfaceToTree( hModel, hTreeItem_Surfaces, 0, pHierarchyOffsets, false);
							R_GLM_AddSurfaceToTree( hModel, hTreeItem_TagSurfaces, 0, pHierarchyOffsets, true);

							// special error check for badly-hierarchied surfaces... (bad test data inadvertently supplied by Rob Gee :-)
							//
							int iNumSurfacesInTree = ModelTree_GetChildCount(hTreeItem_Surfaces);
							if (iNumSurfacesInTree != pMDXMHeader->numSurfaces)
							{
								ErrorBox(va("Model has %d surfaces, but only %d of them are connected up through the heirarchy, the rest will never be recursed into.\n\nThis model needs rebuilding, guys...",pMDXMHeader->numSurfaces,iNumSurfacesInTree));
								bReturn = false;
							}

							if (!ModelTree_ItemHasChildren( hTreeItem_TagSurfaces ))
							{
								ModelTree_DeleteItem( hTreeItem_TagSurfaces );
							}

							// send bone heirarchy to tree...
							//
							mdxaSkelOffsets_t *pSkelOffsets = (mdxaSkelOffsets_t *) ((byte *)pMDXAHeader + sizeof(*pMDXAHeader));

							R_GLM_AddBoneToTree( hModel, hTreeItem_Bones, 0, pSkelOffsets);
						}
						else
						{
							ErrorBox(va("Wrong GLA format version number: %d (expecting %d)\n\n( file: \"%s\" )",pMDXAHeader->version, MDXA_VERSION, pMDXAHeader->name ));
						}
					}
					else
					{
						ErrorBox(va("Wrong GLA file ident: %X (expecting %X)\n\n( file: \"%s\" )",pMDXAHeader->ident, MDXA_IDENT, pMDXAHeader->name ));
					}
				}
//				else
//				{
//					ErrorBox(va("Unable to find corresponding animation file: \"%s\"!",(LPCSTR)strGLAFilename));
//				}
			}
//			else
//			{
//				ErrorBox(va("Unable to find corresponding GLA file \"%s\"!",(LPCSTR)strGLAFilename));
//			}
		}
		else
		{
			ErrorBox(va("Wrong model format version number: %d (expecting %d)",pMDXMHeader->version, MDXM_VERSION));
		}
	}
	else
	{
		ErrorBox(va("Wrong model Ident: %X (expecting %X)",pMDXMHeader->ident, MDXM_IDENT));
	}
			

	if (bReturn)
	{
		bReturn = R_GLMModel_Tree_ReEvalSurfaceText(hModel);

		if (bReturn)
		{
			// let's try looking for "<modelname>.frames" in the same dir for simple sequence info...
			//
			{
				// now fill in the fields we need in the container to avoid GLM-specific queries...
				//
				pContainer->pModelInfoFunction				= GLMModel_Info;
				pContainer->pModelGetBoneNameFunction		= GLMModel_GetBoneName;
				pContainer->pModelGetBoneBoltNameFunction	= GLMModel_GetBoneName;	// same thing in this format
				pContainer->pModelGetSurfaceNameFunction	= GLMModel_GetSurfaceName;
				pContainer->pModelGetSurfaceBoltNameFunction= GLMModel_GetSurfaceName;	// same thing in this format
				pContainer->iNumFrames		= GLMModel_GetNumFrames	( hModel );
				pContainer->iNumLODs		= GLMModel_GetNumLODs	( hModel );
				pContainer->iNumBones		= GLMModel_GetNumBones	( hModel );
				pContainer->iNumSurfaces	= GLMModel_GetNumSurfaces(hModel );

				pContainer->iBoneBolt_MaxBoltPoints		= pContainer->iNumBones;	// ... since these are pretty much the same in this format
				pContainer->iSurfaceBolt_MaxBoltPoints	= pContainer->iNumSurfaces;	// ... since these are pretty much the same in this format

				GLMModel_ReadSkinFiles	  (pContainer->hTreeItem_ModelName, pContainer, psLocalFilename);
				GLMModel_ReadSequenceInfo (pContainer->hTreeItem_ModelName, pContainer, pMDXMHeader->animName);
				GLMModel_ReadBoneAliasFile(pContainer->hTreeItem_ModelName, hTreeItem_Bones, pContainer, pMDXMHeader->animName);
			}
		}
	}

	return bReturn;
}





SurfaceOnOff_t GLMModel_Surface_GetStatus( ModelHandle_t hModel, int iSurfaceIndex )
{
	ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);
	
	if (pContainer)
	{
		LPCSTR psSurfaceName = GLMModel_GetSurfaceName( hModel, iSurfaceIndex );
		
		return trap_G2_IsSurfaceOff(hModel, pContainer->slist, psSurfaceName);
	}
	return SURF_ERROR;
}

bool GLMModel_Surface_SetStatus( ModelHandle_t hModel, int iSurfaceIndex, SurfaceOnOff_t eStatus )
{
	bool bReturn = false;

	ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);

	if (pContainer)
	{
		LPCSTR psSurfaceName = GLMModel_GetSurfaceName( hModel, iSurfaceIndex );

		if (trap_G2_SetSurfaceOnOff (hModel, pContainer->slist, psSurfaceName, eStatus, iSurfaceIndex))
		{
			R_GLMModel_Tree_ReEvalSurfaceText(hModel);
			bReturn = true;
		}
		else
		{
			ErrorBox(va("G2_SetSurfaceOnOff(): Error, probably too many surfaces turned off? (max = %d)",MAX_G2_SURFACES));
		}
	}

	if (bReturn)
		ModelList_ForceRedraw();

	return bReturn;
}

// only call this once the model is fully up and running, not as part of the load code (because of container usage)...
//
void GLMModel_Surfaces_DefaultAll(ModelHandle_t hModel)
{
	ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);

	if (pContainer)
	{
		trap_G2_SurfaceOffList(hModel, &pContainer->slist);
		R_GLMModel_Tree_ReEvalSurfaceText(hModel);
	}
}

bool GLMModel_Surface_Off( ModelHandle_t hModel, int iSurfaceIndex )
{
	return GLMModel_Surface_SetStatus( hModel, iSurfaceIndex, SURF_OFF );
}

bool GLMModel_Surface_On( ModelHandle_t hModel, int iSurfaceIndex )
{
	return GLMModel_Surface_SetStatus( hModel, iSurfaceIndex, SURF_ON );
}

bool GLMModel_Surface_NoDescendants(ModelHandle_t hModel, int iSurfaceIndex )
{
	return GLMModel_Surface_SetStatus( hModel, iSurfaceIndex, SURF_NO_DESCENDANTS );
}

mdxaBone_t *GLMModel_GetBasePoseMatrix(ModelHandle_t hModel, int iBoneIndex)
{
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	assert( iBoneIndex < pMDXAHeader->numBones);
	
	mdxaSkelOffsets_t *pSkelOffsets = (mdxaSkelOffsets_t *) ((byte *)pMDXAHeader + sizeof(*pMDXAHeader));
	mdxaSkel_t *pSkelEntry = (mdxaSkel_t *) ((byte *) pSkelOffsets + pSkelOffsets->offsets[ iBoneIndex ] );

	return &pSkelEntry->BasePoseMat;
}



// This code was put in for Keith to auto-measure models, it works fine, but I've REM'd it for now since it
//	doesn't really serve any useful purpose in ModView other than to look interesting...   :-)
//

void ClearBounds (vec3_t mins, vec3_t maxs)
{
	mins[0] = mins[1] = mins[2] = 99999;
	maxs[0] = maxs[1] = maxs[2] = -99999;
}


void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs)
{
	int		i;
	vec_t	val;

	for (i=0 ; i<3 ; i++)
	{
		val = v[i];
		if (val < mins[i])
			mins[i] = val;
		if (val > maxs[i])
			maxs[i] = val;
	}
}

#include "MatComp.h"

extern void Multiply_4x4Matrix(mdxBone4_t *out, mdxBone4_t *in2, mdxBone4_t *in);
extern void From3x4(mdxaBone_t *mat, mdxBone4_t *out);
extern void To3x4(mdxBone4_t *in, mdxaBone_t *mat);

extern void UnCompressBone(float mat[3][4], int iBoneIndex, const mdxaHeader_t *pMDXAHeader, int iFrame);

// transform each individual bone's information - making sure to use any override information provided, both for angles and for animations, as
// well as multiplying each bone's matrix by it's parents matrix 
static void CUTDOWN_G2_TransformBone ( int newFrame, int parent, int child, mdxaHeader_t *header, mdxaBone_t *bonePtr)
{
	mdxaBone_t		tbone[3];
	mdxaSkel_t		*skel;
	mdxaSkelOffsets_t *offsets;
	int				i;//, boneListIndex;
	mdxBone4_t		outMatrix, inMatrix, in2Matrix;
	int				angleOverride = 0;

	// decide where the transformed bone is going

  	//
  	// lerp this bone - use the temp space on the ref entity to put the bone transforms into
  	//
  	if (child)
  	{
//		MC_UnCompress(tbone[2].matrix,pCompBonePool[aframe->boneIndexes[child]].Comp);
		UnCompressBone(tbone[2].matrix, child, header, newFrame);		
  	}
  	else
  	{
//		MC_UnCompress(bonePtr[child].matrix,pCompBonePool[aframe->boneIndexes[child]].Comp);
		UnCompressBone(bonePtr[child].matrix, child, header, newFrame);		
  	}

	// now transform the matrix by it's parent, asumming we have a parent, and we aren't overriding the angles absolutely
	if (child)
	{
		// convert from 3x4 matrix to a 4x4 matrix
		From3x4(&bonePtr[parent], &inMatrix);
		From3x4(&tbone[2], &in2Matrix);

		// do the multiplication
		Multiply_4x4Matrix(&outMatrix, &in2Matrix, &inMatrix);

		// convert result back into a 3x4 matrix for use later
		To3x4(&outMatrix, &bonePtr[child]);
	}


	// figure out where the bone hirearchy info is
	offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t));
	skel = (mdxaSkel_t *)((byte *)header + sizeof(mdxaHeader_t) + offsets->offsets[child]);

	// now work out what children we have to call this recursively for
	for (i=0; i< skel->numChildren; i++)
	{
		CUTDOWN_G2_TransformBone( newFrame, child, skel->children[i], header, bonePtr );
	}
}


// start the recursive hierarchical bone transform and lerp process for this model...
//
static void CUTDOWN_G2_TransformGhoulBones( int iFrame, mdxaHeader_t *header, mdxaBone_t *bonePtr)
{
	CUTDOWN_G2_TransformBone( iFrame, 0, 0, header, bonePtr);
}

// horrible hacky mess
static void CUTDOWN_G2_RecurseSurfaces( ModelHandle_t hModel, ModelContainer_t *pContainer, int iSurface, mdxmLODSurfOffset_t *pLODSurfOffset, mdxaBone_t *bonePtr, vec3_t &v3Mins, vec3_t &v3Maxs, mdxmHeader_t *pMDXMHeader)
{
	mdxmHierarchyOffsets_t	*surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)pMDXMHeader + sizeof(mdxmHeader_t));
	mdxmSurfHierarchy_t		*surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[iSurface]);

	mdxmSurface_t *pSurface = (mdxmSurface_t *) ((byte *) pLODSurfOffset +  pLODSurfOffset->offsets[iSurface]);
	int *piBoneRefTable = (int*)((byte*)pSurface + pSurface->ofsBoneReferences);

	//////////////////////////////
	//
	// remove if this code is cut/paste outside of ModView...
	//
	if (AppVars.iSurfaceNumToHighlight == iSurface ||
		trap_G2_IsSurfaceOff (hModel, pContainer->slist, GLMModel_GetSurfaceName( hModel, iSurface)) == SURF_ON)
	//
	//////////////////////////////
	{
		if (AppVars.bShowTagSurfaces || !GLMModel_SurfaceIsTag(hModel, iSurface))
		{
			// whip through and actually transform each vertex
			//		
			mdxmVertex_t *v = (mdxmVertex_t *) ((byte *)pSurface + pSurface->ofsVerts);
			mdxmVertexTexCoord_t *pTexCoords = (mdxmVertexTexCoord_t *) &v[pSurface->numVerts];
			for ( int j=0; j<pSurface->numVerts; j++ )
			{
				vec3_t			tempVert;
//				mdxmWeight_t	*w;

				VectorClear( tempVert );
//				w = v->weights;		

				const int iNumWeights = G2_GetVertWeights( v );
			
				float fTotalWeight = 0.0f;
				for ( int k=0 ; k<iNumWeights ; k++ ) 
				{
					int		iBoneIndex	= G2_GetVertBoneIndex( v, k );
					float	fBoneWeight	= G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights );

					mdxaBone_t *bone = &bonePtr[piBoneRefTable[iBoneIndex]];

					tempVert[0] += fBoneWeight * ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] );
					tempVert[1] += fBoneWeight * ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] );
					tempVert[2] += fBoneWeight * ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] );
				}

				AddPointToBounds(tempVert, v3Mins, v3Maxs);

//					if (v3Mins[1] < -33)
//					{
//						int z=1;
//					}

				v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/pSurface->maxVertBoneWeights];
			}
		}
	}

	// recurse this surface's children...
	//
	for (int iChildSurface = 0; iChildSurface < surfInfo->numChildren; iChildSurface++)
	{
		CUTDOWN_G2_RecurseSurfaces( hModel, pContainer, surfInfo->childIndexes[iChildSurface], pLODSurfOffset, bonePtr, v3Mins, v3Maxs, pMDXMHeader);
	}
}


bool GLMModel_GetBounds(ModelHandle_t hModel, int iLODNumber, int iFrameNumber, vec3_t &v3Mins, vec3_t &v3Maxs)
{	
	mdxmHeader_t	*pMDXMHeader	= (mdxmHeader_t	*) RE_GetModelData(hModel);
	mdxaHeader_t	*pMDXAHeader	= (mdxaHeader_t	*) RE_GetModelData(pMDXMHeader->animIndex);

	ClearBounds (v3Mins, v3Maxs);

	if (iLODNumber >= pMDXMHeader->numLODs)
		return false;	// can't reference it if we don't have a LOD of this level of course

	if (iFrameNumber >= pMDXAHeader->numFrames)
		return false;

	mdxmLOD_t *pLOD = (mdxmLOD_t *)((byte *)pMDXMHeader + pMDXMHeader->ofsLODs);
	for (int iLOD = 0; iLOD < iLODNumber && iLOD < pMDXMHeader->numLODs-1; iLOD++)
	{
		pLOD = (mdxmLOD_t *)((byte *)pLOD + pLOD->ofsEnd);
	}	
	mdxmLODSurfOffset_t *pLODSurfOffset = (mdxmLODSurfOffset_t *) &pLOD[1];


	mdxaBone_t *bonePtr = (mdxaBone_t *) malloc( sizeof(mdxaBone_t) * pMDXAHeader->numBones);
	if (!bonePtr)	
		return false;

	//////////////////////////////
	//
	// remove if this code is cut/paste outside of ModView...
	//
	ModelContainer_t *pContainer = ModelContainer_FindFromModelHandle(hModel);	
	if (!pContainer)
		return false;
	//
	//////////////////////////////


	CUTDOWN_G2_TransformGhoulBones( iFrameNumber, pMDXAHeader, bonePtr);

	int iStartSurface = pContainer->iSurfaceNum_RootOverride;
	if (iStartSurface == -1)
	{
		iStartSurface = 0;
	}

	CUTDOWN_G2_RecurseSurfaces( hModel, pContainer, iStartSurface, pLODSurfOffset, bonePtr, v3Mins, v3Maxs, pMDXMHeader);

	free(bonePtr);
	return true;
}

// work out an edge-or-not bool for every vert in the specified LOD of the model...
//
// return value is the corrected (if over the model limit) LOD level
//
int GLMModel_EnsureGenerated_VertEdgeInfo(ModelHandle_t hModel, int iLOD, SurfaceEdgeInfoPerLOD_t &SurfaceEdgeInfoPerLOD)
{
	mdxmHeader_t *pMDXMHeader = (mdxmHeader_t *) RE_GetModelData(hModel);
	if (iLOD >= pMDXMHeader->numLODs)
		iLOD  = pMDXMHeader->numLODs-1;	// correct it before looking up...

	SurfaceEdgeInfoPerLOD_t::iterator itSurfaceEdgeInfoPerLOD = SurfaceEdgeInfoPerLOD.find(iLOD);
	if (itSurfaceEdgeInfoPerLOD == SurfaceEdgeInfoPerLOD.end())
	{
		// info not present for this LOD, so generate it...
		//
		// First, get various model pointers ready...
		//		
		mdxmHierarchyOffsets_t	*pHierarchyOffsets	= (mdxmHierarchyOffsets_t *) ((byte *) pMDXMHeader + sizeof(*pMDXMHeader));
	//	mdxmSurfHierarchy_t		*pSurfHierarchy		= (mdxmSurfHierarchy_t *) ((byte *) pHierarchyOffsets + pHierarchyOffsets->offsets[iSurfaceNumber]);

		mdxmLOD_t *pLOD = (mdxmLOD_t *)((byte *)pMDXMHeader + pMDXMHeader->ofsLODs);
		for (int _iLOD = 0; _iLOD < iLOD && _iLOD < pMDXMHeader->numLODs-1; _iLOD++)
		{
			pLOD = (mdxmLOD_t *)((byte *)pLOD + pLOD->ofsEnd);
		}	

		mdxmLODSurfOffset_t *pLODSurfOffset = (mdxmLODSurfOffset_t *) &pLOD[1];

		// now go through the model and flag each vert as edge or not...
		//		
		for (int iSurface = 0; iSurface<pMDXMHeader->numSurfaces; iSurface++)
		{
			if (iSurface == 59)
			{
				int z=1;
			}
			mdxmSurface_t	*pSurface	= (mdxmSurface_t  *) ((byte *) pLODSurfOffset +  pLODSurfOffset->offsets[iSurface]);
			mdxmTriangle_t	*pTriangles	= (mdxmTriangle_t *) ((byte *) pSurface + pSurface->ofsTriangles);

			VertIsEdge_t	vVertIsEdge;
							vVertIsEdge.resize(pSurface->numVerts);

			// To work out if a vert is an edge or not:
			//
			// for every vert, find the triangles using it, and their verts,
			//	then for every one of those other verts if they're used by two triangles
			//	then the original vert is an internal (non-edge) one...
			//
			mdxmVertex_t *pThisVert = (mdxmVertex_t *) ((byte *)pSurface + pSurface->ofsVerts);
			for (int iThisVert = 0; iThisVert<pSurface->numVerts; iThisVert++)
			{
				// build up tris-using-this-vert list...
				//
				vector <mdxmTriangle_t *> vTrisUsing;
				set	   <int>			  stOtherVertsUsed;

				for (int iTrisUsing=0; iTrisUsing<pSurface->numTriangles; iTrisUsing++)
				{
					mdxmTriangle_t *pTriUsing = &pTriangles[iTrisUsing];

					if (pTriUsing->indexes[0] == iThisVert || 
						pTriUsing->indexes[1] == iThisVert || 
						pTriUsing->indexes[2] == iThisVert
						)
					{
						// store this tri...
						//
						vTrisUsing.push_back( pTriUsing );
						//
						// store it's other verts...
						//
						stOtherVertsUsed.insert(stOtherVertsUsed.end(), pTriUsing->indexes[0]);
						stOtherVertsUsed.insert(stOtherVertsUsed.end(), pTriUsing->indexes[1]);
						stOtherVertsUsed.insert(stOtherVertsUsed.end(), pTriUsing->indexes[2]);
					}
				}

				// (algorithm next:) 
				//
				// now scan through every vert in the list (except the original) and see if they're used by
				//	two triangles only...
				//
				bool bThisVertIsInternal = true;
				for (set <int>::iterator it = stOtherVertsUsed.begin(); bThisVertIsInternal && it != stOtherVertsUsed.end(); ++it)
				{
					int iScanVert = (*it);

					if (iScanVert != iThisVert)
					{
						int iTrisUsingThisScanVert = 0;
						for (int iScanTri = 0; iScanTri < vTrisUsing.size(); iScanTri++)
						{
							mdxmTriangle_t *pScanTri = vTrisUsing[iScanTri];

							if (pScanTri->indexes[0] == iScanVert || 
								pScanTri->indexes[1] == iScanVert || 
								pScanTri->indexes[2] == iScanVert
								)
							{
								iTrisUsingThisScanVert++;
							}
						}
						if (iTrisUsingThisScanVert != 2)
						{
							bThisVertIsInternal = false;
						}
					}
				}

				// finally, record the result...
				//				
				vVertIsEdge[ iThisVert ] = !bThisVertIsInternal;

				// next vert...
				//
				pThisVert++;// = (mdxmVertex_t *)&pThisVert->weights[/*pThisVert->numWeights*/pSurface->maxVertBoneWeights];
			}

			// record this set of surface vert-edge bools...
			//
			SurfaceEdgeVertBools_t &SurfaceEdgeVertBools = SurfaceEdgeInfoPerLOD[iLOD];
									SurfaceEdgeVertBools[ iSurface ] = vVertIsEdge;
		}
	}

	return iLOD;
}

//////////////// eof //////////////



