// Filename:-	R_Model.cpp
//
// interface to all block-paste other-format code that doesn't know it's inside ModView
//

#include "stdafx.h"
#include "includes.h"
#include "R_Common.h"
#include "textures.h"
//
#include "R_Model.h"


trGlobals_t tr;
refimport_t ri;


void Com_Printf( const char *format, ... )
{
	va_list		argptr;
	static char		string[16][1024];
	static index = 0;

	index = (++index)&15;
	
	va_start (argptr, format);
	vsprintf (string[index], format,argptr);
	va_end (argptr);

	OutputDebugString(string[index]);	
//	assert(0);
	ErrorBox(string[index]);
}

void Q_strncpyz( char *dest, LPCSTR src, int destlen)
{
	strncpy(dest,src,destlen);
	dest[destlen-1] = '\0';
}

float Com_Clamp( float min, float max, float value ) {
	if ( value < min ) {
		return min;
	}
	if ( value > max ) {
		return max;
	}
	return value;
}


/*
============
COM_SkipPath
============
*/
char *COM_SkipPath (char *pathname)
{
	char	*last;
	
	last = pathname;
	while (*pathname)
	{
		if (*pathname=='/')
			last = pathname+1;
		pathname++;
	}
	return last;
}

/*
============
COM_StripExtension
============
*/
void COM_StripExtension( const char *in, char *out ) {
	while ( *in && *in != '.' ) {
		*out++ = *in++;
	}
	*out = 0;
}


void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
	int		len;
	va_list		argptr;
	char	bigbuffer[32000];	// big, but small enough to fit in PPC stack

	va_start (argptr,fmt);
	len = vsprintf (bigbuffer,fmt,argptr);
	va_end (argptr);
	if ( len >= sizeof( bigbuffer ) ) {
		Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" );
	}
	if (len >= size) {
		Com_Printf ("Com_sprintf: overflow of %i in %i ( string: \"%s\" )\n", len, size, bigbuffer);
	}
	Q_strncpyz (dest, bigbuffer, size );
}

int    LongSwap (int l)
{
	byte    b1,b2,b3,b4;

	b1 = l&255;
	b2 = (l>>8)&255;
	b3 = (l>>16)&255;
	b4 = (l>>24)&255;

	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
}



/*
==================
COM_DefaultExtension
==================
*/
void COM_DefaultExtension (char *path, int maxSize, const char *extension ) {
	char	oldPath[MAX_QPATH];
	char    *src;

//
// if path doesn't have a .EXT, append extension
// (extension should include the .)
//
	src = path + strlen(path) - 1;

	while (*src != '/' && src != path) {
		if ( *src == '.' ) {
			return;                 // it has an extension
		}
		src--;
	}

	Q_strncpyz( oldPath, path, sizeof( oldPath ) );
	Com_sprintf( path, maxSize, "%s%s", oldPath, extension );
}


void Crap_Printf( int printLevel, const char *format, ...)
{
	va_list		argptr;
	static char		string[16][16384];
	static index = 0;

	index = (++index)&15;
	
	va_start (argptr, format);
//	vsprintf (string[index], format,argptr);
	_vsnprintf(string[index], sizeof(string[0]), format, argptr);
	va_end (argptr);
	string[index][sizeof(string[0])-1] = '\0';

	char *psString = string[index];	

	switch (printLevel)
	{
		case PRINT_ALL:			

			InfoBox(psString);
			break;

		case PRINT_DEVELOPER:	
			
			OutputDebugString( psString);	
			break;

		case PRINT_WARNING:

			WarningBox(psString);
			break;

		default: assert(0);
		case PRINT_ERROR:

			ErrorBox(psString);
			break;
	}
}

void Crap_Error( int errorLevel, const char *format, ...)
{
	va_list		argptr;
	static char	string[1024];
	
	va_start (argptr, format);
	vsprintf (string, format,argptr);
	va_end (argptr);

	// should maybe switch-case off these, but for now...
	//
/*
typedef enum {
	ERR_FATAL,					// exit the entire game with a popup window
	ERR_DROP,					// print to console and disconnect from game
	ERR_DISCONNECT,				// don't kill server
	ERR_NEED_CD					// pop up the need-cd dialog
} errorParm_t;
*/

	throw (string );
}

void *Crap_Malloc( int bytes )
{
	void *pvBlah = malloc(bytes);
	if (pvBlah)
	{
		memset(pvBlah,0,bytes);
	}
	else
	{
		ErrorBox(va("Dammit: Failed to allocate %d bytes for some reason\n\nGonna have to exit....",bytes));
		exit(1);
	}

	return pvBlah;
}

void Crap_Free( void *buf )
{
	free(buf);
}

// change this?  Nah....
void *Crap_HunkAlloc(int size)
{
	return Crap_Malloc(size);
}



// ============
// FS_ReadFile
// 
// Filename are relative to the quake search path
// a null buffer will just return the file length without loading
// ============
bool bHackToAllowFullPathDuringTestFunc = false;	// leave as false!!!!!!!!
int Crap_FS_ReadFile( const char *qpath, void **buffer )
{
	byte*	buf = NULL;

	if ( !gamedir[0]) {
		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
	}

	if ( !qpath || !qpath[0] ) {
		Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
	}

	char sTemp[1024];
	sprintf(sTemp,"%s%s",bHackToAllowFullPathDuringTestFunc?"":gamedir,qpath);

	if (!FileExists(sTemp))
	{
		if ( buffer ) {
			*buffer = NULL;
		}
		return -1;
	}
	
	if ( !buffer ) {
		return FileLen( sTemp );
	}

	return LoadFile (sTemp, buffer);
}

// ============
// FS_ReadFile
// 
// Filename are relative to the quake search path
// a null buffer will just return the file length without loading
// ============
// -1 return = fail, else len written

int Crap_FS_WriteFile( const char *qpath, const void *pBuffer, int iSize )
{
	if ( !gamedir[0]) {
		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
	}

	if ( !qpath || !qpath[0] ) {
		Com_Error( ERR_FATAL, "FS_WriteFile with empty name\n" );
	}

	char sTemp[1024];
	sprintf(sTemp,"%s%s",gamedir,qpath);

	return SaveFile (sTemp, pBuffer, iSize);
}


void Crap_FS_FreeFile( void *buffer ) 
{		
	if ( !buffer ) {
		Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
	}

	free(buffer);
}




int R_ComputeLOD( trRefEntity_t *ent )
{
	return AppVars.iLOD;	// MODVIEW change: this is under manual/user control in the viewer)
}


void AxisClear( vec3_t axis[3] ) {
	axis[0][0] = 1;
	axis[0][1] = 0;
	axis[0][2] = 0;
	axis[1][0] = 0;
	axis[1][1] = 1;
	axis[1][2] = 0;
	axis[2][0] = 0;
	axis[2][1] = 0;
	axis[2][2] = 1;
}


/*

the drawsurf sort data is packed into a single 32 bit value so it can be
compared quickly during the qsorting process

the bits are allocated as follows:

22 - 31	: sorted shader index
12 - 21	: entity index
3 - 7	: fog index
2		: used to be clipped flag
0 - 1	: dlightmap index
*/
#define	QSORT_SHADERNUM_SHIFT	22
#define	QSORT_ENTITYNUM_SHIFT	12
#define	QSORT_FOGNUM_SHIFT		3
//
void R_AddDrawSurf( surfaceType_t *surface, GLuint gluiTextureBind)
{
	// instead of checking for overflow, we just mask the index
	// so it wraps around

	int index = tr.refdef.numDrawSurfs & DRAWSURF_MASK;

	tr.refdef.drawSurfs[index].sort = (gluiTextureBind/*shader->sortedIndex*/ << QSORT_SHADERNUM_SHIFT)
									| (tr.currentEntityNum		<< QSORT_ENTITYNUM_SHIFT)
									| (0/* fogIndex*/			<< QSORT_FOGNUM_SHIFT)
									|  0/*(int)dlightMap*/;

	tr.refdef.drawSurfs[index].surface = surface;
	tr.refdef.numDrawSurfs++;
}




void R_DecomposeSort( unsigned sort, int *entityNum, GLuint* gluiTextureBind
					 // MODVIEWREM //,shader_t **shader, int *fogNum, int *dlightMap 
					 )
{
//	*fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31;
//	*shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ];
	*gluiTextureBind = ( sort >> QSORT_SHADERNUM_SHIFT );
	*entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & 1023;
//	*dlightMap = sort & 3;
}




//model_t	*loadmodel;

static model_t *R_AllocModel( void ) 
{	
	model_t		*mod;

	if ( tr.numModels == MAX_MOD_KNOWN ) {
		return NULL;
	}

	mod = (model_s *)ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ) );	
	mod->index = tr.numModels;
	tr.models[tr.numModels] = mod;
	tr.numModels++;

	return mod;
}


static void RE_EnsureOneBadModel(void)
{
	if (tr.numModels == 0)
	{
		model_t *mod = R_AllocModel();
		mod->type = MOD_BAD;
	}
}


// note, I only offer an API call to delete all models at once, since model handles are just indexes into
//	an array, and any stored pointers would invalidate if I deleted any in the middle
//
void RE_DeleteModels( void )
{
	for (int i=0; i<tr.numModels; i++)
	{
		if (strcmp(tr.models[i]->name,"*default.gla"))	// can't free global fake-GLA struct
		{
			SAFEFREE(tr.models[i]->pvData);	
		}
		SAFEFREE(tr.models[i]);
	}

	tr.numModels = 0;
}

static void R_ModelInitOnceOnly( void ) 
{	
	RE_DeleteModels();	
	RE_EnsureOneBadModel();
}


model_t	*R_GetModelByHandle( ModelHandle_t index ) 
{
	RE_EnsureOneBadModel();	

	// out of range gets the default model
	if ( index < 1 || index >= tr.numModels ) {
		return tr.models[0];
	}

	model_t	*mod = tr.models[index];

	return mod;
}

//===============================================================================


// used by higher-level app to gain access to actual loaded data...
//
// returns NULL if error...
//
void *RE_GetModelData( ModelHandle_t hModel )
{
	RE_EnsureOneBadModel();	

	model_t	*pModel = R_GetModelByHandle( hModel );
	if (pModel->type != MOD_BAD)
		return pModel->pvData;

	assert(0);
	return NULL;
}


modtype_t RE_GetModelType( ModelHandle_t hModel )
{
	RE_EnsureOneBadModel();	

	model_t	*pModel = R_GetModelByHandle( hModel );
	return pModel->type;
}


// called only from media_delete...
//
typedef struct
{
	byte *pData;
	int iSize;
} CachedBin_t;
typedef map <string, CachedBin_t>	CachedModelBins_t;
									CachedModelBins_t CachedModelBins;
void RE_ModelBinCache_DeleteAll()
{
	for (CachedModelBins_t::iterator it = CachedModelBins.begin(); it != CachedModelBins.end(); ++it)
	{
		CachedBin_t &Bin = (*it).second;
		free(Bin.pData);
	}

	CachedModelBins.clear();
}

static byte *RE_ModelBinCache_Find( const char *psName, int &iSize )
{
	CachedModelBins_t::iterator it = CachedModelBins.find(psName);
	if (it != CachedModelBins.end())
	{
		CachedBin_t &Bin = (*it).second;
		iSize = Bin.iSize;
		return Bin.pData;
	}

	return NULL;
}


static void RE_ModelBinCache_Insert( const char *psName, int iBytesRead, const byte *pBuf )
{
	// don't exceed (say) 300MB?...
	//
	int iTotalBytes = 0;
	for (CachedModelBins_t::iterator it = CachedModelBins.begin(); it != CachedModelBins.end(); ++it)
	{
		CachedBin_t &Bin = (*it).second;
		iTotalBytes += Bin.iSize;
	}

	if (iTotalBytes < 300 * 1024 * 1024)
	{
		CachedBin_t Cache;
					Cache.pData = (byte *) malloc(iBytesRead);
					Cache.iSize = iBytesRead;

		memcpy(Cache.pData,pBuf,iBytesRead);
		CachedModelBins[ psName ] = Cache;
	}
}


/*
====================
Loads in a model for the given name

Zero will be returned if the model fails to load.
An entry will be retained for failed models as an
optimization to prevent disk rescanning if they are
asked for again.
====================
*/
ModelHandle_t RE_RegisterModel( const char *name ) {
	model_t		*mod;
	unsigned	*buf;
	int			lod;
	int			ident;
	qboolean	loaded;	
	int			numLoaded;

	RE_EnsureOneBadModel();	

	if ( !name || !name[0] ) {
		ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" );
		return 0;
	}

	if ( strlen( name ) >= MAX_QPATH ) {
		Com_Printf( "Model name %s exceeds MAX_QPATH\n",name );
		return 0;
	}

// MODVIEW: Note, because of the way I pass model handles to functions as unique handles that then have their
	//	owning-containers derived from that handle then they must be unique, so ignore cached models. Yes I know
	//	it's lame, but it was easier thatn rewriting the whole thing once I'd realised what direction this app was
	//	going etc. Anyway, since the model cache is already deleted when the primary is loaded then the only time
	//	this becomes inefficient is when you have (say) a guy with the same weapon bolted into both his left and right
	//	hands, then I end up with 2 copies of the loaded models, not one. Whoopee-do.  This is a quick-an-dirty app, 
	//	and I've got other things to get on with so it's a lot quicker to do something like this that makes no hands-on
	//	difference to the user, no matter how evil it is inside... :-)
/*
	//
	// search the currently loaded models
	//
	for (qhandle_t	hModel = 1 ; hModel < tr.numModels; hModel++ ) {
		mod = tr.models[hModel];
		if ( !strcmp( mod->name, name ) ) {
			if( mod->type == MOD_BAD ) {
				return 0;
			}
			return hModel;
		}
	}
*/
	// allocate a new model_t

	if ( ( mod = R_AllocModel() ) == NULL ) {
		ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name);
		return 0;
	}

	Q_strncpyz( mod->name, name, sizeof( mod->name ) );


	// make sure the render thread is stopped
//	R_SyncRenderThread();

	mod->numLods = 0;

	//
	// load the files
	//
	numLoaded = 0;

	int LODStart=MD3_MAX_LODS-1;	//this loads the md3s in reverse so they can be biased
	if (strstr (name, ".mdr") || strstr (name, ".gla") || strstr (name, ".glm")) 
	{
		LODStart = 0;
	}

	for ( lod = LODStart ; lod >= 0 ; lod-- ) 
	{
		char filename[1024];

		strcpy( filename, name );
		strlwr( filename );	// for bin map<> cacheing to work

		if ( lod != 0 ) {
			char namebuf[80];

			if ( strrchr( filename, '.' ) ) 
			{
				*strrchr( filename, '.' ) = 0;
			}
			sprintf( namebuf, "_%d.md3", lod );
			strcat( filename, namebuf );
		}

		bool bIsFakeGLA = false;
		if (!strcmp(filename,"*default.gla"))
		{
			bIsFakeGLA = true;
		}

		if (!bIsFakeGLA)
		{
			int iBytesRead;

			const byte *pCachedVersion = RE_ModelBinCache_Find( filename, iBytesRead );
			if (pCachedVersion)
			{
				buf = (unsigned *) malloc(iBytesRead);
				memcpy(buf,pCachedVersion, iBytesRead);
			}
			else
			{
				iBytesRead = ri.FS_ReadFile( filename, (void **)&buf );

				if (buf)
				{
					RE_ModelBinCache_Insert( filename, iBytesRead, (byte *) buf );
				}
			}
		}
		else
		{
			buf = (unsigned*) GLMModel_GetDefaultGLA();
		}
		
		if ( !buf ) 
		{
			continue;
		}
		
//		loadmodel = mod;
		
		ident = LL(*(unsigned *)buf);	// shouldn't really LL this if a fake GLA, but ModView is Intel-endian, so NP.
		if ( ident == MD4_IDENT ) 
		{
			loaded = R_LoadMD4( mod, buf, name );
			lod = MD3_MAX_LODS;
		}
		else if ( ident == MDXA_IDENT )
		{
			if (!bIsFakeGLA)
			{
				loaded = R_LoadMDXA( mod, buf, name );
			}
			else
			{
				mod->type = MOD_MDXA;
				mod->dataSize += ((mdxaHeader_t *)buf)->ofsEnd;
				mod->mdxa = (mdxaHeader_t *) buf;
				loaded = qtrue;
			}
		}
		else if ( ident == MDXM_IDENT ) 
		{
		   loaded = R_LoadMDXM( mod, buf, name );
		// else try load the file as an md3

		}
		else 
		{
			if ( ident != MD3_IDENT ) 
			{
				ri.Printf (PRINT_WARNING,"RE_RegisterModel: unknown fileid for %s\n", name);
				goto fail;
			}

			loaded = R_LoadMD3( mod, lod, buf, name );
		}
		
		if (!bIsFakeGLA)
		{
			ri.FS_FreeFile (buf);
		}

		if ( !loaded ) 
		{
			if ( lod == 0 ) 
			{
				goto fail;
			} else 
			{
				break;
			}
		} else 
		{
			mod->numLods++;
			numLoaded++;
//			// if we have a valid model and are biased
//			// so that we won't see any higher detail ones,
//			// stop loading them
//			if ( lod <= r_lodbias->integer ) 
//			{
//				break;
//			}
		}
	}

	if ( numLoaded ) 
	{
		// duplicate into higher lod spots that weren't
		// loaded, in case the user changes r_lodbias on the fly
		for ( lod-- ; lod >= 0 ; lod-- ) 
		{
			mod->numLods++;
			mod->md3[lod] = mod->md3[lod+1];
		}

		return mod->index;
	}

fail:
	// we still keep the model_t around, so if the model name is asked for
	// again, we won't bother scanning the filesystem
	mod->type = MOD_BAD;
	return 0;
}


/*
=================
R_LoadMD3
=================
*/
static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name ) {
	int					i, j;
	md3Header_t			*pinmodel;
    md3Frame_t			*frame;
	md3Surface_t		*surf;
	md3Shader_t			*shader;
	md3Triangle_t		*tri;
	md3St_t				*st;
	md3XyzNormal_t		*xyz;
	md3Tag_t			*tag;
	int					version;
	int					size;

	pinmodel = (md3Header_t *)buffer;

	version = LL (pinmodel->version);
	if (version != MD3_VERSION) {
		ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n",
				 mod_name, version, MD3_VERSION);
		return qfalse;
	}

	mod->type = MOD_MESH;
	size = LL(pinmodel->ofsEnd);
	mod->dataSize += size;
	mod->md3[lod] = (md3Header_t *)ri.Hunk_Alloc( size );

	memcpy (mod->md3[lod], buffer, LL(pinmodel->ofsEnd) );

    LL(mod->md3[lod]->ident);
    LL(mod->md3[lod]->version);
    LL(mod->md3[lod]->numFrames);
    LL(mod->md3[lod]->numTags);
    LL(mod->md3[lod]->numSurfaces);
    LL(mod->md3[lod]->ofsFrames);
    LL(mod->md3[lod]->ofsTags);
    LL(mod->md3[lod]->ofsSurfaces);
    LL(mod->md3[lod]->ofsEnd);

	if ( mod->md3[lod]->numFrames < 1 ) {
		ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has no frames\n", mod_name );
		return qfalse;
	}
    
	// swap all the frames
    frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames );
    for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) {
    	frame->radius = LF( frame->radius );
        for ( j = 0 ; j < 3 ; j++ ) {
            frame->bounds[0][j] = LF( frame->bounds[0][j] );
            frame->bounds[1][j] = LF( frame->bounds[1][j] );
	    	frame->localOrigin[j] = LF( frame->localOrigin[j] );
        }
	}

	// swap all the tags
    tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags );
    for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) {
        for ( j = 0 ; j < 3 ; j++ ) {
			tag->origin[j] = LF( tag->origin[j] );
			tag->axis[0][j] = LF( tag->axis[0][j] );
			tag->axis[1][j] = LF( tag->axis[1][j] );
			tag->axis[2][j] = LF( tag->axis[2][j] );
        }
	}

	// swap all the surfaces
	surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces );
	for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) {

        LL(surf->ident);
        LL(surf->flags);
        LL(surf->numFrames);
        LL(surf->numShaders);
        LL(surf->numTriangles);
        LL(surf->ofsTriangles);
        LL(surf->numVerts);
        LL(surf->ofsShaders);
        LL(surf->ofsSt);
        LL(surf->ofsXyzNormals);
        LL(surf->ofsEnd);
		
		if ( surf->numVerts > (bQ3RulesApply?SHADER_MAX_VERTEXES:ACTUAL_SHADER_MAX_VERTEXES) ) {
			ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
				mod_name, (bQ3RulesApply?SHADER_MAX_VERTEXES:ACTUAL_SHADER_MAX_VERTEXES), surf->numVerts );
		}
		if ( surf->numTriangles*3 > (bQ3RulesApply?SHADER_MAX_INDEXES:ACTUAL_SHADER_MAX_INDEXES) ) {
			ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
				mod_name, (bQ3RulesApply?SHADER_MAX_INDEXES:ACTUAL_SHADER_MAX_INDEXES) / 3, surf->numTriangles );
		}
	
		// change to surface identifier
		surf->ident = SF_MD3;

		// lowercase the surface name so skin compares are faster
		Q_strlwr( surf->name );

		// strip off a trailing _1 or _2
		// this is a crutch for q3data being a mess
		j = strlen( surf->name );
		if ( j > 2 && surf->name[j-2] == '_' ) {
			surf->name[j-2] = 0;
		}

        // register the shaders
        shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders );
        for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) {
//            shader_t	*sh;

			shader->shaderIndex = Texture_Load(shader->name);
        }

		// swap all the triangles
		tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
		for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
			LL(tri->indexes[0]);
			LL(tri->indexes[1]);
			LL(tri->indexes[2]);
		}

		// swap all the ST
        st = (md3St_t *) ( (byte *)surf + surf->ofsSt );
        for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
            st->st[0] = LF( st->st[0] );
            st->st[1] = LF( st->st[1] );
        }

		// swap all the XyzNormals
        xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals );
        for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) 
		{
            xyz->xyz[0] = LS( xyz->xyz[0] );
            xyz->xyz[1] = LS( xyz->xyz[1] );
            xyz->xyz[2] = LS( xyz->xyz[2] );

            xyz->normal = LS( xyz->normal );
        }


		// find the next surface
		surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd );
	}
    
	return qtrue;
}



/*
=================
R_LoadMD4
=================
*/
static qboolean R_LoadMD4( model_t *mod, void *buffer, const char *mod_name ) {
	int					i, j, k;
	md4Header_t			*pinmodel, *md4;
    md4Frame_t			*frame;
	md4LOD_t			*lod;
	md4Surface_t		*surf;
	md4Triangle_t		*tri;
	md4Vertex_t			*v;
	int					version;
	int					size;
//	shader_t			*sh;
	int					frameSize;

	pinmodel = (md4Header_t *)buffer;

	version = LL (pinmodel->version);
	if (version != MD4_VERSION) {
		ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has wrong version (%i should be %i)\n",
				 mod_name, version, MD4_VERSION);
		return qfalse;
	}

	mod->type = MOD_MD4;
	size = LL(pinmodel->ofsEnd);
	mod->dataSize += size;
	md4 = mod->md4 = (md4Header_t *)ri.Hunk_Alloc( size );

	memcpy( md4, buffer, LL(pinmodel->ofsEnd) );

    LL(md4->ident);
    LL(md4->version);
    LL(md4->numFrames);
    LL(md4->numBones);
    LL(md4->numLODs);
    LL(md4->ofsFrames);
    LL(md4->ofsLODs);
    LL(md4->ofsEnd);

	if ( md4->numFrames < 1 ) {
		ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has no frames\n", mod_name );
		return qfalse;
	}

    // we don't need to swap tags in the renderer, they aren't used
    
	// swap all the frames
	frameSize = (int)( &((md4Frame_t *)0)->bones[ md4->numBones ] );
    for ( i = 0 ; i < md4->numFrames ; i++, frame++) {
	    frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize );
    	frame->radius = LF( frame->radius );
        for ( j = 0 ; j < 3 ; j++ ) {
            frame->bounds[0][j] = LF( frame->bounds[0][j] );
            frame->bounds[1][j] = LF( frame->bounds[1][j] );
	    	frame->localOrigin[j] = LF( frame->localOrigin[j] );
        }
		for ( j = 0 ; j < md4->numBones * sizeof( md4Bone_t ) / 4 ; j++ ) {
			((float *)frame->bones)[j] = LF( ((float *)frame->bones)[j] );
		}
	}

	// swap all the LOD's
	lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs );
	for ( i = 0 ; i < md4->numLODs ; i++) {

		// swap all the surfaces
		surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces );
		for ( i = 0 ; i < lod->numSurfaces ; i++) {
			LL(surf->ident);
			LL(surf->numTriangles);
			LL(surf->ofsTriangles);
			LL(surf->numVerts);
			LL(surf->ofsVerts);
			LL(surf->ofsEnd);
			
			if ( surf->numVerts > (bQ3RulesApply?SHADER_MAX_VERTEXES:ACTUAL_SHADER_MAX_VERTEXES) ) {
				ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
					mod_name, (bQ3RulesApply?SHADER_MAX_VERTEXES:ACTUAL_SHADER_MAX_VERTEXES), surf->numVerts );
			}
			if ( surf->numTriangles*3 > (bQ3RulesApply?SHADER_MAX_INDEXES:ACTUAL_SHADER_MAX_INDEXES) ) {
				ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
					mod_name, (bQ3RulesApply?SHADER_MAX_INDEXES:ACTUAL_SHADER_MAX_INDEXES) / 3, surf->numTriangles );
			}

			// register the shaders
			surf->shaderIndex = Texture_Load(surf->shader);

			// swap all the triangles
			tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
			for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
				LL(tri->indexes[0]);
				LL(tri->indexes[1]);
				LL(tri->indexes[2]);
			}

			// swap all the vertexes
			v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts );
			for ( j = 0 ; j < surf->numVerts ; j++ ) {
				v->normal[0] = LF( v->normal[0] );
				v->normal[1] = LF( v->normal[1] );
				v->normal[2] = LF( v->normal[2] );

				v->texCoords[0] = LF( v->texCoords[0] );
				v->texCoords[1] = LF( v->texCoords[1] );

				v->numWeights = LL( v->numWeights );

				for ( k = 0 ; k < v->numWeights ; k++ ) {
					v->weights[k].boneIndex = LL( v->weights[k].boneIndex );
					v->weights[k].boneWeight = LF( v->weights[k].boneWeight );
				   v->weights[k].offset[0] = LF( v->weights[k].offset[0] );
				   v->weights[k].offset[1] = LF( v->weights[k].offset[1] );
				   v->weights[k].offset[2] = LF( v->weights[k].offset[2] );
				}
				v = (md4Vertex_t *)&v->weights[v->numWeights];
			}

			// find the next surface
			surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd );
		}

		// find the next LOD
		lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd );
	}

	return qtrue;
}




//=============================================================================

/*
void RE_BeginRegistration( glconfig_t *glconfigOut ) {
	ri.Hunk_Clear();

	R_Init();
	*glconfigOut = glConfig;

	R_SyncRenderThread();

	tr.viewCluster = -1;		// force markleafs to regenerate
	R_ClearFlares();
	RE_ClearScene();

	tr.registered = qtrue;
}
*/
//=============================================================================


/*
void R_Modellist_f( void ) {
	int		i, j;
	model_t	*mod;
	int		total;
	int		lods;

	total = 0;
	for ( i = 1 ; i < tr.numModels; i++ ) {
		mod = tr.models[i];
		if (mod->mdxm || mod->mdxa)
		{
			ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, mod->numLods, mod->name );
		}
		else
		{
			lods = 1;
			for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) {
				if ( mod->md3[j] && mod->md3[j] != mod->md3[j-1] ) {
					lods++;
				}
			}
			ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name );
		}
		total += mod->dataSize;
	}
	ri.Printf( PRINT_ALL, "%8i : Total models\n", total );

#if	0		// not working right with new hunk
	if ( tr.world ) {
		ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name );
	}
#endif
}

//=============================================================================


static md3Tag_t *R_GetTag( md3Header_t *mod, int frame, const char *tagName ) {
	md3Tag_t		*tag;
	int				i;

	if ( frame >= mod->numFrames ) {
		// it is possible to have a bad frame while changing models, so don't error
		frame = mod->numFrames - 1;
	}

	tag = (md3Tag_t *)((byte *)mod + mod->ofsTags) + frame * mod->numTags;
	for ( i = 0 ; i < mod->numTags ; i++, tag++ ) {
		if ( !strcmp( tag->name, tagName ) ) {
			return tag;	// found it
		}
	}

	return NULL;
}

void	R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, 
					 float frac, const char *tagName ) {
	md3Tag_t	*start, *end;
	int		i;
	float		frontLerp, backLerp;
	model_t		*model;

	model = R_GetModelByHandle( handle );
	if ( !model->md3[0] ) {
		AxisClear( tag->axis );
		VectorClear( tag->origin );
		return;
	}

	start = R_GetTag( model->md3[0], startFrame, tagName );
	end = R_GetTag( model->md3[0], endFrame, tagName );
	if ( !start || !end ) {
		AxisClear( tag->axis );
		VectorClear( tag->origin );
		return;
	}

	frontLerp = frac;
	backLerp = 1.0 - frac;

	for ( i = 0 ; i < 3 ; i++ ) {
		tag->origin[i] = start->origin[i] * backLerp +  end->origin[i] * frontLerp;
		tag->axis[0][i] = start->axis[0][i] * backLerp +  end->axis[0][i] * frontLerp;
		tag->axis[1][i] = start->axis[1][i] * backLerp +  end->axis[1][i] * frontLerp;
		tag->axis[2][i] = start->axis[2][i] * backLerp +  end->axis[2][i] * frontLerp;
	}
	VectorNormalize( tag->axis[0] );
	VectorNormalize( tag->axis[1] );
	VectorNormalize( tag->axis[2] );
}


void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) {
	model_t		*model;
	md3Header_t	*header;
	md3Frame_t	*frame;

	model = R_GetModelByHandle( handle );

	if ( model->bmodel ) {
		VectorCopy( model->bmodel->bounds[0], mins );
		VectorCopy( model->bmodel->bounds[1], maxs );
		return;
	}

	if ( !model->md3[0] ) {
		VectorClear( mins );
		VectorClear( maxs );
		return;
	}

	header = model->md3[0];

	frame = (md3Frame_t *)( (byte *)header + header->ofsFrames );

	VectorCopy( frame->bounds[0], mins );
	VectorCopy( frame->bounds[1], maxs );
}
*/


void OnceOnlyCrap(void)
{
	ri.Hunk_Alloc				= Crap_HunkAlloc;
	
	ri.Hunk_AllocateTempMemory	= Crap_HunkAlloc;
	ri.Hunk_FreeTempMemory		= Crap_Free;
	
	ri.Malloc					= Crap_Malloc;
	ri.Free						= Crap_Free;	

	ri.Printf					= Crap_Printf;
	ri.Error					= Crap_Error;

	ri.FS_ReadFile				= Crap_FS_ReadFile;
	ri.FS_WriteFile				= Crap_FS_WriteFile;
	ri.FS_FreeFile				= Crap_FS_FreeFile;

	ZEROMEM(tr);
	ZEROMEM(tess);

	R_ModelInitOnceOnly();
}




void trap_G2_SurfaceOffList( int a, void *b)
{
	G2_GetSurfaceList( (qhandle_t) a, (surfaceInfo_t *) b);
}

qboolean trap_G2_SetSurfaceOnOff (qhandle_t model, surfaceInfo_t *slist, const char *surfaceName, const SurfaceOnOff_t offFlags, const int surface)
{
	return G2_SetSurfaceOnOff(model, slist, surfaceName, offFlags, surface);
}

SurfaceOnOff_t trap_G2_IsSurfaceOff (qhandle_t model, surfaceInfo_t *slist, const char *surfaceName)
{
	return G2_IsSurfaceOff (model, slist, surfaceName);
}

void trap_G2_Init_Bone_List(void *a)
{
	G2_Init_Bone_List((boneInfo_t *)a);
}

qboolean trap_G2_Set_Bone_Anim(int a, void *b, void *c, int d, int e, int f, float g)
{
	return G2_Set_Bone_Anim((qhandle_t) a, (boneInfo_t *) b, (char *) c, d, e, f, g);
}



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

