//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/sinmdl.c                                               $
// $Revision:: 4                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 2/5/02 7:39 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// sin model loading and caching
//


#ifdef SINED
#include "qe3.h"
#include <assert.h>
#else
#include "q_shared.h"
#include "qcommon.h"
#include "alias.h"
#endif
#include "sinmdl.h"

#if 0
static float FixFloat( float *in )
{
	if ((Word32)in&3) {
		Halt();
	}
	return LittleFloat( in[0] );
}
#else
#define FixFloat( a ) LittleFloat((a)[0] )
#endif
/*
==============================================================================

SIN BASE MODELS and ANIMATIONS

==============================================================================
*/
#define MAX_MODEL_PATH_LENGTH 56

// MED FIXME HACK make this more dynamic
#define MAX_BASEMODEL_CACHE_SIZE 512
#define MAX_ANIM_CACHE_SIZE 2048
#define MAX_DEF_CACHE_SIZE 512

typedef struct
{
	char path[MAX_MODEL_PATH_LENGTH];
	dsinbasemdl_t * basemodel;
	int size;
	int numuses;
} basemdlcache_t;

static int cache_numbasemodels = 0;
static int cache_maxbasemodels = -1;
static basemdlcache_t basecache[MAX_BASEMODEL_CACHE_SIZE];

typedef struct
{
	char path[MAX_MODEL_PATH_LENGTH];
	dsinanim_t * anim;
	int size;
	int numuses;
} animcache_t;

static int cache_numanims = 0;
static int cache_maxanims = -1;
static animcache_t animcache[MAX_ANIM_CACHE_SIZE];

typedef struct
{
	char path[MAX_MODEL_PATH_LENGTH];
	int size;
	dsinmdl_t * def;
} defcache_t;

static int cache_numdefs = 0;
static int cache_maxdefs = -1;
static defcache_t defcache[MAX_DEF_CACHE_SIZE];

//
// frame and init cmds
//
#define MAX_SINMDL_CMD_STRUCTS 16
static sinmdl_cmd_t sinmdl_cmds[MAX_SINMDL_CMD_STRUCTS];
static int sinmdl_cmd_index;

/*
=================
SINMDL_FindBaseModel
=================
*/
static basemdlcache_t * SINMDL_FindBaseModel( const char * path )
{
	int i;
	int num = cache_numbasemodels;
	basemdlcache_t * cache;

	for( i=0, cache = &basecache[0]; i<MAX_BASEMODEL_CACHE_SIZE; i++, cache++ )
	{
		if (!cache->basemodel)
			continue;
		if (!Q_stricmp ( cache->path, path ) )
			return cache;
		if (!num--)
			break;
	}
	return NULL;
}

/*
=================
SINMDL_FindFreeBaseModel
=================
*/
static basemdlcache_t * SINMDL_FindFreeBaseModel( void )
{
	int i;
	basemdlcache_t * cache;

	for( i=0, cache = &basecache[0]; i<MAX_BASEMODEL_CACHE_SIZE; i++, cache++ )
	{
		if (!cache->basemodel)
			return cache;
	}
	return NULL;
}

/*
=================
SINMDL_FreeBaseModel
=================
*/
static void SINMDL_FreeBaseModel( int index )
{

	if (index < 0 || index > cache_maxbasemodels)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeBaseModel: handle %d out of range\n",index);
		return;
	}
	if (basecache[index].basemodel)
	{
		Z_Free( basecache[index].basemodel );
		basecache[index].basemodel = NULL;
		cache_numbasemodels--;
	}
	else
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeBaseModel: NULL pointer for %s\n",basecache[index].path);
		return;
	}
}


/*
=================
SINMDL_FindAnim
=================
*/
static animcache_t * SINMDL_FindAnim( const char * path )
{
	int i;
	int num = cache_numanims;
	animcache_t * cache;

	for( i=0, cache = &animcache[0]; i<MAX_ANIM_CACHE_SIZE; i++, cache++ )
	{
		if (!cache->anim)
			continue;
		if (!Q_stricmp ( cache->path, path ) )
			return cache;
		if (!num--)
			break;
	}
	return NULL;
}

/*
=================
SINMDL_FindFreeAnim
=================
*/
static animcache_t * SINMDL_FindFreeAnim( void )
{
	int i;
	animcache_t * cache;

	for( i=0, cache = &animcache[0]; i<MAX_ANIM_CACHE_SIZE; i++, cache++ )
	{
		if (!cache->anim)
			return cache;
	}
	return NULL;
}

/*
=================
SINMDL_FreeAnim
=================
*/
static void SINMDL_FreeAnim( int index )
{
	if (index < 0 || index > cache_maxanims)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeAnim: handle %d out of range\n",index);
		return;
	}
	if (animcache[index].anim)
	{
		Z_Free( animcache[index].anim );
		animcache[index].anim = NULL;
		cache_numanims--;
	}
	else
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeAnim: NULL pointer for %s\n",animcache[index].path);
		return;
	}
}

/*
=================
SINMDL_LoadAnim
=================
*/
static qboolean SINMDL_LoadAnim( const char * path, animcache_t * cache )
{
	int i, j;
	dsintriframe_t *pinframe, *poutframe;
	dsinanim_t     *pinmodel, *pheader;
	dtrivertx_t    *pinvert, *poutvert;
	int version;
	unsigned header;
	void *buffer;

	//
	// load the file
	//
	FS_LoadFile ( path, &buffer );
	if (!buffer)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadAnim: Could not load %s\n", path);
		return false;
	}

	// clear out the cache
	memset( cache, 0, sizeof( *cache ) );
	// copy over the path name
	strncpy( cache->path, path, sizeof(cache->path)-1 );
	pinmodel = (dsinanim_t *)buffer;

	// check out the header
	header = LittleLong( pinmodel->ident );
	if (header != SINANIMMODEL_HEADER)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "anim %s has wrong header (%i should be %i)\n",
		             path, header, SINANIMMODEL_HEADER);
		return false;
	}

	version = LittleLong ( pinmodel->version );
	if (version != SINANIMMODEL_VERSION)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "anim %s has wrong version (%i should be %i)\n",
		             path, version, SINANIMMODEL_VERSION);
		return false;
	}

	i = LittleLong( pinmodel->ofs_end );
	pheader = (dsinanim_t*)Z_Malloc( i );
	cache->size = i;
//	Com_DPrintf(DEVELOPER_MSG_SINMDL, "anim %s using %d memory.\n", path, LittleLong(pinmodel->ofs_end));

	// byte swap the header fields and sanity check
	pheader->ident = LittleLong( pinmodel->ident );
	pheader->version = LittleLong( pinmodel->version );
	strcpy( pheader->name, pinmodel->name );
	pheader->num_xyz = LittleLong( pinmodel->num_xyz );
	pheader->num_frames = LittleLong( pinmodel->num_frames );
	pheader->ofs_frames = LittleLong( pinmodel->ofs_frames );
	for (i=0; i<3; i++)
	{
		pheader->scale[i] = FixFloat( &pinmodel->scale[i] );
		pheader->translate[i] = FixFloat( &pinmodel->translate[i] );
		pheader->totaldelta[i] = FixFloat( &pinmodel->totaldelta[i] );
	}
	pheader->totaltime = FixFloat( &pinmodel->totaltime );

	if (pheader->num_frames <= 0)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "anim %s has no frames\n", path);
		return false;
	}
//
// load the frames
//
	pinframe = (dsintriframe_t *) ((byte *)pinmodel + pheader->ofs_frames);
	poutframe = (dsintriframe_t *) ((byte *)pheader + pheader->ofs_frames);
	for (i=0; i<pheader->num_frames; i++, pinframe++, poutframe++)
	{
		poutframe->frametime = FixFloat ( &pinframe->frametime );
		poutframe->frametime = pheader->totaltime / pheader->num_frames;
		poutframe->ofs_verts = LittleLong ( pinframe->ofs_verts );
		for (j=0; j<3; j++)
		{
			poutframe->movedelta[j] = FixFloat ( &pinframe->movedelta[j] );
			poutframe->scale[j] = FixFloat( &pinframe->scale[j] );
			poutframe->translate[j] = FixFloat( &pinframe->translate[j] );
		}
		// verts are all 8 bit, so no swapping needed
		pinvert = (dtrivertx_t *) ((byte *)pinframe + poutframe->ofs_verts);
		poutvert = (dtrivertx_t *) ((byte *)poutframe + poutframe->ofs_verts);
		memcpy ( poutvert, pinvert, pheader->num_xyz * sizeof( dtrivertx_t ) );
	}
	cache->anim = pheader;
	cache_numanims++;
	// free up the buffer
	FS_FreeFile ( buffer );
	return true;
}

/*
=================
SINMDL_LoadBaseModel
=================
*/
static qboolean SINMDL_LoadBaseModel( const char * path, basemdlcache_t * cache )
{
	int i, j, g;
	dsinbasemdl_t  *pinmodel, *pheader;
	dsinstvert_t   *pinst, *poutst;
	dsintriangle_t *pintri, *pouttri;
	int            *pincmd, *poutcmd;
	dsintrigroup_t *pintrigroup, *pouttrigroup;
	int version;
	unsigned header;
	void *buffer;

	//
	// load the file
	//
	FS_LoadFile ( path, &buffer );
	if (!buffer) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadBaseModel: Could not load %s\n", path);
		return false;
	}

	// clear out the cache
	memset( cache, 0, sizeof( *cache ) );
	// copy over the path name
	strncpy( cache->path, path, sizeof(cache->path)-1 );
	pinmodel = (dsinbasemdl_t *)buffer;

	// check out the header
	header = LittleLong( pinmodel->ident );
	if (header != SINBASEMODEL_HEADER) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "base model %s has wrong header (%i should be %i)\n",
		             path, header, SINBASEMODEL_HEADER);
		return false;
	}

	version = LittleLong ( pinmodel->version );
	if (version != SINBASEMODEL_VERSION) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "base model %s has wrong version (%i should be %i)\n",
		             path, version, SINBASEMODEL_VERSION);
		return false;
	}

	i = LittleLong( pinmodel->ofs_end );
	pheader = (dsinbasemdl_t *)Z_Malloc( i );
	cache->size = i;
	//Com_DPrintf(DEVELOPER_MSG_SINMDL, "base model %s using %d memory.\n", path, LittleLong(pinmodel->ofs_end));

	// byte swap the header fields and sanity check
	pheader->ident = LittleLong( pinmodel->ident );
	pheader->version = LittleLong( pinmodel->version );
	pheader->num_xyz = LittleLong( pinmodel->num_xyz );
	pheader->num_st = LittleLong( pinmodel->num_st );
	pheader->num_groups = LittleLong( pinmodel->num_groups );
	pheader->ofs_st = LittleLong( pinmodel->ofs_st );

	if (pheader->num_xyz <= 0) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "model %s has no vertices\n", path);
		return false;
	}

	if (pheader->num_xyz > MAX_VERTS) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "model %s has too many vertices\n", path);
		return false;
	}

	if (pheader->num_st <= 0) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "model %s has no st vertices\n", path);
		return false;
	}
//
// load base s and t vertices (not used in gl version)
//
	pinst = (dsinstvert_t *) ((byte *)pinmodel + pheader->ofs_st);
	poutst = (dsinstvert_t *) ((byte *)pheader + pheader->ofs_st);

//
// edge in the texture coordinates because of software
//
	for (i=0; i<pheader->num_st; i++) {
		poutst[i].s = FixFloat ( &pinst[i].s );
		if (poutst[i].s < 0)
			poutst[i].s = 0;
		if (poutst[i].s > 0.995f)
			poutst[i].s = 0.995f;
		poutst[i].t = FixFloat ( &pinst[i].t );
		if (poutst[i].t < 0)
			poutst[i].t = 0;
		if (poutst[i].t > 0.995f)
			poutst[i].t = 0.995f;
	}

//
// go throught the groups
//
	for (g=0; g<pheader->num_groups; g++)
	{
		pintrigroup = &pinmodel->groups[g];
		pouttrigroup = &pheader->groups[g];

		// swap some stuff
		pouttrigroup->id = LittleLong( pintrigroup->id );
		pouttrigroup->num_tris = LittleLong( pintrigroup->num_tris );
		pouttrigroup->num_glcmds = LittleLong( pintrigroup->num_glcmds );
		pouttrigroup->ofs_tris = LittleLong( pintrigroup->ofs_tris );
		pouttrigroup->ofs_glcmds = LittleLong( pintrigroup->ofs_glcmds );
		//
		// load triangle lists
		//
		pintri = (dsintriangle_t *) ((byte *)pintrigroup + pouttrigroup->ofs_tris);
		pouttri = (dsintriangle_t *) ((byte *)pouttrigroup + pouttrigroup->ofs_tris);

		for (i=0; i</*pintrigroup*/ pouttrigroup->num_tris; i++)
		{
			for (j=0; j<3; j++)
			{
				pouttri[i].index_xyz[j] = LittleShort ( pintri[i].index_xyz[j] );
				pouttri[i].index_st[j] = LittleShort ( pintri[i].index_st[j] );
			}
			pouttri[i].id = LittleLong ( pintri[i].id );
		}
		//
		// load the glcmds
		//
		pincmd = (int *) ((byte *)pintrigroup + pouttrigroup->ofs_glcmds);
		poutcmd = (int *) ((byte *)pouttrigroup + pouttrigroup->ofs_glcmds);
		for (i=0; i</*pintrigroup*/ pouttrigroup->num_glcmds; i++)
			poutcmd[i] = LittleLong ( pincmd[i] );
	}
	cache->basemodel = pheader;
	cache_numbasemodels++;
	// free up the buffer
	FS_FreeFile ( buffer );
	return true;
}

/*
=================
SINMDL_GetAnim
=================
*/
dsinanim_t * SINMDL_GetAnim( int index )
{
	if (index < 0 || index > cache_maxanims)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_GetAnim: handle %d out of range\n",index);
		return NULL;
	}
	if (!animcache[index].anim)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_GetAnim: NULL pointer for %s\n",animcache[index].path);
		return NULL;
	}
	else
		return animcache[index].anim;
}

/*
=================
SINMDL_GetBaseModel
=================
*/
dsinbasemdl_t * SINMDL_GetBaseModel( int index )
{
	if (index < 0 || index > cache_maxbasemodels)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_GetBaseModel: handle %d out of range\n",index);
		return NULL;
	}
	if (!basecache[index].basemodel)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_GetBaseModel: NULL pointer for %s\n",basecache[index].path);
		return NULL;
	}
	else
		return basecache[index].basemodel;
}

/*
=================
SINMDL_RegisterAnim
=================
*/
int SINMDL_RegisterAnim( const char * path )
{
	int index;
	animcache_t * cache;

	cache = SINMDL_FindAnim ( path );
	if (!cache)
	{
		cache = SINMDL_FindFreeAnim();
		if (!cache)
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_RegisterAnim: No free spots open in anim cache for %s\n",path);
			return -1;
		}
		cache->numuses = 0;
		index = cache-animcache;
		if (index > cache_maxanims)
			cache_maxanims = index;
		if (!SINMDL_LoadAnim( path, cache ))
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_RegisterAnim: LoadAnim failed for %s\n",path);
			return -1;
		}
	}
	// return index to this animcache
	return (cache - animcache);
}

/*
=================
SINMDL_RegisterBaseModel
=================
*/
int SINMDL_RegisterBaseModel( const char * path )
{
	basemdlcache_t * cache;
	int index;

	cache = SINMDL_FindBaseModel ( path );
	if (!cache)
	{
		cache = SINMDL_FindFreeBaseModel();
		if (!cache)
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_RegisterBaseModel: No free spots open in anim cache for %s\n",path);
			return -1;
		}
		cache->numuses = 0;
		index = cache-basecache;
		if (index > cache_maxbasemodels)
			cache_maxbasemodels = index;
		if (!SINMDL_LoadBaseModel( path, cache ))
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_RegisterBaseModel: LoadBaseModel failed for %s\n",path);
			return -1;
		}
	}
	// return index to this basecache
	return (cache - basecache);
}

/*
==============================================================================

SIN DEF FILES

==============================================================================
*/
#define MAX_DEF_ARGS 32
#define MAX_DEF_ARG_LENGTH 64
#define MAX_DEF_LOAD_ANIMS 192
#define MAX_DEF_LOAD_SKINS 16
#define MAX_DEF_LOAD_GROUPS 24
#define MAX_DEF_LOAD_FRAMECMD_ARGS (MAX_ARGS_PER_CMD)
#define MAX_DEF_LOAD_FRAMECMD_ARG_LENGTH 48
#define MAX_DEF_LOAD_FRAMECMDS 192
#define MAX_DEF_LOAD_BONES 32

typedef struct
{
	int anim_num;
	int frame_num;
	int num_args;
	char args[MAX_DEF_LOAD_FRAMECMD_ARGS][MAX_DEF_LOAD_FRAMECMD_ARG_LENGTH];
} dloadcmd_t;

typedef struct
{
	char args[MAX_DEF_ARGS][MAX_DEF_ARG_LENGTH];
	dsinanimdef_t loadanims[MAX_DEF_LOAD_ANIMS];
	dsinskin_t loadskins[MAX_DEF_LOAD_SKINS];
	dsingroup_t loadgroups[MAX_DEF_LOAD_GROUPS];
	dloadcmd_t loadservercmds[MAX_DEF_LOAD_FRAMECMDS];
	dloadcmd_t loadclientcmds[MAX_DEF_LOAD_FRAMECMDS];
	dloadcmd_t loadserverinitcmds[MAX_DEF_LOAD_FRAMECMDS];
	dloadcmd_t loadclientinitcmds[MAX_DEF_LOAD_FRAMECMDS];
	dbone_t loadbones[MAX_DEF_LOAD_BONES];
	int processed[MAX_DEF_LOAD_ANIMS];
	int numanims;
	int numskins;
	int numgroups;
	int numservercmds;
	int numclientcmds;
	int numserverinitcmds;
	int numclientinitcmds;
	int numbones;
	int loadbasehandle;
	float load_scale;
	int numargs;
	int argnum;
	vec3_t load_origin;
	byte *buffer;
	char com_token[MAX_STRING_CHARS];
} dloaddef_t;

/*
=================
SINMDL_FindDef
=================
*/
static defcache_t * SINMDL_FindDef( const char * path )
{
	int i;
	defcache_t * cache;
	int num = cache_numdefs;

	for( i=0, cache = &defcache[0]; i<MAX_DEF_CACHE_SIZE; i++, cache++ )
	{
		if (!cache->def)
			continue;
		if (!Q_stricmp ( cache->path, path ) )
			return cache;
		if (!num--)
			break;
	}
	return NULL;
}

/*
=================
SINMDL_FindFreeDef
=================
*/
static defcache_t * SINMDL_FindFreeDef( void )
{
	int i;
	defcache_t * cache;

	for( i=0, cache = &defcache[0]; i<MAX_DEF_CACHE_SIZE; i++, cache++ )
	{
		if (!cache->def)
			return cache;
	}
	return NULL;
}

/*
============
SINMDL_ExtractPath
============
*/
static void SINMDL_ExtractPath( const char *in, char *out )
{
	const char *s;
	int len;

	s = in + strlen( in ) - 1;

	while (s != in && *s != '/' && *s != '\\')
		s--;

	len = s-in+1;
	if (len<2)
	{
		strcpy( out,"?model?" );
	}
	else
	{
		memcpy( out,in,len );
		out[len] = 0;
	}
}

/*
=================
SINMDL_LoadDEFInitCmd
=================
*/
static void SINMDL_LoadDEFInitCmd( dloaddef_t *ld, const char * path )
{
	dloadcmd_t *pclientcmd;
	dloadcmd_t *pservercmd;
	int i, startcmd;
	qboolean clientcmd;
	qboolean servercmd;

	ld->numserverinitcmds = 0;
	ld->numclientinitcmds = 0;
	// grab each line as we go
	while ( 1 )
	{
		ld->numargs = 0;
		// grab each line as we go
		strcpy( ld->com_token, COM_GetToken( (const char **)&ld->buffer, true ) );
		if (!ld->com_token[0])
			break;
		// if we reached the end of the init section, return
		if (ld->com_token[0] == '!')
			return;
		strcpy( ld->args[ld->numargs++], ld->com_token );
		// get the rest of the line
		while( 1 )
		{
			strcpy( ld->com_token, COM_GetToken( (const char **)&ld->buffer, false ) );
			if (!ld->com_token[0])
				break;
			if (strlen( ld->com_token ) >= MAX_DEF_ARG_LENGTH)
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: argument too long, truncating in %s.\n", path);
				ld->com_token[MAX_DEF_ARG_LENGTH - 1] = 0;
			}
			strcpy( ld->args[ld->numargs], ld->com_token );
			if ( ld->numargs < MAX_DEF_ARGS )
				ld->numargs++;
			else
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many args in %s\n", path);
		}
		//
		// find out what kind of command this is
		//
		pclientcmd = NULL;
		pservercmd = NULL;
		clientcmd = true;
		servercmd = true;
		startcmd = 0;
		if ( !Q_stricmp( ld->args[0], "client" ) )
		{
			startcmd = 1;
			servercmd = false;
		}
		else if ( !Q_stricmp( ld->args[0], "server" ) )
		{
			startcmd = 1;
			// 2015 code
			if ( Q_stricmp( ld->args[1], "aliascache" ) )
				clientcmd = false;
		}
		// now process the cmd
		if (clientcmd)
		{
			pclientcmd = &ld->loadclientinitcmds[ld->numclientinitcmds];
			if ( ld->numclientinitcmds == MAX_DEF_LOAD_FRAMECMDS )
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many client init cmds for %s\n", path);
				continue;
			}
			ld->numclientinitcmds++;
			pclientcmd->num_args = ld->numargs - startcmd;
			if (pclientcmd->num_args > MAX_DEF_LOAD_FRAMECMD_ARGS)
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many arguments for init cmd %s in %s\n", ld->args[startcmd], path);
				pclientcmd->num_args = MAX_DEF_LOAD_FRAMECMD_ARGS;
			}
		}
		if (servercmd)
		{
			pservercmd = &ld->loadserverinitcmds[ld->numserverinitcmds];
			if ( ld->numserverinitcmds == MAX_DEF_LOAD_FRAMECMDS )
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many server init cmds for %s\n", path);
				continue;
			}
			ld->numserverinitcmds++;
			pservercmd->num_args = ld->numargs - startcmd;
			if (pservercmd->num_args > MAX_DEF_LOAD_FRAMECMD_ARGS)
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many arguments for init cmd %s in %s\n", ld->args[startcmd], path);
				pservercmd->num_args = MAX_DEF_LOAD_FRAMECMD_ARGS;
			}
		}

		// add the rest of the command
		for (i=startcmd; i<ld->numargs; i++)
		{
			int index;

			index = ( i - startcmd );
			if (pclientcmd)
			{
				if ( index < pclientcmd->num_args )
					strcpy( pclientcmd->args[index], ld->args[i] );
			}
			if (pservercmd)
			{
				if ( index < pservercmd->num_args )
					strcpy( pservercmd->args[index], ld->args[i] );
			}
		}
	}
}

/*
============
SINMDL_LoadDEFAnimCmds
============
*/
static void SINMDL_LoadDEFAnimCmds( dloaddef_t *ld, const char * path )
{
	dloadcmd_t *pclientcmd;
	dloadcmd_t *pservercmd;
	int animnum;
	int framenum;
	dsinanim_t *curanim;
	int i, startcmd;
	qboolean clientcmd;
	qboolean servercmd;

	ld->numservercmds = 0;
	ld->numclientcmds = 0;
	if (ld->buffer && !Q_stricmp ( ld->com_token, "!main:" ) )
	{
		// load in the all the rest of the commands
		while (1)
		{
			ld->numargs = 0;
			// grab each line as we go
			strcpy( ld->com_token, COM_GetToken( (const char **)&ld->buffer, true ) );
			if (!ld->com_token[0])
				break;
			// if we reached the end of the main section, return
			if (ld->com_token[0] == '!')
				return;
			strcpy( ld->args[ld->numargs++], ld->com_token );
			// get the rest of the line
			while( 1 )
			{
				strcpy( ld->com_token, COM_GetToken( (const char **)&ld->buffer, false ) );
				if (!ld->com_token[0])
					break;
				if (strlen( ld->com_token ) >= MAX_DEF_ARG_LENGTH)
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: argument too long, truncating in %s\n", path);
					ld->com_token[MAX_DEF_ARG_LENGTH - 1] = 0;
				}
				strcpy( ld->args[ld->numargs], ld->com_token );
				if ( ld->numargs < MAX_DEF_ARGS )
					ld->numargs++;
				else
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many args in %s\n", path);
			}
			//
			// find out what kind of command this is
			//
			pclientcmd = NULL;
			pservercmd = NULL;
			clientcmd = true;
			servercmd = true;
			startcmd = 0;
			if ( !Q_stricmp( ld->args[0], "client" ) )
			{
				startcmd = 1;
				servercmd = false;
			}
			else if ( !Q_stricmp( ld->args[0], "server" ) )
			{
				startcmd = 1;
				clientcmd = false;
			}
			// now process the cmd
			// find the anim
			for (i=0; i<ld->numanims; i++)
			{
				if (!Q_stricmp( ld->args[startcmd], ld->loadanims[i].alias ) )
					break;
			}
			if (i==ld->numanims)
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: cmd for non-existent animation in %s, cmd is %s %s %s\n",
				             path, ld->args[0], ld->args[1], ld->args[2]);
				continue;
			}
			startcmd++;
			animnum = i;
			curanim = SINMDL_GetAnim( ld->loadanims[animnum].handle );
			// find the frame
			if ( !Q_stricmp( ld->args[startcmd], "start" ) )
				framenum = 0;
			else if ( !Q_stricmp( ld->args[startcmd], "first" ) )
				framenum = 0;
			else if ( !Q_stricmp( ld->args[startcmd], "end" ) )
				framenum = curanim->num_frames - 1;
			else if ( !Q_stricmp( ld->args[startcmd], "last" ) )
				framenum = curanim->num_frames - 1;
			else
				framenum = atol( ld->args[startcmd] );
			if (framenum < 0 || framenum > curanim->num_frames)
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: illegal frame number %d for cmd in %s,\n cmd is %s %s %s %s\n",
				             framenum, path, ld->args[0], ld->args[1], ld->args[2], ld->args[3]);
				continue;
			}
			startcmd++;
			if (clientcmd)
			{
				pclientcmd = &ld->loadclientcmds[ld->numclientcmds];
				if ( ld->numclientcmds == MAX_DEF_LOAD_FRAMECMDS )
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many client cmds for %s\n", path);
					continue;
				}
				ld->numclientcmds++;
				pclientcmd->anim_num = animnum;
				pclientcmd->frame_num = framenum;
				pclientcmd->num_args = ld->numargs - startcmd;
				if (pclientcmd->num_args > MAX_DEF_LOAD_FRAMECMD_ARGS)
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many arguments for cmd %s in %s\n", ld->args[startcmd], path);
					pclientcmd->num_args = MAX_DEF_LOAD_FRAMECMD_ARGS;
				}
			}
			if (servercmd)
			{
				pservercmd = &ld->loadservercmds[ld->numservercmds];
				if ( ld->numservercmds == MAX_DEF_LOAD_FRAMECMDS )
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many server cmds for %s\n", path);
					continue;
				}
				ld->numservercmds++;
				pservercmd->anim_num = animnum;
				pservercmd->frame_num = framenum;
				pservercmd->num_args = ld->numargs - startcmd;
				if (pservercmd->num_args > MAX_DEF_LOAD_FRAMECMD_ARGS)
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many arguments for cmd %s in %s\n", ld->args[startcmd], path);
					pservercmd->num_args = MAX_DEF_LOAD_FRAMECMD_ARGS;
				}
			}

			// add the rest of the command
			for (i=startcmd; i<ld->numargs; i++)
			{
				int index;

				index = ( i - startcmd );
				if (pclientcmd)
				{
					if ( index < pclientcmd->num_args )
						strcpy( pclientcmd->args[index], ld->args[i] );
				}
				if (pservercmd)
				{
					if ( index < pservercmd->num_args )
						strcpy( pservercmd->args[index], ld->args[i] );
				}
			}
		}
	}
}

/*
============
SINMDL_FillDEFStructure
============
*/
static int defsize;
static dsinmdl_t * SINMDL_FillDEFStructure( dloaddef_t *ld, const char * path )
{
	int i,j,k,l,x;
	byte           *ptr;
	dsinmdl_t      *psinmdl;
	dsinanimdef_t  *panim;
	dsincmd_t      *pcmds;
	int size;
	int smallest;
//	dsinanimdef_t  temploadanim;

	// fill in the number of cmds for each anim
	for (i=0; i<ld->numservercmds; i++)
	{
		ld->loadanims[ld->loadservercmds[i].anim_num].num_server_cmds++;
	}
	// fill in the number of client cmds for each anim
	for (i=0; i<ld->numclientcmds; i++)
	{
		ld->loadanims[ld->loadclientcmds[i].anim_num].num_client_cmds++;
	}
	/*
	// we need to sort the animdefs in place according to alias because of random animation issues
	for (i=0; i<ld->numanims; i++)
	{
		smallest = i;
		for (j=i+1; j<ld->numanims; j++)
		{
			if (!Q_stricmp(ld->loadanims[j].alias, ld->loadanims[smallest].alias) < 0)
				smallest = j;
		}
		temploadanim = ld->loadanims[i];
		ld->loadanims[i] = ld->loadanims[smallest];
		ld->loadanims[smallest] = temploadanim;
	}
	*/
	// now that we have all the cmds as well, all we have left to do is to
	// total everything up (memory-wise), malloc the final model,
	// fill in all the offset information, and free the temp space
	defsize = 0;

	// initially lets take the model file plus the additional offsets for the animdefs
	defsize += sizeof( dsinmdl_t );
	defsize += sizeof( int ) * (ld->numanims - 1);
	// add in the size of the initcmd
	defsize += sizeof( dsincmd_t ) * ld->numserverinitcmds;
	defsize += sizeof( dsincmd_t ) * ld->numclientinitcmds;
	// add in the size of all the server initcmds
	for (i=0; i<ld->numserverinitcmds; i++)
	{
		for (j=0; j<ld->loadserverinitcmds[i].num_args; j++)
		{
			defsize += (int)strlen( ld->loadserverinitcmds[i].args[j] ) + 1;
		}
	}
	// add in the size of all the client initcmds
	for (i=0; i<ld->numclientinitcmds; i++)
	{
		for (j=0; j<ld->loadclientinitcmds[i].num_args; j++)
		{
			defsize += (int)strlen( ld->loadclientinitcmds[i].args[j] ) + 1;
		}
	}
	// add the size of the animdefs
	for (i=0; i<ld->numanims; i++)
	{
		defsize += sizeof( dsinanimdef_t );
		defsize += sizeof( dsincmd_t ) * ld->loadanims[i].num_server_cmds;
		defsize += sizeof( dsincmd_t ) * ld->loadanims[i].num_client_cmds;
	}
	// add in the size of the skins
	defsize += sizeof( dsinskin_t ) * ld->numskins;
	// add in the size of the groups
	defsize += sizeof( dsingroup_t ) * ld->numgroups;
	// add in the size of the bones
	defsize += sizeof( dbone_t ) * ld->numbones;
	// add in the size of all the server cmds
	for (i=0; i<ld->numservercmds; i++)
	{
		for (j=0; j<ld->loadservercmds[i].num_args; j++)
		{
			defsize += (int)strlen( ld->loadservercmds[i].args[j] ) + 1;
		}
	}
	// add in the size of all the client cmds
	for (i=0; i<ld->numclientcmds; i++)
	{
		for (j=0; j<ld->loadclientcmds[i].num_args; j++)
		{
			defsize += (int)strlen( ld->loadclientcmds[i].args[j] ) + 1;
		}
	}
	// now that we have a total lets allocate and start filling in the sucker
	// create the memory space needed for the file

	psinmdl = (dsinmdl_t *)Z_Malloc( defsize );
	size = defsize;
//	Com_DPrintf(DEVELOPER_MSG_SINMDL, "def using %d memory.\n", size);

	// clear it out to begin with
	psinmdl->num_anims = ld->numanims;
	psinmdl->num_skins = ld->numskins;
	psinmdl->num_groups = ld->numgroups;
	psinmdl->num_bones = ld->numbones;
	psinmdl->load_scale = ld->load_scale;
	psinmdl->base_handle = ld->loadbasehandle;
	psinmdl->num_client_initcmds = ld->numclientinitcmds;
	psinmdl->num_server_initcmds = ld->numserverinitcmds;
	//
	// copy over the name
	//
	strcpy( psinmdl->name, path );
	//
	// record the fact that we are using this base model
	//
	basecache[psinmdl->base_handle].numuses++;

	VectorCopy( ld->load_origin, psinmdl->load_origin );
	ClearBounds ( psinmdl->mins, psinmdl->maxs );

	// point to the end of this structure
	ptr = (byte *)&psinmdl->ofs_animdefs[psinmdl->num_anims];
	// store the server initcmds
	psinmdl->ofs_server_initcmds = ptr - (byte *)psinmdl;
	pcmds = (dsincmd_t *)ptr;
	// point to the end of the commands
	ptr += sizeof( dsincmd_t ) * psinmdl->num_server_initcmds;
	for (i=0; i<psinmdl->num_server_initcmds; i++)
	{
		pcmds[i].num_args = ld->loadserverinitcmds[i].num_args;
		for (j=0; j<pcmds[i].num_args; j++)
		{
			pcmds[i].ofs_args[j] = ptr - (byte *)&pcmds[i];
			size = (int)strlen( ld->loadserverinitcmds[i].args[j] ) + 1;
			memcpy( ptr, ld->loadserverinitcmds[i].args[j], size - 1 );
			ptr += size;
		}
	}
	// store the client initcmds
	psinmdl->ofs_client_initcmds = ptr - (byte *)psinmdl;
	pcmds = (dsincmd_t *)ptr;
	// point to the end of the commands
	ptr += sizeof( dsincmd_t ) * psinmdl->num_client_initcmds;
	for (i=0; i<psinmdl->num_client_initcmds; i++)
	{
		pcmds[i].num_args = ld->loadclientinitcmds[i].num_args;
		for (j=0; j<pcmds[i].num_args; j++)
		{
			pcmds[i].ofs_args[j] = ptr - (byte *)&pcmds[i];
			size = (int)strlen( ld->loadclientinitcmds[i].args[j] ) + 1;
			memcpy( ptr, ld->loadclientinitcmds[i].args[j], size - 1 );
			ptr += size;
		}
	}
	// store the skins
	psinmdl->ofs_skins = ptr - (byte *)psinmdl;
	size = psinmdl->num_skins * sizeof( dsinskin_t );
	memcpy( ptr, ld->loadskins, size );
	// skip over the skins
	ptr += size;
	// store the groups
	psinmdl->ofs_groups = ptr - (byte *)psinmdl;
	size = psinmdl->num_groups * sizeof( dsingroup_t );
	memcpy( ptr, ld->loadgroups, size );
	// skip over the groups
	ptr += size;
	// store the bones
	psinmdl->ofs_bones = ptr - (byte *)psinmdl;
	size = psinmdl->num_bones * sizeof( dbone_t );
	memcpy( ptr, ld->loadbones, size );
	// skip over the bones
	ptr += size;
	// process each animation
	for (i=0; i<psinmdl->num_anims; i++)
	{
		dsinanim_t * psinanim;
		vec3_t v,v2;

		//
		// sort them as we go
		//
		smallest = -1;
		for (l=0; l<psinmdl->num_anims; l++)
		{
			if (ld->processed[l])
			{
				continue;
			}
			if (smallest < 0)
				smallest = l;
			else if (stricmp( ld->loadanims[l].alias, ld->loadanims[smallest].alias ) < 0)
				smallest = l;
		}
		ld->processed[smallest] = 1;
		psinmdl->ofs_animdefs[i] = ptr - (byte *)psinmdl;
		panim = (dsinanimdef_t *)ptr;
		// skip over this anim
		ptr += sizeof( dsinanimdef_t );
		strcpy( panim->alias, ld->loadanims[smallest].alias );
		panim->weight = ld->loadanims[smallest].weight;
		panim->handle = ld->loadanims[smallest].handle;
		panim->num_server_cmds = ld->loadanims[smallest].num_server_cmds;
		panim->num_client_cmds = ld->loadanims[smallest].num_client_cmds;

		psinanim = SINMDL_GetAnim ( panim->handle );
		//
		// record the fact that we are using this animation
		//
		animcache[panim->handle].numuses++;

		// get mins
		VectorCopy( psinanim->translate, v );
		VectorAdd( v, psinmdl->load_origin, v );
		AddPointToBounds ( v, psinmdl->mins, psinmdl->maxs );
		// get maxs
		VectorScale( psinanim->scale, 255.0, v2 );
		VectorAdd( v, v2, v );
		AddPointToBounds ( v, psinmdl->mins, psinmdl->maxs );

		panim->ofs_server_cmds = ptr - (byte *)panim;
		pcmds = (dsincmd_t *)ptr;
		ptr += sizeof( dsincmd_t ) * panim->num_server_cmds;
		for (j=0,k=0; j<ld->numservercmds; j++)
		{
			if (ld->loadservercmds[j].anim_num == smallest)
			{
				// if this cmd belongs to this anim, put it here
				pcmds[k].num_args = ld->loadservercmds[j].num_args;
				pcmds[k].frame_num = ld->loadservercmds[j].frame_num;
				for (x=0; x<pcmds[k].num_args; x++)
				{
					pcmds[k].ofs_args[x] = ptr - (byte *)&pcmds[k];
					size = (int)strlen( ld->loadservercmds[j].args[x] ) + 1;
					memcpy( ptr, ld->loadservercmds[j].args[x], size - 1 );
					ptr += size;
				}
				k++;
			}
		}
		panim->ofs_client_cmds = ptr - (byte *)panim;
		pcmds = (dsincmd_t *)ptr;
		ptr += sizeof( dsincmd_t ) * panim->num_client_cmds;
		for (j=0,k=0; j<ld->numclientcmds; j++)
		{
			if (ld->loadclientcmds[j].anim_num == smallest)
			{
				// if this cmd belongs to this anim, put it here
				pcmds[k].num_args = ld->loadclientcmds[j].num_args;
				pcmds[k].frame_num = ld->loadclientcmds[j].frame_num;
				for (x=0; x<pcmds[k].num_args; x++)
				{
					pcmds[k].ofs_args[x] = ptr - (byte *)&pcmds[k];
					size = (int)strlen( ld->loadclientcmds[j].args[x] ) + 1;
					memcpy( ptr, ld->loadclientcmds[j].args[x], size - 1 );
					ptr += size;
				}
				k++;
			}
		}
	}
	return psinmdl;
}

/*
============
SINMDL_ParseDEFGroup
============
*/
static void SINMDL_ParseDEFGroup( dloaddef_t *ld, const char * path )
{
	int i;
	char groupname[MAX_GROUPDEFNAME];
	dsingroup_t *pgroup;

	//
	// skip over the "group" token
	//
	ld->argnum++;
	//
	// get the group name, skipping over the %
	//
	strncpy( groupname, ld->args[ld->argnum], MAX_GROUPDEFNAME );
	//
	// find the group
	//
	for (i=0; i<ld->numgroups; i++)
	{
		if (!Q_stricmp ( ld->loadgroups[i].name, groupname ))
			break;
	}
	if (i==ld->numgroups)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "Group %s not found in %s\n", groupname, path);
		return;
	}
	pgroup = &ld->loadgroups[i];
	//
	// grab the rest of the tokens
	//
	ld->argnum++;
	while (ld->argnum < ld->numargs)
	{
		if (!Q_stricmp ( ld->args[ld->argnum], "damage" ))
		{
			ld->argnum++;
			pgroup->damage_multiplier = Sinatof( ld->args[ld->argnum] );
			ld->argnum++;
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "Unknown group token %s in %s\n", ld->args[ld->argnum], path);
		}
	}
}

/*
============
SINMDL_ParseGroupFlag
============
*/
#define SURF_NUM_TYPES 16
static char surf_types[SURF_NUM_TYPES][16]=
{
	"None", "Wood", "Metal", "Stone", "Concrete", "Dirt", "Flesh", "Grill", "Glass", "Fabric", "Monitor", "Gravel", "Vegetation", "Paper", "Duct", "Water"
};

static int SINMDL_ParseGroupFlag( const char * token )
{
	int i;
	int flags;

	flags = 0;
	if (!Q_stricmp ( token, "skin1" ))
	{
		flags |= MDL_GROUP_SKINOFFSET_BIT0;
	}
	else if (!Q_stricmp ( token, "skin2" ))
	{
		flags |= MDL_GROUP_SKINOFFSET_BIT1;
	}
	else if (!Q_stricmp ( token, "skin3" ))
	{
		flags |= MDL_GROUP_SKINOFFSET_BIT0;
		flags |= MDL_GROUP_SKINOFFSET_BIT1;
	}
	else if (!Q_stricmp ( token, "nodraw" ))
	{
		flags |= MDL_GROUP_NODRAW;
	}
	else if (!Q_stricmp ( token, "twosided" ))
	{
		flags |= MDL_GROUP_TWOSIDED;
	}
	else if (!Q_stricmp ( token, "masked" ))
	{
		flags |= MDL_GROUP_MASKED;
	}
	else if (!Q_stricmp ( token, "envmapped" ))
	{
		flags |= MDL_GROUP_ENVMAPPED_SILVER;
	}
	else if (!Q_stricmp ( token, "goldenvmapped" ))
	{
		flags |= MDL_GROUP_ENVMAPPED_GOLD;
	}
	else if (!Q_stricmp ( token, "translucent33" ))
	{
		flags |= MDL_GROUP_TRANSLUCENT_33;
	}
	else if (!Q_stricmp ( token, "translucent66" ))
	{
		flags |= MDL_GROUP_TRANSLUCENT_66;
	}
	else if (!Q_stricmp ( token, "nodamage" ))
	{
		flags |= MDL_GROUP_SKIN_NO_DAMAGE;
	}
	else if (!Q_stricmp ( token, "ricochet" ))
	{
		flags |= MDL_GROUP_RICOCHET;
	}
	else if (!Q_stricmp ( token, "fullbright" ))
	{
		flags |= MDL_GROUP_FULLBRIGHT;
	}
	else
	{
		//
		// see if it is a surface type
		//
		for ( i=0; i < SURF_NUM_TYPES; i++ )
		{
			if (!Q_stricmp ( token, surf_types[i] ))
				break;
		}
		if (i < SURF_NUM_TYPES)
		{
			flags |= (i<<8);
		}
		else
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "Unknown group flag %s\n", token);
	}
	return flags;
}


/*
============
SINMDL_ParseDEFSubGroup
============
*/
static void SINMDL_ParseDEFSubGroup( dloaddef_t *ld, const char * path )
{
	int i;
	int id;
	int idindex;
	int flags;
	dsinbasemdl_t *base;
	char groupname[MAX_GROUPDEFNAME];

	if (ld->loadbasehandle < 0)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "WARNING: base model invalid, skipping sub group in %s\n", path);
		return;
	}
	//
	// get the base model
	//
	base = SINMDL_GetBaseModel ( ld->loadbasehandle );
	//
	// clear some variables
	//
	id = 0;
	flags = 0;
	groupname[0] = 0;
	//
	// skip over 'id'
	//
	ld->argnum++;
	//
	// get the id number
	//
	id = atol( ld->args[ld->argnum] );
	//
	// decrement id, because it is 0 based, not 1 based like in max
	//
	id--;
	//
	// find the id index
	//
	for (i=0; i<base->num_groups; i++)
	{
		if (base->groups[i].id == id)
			break;
	}
	if (i == base->num_groups)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "WARNING: id %d was not found for %s\n", id+1, path);
		return;
	}
	idindex = i;
	//
	// skip over the first argument
	//
	ld->argnum++;
	while (ld->argnum < ld->numargs)
	{
		if (!Q_stricmp ( ld->args[ld->argnum], "group" ))
		{
			ld->argnum++;
			strncpy( groupname, ld->args[ld->argnum], MAX_GROUPDEFNAME );
			ld->argnum++;
		}
		else if (!Q_stricmp ( ld->args[ld->argnum], "bone" ))
		{
			if (ld->numbones < MAX_DEF_LOAD_BONES)
			{
				ld->loadbones[ld->numbones].basegroupindex = idindex;
				strncpy( ld->loadbones[ld->numbones].name, ld->args[ld->argnum + 1], MAX_BONEDEFNAME );
				ld->loadbones[ld->numbones].tri_num = 0;
				ld->loadbones[ld->numbones].orientation[0] = Sinatof( ld->args[ld->argnum + 2] );
				ld->loadbones[ld->numbones].orientation[1] = Sinatof( ld->args[ld->argnum + 3] );
				ld->loadbones[ld->numbones].orientation[2] = Sinatof( ld->args[ld->argnum + 4] );
				// 2015 code
				if(ld->argnum + 5 < ld->numargs)
				{
					if(!Q_stricmp ( ld->args[ld->argnum + 5], "2015attach" ))
					{
						ld->loadbones[ld->numbones].tri_num |= BONE_2015ATTACH;
						ld->argnum++;
					}
				}
				//
				// see if the bone name is "gun"
				//
//				if (!Q_stricmp(ld->loadbones[ld->numbones].name, "gun"))
//				{
//					ld->loadbones[ld->numbones].tri_num |= BONE_2015ATTACH;
//				}
				//
				// see if this is a valid id and if there is only one triangle in that group
				//
				if (base->groups[idindex].num_tris > 1)
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "WARNING: More than one triangle defined for groupid %d in %s\n", id+1, path);
				}
				ld->numbones++;
			}
			else
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "Too many bones defined in %s\n", path);
			ld->argnum += 5;
		}
		else
		{
			flags |= SINMDL_ParseGroupFlag ( ld->args[ld->argnum] );
			ld->argnum++;
		}
	}
	if (!groupname[0])
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "Undefined group for group_id %d in %s\n", id+1, path);
		return;
	}
	//
	// add this id to its group
	//
	for (i=0; i<ld->numgroups; i++)
	{
		if (!Q_stricmp ( ld->loadgroups[i].name, groupname ))
			break;
	}
	if (i==ld->numgroups)
	{
		if (ld->numgroups < MAX_DEF_LOAD_GROUPS)
		{
			ld->numgroups++;
			strncpy( ld->loadgroups[i].name, groupname, MAX_GROUPDEFNAME );
			ld->loadgroups[i].damage_multiplier = 1.0f;
			ld->loadgroups[i].num_subgroups = 0;
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "Too many groups defined in %s\n", path);
			return;
		}
	}
	if (ld->loadgroups[i].num_subgroups < MAX_SIN_SUB_GROUPS)
	{
		ld->loadgroups[i].subgroups[ld->loadgroups[i].num_subgroups].basegroupnum = idindex;
		ld->loadgroups[i].subgroups[ld->loadgroups[i].num_subgroups].flags = flags;
		ld->loadgroups[i].num_subgroups++;
	}
	else
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "Too many subgroups defined for group %s in %s\n", groupname, path);
		return;
	}
}

/*
=================
SINMDL_LoadDEFfile
=================
*/
static qboolean SINMDL_LoadDEFfile( const char * path, defcache_t * cache )
{
	dloaddef_t *ld;
	dsinmdl_t  *psinmdl;

	char alias[128];
	char file[128];
	char ext[32];
	byte *buffer_start;
	char load_path[128];
	char tempload_path[128];
	size_t sz;

	float weight;
	int header;
	int length;

	// create our temp space for loading stuff
	ld = ( dloaddef_t * )Z_Malloc( sizeof( *ld ) );
	if (!ld )
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: could not create temp space for loading files\n");
		return false;
	}
	//
	// load the file
	//
	length = FS_LoadFile ( path, (void **)&ld->buffer );
	if (!ld->buffer)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: Could not load %s\n", path);
		// free up the temp space
		Z_Free( ld );
		return false;
	}

	// allocate virtual mem for all the files within this def file

	// save off the beginning address of the buffer
	buffer_start = ld->buffer;
	// check out the header
	header = LittleLong( *((unsigned *)ld->buffer));

	if (header != SINDEF_HEADER)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: def file %s has wrong header (%i should be %i)\n",
		             path, header, SINDEF_HEADER);
		// free up the temp space
		FS_FreeFile ( buffer_start );
		Z_Free( ld );
		return false;
	}
	// skip over the header
	ld->buffer += sizeof( unsigned );

	// initialize some variables
	VectorCopy( vec3_origin, ld->load_origin );
	ld->load_scale = 1.0f;
	ld->numanims = 0;
	ld->numskins = 0;
	ld->numgroups = 0;
	ld->numbones = 0;
	ld->loadbasehandle = -1;

	// get the original path
	SINMDL_ExtractPath ( path, load_path );

	// read in the first part of the def file, the definition section
	// the definition section is terminated by tokens beginning with the !
	// character which denotes a script command
	while (1)
	{
		ld->numargs = 0;
		// see if we reached the end of the file
		strcpy( ld->com_token, COM_GetToken( (const char **)&ld->buffer, true ) );
		if (!ld->com_token[0])
			break;

		// see if we reached the end of the definition portion
		else if (ld->com_token[0] == '!')
			break;

		if (strlen( ld->com_token ) >= MAX_DEF_ARG_LENGTH)
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: argument too long, truncating in %s\n", path);
			ld->com_token[MAX_DEF_ARG_LENGTH - 1] = 0;
		}
		strcpy( ld->args[ld->numargs++], ld->com_token );
		// get the rest of the line
		while( 1 )
		{
			strcpy( ld->com_token, COM_GetToken( (const char **)&ld->buffer, false ) );
			if (!ld->com_token[0])
				break;
			if (strlen( ld->com_token ) >= MAX_DEF_ARG_LENGTH)
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: argument too long, truncating in %s\n", path);
				ld->com_token[MAX_DEF_ARG_LENGTH - 1] = 0;
			}
			strcpy( ld->args[ld->numargs], ld->com_token );
			if ( ld->numargs < MAX_DEF_ARGS )
				ld->numargs++;
			else
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: too many args in %s\n", path);
		}

		// now that we have the whole line, process it
		ld->argnum = 0;
		// see if it is a special command
		if (!Q_stricmp ( ld->args[ld->argnum], "scale" ))
		{
			ld->load_scale = Sinatof( ld->args[ld->argnum+1] );
			if (ld->load_scale <= 0)
				ld->load_scale = 1.0f;
			continue;
		}
		else if (!Q_stricmp ( ld->args[ld->argnum], "path" ))
		{
			strcpy( load_path, ld->args[ld->argnum+1] );
			sz = strlen(load_path);
			if (sz && (load_path[sz-1] != '/' || load_path[sz-1] != '\\'))
				strcat( load_path, "/" );
			continue;
		}
		else if (!Q_stricmp ( ld->args[ld->argnum], "origin" ))
		{
			ld->load_origin[0] = Sinatof( ld->args[ld->argnum + 1] );
			ld->load_origin[1] = Sinatof( ld->args[ld->argnum + 2] );
			ld->load_origin[2] = Sinatof( ld->args[ld->argnum + 3] );
			continue;
		}
		else if (!Q_stricmp ( ld->args[ld->argnum], "id" ))
		{
			SINMDL_ParseDEFSubGroup ( ld, path );
			continue;
		}
		else if (!Q_stricmp ( ld->args[ld->argnum], "group" ))
		{
			// group attributes
			SINMDL_ParseDEFGroup ( ld, path );
			continue;
		}
		else
		{
			// it must be a skin, base model or anim declaration
			if ( ld->numargs > 2 )
			{
				strcpy( alias, ld->args[ld->argnum] );
				strcpy( file, ld->args[ld->argnum + 1] );
				weight = Sinatof( ld->args[ld->argnum + 2] );
			}
			else if (ld->numargs > 1)
			{
				strcpy( alias, ld->args[ld->argnum] );
				strcpy( file, ld->args[ld->argnum + 1] );
				weight = 1.0;
			}
			else
			{
				strcpy( file, ld->args[ld->argnum] );
				sz = strlen(ld->args[ld->argnum]);
				if (sz > 4) sz -= 4;
				// copy the filename minus the extension
				strncpy(alias, ld->args[ld->argnum], sz);
				alias[sz] = 0;
				weight = 1.0;
			}
			strcpy( ext, (char *)COM_FileExtension( file ) );
			if (!Q_stricmp ( ext, "sam" ))
			{
				if (ld->numanims == MAX_DEF_LOAD_ANIMS)
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: Too many animations defined in %s\n", path);
				}
				else
				{
					strcpy( tempload_path,load_path );
					strcat( tempload_path,file );
					ld->loadanims[ld->numanims].handle = SINMDL_RegisterAnim ( tempload_path );
					if (ld->loadanims[ld->numanims].handle < 0)
					{
						// it couldn't be found or loaded
						Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: Skipping %s in %s.\n", file, path);
					}
					else
					{
						strcpy( ld->loadanims[ld->numanims].alias, alias );
						ld->loadanims[ld->numanims].weight = weight;
						ld->loadanims[ld->numanims].num_server_cmds = 0;
						ld->loadanims[ld->numanims].num_client_cmds = 0;
						ld->numanims++;
					}
				}
			}
			else if (!Q_stricmp ( ext, "sbm" ))
			{
				strcpy( tempload_path,load_path );
				strcat( tempload_path,file );
				ld->loadbasehandle = SINMDL_RegisterBaseModel ( tempload_path );
				if (ld->loadbasehandle < 0)
				{
					// it couldn't be found or loaded
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: Skipping %s in %s.\n", file, path);
				}
			}
			else if (!Q_stricmp ( ext, "tga" ))
			{
				if (ld->numskins == MAX_DEF_LOAD_SKINS)
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: Too many skins defined in %s\n", path);
				else
				{
					strcpy( ld->loadskins[ld->numskins].alias, alias );
					strcpy( tempload_path, load_path );
					strcat( tempload_path, file );
					strcpy( ld->loadskins[ld->numskins].path, tempload_path );
					ld->loadskins[ld->numskins].weight = weight;
					ld->numskins++;
				}
			}
		}
	}
	if ( (ld->loadbasehandle < 0) || !ld->numskins || !ld->numanims )
	{
		// these are too serious to continue loading the rest of the DEF file
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_LoadDEFfile: could not load %s.\n", path);
		// free up the temp space
		FS_FreeFile ( buffer_start );
		Z_Free( ld );
		return false;
	}
	// we either have a token in memory or we are at the end of the file
	if (ld->buffer && !Q_stricmp ( ld->com_token, "!init:" ) )
	{
		// load in the initcmd
		SINMDL_LoadDEFInitCmd( ld, path );
	}
	// load in the rest of the cmds
	SINMDL_LoadDEFAnimCmds( ld, path );
	// create the structure
	psinmdl = SINMDL_FillDEFStructure( ld, path );
#ifndef SINED
	// Calculate checksum
	psinmdl->checksum = Com_BlockChecksum( buffer_start, length );
#endif
	// we should now be done
	// clear out the cache
	memset( cache, 0, sizeof( *cache ) );
	// copy over the path name
	strncpy( cache->path, path, sizeof(cache->path)-1 );
	// make this official
	cache->def = psinmdl;
	cache->size = defsize;
	// free up the temp space
	Z_Free( ld );
	// free up the buffer
	FS_FreeFile ( buffer_start );
	// increment the internal count
	cache_numdefs++;
	return true;
}

/*
=================
SINMDL_GetDef
=================
*/
dsinmdl_t * SINMDL_GetDef( int index )
{
	if (index < 0 || index > cache_maxdefs)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_GetDef: handle %d out of range\n",index);
		return NULL;
	}
	if (!defcache[index].def)
	{
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_GetDef: NULL pointer for %s\n",defcache[index].path);
		return NULL;
	}
	else
		return defcache[index].def;
}

/*
=================
SINMDL_DefHandleValid
=================
*/
qboolean SINMDL_DefHandleValid( int index, const char * name )
{
	if (index < 0 || index > cache_maxdefs)
		return false;
	if (!defcache[index].def)
		return false;
	if (Q_stricmp ( defcache[index].path, name ) )
		return false;
	else
		return true;
}

/*
=================
SINMDL_RegisterDef
=================
*/
int SINMDL_RegisterDef( const char * path )
{
	defcache_t * cache;
	int index;

	cache = SINMDL_FindDef ( path );
	if (!cache) {
		cache = SINMDL_FindFreeDef();
		if (!cache) {
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_RegisterDef: No free spots open in def cache for %s\n",path);
			return -1;
		}
		index = cache - defcache;
		if (index > cache_maxdefs)
			cache_maxdefs = index;
		if (!SINMDL_LoadDEFfile( path, cache )) {
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_RegisterDef: LoadDEFfile failed for %s\n",path);
			return -1;
		}
	}
	// return index to this defcache
	return (cache - defcache);
}

/*
=================
SINMDL_FreeDef
=================
*/
void SINMDL_FreeDef( int handle )
{
	dsinmdl_t * sinmdl;
	dsinanimdef_t * panimdef;
	int i;

	if (handle < 0 || handle > cache_maxdefs) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeDef: handle %d out of range\n",handle);
		return;
	}
	sinmdl = defcache[handle].def;
	if (!sinmdl) {
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeDef: NULL pointer for %s\n",defcache[handle].path);
		return;
	}
	//
	// see if we need to free our alias_list
	//
	if ( sinmdl->alias_list ) {
		Alias_ListDelete( (AliasList_t *)sinmdl->alias_list );
	}
	//
	// see if our base frame is being used by anyone else
	//
	if (sinmdl->base_handle >= 0 && sinmdl->base_handle <= cache_maxbasemodels) {
		basecache[sinmdl->base_handle].numuses--;
		if (!basecache[sinmdl->base_handle].numuses) {
			SINMDL_FreeBaseModel( sinmdl->base_handle );
		}
	}
	//
	// see if out anims need to be freed
	//
	for (i=0; i<sinmdl->num_anims; i++) {
		panimdef = ( dsinanimdef_t * )( (byte *)sinmdl + sinmdl->ofs_animdefs[i] );
		if (panimdef->handle >= 0 && panimdef->handle <= cache_maxanims) {
			animcache[panimdef->handle].numuses--;
			if (!animcache[panimdef->handle].numuses) {
				SINMDL_FreeAnim( panimdef->handle );
			}
		}
	}
	Z_Free( defcache[handle].def );
	defcache[handle].def = NULL;
	cache_numdefs--;
}

/*
=================
SINMDL_FreeAll
=================
*/
void SINMDL_FreeAll( void )
{
	dsinmdl_t * sinmdl;
	int i;

	for ( i = 0; i <= cache_maxdefs; i++ ) {
		sinmdl = defcache[i].def;
		if (sinmdl) {
			SINMDL_FreeDef( i );
		}
	}
	if ( cache_numdefs )
		Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_FreeAll: cache_numdefs non-zero\n");

	cache_maxdefs = 0;
}

/*
==============================================================================

SIN MODEL UTILITY FUNCTIONS

==============================================================================
*/

//
// DEF SPECIFIC STUFF
//
#define SERVER_INITCMD( MDL, NUM ) ( &((dsincmd_t *) ((byte *)(MDL) + (MDL)->ofs_server_initcmds))[(NUM)] )
#define CLIENT_INITCMD( MDL, NUM ) ( &((dsincmd_t *) ((byte *)(MDL) + (MDL)->ofs_client_initcmds))[(NUM)] )

#ifndef SINED
/*
=================
SINMDL_Alias_Find
=================
*/
const char * SINMDL_Alias_Find( int defhandle, const char * alias )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( pmdl->alias_list )
		{
			return Alias_ListFind( ( AliasList_t * )pmdl->alias_list, alias );
		}
		else
			return NULL;
	}
	else
		return NULL;
}

/*
=================
SINMDL_Alias_Add
=================
*/
qboolean SINMDL_Alias_Add( int defhandle, const char * alias, const char * name, float weight )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl) {
		if ( !pmdl->alias_list ) {
			pmdl->alias_list = AliasList_New( pmdl->name );
		}
		return Alias_ListAdd( ( AliasList_t * )pmdl->alias_list, alias, name, weight );
	}
	return false;
}

/*
=================
SINMDL_Alias_Dump
=================
*/
void SINMDL_Alias_Dump( int defhandle )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( pmdl->alias_list )
		{
			Alias_ListDump( ( AliasList_t * )pmdl->alias_list );
		}
	}
}

/*
=================
SINMDL_Alias_Clear
=================
*/
void SINMDL_Alias_Clear( int defhandle )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl) {
		if ( pmdl->alias_list ) {
			Alias_ListClear( ( AliasList_t * )pmdl->alias_list );
		}
	}
}

/*
=================
SINMDL_Alias_FindRandom
=================
*/
const char * SINMDL_Alias_FindRandom( int defhandle, const char * alias )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( pmdl->alias_list )
		{
			return Alias_ListFindRandom( ( AliasList_t * )pmdl->alias_list, alias );
		}
		else
			return NULL;
	}
	else
		return NULL;
}
#endif

/*
=================
SINMDL_NumAnims
=================
*/
int SINMDL_NumAnims( int defhandle )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
		return pmdl->num_anims;
	else
		return 0;
}

/*
===============
SINMDL_Checksum
===============
*/
unsigned SINMDL_Checksum( int defhandle )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
		return pmdl->checksum;
	else
		return 0;
}

/*
=================
SINMDL_NumSkins
=================
*/
int SINMDL_NumSkins( int defhandle )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
		return pmdl->num_skins;
	else
		return 0;
}

/*
=================
SINMDL_NumGroups
=================
*/
int SINMDL_NumGroups( int defhandle )
{
	dsinmdl_t * pmdl;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
		return pmdl->num_groups;
	else
		return 0;
}

/*
=================
SINMDL_InitCommands_Server
=================
*/
sinmdl_cmd_t *SINMDL_InitCommands_Server( int defhandle )
{
	dsinmdl_t * pmdl;
	dsincmd_t * pcmd;
	int i,j;
	char * cmds;
	sinmdl_cmd_t * sinmdl_cmd;

	sinmdl_cmd = &sinmdl_cmds[sinmdl_cmd_index];
	sinmdl_cmd_index = ( sinmdl_cmd_index + 1 ) & ( MAX_SINMDL_CMD_STRUCTS - 1 );

	sinmdl_cmd->num_cmds = 0;
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		for ( i=0; i < pmdl->num_server_initcmds; i++ )
		{
			pcmd = SERVER_INITCMD( pmdl, i );

			if (sinmdl_cmd->num_cmds < SINMDL_CMD_MAX_CMDS)
			{
				if ( pcmd->num_args > SINMDL_CMD_MAX_ARGS )
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_InitCommands_Server: Too many args for cmd in %s\n", pmdl->name);
					continue;
				}
				sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].num_args = pcmd->num_args;
				for (j=0; j<pcmd->num_args; j++)
				{
					cmds = ( (char *)pcmd + pcmd->ofs_args[j] );
					sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].args[j] = cmds;
				}
				sinmdl_cmd->num_cmds++;
			}
			else
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_InitCommands_Server: Too many cmds for %s\n", pmdl->name);
				return NULL;
			}
		}
		return sinmdl_cmd;
	}
	else
		return NULL;
}

/*
=================
SINMDL_InitCommands_Client
=================
*/
sinmdl_cmd_t *SINMDL_InitCommands_Client( int defhandle )
{
	dsinmdl_t * pmdl;
	dsincmd_t * pcmd;
	int i,j;
	char * cmds;
	sinmdl_cmd_t * sinmdl_cmd;

	sinmdl_cmd = &sinmdl_cmds[sinmdl_cmd_index];
	sinmdl_cmd_index = ( sinmdl_cmd_index + 1 ) & ( MAX_SINMDL_CMD_STRUCTS - 1 );

	sinmdl_cmd->num_cmds = 0;
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		for ( i=0; i < pmdl->num_client_initcmds; i++ )
		{
			pcmd = CLIENT_INITCMD( pmdl, i );

			if (sinmdl_cmd->num_cmds < SINMDL_CMD_MAX_CMDS)
			{
				if ( pcmd->num_args > SINMDL_CMD_MAX_ARGS )
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_InitCommands_Client: Too many args for cmd in %s\n", pmdl->name);
					continue;
				}
				sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].num_args = pcmd->num_args;
				for (j=0; j<pcmd->num_args; j++)
				{
					cmds = ( (char *)pcmd + pcmd->ofs_args[j] );
					sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].args[j] = cmds;
				}
				sinmdl_cmd->num_cmds++;
			}
			else
			{
				Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_InitCommands_Client: Too many cmds for %s.\n", pmdl->name);
				return NULL;
			}
		}
		return sinmdl_cmd;
	}
	else
		return NULL;
}

/*
=================
SINMDL_CalculateBounds
=================
*/
void SINMDL_CalculateBounds( int defhandle, float scale, vec3_t mins, vec3_t maxs )
{
	dsinmdl_t * pmdl;
	int j;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		for (j=0; j<3; j++)
		{
			mins[j] = pmdl->mins[j]*pmdl->load_scale*scale;
			maxs[j] = pmdl->maxs[j]*pmdl->load_scale*scale;
		}
	}
}


//
// ANIM SPECIFIC STUFF
//
#define ANIM_DEF( MODEL, ANIM ) ( (dsinanimdef_t *) ((byte *)(MODEL) + (MODEL)->ofs_animdefs[(ANIM)]) )
#define ANIM_OUT_OF_RANGE( MODEL, ANIM ) (((ANIM) >= (MODEL)->num_anims) || ((ANIM) < 0))

/*
=================
SINMDL_Anim_NameForNum
=================
*/
const char * SINMDL_Anim_NameForNum( int defhandle, int animnum )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( !ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			panimdef = ANIM_DEF( pmdl, animnum );
			return panimdef->alias;
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Anim_NameForNum: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return NULL;
		}
	}
	else
		return NULL;
}

/*
=================
SINMDL_Anim_NumForName
=================
*/
int SINMDL_Anim_NumForName( int defhandle, const char * name )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;
	int i;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		for ( i=0; i<pmdl->num_anims; i++ )
		{
			panimdef = ANIM_DEF( pmdl, i );
			if (!Q_stricmp( panimdef->alias, name ))
				return i;
		}
		return -1;
	}
	else
		return -1;
}

/*
=================
SINMDL_Anim_Random
=================
*/
int SINMDL_Anim_Random( int defhandle, const char * name )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;
	int i;
	float totalweight;
	float weights[16];
	int anim[16];
	int num;
	int len;

	assert( name );
	if ( !name )
	{
		return -1;
	}

	len = (int)strlen( name );
	if ( len == 0 )
	{
		return -1;
	}

	totalweight = 0;
	num = 0;
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		for ( i=0; i<pmdl->num_anims; i++ )
		{
			int diff;
			panimdef = ANIM_DEF( pmdl, i );
			diff = strncmp( panimdef->alias, name, len );
			if ( !diff && ( panimdef->alias[len] != '_' ) )
			{
				if (num < 16)
				{
					totalweight += panimdef->weight;
					anim[num] = i;
					weights[num++] = panimdef->weight;
				}
				else
					break;
			}
			else if ( diff > 0 )
				break;
		}
		if (num)
		{
			float weight;

			weight = totalweight * (float)rand()/(float)RAND_MAX;
			for ( i=0; i<num; i++ )
			{
				weight -= weights[i];
				if (weight <= 0)
					break;
			}
			return anim[i];
		}
		else
			return -1;
	}
	else
		return -1;
}

/*
=================
SINMDL_Anim_NumFrames
=================
*/
int SINMDL_Anim_NumFrames( int defhandle, int animnum )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;
	dsinanim_t * panim;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( !ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			panimdef = ANIM_DEF( pmdl, animnum );
			panim = SINMDL_GetAnim( panimdef->handle );
			return panim->num_frames;
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Anim_NumFrames: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return 0;
		}
	}
	else
		return 0;
}

/*
=================
SINMDL_Anim_Time
=================
*/
float SINMDL_Anim_Time( int defhandle, int animnum )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;
	dsinanim_t * panim;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( !ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			panimdef = ANIM_DEF( pmdl, animnum );
			panim = SINMDL_GetAnim( panimdef->handle );
			return panim->totaltime;
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Anim_Time: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return 0;
		}
	}
	else
		return 0;
}

/*
=================
SINMDL_Anim_Delta
=================
*/
void SINMDL_Anim_Delta( int defhandle, int animnum, vec3_t delta )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;
	dsinanim_t * panim;

	VectorClear( delta );
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( !ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			panimdef = ANIM_DEF( pmdl, animnum );
			panim = SINMDL_GetAnim( panimdef->handle );
			VectorScale( panim->totaldelta, pmdl->load_scale, delta );
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Anim_Delta: Anim %d out of range for %s.\n",animnum, pmdl->name);
		}
	}
}

/*
=================
SINMDL_Anim_CalculateBounds
=================
*/
void SINMDL_Anim_CalculateBounds( int defhandle, int animnum, float scale, vec3_t mins, vec3_t maxs )
{
	dsinmdl_t * pmdl;
	dsinanimdef_t *panimdef;
	dsinanim_t * panim;
	int j;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( !ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			vec3_t v, v2;

			panimdef = ANIM_DEF( pmdl, animnum );
			panim = SINMDL_GetAnim( panimdef->handle );
			// get mins
			VectorCopy( panim->translate, v );
			VectorAdd( v, pmdl->load_origin, v );
			// get maxs
			VectorScale( panim->scale, 255.0, v2 );
			VectorAdd( v, v2, v2 );

			for (j=0; j<3; j++)
			{
				mins[j] = v[j]*pmdl->load_scale*scale;
				maxs[j] = v2[j]*pmdl->load_scale*scale;
			}
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Anim_CalculateBounds: Anim %d out of range for %s.\n",animnum, pmdl->name);
		}
	}
}


//
// FRAME SPECIFIC STUFF
//

#define FRAME( ANIM, NUM ) ( &( (dsintriframe_t *) ((byte *)(ANIM) + (ANIM)->ofs_frames) )[(NUM)] )
#define FRAME_SERVER_CMD( ANIMDEF, NUM ) ( &((dsincmd_t *) ((byte *)(ANIMDEF) + (ANIMDEF)->ofs_server_cmds) )[(NUM)] )
#define FRAME_CLIENT_CMD( ANIMDEF, NUM ) ( &((dsincmd_t *) ((byte *)(ANIMDEF) + (ANIMDEF)->ofs_client_cmds) )[(NUM)] )
#define FRAME_OUT_OF_RANGE( ANIM, FRAME ) (((FRAME) >= (ANIM)->num_frames) || ((FRAME) < 0))

/*
=================
SINMDL_Frame_Commands_Server
=================
*/
sinmdl_cmd_t *SINMDL_Frame_Commands_Server( int defhandle, int animnum, int framenum )
{
	dsinmdl_t * pmdl;
	dsincmd_t * pcmd;
	dsinanimdef_t *panimdef;
	int i,j;
	sinmdl_cmd_t * sinmdl_cmd;

	sinmdl_cmd = &sinmdl_cmds[sinmdl_cmd_index];
	sinmdl_cmd_index = ( sinmdl_cmd_index + 1 ) & ( MAX_SINMDL_CMD_STRUCTS - 1 );

	sinmdl_cmd->num_cmds = 0;
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Commands_Server: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return NULL;
		}
		panimdef = ANIM_DEF( pmdl, animnum );
		for ( i=0; i < panimdef->num_server_cmds; i++ )
		{
			pcmd = FRAME_SERVER_CMD( panimdef, i );
			if (pcmd->frame_num == framenum)
			{
				char * cmds;

				if (sinmdl_cmd->num_cmds < SINMDL_CMD_MAX_CMDS)
				{
					sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].num_args = pcmd->num_args;
					for (j=0; j<pcmd->num_args; j++)
					{
						cmds = ( (char *)pcmd + pcmd->ofs_args[j] );
						sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].args[j] = cmds;
					}
					sinmdl_cmd->num_cmds++;
				}
				else
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Commands_Server: Too many cmds for anim %d frame %d in %s.\n",animnum, framenum, pmdl->name);
					return NULL;
				}
			}
			else if (pcmd->frame_num > framenum)
				break;
		}
		return sinmdl_cmd;
	}
	else
		return NULL;
}
/*
=================
SINMDL_Frame_Commands_Client
=================
*/
sinmdl_cmd_t *SINMDL_Frame_Commands_Client( int defhandle, int animnum, int framenum )
{
	dsinmdl_t * pmdl;
	dsincmd_t * pcmd;
	dsinanimdef_t *panimdef;
	int i,j;
	sinmdl_cmd_t * sinmdl_cmd;

	sinmdl_cmd = &sinmdl_cmds[sinmdl_cmd_index];
	sinmdl_cmd_index = ( sinmdl_cmd_index + 1 ) & ( MAX_SINMDL_CMD_STRUCTS - 1 );

	sinmdl_cmd->num_cmds = 0;
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Commands_Client: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return NULL;
		}
		panimdef = ANIM_DEF( pmdl, animnum );
		for ( i=0; i < panimdef->num_client_cmds; i++ )
		{
			pcmd = FRAME_CLIENT_CMD( panimdef, i );
			if (pcmd->frame_num == framenum)
			{
				char * cmds;

				if (sinmdl_cmd->num_cmds < SINMDL_CMD_MAX_CMDS)
				{
					sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].num_args = pcmd->num_args;
					for (j=0; j<pcmd->num_args; j++)
					{
						cmds = ( (char *)pcmd + pcmd->ofs_args[j] );
						sinmdl_cmd->cmds[sinmdl_cmd->num_cmds].args[j] = cmds;
					}
					sinmdl_cmd->num_cmds++;
				}
				else
				{
					Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Commands_Client: Too many cmds for anim %d frame %d for %s.\n",animnum, framenum, pmdl->name);
					return NULL;
				}
			}
			else if (pcmd->frame_num > framenum)
				break;
		}
		return sinmdl_cmd;
	}
	else
		return NULL;
}

/*
=================
SINMDL_Frame_Delta
=================
*/
void SINMDL_Frame_Delta( int defhandle, int animnum, int framenum, vec3_t delta )
{
	dsinmdl_t      *pmdl;
	dsinanimdef_t  *panimdef;
	dsinanim_t     *panim;
	dsintriframe_t *pframe;

	VectorClear( delta );
	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Delta: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return;
		}
		panimdef = ANIM_DEF( pmdl, animnum );
		panim = SINMDL_GetAnim( panimdef->handle );
		if ( FRAME_OUT_OF_RANGE( panim, framenum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Delta: Frame %d out of range for %s.\n",framenum, pmdl->name);
			return;
		}
		pframe = FRAME( panim, framenum );
		VectorScale( pframe->movedelta, pmdl->load_scale, delta );
	}
}

/*
=================
SINMDL_Frame_Time
=================
*/
float SINMDL_Frame_Time( int defhandle, int animnum, int framenum )
{
	dsinmdl_t      *pmdl;
	dsinanimdef_t  *panimdef;
	dsinanim_t     *panim;
	dsintriframe_t *pframe;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( ANIM_OUT_OF_RANGE( pmdl, animnum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Time: Anim %d out of range for %s.\n",animnum, pmdl->name);
			return 0;
		}
		panimdef = ANIM_DEF( pmdl, animnum );
		panim = SINMDL_GetAnim( panimdef->handle );
		if ( FRAME_OUT_OF_RANGE( panim, framenum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Frame_Time: Frame %d out of range %s.\n",framenum, pmdl->name);
			return 0;
		}
		pframe = FRAME( panim, framenum );
		return pframe->frametime;
	}
	else
		return 0;
}


//
// SKIN SPECIFIC STUFF
//
#define SKIN( MODEL, SKINNUM ) ( &((dsinskin_t *) ((byte *)(MODEL) + (MODEL)->ofs_skins))[(SKINNUM)] )
#define SKIN_OUT_OF_RANGE( MODEL, SKIN ) (((SKIN) >= (MODEL)->num_skins) || ((SKIN) < 0))

/*
==========================
SINMDL_Skin_FullNameForNum
==========================
*/
const char * SINMDL_Skin_FullNameForNum( int defhandle, int skinnum )
{
	dsinmdl_t * pmdl;
	dsinskin_t * pskin;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( SKIN_OUT_OF_RANGE( pmdl, skinnum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Skin_NameForNum: Skin %d out of range for %s.\n",skinnum, pmdl->name);
			return NULL;
		}
		pskin = SKIN( pmdl, skinnum );
		return pskin->path;
	}
	else
		return NULL;
}

/*
=================
SINMDL_Skin_NameForNum
=================
*/
const char * SINMDL_Skin_NameForNum( int defhandle, int skinnum )
{
	dsinmdl_t * pmdl;
	dsinskin_t * pskin;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		if ( SKIN_OUT_OF_RANGE( pmdl, skinnum ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Skin_NameForNum: Skin %d out of range for %s.\n",skinnum, pmdl->name);
			return NULL;
		}
		pskin = SKIN( pmdl, skinnum );
		return pskin->alias;
	}
	else
		return NULL;
}

/*
=================
SINMDL_Skin_NumForName
=================
*/
int SINMDL_Skin_NumForName( int defhandle, const char * name )
{
	dsinmdl_t * pmdl;
	dsinskin_t * pskin;
	int i;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		for ( i=0; i<pmdl->num_skins; i++ )
		{
			pskin = SKIN( pmdl, i );
			if (!Q_stricmp( pskin->alias, name ))
				return i;
		}
		return -1;
	}
	else
		return -1;
}

// =======================================================
// GROUP STUFF
// =======================================================

/*
=================
SINMDL_Group_NameToNum
=================
*/
int SINMDL_Group_NameToNum( int defhandle, const char * name )
{
	int i;
	dsinmdl_t   *pmdl;
	dsingroup_t *pgroup;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		pgroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
		for (i=0; i<pmdl->num_groups; i++,pgroup++)
		{
			if (!Q_stricmp ( pgroup->name, name ))
			{
				return i;
			}
		}
	}
	return -1;
}

/*
=================
SINMDL_Group_NumToName
=================
*/
const char * SINMDL_Group_NumToName( int defhandle, int num )
{
	dsinmdl_t   *pmdl;
	dsingroup_t *pgroup;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		pgroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
		if (num < 0 || num >= pmdl->num_groups)
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Group_NumToName: Group %d out of range for %s.\n",num, pmdl->name);
			return NULL;
		}
		return pgroup[num].name;
	}
	return NULL;
}

/*
=================
SINMDL_Group_DamageMultiplier
=================
*/
float SINMDL_Group_DamageMultiplier( int defhandle, int num )
{
	dsinmdl_t   *pmdl;
	dsingroup_t *pgroup;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		pgroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
		if (num < 0 || num >= pmdl->num_groups)
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Group_DamageMultiplier: Group %d out of range for %s.\n",num, pmdl->name);
			return 0;
		}
		return pgroup[num].damage_multiplier;
	}
	return 0;
}

/*
=================
SINMDL_Group_Flags
=================
*/
int SINMDL_Group_Flags( int defhandle, int num )
{
	int i;
	int flags;
	dsinmdl_t   *pmdl;
	dsingroup_t *pgroup;

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		pgroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
		if (num < 0 || num >= pmdl->num_groups)
		{
			Com_DPrintf(DEVELOPER_MSG_SINMDL, "SINMDL_Group_DamageMultiplier: Group %d out of range for %s.\n",num, pmdl->name);
			return 0;
		}
		flags = 0;
		for ( i = 0; i < pgroup[num].num_subgroups; i++ )
		{
			flags |= pgroup[num].subgroups[i].flags;
		}
		flags &= 0xff;
		return flags;
	}
	return 0;
}


// =======================================================
// BONE STUFF
// =======================================================
#ifndef SINED
/*
=================
SINMDL_GetBoneInfo
=================
*/
qboolean SINMDL_GetBoneInfo( int defhandle,const char * bonename,int * groupindex,int * tri_num,vec3_t orientation )
{
	int i;
	dsinmdl_t *pmdl;
	dbone_t *pbones;

	// initialize the variables
	*groupindex = 0;
	*tri_num = 0;
	VectorClear( orientation );

	// 2015 code
	// that works like the modelindexes in Quake2
	if(!Q_stricmp( bonename, "origin" ))
	{
		*tri_num = BONE_ORIGIN;
		return true;
	}

	pmdl = SINMDL_GetDef( defhandle );
	if (pmdl)
	{
		// get the bones
		pbones = (dbone_t *)((byte *)pmdl + pmdl->ofs_bones);
		for (i=0; i<pmdl->num_bones; i++)
			if (!Q_stricmp ( pbones[i].name, bonename ))
				break;
		if (i==pmdl->num_bones)
			return false;

		*groupindex  = pbones[i].basegroupindex;
		*tri_num  = pbones[i].tri_num;
		VectorCopy( pbones[i].orientation, orientation );
	}
	else
		return false;
	return true;
}

/*
=================
SINMDL_GetBoneGroupName
=================
*/
const char * SINMDL_GetBoneGroupName( int defhandle,const char * bonename )
{
	int groupindex;
	int tri_num;
	vec3_t orientation;

	if ( SINMDL_GetBoneInfo( defhandle, bonename, &groupindex, &tri_num, orientation ) )
	{
		int i,j;
		dsinmdl_t   *pmdl;
		dsingroup_t *pgroup;

		// get the model
		pmdl = SINMDL_GetDef( defhandle );

		if ( !pmdl )
			return NULL;

		pgroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
		for ( i = 0; i < pmdl->num_groups; i++, pgroup++ )
		{
			for ( j = 0; j < pgroup->num_subgroups; j++ )
			{
				if ( pgroup->subgroups[j].basegroupnum == groupindex )
				{
					return pgroup->name;
				}
			}
		}
	}

	return NULL;
}

/*
=================
SINMDL_GetBoneTransform
=================
*/
qboolean SINMDL_GetBoneTransform( int defhandle,int groupindex,int tri_num,vec3_t orient,int anim,int frame,float scale,vec3_t trans[3],vec3_t pos )
{
	int i, j;
	dsinmdl_t      *pmdl;
	dsinbasemdl_t  *base;
	dsintriframe_t *pframe;
	dsintriframe_t *pframes;
	dsinanimdef_t  *panimdef;
	dsinanim_t     *panim;
	dsintrigroup_t *pgroup;
	dsintriangle_t *tris;
	dtrivertx_t     *pverts;
	vec3_t voffset, vscale, tri_indices;
	vec3_t tri_points[3];
	vec3_t tri_trans[3];
	vec3_t bone_trans[3];
	// 2015 code
	vec3_t hack;        // added to correct attached models
	qboolean do2015attach;      // set to true when using corrected attach method

	VectorClear( pos );

	// 2015 code
// that works like the modelindexes in Quake2
	if(tri_num == BONE_ORIGIN)
	{
		VectorClear( voffset );
		AnglesToMat( voffset, trans );
		return true;
	}

	if(tri_num & BONE_2015ATTACH)
	{
		tri_num &= ~BONE_2015ATTACH;
		do2015attach = true;
	}
	else
	{
		do2015attach = false;
	}

	pmdl = SINMDL_GetDef(defhandle);
	if (!pmdl)
		return false;

	// get current base
	base = SINMDL_GetBaseModel(pmdl->base_handle);
	if (!base)
		return false;

	if (groupindex < 0 || groupindex >= base->num_groups)
	{
		Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_GetBoneTransform: group %d out of range for %s\n", groupindex, pmdl->name);
		return false;
	}
	pgroup = &base->groups[groupindex];
	if (tri_num < 0 || tri_num >= pgroup->num_tris)
	{
		Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_GetBoneTransform: triangle %d out of rangefor %s\n", tri_num, pmdl->name);
		return false;
	}

	// get current anim
	if (anim >= pmdl->num_anims || anim < 0)
	{
		Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_GetBoneTransform: no such anim %d in %s\n", anim, pmdl->name);
		return false;
	}

	panimdef = ( dsinanimdef_t * )( (byte *)pmdl + pmdl->ofs_animdefs[anim] );
	panim = SINMDL_GetAnim( panimdef->handle );
	if (!panim)
		return false;

	// get current frame
	if ( (frame >= panim->num_frames) || (frame < 0) )
	{
		Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_GetBoneTransform: no such frame %d in anim %d for %s\n", frame, anim, pmdl->name);
		return false;
	}

	// point to the data
	pframes = ( dsintriframe_t *) ((byte *)panim + panim->ofs_frames);
	pframe = &pframes[frame];
	pverts = (dtrivertx_t *) ((byte *)pframe + pframe->ofs_verts);

	for (i=0; i<3; i++)
	{
		voffset[i] = ( pframe->translate[i] + pmdl->load_origin[i] ) * scale * pmdl->load_scale;
		vscale[i] = pframe->scale[i] * scale * pmdl->load_scale;
	}

	tris = (dsintriangle_t *)((byte *)pgroup + pgroup->ofs_tris);
	for (j=0; j<3; j++)
		tri_indices[j] = tris[tri_num].index_xyz[j];

	for ( i=0; i < 3; i++ )
	{
		for ( j=0; j < 3; j++ )
		{
			tri_points[i][j] = voffset[j] + (pverts[ (int)tri_indices[i] ].v[j]*vscale[j]);
		}
	}

// 2015 code
//      The following changes to correct attached models
//      This is a bit of a hack, to correct already existing
//      DEF files. It mostly works, and minor differences
//      which remain are due to normalizing vectors.
	if(do2015attach)
	{
		TransformFromTriangle_2015( tri_points, tri_trans, pos );
		VectorCopy( orient,hack );
		hack[0] = -hack[0];
		hack[2] += 180;
		AnglesToMat( hack, bone_trans );
	}
	else
	{
		TransformFromTriangle( tri_points, tri_trans, pos );
		AnglesToMat( orient, bone_trans );
	}

	R_ConcatRotations( bone_trans, tri_trans, trans );
	return true;
}

/*
=================
SINMDL_GetNamedBoneTransform
=================
*/
qboolean SINMDL_GetNamedBoneTransform( int defhandle,const char * bonename,int anim,int frame,float scale,vec3_t trans[3],vec3_t pos )
{
	int groupindex;
	int tri_num;
	vec3_t orient;

	// get the bone information
	if ( !SINMDL_GetBoneInfo( defhandle, bonename, &groupindex, &tri_num, orient ) )
		return false;
	return SINMDL_GetBoneTransform( defhandle, groupindex, tri_num, orient, anim, frame, scale, trans, pos );
}

#define RAY_CLIP_LEFT   1
#define RAY_CLIP_RIGHT  2
#define RAY_CLIP_TOP    4
#define RAY_CLIP_BOTTOM 8
#define RAY_CLIP_FRONT  16
#define RAY_CLIP_BACK   32
#define RAY_CLIP_NUMFLAGS   6

#define MAX_CANDIDATES  1024

#define NUMVERTEXNORMALS    162

static float vertexnormals[NUMVERTEXNORMALS][3] = {
#include "anorms.h"
};

static sinmdl_intersection_t sinmdl_intersect;
/*static qboolean twosided;*/


/*
=================
SINMDL_RayTriangleIntersect
=================
*/
#define EPSILON 0.0001
/*
 #define CROSS(dest,v1,v2) \
          dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
          dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
          dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
 #define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])

   int
 */
#define SUB( dest,v1,v2 ) { dest[0]=v1[0]-v2[0]; dest[1]=v1[1]-v2[1];  dest[2]=v1[2]-v2[2]; }

static qboolean SINMDL_RayTriangleIntersect( vec3_t orig,vec3_t dir,dtrivertx_t *verts,short * vertindex,float radius,float *t,qboolean twosided )
{
	float u, v;
	vec3_t edge1, edge2, tvec, pvec, qvec;
	float det,inv_det,rad_det;

	SUB( edge1, verts[vertindex[1]].v, verts[vertindex[0]].v );
	SUB( edge2, verts[vertindex[2]].v, verts[vertindex[0]].v );

	/* begin calculating determinant - also used to calculate U parameter */
	CrossProduct( dir, edge2, pvec );

	/* if determinant is near zero, ray lies in plane of triangle */
	det = DotProduct( edge1, pvec );

	if ( !twosided && det < EPSILON )
	{
		return 0;
	}

	rad_det = det + radius;

	/* calculate distance from vert0 to ray origin */
	SUB( tvec, orig, verts[vertindex[0]].v );

	if ( twosided )
	{
		CrossProduct( tvec, edge1, qvec );
	}
	else
	{
		/* calculate U parameter and test bounds */
		u = DotProduct( tvec, pvec );
		if (u < -radius || u > rad_det )
			return 0;

		rad_det += radius;

		/* prepare to test V parameter */
		CrossProduct( tvec, edge1, qvec );

		/* calculate V parameter and test bounds */
		v = DotProduct( dir, qvec );
		if ( v < -radius || u + v > rad_det )
			return 0;
	}

	/* calculate t, scale parameters, ray intersects triangle */
	*t = DotProduct( edge2, qvec );
	inv_det = 1.0 / det;
	*t *= inv_det;
	return 1;
}

typedef struct
{
	dsintriangle_t * tri;
	int groupindex;
	int tri_num;
	//vec3_t normal;
} tri_candidate_t;

/*
=================
SINMDL_RayIntersect
=================
*/
sinmdl_intersection_t * SINMDL_RayIntersect( int defhandle,int anim,int frame,float scale,vec3_t origin,float mat[3][3],const vec3_t start,const vec3_t end,float radius )
{
	int i, j, k;
	dsinmdl_t      *pmdl;
	dsinbasemdl_t  *base;
	dsintriframe_t *pframe;
	dsinanimdef_t  *panimdef;
	dsinanim_t     *panim;
	dsintrigroup_t *pgroup;
	dsingroup_t    *psingroup;
	dsintriangle_t *tris;
	dtrivertx_t    *pverts;
	vec3_t voffset, vscale;
	vec3_t world_to_model[3];
	vec3_t trans_start, trans_end;
	vec3_t new_start, new_end;
	vec3_t m_start, m_end;
	vec3_t dir;
	vec3_t world_dir;
	vec3_t trans_dir;
	float best_t;
	int best;
	int num_candidates;
	tri_candidate_t candidates[MAX_CANDIDATES];
	byte normalokay[NUMVERTEXNORMALS];
	int rendergroups[32];


	pmdl = SINMDL_GetDef(defhandle);
	if (!pmdl)
		return NULL;

	// get current base
	base = SINMDL_GetBaseModel(pmdl->base_handle);
	if (!base)
		return NULL;

	if (!base->num_groups)
		return NULL;

	// get current anim
	if (anim >= pmdl->num_anims || anim < 0)
	{
		Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_RayIntersect: no such anim %d in %s\n", anim, pmdl->name);
		return NULL;
	}

	panimdef = (dsinanimdef_t *) ((byte *)pmdl + pmdl->ofs_animdefs[anim]);
	panim = SINMDL_GetAnim( panimdef->handle );
	if (!panim)
		return NULL;

	// get current frame
	if (frame >= panim->num_frames || frame < 0)
	{
		Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_RayIntersect: no such frame %d in anim %d for %s\n", frame, anim, pmdl->name);
		return NULL;
	}

	// point to the data
	pframe = &((dsintriframe_t *) ((byte *)panim + panim->ofs_frames))[frame];
	pverts = (dtrivertx_t *) ((byte *)pframe + pframe->ofs_verts);

	//
	// subtract the origin of the object
	//
	VectorSubtract( start, origin, new_start );
	VectorSubtract( end, origin, new_end );
	//
	// transpose the current matrix to get world to model from model to world
	//
	TransposeMatrix( mat, world_to_model );

	//
	// transform the start and end positions into the model space
	//
	MatrixTransformVector( new_start, world_to_model, trans_start );
	MatrixTransformVector( new_end, world_to_model, trans_end );
	//
	// translate start and end into model space
	// caculate total origin;
	//
	for (i=0; i<3; i++)
	{
		voffset[i] = ( ( pframe->translate[i] + pmdl->load_origin[i] ) * scale * pmdl->load_scale );// + origin[i];
		vscale[i] = pframe->scale[i] * scale * pmdl->load_scale;
	}
	VectorSubtract( trans_start, voffset, new_start );
	VectorSubtract( trans_end, voffset, new_end );
	//
	// scale start and end down by current values
	//
	for (i=0; i<3; i++)
	{
		m_start[i] = new_start[i] / vscale[i];
		m_end[i] = new_end[i] / vscale[i];
	}
	//
	// scale down the radius
	//
	radius /= VectorLength( vscale );

	//
	// calculate dir
	//
	VectorSubtract( m_end, m_start, dir );
	VectorNormalize( dir );

	//
	// pre-calculate all the normals
	//
	//
	// calculate world_dir
	//
	VectorSubtract( end, start, world_dir );
	VectorNormalize( world_dir );
	MatrixTransformVector( world_dir, world_to_model, trans_dir );
	VectorNormalize( trans_dir );
	memset( normalokay, 0, sizeof( normalokay ) );
	for ( i = 0; i < NUMVERTEXNORMALS; i++ )
	{
		float dot;

		dot = DotProduct( trans_dir, vertexnormals[i] );
		if ( dot < 0 )
			normalokay[i] = 1;
	}
	//
	// get all the group flags
	//
	memset( rendergroups, 0, sizeof( rendergroups ) );
	psingroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
	for (i=0; i<pmdl->num_groups; i++,psingroup++)
	{
		for (j=0; j<psingroup->num_subgroups; j++)
		{
			int index;
			index = psingroup->subgroups[j].basegroupnum;
			rendergroups[index] |= psingroup->subgroups[j].flags;
		}
	}

	num_candidates = 0;
	for( i = 0; i < base->num_groups; i++ )
	{
		if ( rendergroups[i] & ( MDL_GROUP_NODRAW | MDL_GROUP_MASKED ) )
			continue;

		pgroup = &base->groups[i];
		tris = (dsintriangle_t *)((byte *)pgroup + pgroup->ofs_tris);
		for( j = 0; j < pgroup->num_tris; j++, tris++ )
		{
			int sum;

			if ( rendergroups[i] & MDL_GROUP_TWOSIDED )
			{
				sum = 3;
			}
			else
			{
				sum = 0;
				for( k = 0; k < 3; k++ )
				{
					sum += normalokay[ pverts[tris->index_xyz[k]].lightnormalindex ];
				}
			}
			if ( ( sum > 1 ) && (num_candidates < MAX_CANDIDATES) )
			{
				candidates[num_candidates].groupindex = i;
				candidates[num_candidates].tri_num = j;
				candidates[num_candidates++].tri = tris;
			}
		}
	}
	if (!num_candidates)
		return NULL;
	sinmdl_intersect.valid = false;
	best = -1;
	best_t = 0;/* silence compiler */
	for (i = 0; i < num_candidates; i++)
	{
		float t;
		if (SINMDL_RayTriangleIntersect
			(
				m_start,
				dir,
				pverts,
				candidates[i].tri->index_xyz,
				radius,
				&t,
				false
				//rendergroups[ candidates[i].groupindex ] & MDL_GROUP_TWOSIDED
			)
		    )
		{
			if (t >= 0 && (best == -1 || t < best_t))
			{
				best = i;
				best_t = t;
			}
		}
	}
	if (best != -1)
	{
		vec3_t normal;
		vec3_t pos;
		dtrivertx_t * vert;

		sinmdl_intersect.valid = true;
		sinmdl_intersect.group = candidates[best].groupindex;
		sinmdl_intersect.tri_num = candidates[best].tri_num;
		pgroup = &base->groups[sinmdl_intersect.group];
		tris = (dsintriangle_t *)((byte *)pgroup + pgroup->ofs_tris);
		for ( j = 0; j < 3; j++ )
		{
			for ( i = 0; i < 3; i++ )
			{
				if (!j)
				{
					normal[i] = 0;
					pos[i] = 0;
				}
				vert = &pverts[ tris[sinmdl_intersect.tri_num].index_xyz[j] ];
				pos[i] += voffset[i] + (vert->v[i] * vscale[i] );
				normal[i] += vertexnormals[vert->lightnormalindex][i];
			}
		}
		VectorScale( normal, 1.0/3.0f, normal );
		MatrixTransformVector( normal, mat, sinmdl_intersect.normal );
		VectorScale( pos, 1.0/3.0f, pos );
		MatrixTransformVector( pos, mat, sinmdl_intersect.position );
		VectorAdd( sinmdl_intersect.position, origin, sinmdl_intersect.position );
		MatrixTransformVector( normal, mat, sinmdl_intersect.normal );
	}
//	Com_DPrintf (DEVELOPER_MSG_SINMDL, "SINMDL_RayIntersect: %d num_candidates\n", num_candidates);
	if (!sinmdl_intersect.valid)
		return NULL;

	sinmdl_intersect.parentgroup = -1;
	sinmdl_intersect.damage_multiplier = 1.0f;
	psingroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
	for (i=0; i<pmdl->num_groups; i++,psingroup++)
	{
		for (j=0; j<psingroup->num_subgroups; j++)
		{
			if (sinmdl_intersect.group == psingroup->subgroups[j].basegroupnum)
			{
				sinmdl_intersect.parentgroup = i;
				sinmdl_intersect.damage_multiplier = psingroup->damage_multiplier;
				break;
			}
		}
		if (sinmdl_intersect.parentgroup >= 0)
			break;
	}
	return &sinmdl_intersect;
}

/*
=================
SINMDL_ModelList
=================
*/
void SINMDL_ModelList( void )
{
	int i, num;
	int basetotal, animtotal, deftotal;
	defcache_t * dcache;
	basemdlcache_t * bcache;
	animcache_t * acache;

	basetotal = 0;
	Com_Printf( "===============\n" );
	Com_Printf( "= Base Models =\n" );
	Com_Printf( "===============\n" );
	num = cache_numbasemodels;
	for( i=0, bcache = &basecache[0]; i<MAX_BASEMODEL_CACHE_SIZE; i++, bcache++ ) {
		if (!bcache->basemodel)
			continue;
		Com_Printf( "B %6d : %s\n", bcache->size, bcache->path );
		basetotal += bcache->size;
		if (!num--)
			break;
	}
	Com_Printf ( "Total Base: %i\n\n", basetotal );

	animtotal = 0;
	Com_Printf( "==============\n" );
	Com_Printf( "= Animations =\n" );
	Com_Printf( "==============\n" );
	num = cache_numanims;
	for( i=0, acache = &animcache[0]; i<MAX_ANIM_CACHE_SIZE; i++, acache++ ) {
		if (!acache->anim)
			continue;
		Com_Printf( "A %6d : %s\n", acache->size, acache->path );
		animtotal += acache->size;
		if (!num--)
			break;
	}
	Com_Printf ( "Total Anim: %i\n\n", animtotal );

	deftotal = 0;
	Com_Printf( "===============\n" );
	Com_Printf( "= Definitions =\n" );
	Com_Printf( "===============\n" );
	num = cache_numdefs;
	for( i=0, dcache = &defcache[0]; i<MAX_DEF_CACHE_SIZE; i++, dcache++ ) {
		if (!dcache->def)
			continue;
		Com_Printf( "D %6d : %s\n", dcache->size, dcache->path );
		deftotal += dcache->size;
		if (!num--)
			break;
	}
	Com_Printf ( "Total Defs: %i\n\n", deftotal );
	Com_Printf ( "Total Resident: %i\n", deftotal+animtotal+basetotal );
	Com_Printf ( "Num Base Models: %i\n", cache_numbasemodels );
	Com_Printf ( "Num Anims: %i\n", cache_numanims );
	Com_Printf ( "Num Defs: %i\n", cache_numdefs );
}

/*
=================
SINMDL_ModelInfo
=================
*/
void SINMDL_ModelInfo( int defhandle )
{
	dsinmdl_t * pmdl;
	int i, j, index;
	int basetotal, animtotal, tritotal;
	basemdlcache_t * bcache;
	animcache_t * acache;
	dsinanim_t     *panim;
	dsinbasemdl_t  *base;
	dsintrigroup_t *pgroup;
	dsingroup_t    *psingroup;

	pmdl = SINMDL_GetDef( defhandle );
	if (!pmdl) {
		Com_Printf( "ModelInfo: model not found.\n" );
		return;
	}
	Com_Printf( "MODEL: %s\n", pmdl->name );

	basetotal = 0;
	index = pmdl->base_handle;
	base = SINMDL_GetBaseModel( index );
	if ( !base ) {
		Com_Printf( "ModelInfo: couldn't find base model.\n" );
		return;
	}
	bcache = &basecache[index];
	Com_Printf( "Base Model: %s : %6d\n", bcache->path, bcache->size );
	basetotal += bcache->size;

	animtotal = 0;
	Com_Printf ( "Total Anims: %i\n", pmdl->num_anims );
	for ( i = 0; i < pmdl->num_anims; i++ ) {
		dsinanimdef_t *panimdef;

		panimdef = ANIM_DEF( pmdl, i );
		index = panimdef->handle;
		panim = SINMDL_GetAnim( index );
		if ( !panim ) {
			Com_Printf( "ModelInfo: couldn't find anim %d\n", i );
			return;
		}
		acache = &animcache[index];
		Com_Printf( "   %32s : %6i\n", panim->name, acache->size );
		animtotal += acache->size;
	}

	Com_Printf ( "Total Skins: %i\n", pmdl->num_skins );
	for ( i = 0; i < pmdl->num_skins; i++ ) {
		Com_Printf( "  Skin:  %s\n", SINMDL_Skin_NameForNum( defhandle, i ) );
	}
	Com_Printf( "Model Groups:  %i\n", base->num_groups );
	Com_Printf( "Named Groups:  %i\n", pmdl->num_groups );
	tritotal = 0;
	for ( i = 0; i < pmdl->num_groups; i++ ) {
		psingroup = ( dsingroup_t * )( (byte *)pmdl + pmdl->ofs_groups );
		psingroup += i;
		Com_Printf( "  Group %s\n", SINMDL_Group_NumToName( defhandle, i ) );
		for (j=0; j<psingroup->num_subgroups; j++) {
			int index;
			index = psingroup->subgroups[j].basegroupnum;
			pgroup = &base->groups[index];
			Com_Printf( "     ID %3i TRIS %3i\n", pgroup->id + 1, pgroup->num_tris );
		}
	}
	for ( i = 0; i < base->num_groups; i++ ) {
		pgroup = &base->groups[i];
		tritotal += pgroup->num_tris;
	}

	Com_Printf( "\n" );
	Com_Printf( "ST Vertices: %i\n", base->num_st );
	Com_Printf( "Vertices: %i\n", base->num_xyz );
	Com_Printf( "Triangles: %i\n", tritotal );
	Com_Printf( "\n" );
	Com_Printf ( "Total Base Size  : %i\n", basetotal );
	Com_Printf ( "Total Anims Size : %i\n", animtotal );
	Com_Printf ( "Total Size       : %i\n", animtotal+basetotal );
}

#endif
