/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

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

						 - SPLITMODELS -

==========================================================================
*/
// - Adding the Player model using Skeletal animation blending
// by Jalisk0

#include "cg_local.h"

pmodel_t		cg_entPModels[MAX_EDICTS];
pmodelinfo_t	*cg_PModelInfos;

//======================================================================
//						PlayerModel Registering
//======================================================================

//================
//CG_PModelsInit
//================
void CG_PModelsInit( void )
{
	memset( cg_entPModels, 0, sizeof(cg_entPModels) );
}

//================
//CG_CopyAnimation
//================
static void CG_CopyAnimation( pmodelinfo_t *pmodelinfo, int put, int pick )
{
	pmodelinfo->firstframe[put] = pmodelinfo->firstframe[pick];
	pmodelinfo->lastframe[put] = pmodelinfo->lastframe[pick];
	pmodelinfo->loopingframes[put] = pmodelinfo->loopingframes[pick];
}

//================
//CG_FindBoneNum
//================
static int CG_FindBoneNum( cgs_skeleton_t *skel, char *bonename )
{
	int	j;

	if( !skel || !bonename )
		return -1;

	for( j = 0; j < skel->numBones; j++ ) {
		if( !Q_stricmp(skel->bones[j].name, bonename) )
			return j;
	}

	return -1;
}

//================
//CG_ParseRotationBone
//================
static void CG_ParseRotationBone( pmodelinfo_t *pmodelinfo, char *token, int pmpart )
{
	int boneNumber;
	
	boneNumber = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), token );
	if( boneNumber < 0 ) {
		if( cg_debugPlayerModels->integer )
			CG_Printf( "CG_ParseRotationBone: No such bone name %s\n", token );
		return;
	}
	//register it into pmodelinfo
	if( cg_debugPlayerModels->integer )
		CG_Printf( "Script: CG_ParseRotationBone: %s is %i\n", token, boneNumber );
	pmodelinfo->rotator[pmpart][pmodelinfo->numRotators[pmpart]] = boneNumber;
	pmodelinfo->numRotators[pmpart]++;
}

//================
//CG_ParseTagMask
//================
static void CG_ParseTagMask( struct model_s *model, int bonenum, char *name, float forward, float right, float up )
{
	cg_tagmask_t *tagmask;
	cgs_skeleton_t	*skel;

	if( !name || !name[0] )
		return;

	skel = CG_SkeletonForModel( model );
	if( !skel || skel->numBones <= bonenum ) 
		return;

	//fixme: check the name isn't already in use, or it isn't the same as a bone name

	//now store it
	tagmask = CG_Malloc( sizeof(cg_tagmask_t) );
	Q_snprintfz( tagmask->tagname, sizeof(tagmask->tagname), name );
	Q_snprintfz( tagmask->bonename, sizeof(tagmask->bonename), skel->bones[bonenum].name );
	tagmask->bonenum = bonenum;
	tagmask->offset[0] = forward;
	tagmask->offset[1] = right;
	tagmask->offset[2] = up;
	tagmask->next = skel->tagmasks;
	skel->tagmasks = tagmask;

	if( cg_debugPlayerModels->integer )
		CG_Printf( "Added Tagmask: %s -> %s\n", tagmask->tagname, tagmask->bonename );
}

/*
================
CG_ParseAnimationScript

  Reads the animation config file.

0 = first frame
1 = number of frames/lastframe
2 = looping frames
3 = frame time
  
Note: The animations count begins at 1, not 0. I preserve zero for "no animation change"
---------------
New keywords:
nooffset: Uses the first frame value at the script, and no offsets, for the animation.
alljumps: Uses 3 different jump animations (bunnyhoping)
islastframe: second value of each animation is lastframe instead of numframes
	
Q3 keywords:
sex m/f : sets gender
	  
Q3 Unsupported:
headoffset <value> <value> <value>
footsteps
================
*/
static qboolean CG_ParseAnimationScript( pmodelinfo_t *pmodelinfo, char *filename )
{
	qbyte		*buf;
	char		*ptr, *token;
	int			rounder, counter, i, offset;
	qboolean	lower_anims_have_offset = qfalse;
	qboolean	alljumps = qfalse;
	qboolean	islastframe = qtrue;
	qboolean	debug = qtrue;
	int			anim_data[3][PMODEL_MAX_ANIMS];//splitskel:data is: firstframe, lastframe, loopingframes
	int			rootanims[PMODEL_PARTS];
	int			filenum;
	int			length;

	memset( rootanims, -1, sizeof(rootanims) );
	pmodelinfo->sex = GENDER_MALE;
	pmodelinfo->frametime = 1000/24;	//24fps by default
	rounder = 0;
	counter = 1; //reseve 0 for 'no animation'

	if( !cg_debugPlayerModels->integer )
		debug = qfalse;

	// load the file
	length = trap_FS_FOpenFile( filename, &filenum, FS_READ );
	if( length == -1 ) {
		CG_Printf( "Couldn't find animation script: %s\n", filename );
		return qfalse;
	}

	buf = CG_Malloc( length + 1 );
	length = trap_FS_Read( buf, length, filenum );
	trap_FS_FCloseFile( filenum );
	if( !length ) {
		CG_Free( buf );
		CG_Printf( "Couldn't load animation script: %s\n", filename );
		return qfalse;
	}
	
	//proceed
	ptr = ( char * )buf;
	while( ptr )
	{
		token = COM_ParseExt( &ptr, qtrue );
		if( !token )
			break;
		
		if( *token < '0' || *token > '9' ) {
			
			//gender
			if( !Q_stricmp(token, "sex") ) {
				
				if( debug ) CG_Printf("Script: %s:", token );
				
				token = COM_ParseExt( &ptr, qfalse );
				if( !token )	//Error (fixme)
					break;
				
				if( token[0] == 'm' || token[0] == 'M' ) {
					pmodelinfo->sex = GENDER_MALE;
					if( debug ) CG_Printf( " %s -Gender set to MALE\n", token );
					
				} else if( token[0] == 'f' || token[0] == 'F' ) {
					pmodelinfo->sex = GENDER_FEMALE;
					if( debug ) CG_Printf( " %s -Gender set to FEMALE\n", token );

				} else if( token[0] == 'n' || token[0] == 'N' ) {
					pmodelinfo->sex = GENDER_NEUTRAL;
					if( debug ) CG_Printf( " %s -Gender set to NEUTRAL\n", token );

				} else {
					if( debug ) {
						if(token[0])
							CG_Printf (" WARNING: unrecognized token: %s\n", token);
						else
							CG_Printf (" WARNING: no value after cmd sex: %s\n", token);
					}
					break; //Error
				}
				
			//nooffset
			} else if( !Q_stricmp(token, "offset") ) {
				lower_anims_have_offset = qtrue;
				if( debug ) CG_Printf( "Script: Using offset values for lower frames\n" );

			//alljumps
			} else if( !Q_stricmp(token, "alljumps") ) {
				alljumps = qtrue;
				if( debug ) CG_Printf( "Script: Using all jump animations\n" );

			//islastframe
			} else if( !Q_stricmp(token, "isnumframes") ) {
				islastframe = qfalse;
				if( debug ) CG_Printf( "Script: Second value is read as numframes\n" );

			} else if( !Q_stricmp(token, "islastframe") ) {
				islastframe = qtrue;
				if( debug ) CG_Printf( "Script: Second value is read as lastframe\n" );

			//FPS
			} else if( !Q_stricmp(token, "fps") ) {
				int fps;
				token = COM_ParseExt ( &ptr, qfalse );
				if ( !token ) break; 	//Error (fixme)

				fps = (int)atoi(token);
				if( fps == 0 && debug )	//Error (fixme)
					CG_Printf( "Script: WARNING:Invalid FPS value (%s) in config for playermodel %s \n", token, filename );
				if( fps < 10 )	//never allow less than 10 fps
					fps = 10;

				pmodelinfo->frametime = ( 1000/(float)fps );
				if( debug ) CG_Printf( "Script: FPS: %i\n", fps );

			//Rotation bone
			} else if( !Q_stricmp(token, "rotationbone") ) {
				token = COM_ParseExt( &ptr, qfalse );
				if( !token || !token[0] ) break;	//Error (fixme)

				if( !Q_stricmp(token, "upper") ) {
					token = COM_ParseExt( &ptr, qfalse );
					if( !token || !token[0] ) break;	//Error (fixme)
					CG_ParseRotationBone( pmodelinfo, token, UPPER );
				}
				else if( !Q_stricmp(token, "head") ) {
					token = COM_ParseExt( &ptr, qfalse );
					if( !token || !token[0] ) break;	//Error (fixme)
					CG_ParseRotationBone( pmodelinfo, token, HEAD );
				}
				else if( debug ) {
					CG_Printf ("Script: ERROR: Unrecognized rotation pmodel part %s\n", token);
					CG_Printf ("Script: ERROR: Valid names are: 'upper', 'head'\n");
				}
			
			//Root animation bone
			} else if( !Q_stricmp(token, "rootanim") ) {
				token = COM_ParseExt( &ptr, qfalse );
				if( !token || !token[0] ) break;

				if( !Q_stricmp(token, "upper") ) {
					rootanims[UPPER] = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), COM_ParseExt( &ptr, qfalse ) );
				}
				else if( !Q_stricmp(token, "head") ) {
					rootanims[HEAD] = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), COM_ParseExt( &ptr, qfalse ) );
				}
				else if( !Q_stricmp(token, "lower") ) {
					rootanims[LOWER] = CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), COM_ParseExt( &ptr, qfalse ) );
					//we parse it so it makes no error, but we ignore it later on
					CG_Printf ("Script: WARNING: Ignored rootanim lower: Valid names are: 'upper', 'head' (lower is always skeleton root)\n");
				}
				else if( debug ) {
					CG_Printf ("Script: ERROR: Unrecognized root animation pmodel part %s\n", token);
					CG_Printf ("Script: ERROR: Valid names are: 'upper', 'head'\n");
				}
				
			//Tag bone (format is: tagmask "bone name" "tag name")
			} else if( !Q_stricmp(token, "tagmask") ) {
				int	bonenum;

				token = COM_ParseExt( &ptr, qfalse );
				if( !token || !token[0] )
					break;//Error

				bonenum =  CG_FindBoneNum( CG_SkeletonForModel( pmodelinfo->model ), token );
				if( bonenum != -1 ) {
					char maskname[MAX_QPATH];
					float	forward, right, up;

					token = COM_ParseExt( &ptr, qfalse );
					if( !token || !token[0] ) {
						CG_Printf ("Script: ERROR: missing maskname in tagmask for bone %i\n", bonenum );
						break;//Error
					}
					Q_strncpyz( maskname, token, sizeof(maskname) );
					forward = atof( COM_ParseExt( &ptr, qfalse ) );
					right = atof( COM_ParseExt( &ptr, qfalse ) );
					up = atof( COM_ParseExt( &ptr, qfalse ) );
					CG_ParseTagMask( pmodelinfo->model, bonenum, maskname, forward, right, up );
				} else if( debug ) {
					CG_Printf( "Script: WARNING: Unknown bone name: %s\n", token );
				}

			} else if( token[0] && debug )
				CG_Printf( "Script: WARNING: unrecognized token: %s\n", token );
			
		} else {
			//frame & animation values
			i = (int)atoi(token);
			if( debug ) CG_Printf( "%i - ", i );
			anim_data[rounder][counter] = i;
			rounder++;
			if( rounder > 2 ) {
				rounder = 0;
				if( debug ) CG_Printf( " anim: %i\n", counter );
				counter++;
				if( counter == PMODEL_MAX_ANIMATIONS )
					break;
			}
		}
	}
	
	CG_Free(buf);
	
	//it must contain at least as many animations as a Q3 script to be valid
	if( counter-1 < LEGS_TURN ) {
		CG_Printf( "PModel Error: Not enough animations(%i) at animations script: %s\n", counter, filename );
		return qfalse;
	}
	
	//animation ANIM_NONE (0) is always at frame 0, and it's never 
	//received from the game, but just used on the client when none
	//animation was ever set for a model (head).
	
	anim_data[0][ANIM_NONE] = 0;
	anim_data[1][ANIM_NONE]	= 0;
	anim_data[2][ANIM_NONE]	= 1;
	
	//reorganize to make my life easier
	for( i = 0; i < counter; i++ )
	{
		pmodelinfo->firstframe[i] = anim_data[0][i];
		if( islastframe )
			pmodelinfo->lastframe[i] = anim_data[1][i];
		else
			pmodelinfo->lastframe[i] = ((anim_data[0][i]) + (anim_data[1][i]));

		pmodelinfo->loopingframes[i] = anim_data[2][i];
	}
	
	if( lower_anims_have_offset )
	{
		//find offset (Why did they do this???)  
		offset = pmodelinfo->firstframe[LEGS_CRWALK] - pmodelinfo->firstframe[TORSO_GESTURE];
		//Remove offset from the lower & extra animations
		for( i = LEGS_CRWALK; i < counter; ++i ){
			pmodelinfo->firstframe[i] -= offset;
			pmodelinfo->lastframe[i] -= offset;
		}
		
		if( debug )
			CG_Printf( "PModel: Fixing offset on lower frames (Q3 format)\n" );
	}
	
	//Fix the ranges to fit my looping calculations
	for( i = 0; i < counter; ++i )
	{
		if( pmodelinfo->loopingframes[i] )
			pmodelinfo->loopingframes[i] -= 1;
		if( pmodelinfo->lastframe[i] )
			pmodelinfo->lastframe[i] -= 1;
	}

	// create a bones array listing the animations each bone will play
	{
		cgs_skeleton_t *skel;
		int	j, bonenum;

		skel = CG_SkeletonForModel( pmodelinfo->model );
		rootanims[LOWER] = 0;
		memset( pmodelinfo->boneAnims, LOWER, sizeof(pmodelinfo->boneAnims) );
		for( j = LOWER + 1; j < PMODEL_PARTS; j++ ) {
			if( rootanims[j] == -1 ) continue;
			
			for( i = 0; i < skel->numBones; i++ ) {
				if( i == rootanims[j] ) {
					if( pmodelinfo->boneAnims[i] < j )
						pmodelinfo->boneAnims[i] = j;
					continue;
				}
				// run up to the tree root searching for rootanim bone
				bonenum = i;
				while( skel->bones[bonenum].parent != -1 ) {
					if( bonenum == rootanims[j] ) { // has the desired bone as parent
						if( pmodelinfo->boneAnims[i] < j )
							pmodelinfo->boneAnims[i] = j;
						break;
					}
					bonenum = skel->bones[bonenum].parent;
				}
			}
		}
	}
	
	//Alljumps:	 I use 3 jump animations for bunnyhoping
	//animation support. But they will only be loaded as
	//bunnyhoping animations if the keyword "alljumps" is
	//present at the animation.cfg. Otherwise, LEGS_JUMP1
	//will be used for all the jump styles.
	if( !alljumps )
	{
		CG_CopyAnimation( pmodelinfo, LEGS_JUMP3, LEGS_JUMP1 );
		CG_CopyAnimation( pmodelinfo, LEGS_JUMP3ST, LEGS_JUMP1ST );
		CG_CopyAnimation( pmodelinfo, LEGS_JUMP2, LEGS_JUMP1 );
		CG_CopyAnimation( pmodelinfo, LEGS_JUMP2ST, LEGS_JUMP1ST );
	}
	
	//HACK Uncomplete scripts (for Q3 player models)
	//----------------------------------------------------
	counter--;
	
	if( counter < TORSO_RUN )
		CG_CopyAnimation( pmodelinfo, TORSO_RUN, TORSO_STAND );
	if( counter < TORSO_DROPHOLD )
		CG_CopyAnimation( pmodelinfo, TORSO_DROPHOLD, TORSO_STAND );
	if( counter < TORSO_DROP )
		CG_CopyAnimation( pmodelinfo, TORSO_DROP, TORSO_ATTACK2 );
	if( counter < TORSO_PAIN1 )
		CG_CopyAnimation( pmodelinfo, TORSO_PAIN1, TORSO_STAND2 );
	if( counter < TORSO_PAIN2 )
		CG_CopyAnimation( pmodelinfo, TORSO_PAIN2, TORSO_STAND2 );
	if( counter < TORSO_PAIN3 )
		CG_CopyAnimation( pmodelinfo, TORSO_PAIN3, TORSO_STAND2 );
	if( counter < TORSO_SWIM )
		CG_CopyAnimation( pmodelinfo, TORSO_SWIM, TORSO_STAND );
	
	if( counter < LEGS_WALKBACK )
		CG_CopyAnimation( pmodelinfo, LEGS_WALKBACK, LEGS_RUNBACK );
	if( counter < LEGS_WALKLEFT )
		CG_CopyAnimation( pmodelinfo, LEGS_WALKLEFT, LEGS_WALKFWD );
	if( counter < LEGS_WALKRIGHT )
		CG_CopyAnimation( pmodelinfo, LEGS_WALKRIGHT, LEGS_WALKFWD );
	if( counter < LEGS_RUNLEFT )
		CG_CopyAnimation( pmodelinfo, LEGS_RUNLEFT, LEGS_RUNFWD );
	if( counter < LEGS_RUNRIGHT )
		CG_CopyAnimation( pmodelinfo, LEGS_RUNRIGHT, LEGS_RUNFWD );
	if( counter < LEGS_SWIM )
		CG_CopyAnimation( pmodelinfo, LEGS_SWIM, LEGS_SWIMFWD );

	return qtrue;
}

//================
//CG_LoadPModelWeaponSet
//These models are also used for the 1st person view attached weapon
//================
static void CG_LoadPModelWeaponSet( pmodelinfo_t *pmodelinfo )
{
	int 		i;

	for( i = 0; i < cgs.numWeaponModels; i++ ) 
	{
		pmodelinfo->weaponIndex[i] = CG_RegisterWeaponModel( cgs.weaponModels[i], i );
		if( !cg_vwep->integer )
			break; // only do weapon 0
	}

	//special case for weapon 0. Must always load the animation script
	if( !pmodelinfo->weaponIndex[0] )
		pmodelinfo->weaponIndex[0] = CG_CreateWeaponZeroModel( cgs.weaponModels[0] );
}

//===============
//	CG_PModel_RegisterBoneposes
//	Sets up skeleton with inline boneposes based on frame/oldframe values
//===============
static void CG_PModel_RegisterBoneposes( pmodel_t *pmodel )
{
	cgs_skeleton_t		*skel;

	if( !pmodel->pmodelinfo->model )
		return;

	skel = CG_SkeletonForModel( pmodel->pmodelinfo->model );
	if( skel && skel == pmodel->skel )
		return;

	if( !skel ) // not skeletal model
	{
		if( pmodel->skel )
		{
			if( pmodel->curboneposes )	{
				CG_Free( pmodel->curboneposes );
				pmodel->curboneposes = NULL;
			}
			if( pmodel->oldboneposes ) {
				CG_Free( pmodel->oldboneposes );
				pmodel->oldboneposes = NULL;
			}

			pmodel->skel = NULL;
		}
		return;
	}

	if( !pmodel->skel || ( pmodel->skel && (pmodel->skel->numBones != skel->numBones) )
		|| !pmodel->curboneposes || !pmodel->oldboneposes )
	{
		if( pmodel->curboneposes )	
			CG_Free( pmodel->curboneposes );
		if( pmodel->oldboneposes )
			CG_Free( pmodel->oldboneposes );
		
		pmodel->curboneposes = CG_Malloc( sizeof(bonepose_t) * skel->numBones );
		pmodel->oldboneposes = CG_Malloc( sizeof(bonepose_t) * skel->numBones );
	}

	pmodel->skel = skel;
}

//================
//CG_LoadPlayerModel
//================
static qboolean CG_LoadPlayerModel( pmodelinfo_t *pmodelinfo, char *filename )
{
	qboolean 	loaded_model = qfalse;
	char	anim_filename[MAX_QPATH];
	char	scratch[MAX_QPATH];

	Q_snprintfz( scratch, sizeof(scratch), "%s/tris.skm", filename );
	pmodelinfo->model = CG_RegisterModel( scratch );
	if( !trap_R_SkeletalGetNumBones( pmodelinfo->model, NULL ) ) // pmodels only accept skeletal models
		return qfalse;

	//load animations script
	if( pmodelinfo->model ) {
		Q_snprintfz( anim_filename, sizeof(anim_filename), "%s/animation.cfg", filename );
		loaded_model = CG_ParseAnimationScript( pmodelinfo, anim_filename );
	}
	//clean up if failed
	if( !loaded_model ) {
		pmodelinfo->model = NULL;
		return qfalse;
	}

	pmodelinfo->name = CG_CopyString( filename );

	// load sexed sounds for this model
	CG_UpdateSexedSoundsRegistration( pmodelinfo );
	CG_LoadPModelWeaponSet( pmodelinfo );
	return qtrue;
}
//===============
//CG_RegisterPModel
//PModel is not exactly the model, but the indexes of the
//models contained in the pmodel and it's animation data
//===============
struct pmodelinfo_s *CG_RegisterPlayerModel( char *filename )
{
	pmodelinfo_t	*pmodelinfo;

	for( pmodelinfo = cg_PModelInfos; pmodelinfo; pmodelinfo = pmodelinfo->next )	{
		if( !Q_stricmp( pmodelinfo->name, filename ) )
			return pmodelinfo;
	}

	pmodelinfo = CG_Malloc( sizeof(pmodelinfo_t) );
	if( !CG_LoadPlayerModel( pmodelinfo, filename ) ) {
		CG_Free( pmodelinfo );
		return NULL;
	}
	
	pmodelinfo->next = cg_PModelInfos;
	cg_PModelInfos = pmodelinfo;

	return pmodelinfo;
}

//================
//CG_RegisterBasePModel
//Default fallback replacements
//================
void CG_RegisterBasePModel( void )
{
	char	filename[MAX_QPATH];

	//pmodelinfo
	Q_snprintfz( filename, sizeof(filename), "%s/%s", "models/players", DEFAULT_PLAYERMODEL );
	cgs.basePModelInfo = CG_RegisterPlayerModel( filename );

	Q_snprintfz( filename, sizeof(filename), "%s/%s/%s", "models/players" , DEFAULT_PLAYERMODEL, DEFAULT_PLAYERSKIN );
	cgs.baseSkin = trap_R_RegisterSkinFile( filename );
	if( !cgs.baseSkin )
		CG_Error( "'Default Player Model'(%s): Skin (%s) failed to load", DEFAULT_PLAYERMODEL, filename );

	if( !cgs.basePModelInfo )
		CG_Error( "'Default Player Model'(%s): failed to load", DEFAULT_PLAYERMODEL );
}

//======================================================================
//							tools
//======================================================================


//===============
//CG_GrabTag
//In the case of skeletal models, boneposes must
//be transformed prior to calling this function
//===============
qboolean CG_GrabTag( orientation_t *tag, entity_t *ent, char *tagname )
{
	cgs_skeleton_t	*skel;

	if( !ent->model )
		return qfalse;

	skel = CG_SkeletonForModel( ent->model );
	if( skel )
		return CG_SkeletalPoseGetAttachment( tag, skel, ent->boneposes, tagname );

	return trap_R_LerpTag( tag, ent->model, ent->frame, ent->oldframe, ent->backlerp, tagname );
}

//===============
//CG_PlaceRotatedModelOnTag
//===============
void CG_PlaceRotatedModelOnTag( entity_t *ent, entity_t *dest, orientation_t *tag )
{
	int		i;
	vec3_t	tmpAxis[3];

	VectorCopy( dest->origin, ent->origin );
	VectorCopy( dest->lightingOrigin, ent->lightingOrigin );
	
	for( i = 0 ; i < 3 ; i++ )
		VectorMA( ent->origin, tag->origin[i], dest->axis[i], ent->origin );

	VectorCopy( ent->origin, ent->oldorigin );
	Matrix_Multiply( ent->axis, tag->axis, tmpAxis );
	Matrix_Multiply( tmpAxis, dest->axis, ent->axis );
}

//===============
//CG_PlaceModelOnTag
//===============
void CG_PlaceModelOnTag( entity_t *ent, entity_t *dest, orientation_t *tag )
{
	int i;
	
	VectorCopy( dest->origin, ent->origin );
	VectorCopy( dest->lightingOrigin, ent->lightingOrigin );
	
	for( i = 0 ; i < 3 ; i++ )
		VectorMA( ent->origin, tag->origin[i], dest->axis[i], ent->origin );
	
	VectorCopy( ent->origin, ent->oldorigin );
	Matrix_Multiply( tag->axis, dest->axis, ent->axis );
}

//===============
//CG_MoveToTag
//"move" tag must have an axis and origin set up. Use vec3_origin and axis_identity for "nothing"
//===============
void CG_MoveToTag(	vec3_t move_origin,
					vec3_t move_axis[3],
					vec3_t dest_origin,
					vec3_t dest_axis[3],
					vec3_t tag_origin,
					vec3_t tag_axis[3] ) {
	int		i;
	vec3_t	tmpAxis[3];

	VectorCopy( dest_origin, move_origin );
	
	for( i = 0 ; i < 3 ; i++ )
		VectorMA( move_origin, tag_origin[i], dest_axis[i], move_origin );

	Matrix_Multiply( move_axis, tag_axis, tmpAxis );
	Matrix_Multiply( tmpAxis, dest_axis, move_axis );
}

//===============
//CG_PModel_GetProjectionSource
//It asumes the player entity is up to date
//===============
qboolean CG_PModel_GetProjectionSource( int entnum, orientation_t *tag_result )
{
	centity_t		*cent;
	pmodel_t		*pmodel;

	if( !tag_result )
		return qfalse;

	if( entnum < 1 || entnum >= MAX_EDICTS )
		return qfalse;

	cent = &cg_entities[entnum];
	if( cent->serverFrame != cg.frame.serverFrame ) 
		return qfalse;

	// see if it's the viewweapon
	if( vweap.active && (cg.chasedNum+1 == entnum) && !cg.thirdPerson ) {
		VectorCopy( vweap.projectionSource.origin, tag_result->origin );
		Matrix_Copy( vweap.projectionSource.axis, tag_result->axis );
		return qtrue;
	}
	
	// it's a 3rd person model
	pmodel = &cg_entPModels[entnum];
	VectorCopy( pmodel->projectionSource.origin, tag_result->origin );
	Matrix_Copy( pmodel->projectionSource.axis, tag_result->axis );
	return qtrue;
}

/*
//
//===============
//CG_PModel_CalcBrassSource
//===============
//
void CG_PModel_CalcBrassSource( pmodel_t *pmodel, orientation_t *projection )
{
	orientation_t	tag_weapon;
	orientation_t	ref;

	//the code asumes that the pmodel ents are up-to-date
	if(!pmodel->pmodelinfo || !pmodel->ent.model || !pmodel->ent.model 
		|| !trap_R_LerpTag( &tag_weapon, pmodel->ents[UPPER].model, pmodel->anim.frame[UPPER], pmodel->anim.oldframe[UPPER], pmodel->ent.backlerp, "tag_weapon" ) )
	{
		VectorCopy(pmodel->ents[LOWER].origin, projection->origin);
		projection->origin[2] += 16;
		Matrix_Identity(projection->axis);
	}
	//brass projection is simply tag_weapon
	VectorCopy( pmodel->ents[UPPER].origin, ref.origin );
	Matrix_Copy( pmodel->ents[UPPER].axis, ref.axis );
	CG_MoveToTag ( projection, &ref, &tag_weapon );
	//projection->origin[2] += 8;
}
*/

//===============
//CG_AddQuadShell
//===============
void CG_AddQuadShell( entity_t *ent )
{
	entity_t	shell;

	shell = *ent;
	shell.customSkin = NULL;

	if( shell.flags & RF_WEAPONMODEL )
		shell.customShader = CG_MediaShader( cgs.media.shaderQuadWeapon );
	else
		shell.customShader = CG_MediaShader( cgs.media.shaderPowerupQuad );

	shell.flags |= (RF_FULLBRIGHT|RF_NOSHADOW);
	CG_AddEntityToScene( &shell );
}

//===============
//CG_AddPentShell
//===============
void CG_AddPentShell( entity_t *ent )
{
}

//===============
//CG_AddShellEffects
//===============
void CG_AddShellEffects( entity_t *ent, int effects )
{
	// quad and pent can do different things on client
	if( ent->flags & RF_VIEWERMODEL )
		return;

	if( effects & EF_QUAD ) 
		CG_AddQuadShell(ent);
}

//===============
//CG_AddColorShell
//===============
void CG_AddColorShell( entity_t *ent, int renderfx )
{
	int			i;
	static entity_t	shell;
	static vec4_t shadelight = { 0.0f, 0.0f, 0.0f, 0.3f };

	if( ent->flags & RF_VIEWERMODEL )
		return;
	
	if( !(renderfx & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE)) )
		return;

	shell = *ent;
	shell.customSkin = NULL;
	
	if( renderfx & RF_SHELL_RED )
		shadelight[0] = 1.0;
	if( renderfx & RF_SHELL_GREEN )
		shadelight[1] = 1.0;
	if( renderfx & RF_SHELL_BLUE )
		shadelight[2] = 1.0;
	
	for( i = 0; i < 4; i++ )
		shell.color[i] = shadelight[i] * 255;
	
	if( ent->flags & RF_WEAPONMODEL )
		return;//fixme: try the shell shader for viewweapon, or build a good one
	else
		shell.customShader = CG_MediaShader( cgs.media.shaderShellEffect );

	shell.flags |= (RF_FULLBRIGHT|RF_NOSHADOW);
	CG_AddEntityToScene( &shell );//vicskmblend
}

//===============
//CG_OutlineShaderLODfodDist
//===============
struct shader_s *CG_OutlineShaderLODForDistance( entity_t *e, float scale ) {
	float dist;
	vec3_t dir;

	// Kill if behind the view or if too far away
	VectorSubtract( e->origin, cg.refdef.vieworg, dir );
	dist = VectorNormalize2( dir, dir ) * cg.view_fracDistFOV;
	if( dist > 1024 )
		return NULL;

	if( !( e->flags & RF_WEAPONMODEL ) ) {
		if( DotProduct( dir, cg.v_forward ) < 0 )
			return NULL;
	}

	dist *= scale;

	if( dist < 64 || ( e->flags & RF_WEAPONMODEL ) ) {
		return CG_MediaShader( cgs.media.shaderColoredModelOutlinex1 );
	}
	else if( dist < 128 ) {
		return CG_MediaShader( cgs.media.shaderColoredModelOutlinex2 );
	}
	else if( dist < 256 ) {
		return CG_MediaShader( cgs.media.shaderColoredModelOutlinex3 );
	}
	else if( dist < 512 ) {
		return CG_MediaShader( cgs.media.shaderColoredModelOutlinex4 );
	}
	else {
		return CG_MediaShader( cgs.media.shaderColoredModelOutlinex5 );
	}
}
//===============
//CG_AddColoredOutLineEffect
//===============
void CG_SetOutlineColor( byte_vec4_t outlineColor, vec4_t itemcolor ) {
	float darken = 0.3f;
	outlineColor[0] = ( qbyte )( 255 * (itemcolor[0] * darken) );
	outlineColor[1] = ( qbyte )( 255 * (itemcolor[1] * darken) );
	outlineColor[2] = ( qbyte )( 255 * (itemcolor[2] * darken) );
	outlineColor[3] = ( qbyte )( 255 );
}

//===============
//CG_AddColoredOutLineEffect
//===============
void CG_AddColoredOutLineEffect( entity_t *ent, int effects, qbyte r, qbyte g, qbyte b, qbyte a )
{
	static entity_t	shell;
	struct shader_s *shader;

	if( !cg_outlineModels->integer )
		return;

	if( !(effects & EF_OUTLINE) || (ent->flags & RF_VIEWERMODEL) )
		return;

	if( effects & EF_QUAD ) {
		shader = CG_OutlineShaderLODForDistance( ent, 4.0f );
	} else {
		shader = CG_OutlineShaderLODForDistance( ent, 1.0f );
	}
	if( !shader )
		return;

	shell = *ent;
	shell.customSkin = NULL;
	shell.flags = (RF_FULLBRIGHT|RF_NOSHADOW);
	shell.customShader = shader;
	if( effects & EF_QUAD ) {
		shell.color[0] = ( qbyte )( 255 );
		shell.color[1] = ( qbyte )( 255 );
		shell.color[2] = ( qbyte )( 0 );
		shell.color[3] = ( qbyte )( 255 );
	} else {
		shell.color[0] = ( qbyte )( r );
		shell.color[1] = ( qbyte )( g );
		shell.color[2] = ( qbyte )( b );
		shell.color[3] = ( qbyte )( a );
	}
	
	trap_R_AddEntityToScene( &shell );
}

//===============
//CG_AddCentityOutLineEffect
//===============
void CG_AddCentityOutLineEffect( centity_t *cent )
{
	CG_AddColoredOutLineEffect( &cent->ent, cent->effects, cent->outlineColor[0], cent->outlineColor[1], cent->outlineColor[2], cent->outlineColor[3] );
}

void CG_AddFlagModelOnTag( centity_t *cent, int flag_team, char *tagname );
//===============
//CG_PModel_AddFlag
//===============
void CG_PModel_AddFlag( centity_t *cent ) {

	int flag_team;

	flag_team = (cent->current.team == TEAM_RED) ? TEAM_BLUE : TEAM_RED;
	CG_AddFlagModelOnTag( cent, flag_team, "tag_flag1" );
}

//===============
//CG_PModel_AddHeadIcon
//===============
void CG_AddHeadIcon( centity_t *cent )
{
	entity_t	balloon;
	struct shader_s *iconShader = NULL;
	float		radius = 6, upoffset = 8;

	if( cent->ent.flags & RF_VIEWERMODEL )
		return;

	if( cent->effects & EF_BUSYICON ) {
		iconShader = CG_MediaShader( cgs.media.shaderChatBalloon );
		radius = 12;
		upoffset = 2;
	}
#ifdef VSAYS
	else if( cent->localEffects[LOCALEFFECT_VSAY_HEADICON_TIMEOUT] > cg.time ) {
		if( cent->localEffects[LOCALEFFECT_VSAY_HEADICON] < VSAY_TOTAL )
			iconShader = CG_MediaShader( cgs.media.shaderVSayIcon[cent->localEffects[LOCALEFFECT_VSAY_HEADICON]] );
		else
			iconShader = CG_MediaShader( cgs.media.shaderVSayIcon[VSAY_GENERIC] );

		radius = 6;
		upoffset = 4;
	}
#endif // VSAYS

	// add the current active icon
	if( iconShader != NULL ) {
		memset( &balloon, 0, sizeof( entity_t) );
		balloon.rtype = RT_SPRITE;
		balloon.model = NULL;
		balloon.flags = RF_NOSHADOW;
		balloon.radius = radius;
		balloon.customShader = iconShader;
		balloon.scale = 1.0f;

		balloon.origin[0] = cent->ent.origin[0];
		balloon.origin[1] = cent->ent.origin[1];
		balloon.origin[2] = cent->ent.origin[2] + playerbox_stand_maxs[2] + balloon.radius + upoffset;
		VectorCopy( balloon.origin, balloon.oldorigin );
		Matrix_Identity( balloon.axis );

		trap_R_AddEntityToScene( &balloon );
	}
}


//======================================================================
//							animations
//======================================================================

//===============
// CG_PModel_BlendSkeletalPoses
// Sets up the boneposes array mixing up bones
// from different frames, based on the defined
// bone animations. Result bones are not transformed.
//===============
static void CG_PModel_BlendSkeletalPoses( cgs_skeleton_t *skel, bonepose_t *boneposes, int *boneAnims, int *boneKeyFrames )
{
	int			i, frame;
	bonepose_t	*framed_bonepose;
	
	for( i = 0; i < skel->numBones; i++ ) {
		frame = boneKeyFrames[ boneAnims[i] ];
		if( frame < 0 || frame >= skel->numFrames )
			frame = 0;

		framed_bonepose = skel->bonePoses[frame];
		framed_bonepose += i;
		memcpy( &boneposes[i], framed_bonepose, sizeof(bonepose_t) );
	}		
}

/*
===============
CG_PModelAnimToFrame

  Transforms the values inside state->frame into 3 animation values
  for head, torso and legs (head is always zero right now). It also
  uses those values to handle the animation frames clientside, as 
  also does the backlerp calculation for variable framerates.
  
  The frames and other data is stored inside animationinfo_t. Actually
  only clientinfo has structs of this type inside, but they could also
  be applied to a client-side monsterinfo handler.
  
  It works like this:
  -for animations reveived by BASIC_CHANNEL:
  When a new animation is recieved, the new animation is launched.
  While 0 is recieved, it advances the frames in last recieved animation.
  When the frame gets to the end of the animation, it looks for the
  looping value and sends the frame back to (lastframe - loopingframes)
  and so on. If the looping value is 0, it holds the animation in the
  last frame until a new animation is recieved.

  -for animations reveived by EVENT_CHANNEL:
  The playing BASIC_CHANNEL animation is put back on the animation
  buffer, the EVENT_CHANNEL animation is played, and when it comes to 
  it's end,the BASIC_CHANNEL animation at buffer (the same as we put in, 
  or a new one received by that time) is promoted to be played.
===============
*/
static void CG_PModelAnimToFrame( pmodel_t *pmodel, animationinfo_t *anim )
{
	int i;
	pmodelinfo_t	*pmodelinfo = pmodel->pmodelinfo;
	cgs_skeleton_t	*skel;

	//interpolate
	if( cg.time < anim->nextframetime )
	{
		anim->backlerp = 1.0f - ((cg.time - anim->prevframetime)/(anim->nextframetime - anim->prevframetime));
		if (anim->backlerp > 1)
			anim->backlerp = 1;
		else if (anim->backlerp < 0)
			anim->backlerp = 0;
		
		return;
	}
	
	for( i=0; i<PMODEL_PARTS; i++ )
	{
		//advance frames
		anim->oldframe[i] = anim->frame[i];
		anim->frame[i]++;
		
		//looping
		if( anim->frame[i] > pmodelinfo->lastframe[anim->current[i]] )
		{
			if (anim->currentChannel[i])	
				anim->currentChannel[i] = BASIC_CHANNEL;//kill

			anim->frame[i] = (pmodelinfo->lastframe[anim->current[i]] - pmodelinfo->loopingframes[anim->current[i]]);
		}

		//NEWANIM
		if( anim->buffer[EVENT_CHANNEL].newanim[i] )
		{
			//backup if basic
			if (anim->buffer[BASIC_CHANNEL].newanim[i] +
				anim->currentChannel[i] == BASIC_CHANNEL){//both are zero
				anim->buffer[BASIC_CHANNEL].newanim[i] = anim->current[i];
			}
			//set up
			anim->current[i] = anim->buffer[EVENT_CHANNEL].newanim[i];
			anim->frame[i] = pmodelinfo->firstframe[anim->current[i]];
			anim->currentChannel[i] = EVENT_CHANNEL;
			anim->buffer[EVENT_CHANNEL].newanim[i] = 0;
		}
		else if( anim->buffer[BASIC_CHANNEL].newanim[i]
			&& ( anim->currentChannel[i] != EVENT_CHANNEL ) )
		{
			//set up
			anim->current[i] = anim->buffer[BASIC_CHANNEL].newanim[i];
			anim->frame[i] = pmodelinfo->firstframe[anim->current[i]];
			anim->currentChannel[i] = BASIC_CHANNEL;
			anim->buffer[BASIC_CHANNEL].newanim[i] = 0;
		}
	}

	//new method
	skel = CG_SkeletonForModel( pmodel->pmodelinfo->model );
	if( !skel )
		CG_Error( "Non-skeletal PModel inside 'CG_PModelAnimToFrame'\n" );

	if( skel != pmodel->skel )
		CG_PModel_RegisterBoneposes( pmodel ); // update registration
	
	//blend animations into old-current poses
	CG_PModel_BlendSkeletalPoses( skel, pmodel->curboneposes, pmodel->pmodelinfo->boneAnims, pmodel->anim.frame );
	CG_PModel_BlendSkeletalPoses( skel, pmodel->oldboneposes, pmodel->pmodelinfo->boneAnims, pmodel->anim.oldframe );

	//updated frametime
	anim->prevframetime = cg.time;
	anim->nextframetime = cg.time + pmodelinfo->frametime;
	anim->backlerp = 1.0f;
}

//===============
//CG_ClearEventAnimations
//  Called from CG_PModelUpdateState if the new state has a EV_TELEPORT.
//  This is done so respawning can reset animations, because event
//  animations could persist after dying
//===============
void CG_ClearEventAnimations( int entNum )
{
	int i;
	pmodel_t	*pmodel = &cg_entPModels[entNum];

	for( i = LOWER ; i < PMODEL_PARTS ; i++ ) {
		//clean up the buffer
		pmodel->anim.buffer[EVENT_CHANNEL].newanim[i] = 0;

		//finish up currents
		if( pmodel->anim.currentChannel[i] == EVENT_CHANNEL ) {
			pmodel->anim.frame[i] = pmodel->pmodelinfo->lastframe[pmodel->anim.current[i]];
		}
		pmodel->anim.currentChannel[i] = BASIC_CHANNEL;
	}
}

//===============
//CG_AddPModelAnimation
//===============
void CG_AddPModelAnimation( int entNum, int loweranim, int upperanim, int headanim, int channel)
{
	int	i;
	int newanim[PMODEL_PARTS];
	animationbuffer_t	*buffer;
	pmodel_t	*pmodel = &cg_entPModels[entNum];

	newanim[LOWER] = loweranim;
	newanim[UPPER] = upperanim;
	newanim[HEAD] = headanim;

	buffer = &pmodel->anim.buffer[channel];

	for( i = 0 ; i < PMODEL_PARTS ; i++ ) {
		//ignore new events if in death
		if( channel && buffer->newanim[i] && (buffer->newanim[i] < TORSO_GESTURE) )
			continue;

		if( newanim[i] && (newanim[i] < PMODEL_MAX_ANIMS) )
			buffer->newanim[i] = newanim[i];
	}
}

//==============
//CG_PModels_AddFireEffects
//start fire animations, flashes, etc
//==============
void CG_PModels_AddFireEffects( entity_state_t *state )
{
	if( state->effects & EF_CORPSE )
		return;
	
	//if it's the user in 1st person, viewapon code handles this
	if( state->number == cg.chasedNum+1 && !cg.thirdPerson )
		return;
	
	//activate weapon flash
	if( cg_weaponFlashes->integer ) {
		pmodel_t *pmodel = &cg_entPModels[state->number];
		pmodel->pweapon.flashtime = cg.time + (int)pmodel->pmodelinfo->frametime;
	}
	
	//add effects
	switch( state->weapon )
	{
	case WEAP_GUNBLADE:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
	case WEAP_SHOCKWAVE:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
	case WEAP_ROCKETLAUNCHER:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
	case WEAP_GRENADELAUNCHER:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
	case WEAP_PLASMAGUN:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
	case WEAP_RIOTGUN:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
	case WEAP_ELECTROBOLT:
		CG_AddPModelAnimation( state->number, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		break;
		
	default:
		break;
	}
}


//======================================================================
//							player model
//======================================================================

//==================
//CG_PModelFixOldAnimationMiss
//Run the animtoframe twice so it generates an old skeleton pose
//==================
static void CG_PModelFixOldAnimationMiss( int entNum )
{
	pmodel_t	*pmodel = &cg_entPModels[entNum];

	//set up old frame
	pmodel->anim.nextframetime = cg.time;
	CG_PModelAnimToFrame( pmodel, &pmodel->anim );
}

int CG_ForceTeam( int entNum, int team ) {
	static int forceEnemyTeam = 0;
	static int forceMyTeam = 0;

	if( cg_forceEnemyTeam->modified ) {
		if( !cg_forceEnemyTeam->string || !strlen(cg_forceEnemyTeam->string) ) {
			forceEnemyTeam = 0;
		} else {
			forceEnemyTeam = GS_Teams_TeamFromName( cg_forceEnemyTeam->string );
			if( forceEnemyTeam <= 0 || forceEnemyTeam >= GS_MAX_TEAMS ) {
				CG_Printf( "%sWarning: User tried to force an invalid team%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
				trap_Cvar_Set( "cg_forceEnemyTeam", "" );
				forceEnemyTeam = 0;
			}
		}
		cg_forceEnemyTeam->modified = qfalse;
	}

	if( cg_forceMyTeam->modified ) {
		if( !cg_forceMyTeam->string || !strlen(cg_forceMyTeam->string) ) {
			forceMyTeam = 0;
		} else {
			forceMyTeam = GS_Teams_TeamFromName( cg_forceMyTeam->string );
			if( forceMyTeam <= 0 || forceMyTeam >= GS_MAX_TEAMS ) {
				CG_Printf( "%sWarning: User tried to force an invalid team%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
				trap_Cvar_Set( "cg_forceMyTeam", "" );
				forceMyTeam = 0;
			}
		}
		cg_forceMyTeam->modified = qfalse;
	}

	if( forceEnemyTeam ) {
		if( !GS_Gametype_IsTeamBased(cg.frame.playerState.stats[STAT_GAMETYPE]) ) {
			if( entNum != cg.chasedNum+1 )
				return forceEnemyTeam;
		} else {
			if( team != cg.frame.playerState.stats[STAT_TEAM] )
				return forceEnemyTeam;
		}
	}

	if( forceMyTeam ) {
		if( !GS_Gametype_IsTeamBased(cg.frame.playerState.stats[STAT_GAMETYPE]) ) {
			if( entNum == cg.chasedNum+1 )
				return forceMyTeam;
		} else {
			if( team == cg.frame.playerState.stats[STAT_TEAM] )
				return forceMyTeam;
		}
	}

	return team;
}

void CG_RegisterForceModel( cvar_t *teamForceModel, cvar_t *teamForceSkin, pmodelinfo_t **ppmodelinfo, struct skinfile_s **pskin ) {
	pmodelinfo_t *pmodelinfo;
	struct skinfile_s *skin = NULL;

	if( teamForceModel )
		teamForceModel->modified = qfalse;

	if( teamForceSkin )
		teamForceSkin->modified = qfalse;

	if( !ppmodelinfo || !pskin )
		return;

	*ppmodelinfo = NULL; // disabled force models
	*pskin = NULL;

	// register new ones if possible
	if( teamForceModel->string[0] ) {
		pmodelinfo = CG_RegisterPlayerModel( va( "models/players/%s", teamForceModel->string ) );
		// if it failed, it will be NULL, so also disabled
		if( pmodelinfo ) {
			// when we register a new model, we must re-register the skin, even if the cvar is not modified
			skin = trap_R_RegisterSkinFile( va( "models/players/%s/%s", teamForceModel->string, teamForceSkin->string ) );
			// if the skin failed, we can still try with default value (so only setting model cvar has a visible effect)
			if( !skin ) {
				skin = trap_R_RegisterSkinFile( va( "models/players/%s/%s", teamForceModel->string, teamForceSkin->dvalue ) );
			}
		} else {
			teamForceModel->string[0] = 0;
		}

		if( pmodelinfo && skin ) {
			*ppmodelinfo = pmodelinfo;
			*pskin = skin;
		}
	}
}

// initialize all
void CG_RegisterForceModels( void ) {
	CG_RegisterForceModel( cg_teamPLAYERSmodel, cg_teamPLAYERSskin, &cgs.teamPLAYERSModelInfo, &cgs.teamPLAYERSCustomSkin );
	CG_RegisterForceModel( cg_teamREDmodel, cg_teamREDskin, &cgs.teamREDModelInfo, &cgs.teamREDCustomSkin );
	CG_RegisterForceModel( cg_teamBLUEmodel, cg_teamBLUEskin, &cgs.teamBLUEModelInfo, &cgs.teamBLUECustomSkin );
	CG_RegisterForceModel( cg_teamGREENmodel, cg_teamGREENskin, &cgs.teamGREENModelInfo, &cgs.teamGREENCustomSkin );
	CG_RegisterForceModel( cg_teamYELLOWmodel, cg_teamYELLOWskin, &cgs.teamYELLOWModelInfo, &cgs.teamYELLOWCustomSkin );
}

pmodelinfo_t *CG_PModelForCentity( centity_t *cent ) {
	int team;
	centity_t *owner;

	owner = cent;
	if( cent->effects & EF_CORPSE && cent->current.bodyOwner ) // it's a body
		owner = &cg_entities[cent->current.bodyOwner];

	team = CG_ForceTeam( owner->current.number, owner->current.team );

	// get the team model.
	switch( team ) {
		case TEAM_RED:
			{
				if( cg_teamREDmodel->modified || cg_teamREDskin->modified ) {
					CG_RegisterForceModel( cg_teamREDmodel, cg_teamREDskin, &cgs.teamREDModelInfo, &cgs.teamREDCustomSkin );
				}
				if( cgs.teamREDModelInfo ) { // There is a force model for this team
					return cgs.teamREDModelInfo;
				}
			}
			break;
		case TEAM_BLUE:
			{
				if( cg_teamBLUEmodel->modified || cg_teamBLUEskin->modified ) {
					CG_RegisterForceModel( cg_teamBLUEmodel, cg_teamBLUEskin, &cgs.teamBLUEModelInfo, &cgs.teamBLUECustomSkin );
				}
				if( cgs.teamBLUEModelInfo ) { // There is a force model for this team
					return cgs.teamBLUEModelInfo;
				}
			}
			break;
		case TEAM_GREEN:
			{
				if( cg_teamGREENmodel->modified || cg_teamGREENskin->modified ) {
					CG_RegisterForceModel( cg_teamGREENmodel, cg_teamGREENskin, &cgs.teamGREENModelInfo, &cgs.teamGREENCustomSkin );
				}
				if( cgs.teamGREENModelInfo ) { // There is a force model for this team
					return cgs.teamGREENModelInfo;
				}
			}
			break;
		case TEAM_YELLOW:
			{
				if( cg_teamYELLOWmodel->modified || cg_teamYELLOWskin->modified ) {
					CG_RegisterForceModel( cg_teamYELLOWmodel, cg_teamYELLOWskin, &cgs.teamYELLOWModelInfo, &cgs.teamYELLOWCustomSkin );
				}
				if( cgs.teamYELLOWModelInfo ) { // There is a force model for this team
					return cgs.teamYELLOWModelInfo;
				}
			}
			break;
		case TEAM_PLAYERS:
		default: // spectators will be this
			{
				if( cg_teamPLAYERSmodel->modified || cg_teamPLAYERSskin->modified ) {
					CG_RegisterForceModel( cg_teamPLAYERSmodel, cg_teamPLAYERSskin, &cgs.teamPLAYERSModelInfo, &cgs.teamPLAYERSCustomSkin );
				}
				if( cgs.teamPLAYERSModelInfo ) { // There is a force model for this team
					return cgs.teamPLAYERSModelInfo;
				}
			}
			break;
	}

	// return player defined one
	return cgs.pModelsIndex[cent->current.modelindex];
}

struct skinfile_s *CG_SkinForCentity( centity_t *cent ) {
	int team;
	centity_t *owner;

	owner = cent;
	if( cent->effects & EF_CORPSE && cent->current.bodyOwner ) // it's a body
		owner = &cg_entities[cent->current.bodyOwner];

	team = CG_ForceTeam( owner->current.number, owner->current.team );

	// get the team model.
	switch( team ) {
		case TEAM_RED:
			{
				if( cgs.teamREDCustomSkin ) { // There is a force model for this team
					return cgs.teamREDCustomSkin;
				}
			}
			break;
		case TEAM_BLUE:
			{
				if( cgs.teamBLUECustomSkin ) { // There is a force model for this team
					return cgs.teamBLUECustomSkin;
				}
			}
			break;
		case TEAM_GREEN:
			{
				if( cgs.teamGREENCustomSkin ) { // There is a force model for this team
					return cgs.teamGREENCustomSkin;
				}
			}
			break;
		case TEAM_YELLOW:
			{
				if( cgs.teamYELLOWCustomSkin ) { // There is a force model for this team
					return cgs.teamYELLOWCustomSkin;
				}
			}
			break;
		case TEAM_PLAYERS:
		default: // specators will be this
			{
				if( cgs.teamPLAYERSCustomSkin ) { // There is a force model for this team
					return cgs.teamPLAYERSCustomSkin;
				}
			}
			break;
	}

	// return player defined one
	return cgs.skinPrecache[cent->current.skinnum];
}

void CG_SetPlayerColor( centity_t *cent ) {
	int team;
	centity_t *owner;
	vec4_t	tempcolor;
	cvar_t	*teamForceColor = NULL;
	int		rgbcolor;
	int		*forceColor;

	owner = cent;
	if( cent->effects & EF_CORPSE && cent->current.bodyOwner ) // it's a body
		owner = &cg_entities[cent->current.bodyOwner];

	team = CG_ForceTeam( owner->current.number, owner->current.team );

	switch( team ) {
		case TEAM_RED:
			{
				teamForceColor = cg_teamREDcolor;
				forceColor = &cgs.teamREDColor;
			}
			break;
		case TEAM_BLUE:
			{
				teamForceColor = cg_teamBLUEcolor;
				forceColor = &cgs.teamBLUEColor;
			}
			break;
		case TEAM_GREEN:
			{
				teamForceColor = cg_teamGREENcolor;
				forceColor = &cgs.teamGREENColor;
			}
			break;
		case TEAM_YELLOW:
			{
				teamForceColor = cg_teamYELLOWcolor;
				forceColor = &cgs.teamYELLOWColor;
			}
			break;
		case TEAM_PLAYERS:
		default:
			{
				teamForceColor = cg_teamPLAYERScolor;
				forceColor = &cgs.teamPLAYERSColor;
			}
			break;
	}

	if( teamForceColor->modified ) {
		// load default one if in team based gametype
		if( team >= TEAM_RED ) {
			rgbcolor = COM_ReadColorRGBString( teamForceColor->dvalue );
			if( rgbcolor != -1 ) { // won't be -1 unless some coder defines a weird cvar
				*forceColor = rgbcolor;
			}
		}
		
		// if there is a force color, update with it
		if( teamForceColor->string[0] ) {
			rgbcolor = COM_ReadColorRGBString( teamForceColor->string );
			if( rgbcolor != -1 ) {
				*forceColor = rgbcolor;
			} else {
				teamForceColor->string[0] = 0; // didn't work, disable force color
			}
		}

		teamForceColor->modified = qfalse;
	}

	//if forcemodels is enabled or it is color forced team we do, 
	if( teamForceColor->string[0] || team >= TEAM_RED ) {
		// skin color to team color
		rgbcolor = *forceColor;
		Vector4Set( cent->color, COLOR_R(rgbcolor), COLOR_G(rgbcolor), COLOR_B(rgbcolor), 255 ); 
	}
	else { // user defined colors
		// see if this player model has a client for using his custom color
		if( owner->current.number - 1 < MAX_CLIENTS ) {
			Vector4Copy( cgs.clientInfo[ owner->current.number - 1 ].color, cent->color );
		} else {
			Vector4Set( cent->color, 255, 255, 255, 255 );
		}
	}

	//outlines
	if( cg_outlinePlayersBlack->integer ) {
		Vector4Set( cent->outlineColor, 0, 0, 0, 255 );
	} else {
		Vector4Scale( cent->color, 1.0f/255.0f, tempcolor );
		CG_SetOutlineColor( cent->outlineColor, tempcolor );
	}
}

//==================
//CG_UpdatePlayerModelEnt
//  Called each new serverframe
//==================
void CG_UpdatePlayerModelEnt( centity_t *cent )
{
	int newanim[PMODEL_PARTS];
	int			i;
	pmodel_t	*pmodel;

	// start from clean
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	cent->ent.scale = 1.0f;
	cent->ent.rtype = RT_MODEL;
	Vector4Set( cent->ent.color, 255, 255, 255, 255 );

	pmodel = &cg_entPModels[cent->current.number];
	pmodel->pmodelinfo = CG_PModelForCentity( cent );
	pmodel->skin = CG_SkinForCentity( cent );
	CG_SetPlayerColor( cent );

	//fallback
	if( !pmodel->pmodelinfo || !pmodel->skin ) {
		pmodel->pmodelinfo = cgs.basePModelInfo;
		pmodel->skin = cgs.baseSkin;
	}

	// make sure al poses have their memory space
	CG_RegisterBoneposesForCGEntity( cent, pmodel->pmodelinfo->model );
	CG_PModel_RegisterBoneposes( pmodel );
	cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes;
	
	//Update pweapon
	pmodel->pweapon.weaponInfo = CG_GetWeaponFromPModelIndex( pmodel, cent->current.weapon );

	//update parts rotation angles
	for( i = LOWER ; i < PMODEL_PARTS ; i++ )
		VectorCopy( pmodel->angles[i], pmodel->oldangles[i] );
	
	cent->effects |= EF_OUTLINE; // add always EF_OUTLINE to players.
	if( !(cent->effects & EF_CORPSE) )
	{
		//lower has horizontal direction, and zeroes vertical
		pmodel->angles[LOWER][PITCH] = 0;
		pmodel->angles[LOWER][YAW] = cent->current.angles[YAW];
		pmodel->angles[LOWER][ROLL] = 0;

		//upper marks vertical direction (total angle, so it fits aim)
		if( cent->current.angles[PITCH] > 180 )
			pmodel->angles[UPPER][PITCH] = (-360 + cent->current.angles[PITCH]);
		else
			pmodel->angles[UPPER][PITCH] = cent->current.angles[PITCH];
		
		pmodel->angles[UPPER][YAW] = 0;
		pmodel->angles[UPPER][ROLL] = 0;
		
		//head adds a fraction of vertical angle again
		if( cent->current.angles[PITCH] > 180 )
			pmodel->angles[HEAD][PITCH] = (-360 + cent->current.angles[PITCH])/3;
		else
			pmodel->angles[HEAD][PITCH] = cent->current.angles[PITCH]/3;
		
		pmodel->angles[HEAD][YAW] = 0;
		pmodel->angles[HEAD][ROLL] = 0;
	}

	// copy, not interpolated, starting positions (oldframe ones)
	cent->ent.backlerp = 1.0f;
	VectorCopy( cent->prev.origin, cent->ent.origin );
	VectorCopy( cent->prev.origin, cent->ent.oldorigin );
	VectorCopy( cent->prev.origin, cent->ent.lightingOrigin );
	if( !(cent->effects & EF_CORPSE) )
		AnglesToAxis( pmodel->oldangles[LOWER], cent->ent.axis );
	else
		AnglesToAxis( cent->prev.angles, cent->ent.axis );

	//Spawning (EV_TELEPORT) forces nobacklerp and the interruption of EVENT_CHANNEL animations
	if( (cent->current.events[0] == EV_TELEPORT) || (cent->current.events[1] == EV_TELEPORT) )
	{
		CG_ClearEventAnimations( cent->current.number );
		CG_AddPModelAnimation( cent->current.number, (cent->current.frame)&0x3F, (cent->current.frame>>6)&0x3F, (cent->current.frame>>12)&0xF, BASIC_CHANNEL );
		CG_PModelFixOldAnimationMiss( cent->current.number );
		for( i = LOWER ; i < PMODEL_PARTS ; i++ )
			VectorCopy( pmodel->angles[i], pmodel->oldangles[i] );

		return;
	}
	
	//filter repeated animations coming from state->frame
	newanim[LOWER] = (cent->current.frame&0x3F) * ((cent->current.frame &0x3F) != (cent->prev.frame &0x3F));
	newanim[UPPER] = (cent->current.frame>>6 &0x3F) * ((cent->current.frame>>6 &0x3F) != (cent->prev.frame>>6 &0x3F));
	newanim[HEAD] = (cent->current.frame>>12 &0xF) * ((cent->current.frame>>12 &0xF) != (cent->prev.frame>>12 &0xF));
	
	CG_AddPModelAnimation( cent->current.number, newanim[LOWER], newanim[UPPER], newanim[HEAD], BASIC_CHANNEL);
}


#ifdef _DEBUG
//#define CRAP_DEBUG_BOX  // private test (shows player model bbox)
#endif

void CG_PModel_SpawnTeleportEffect( centity_t *cent );
void CG_AddLinearTrail( centity_t *cent, float lifetime );

//===============
//CG_AddPModelEnt
//===============
void CG_AddPModel( centity_t *cent )
{
	int					i, j;
	pmodel_t			*pmodel;
	vec3_t				tmpangles;
	orientation_t		tag_weapon;
	
	pmodel = &cg_entPModels[cent->current.number];

	// if viewer model, and casting shadows, offset the entity to predicted player position
	// for view and shadow accurary

	if( cent->ent.flags & RF_VIEWERMODEL && !(cent->renderfx & RF_NOSHADOW) ) {
		vec3_t		org;
		if( cg_predict->integer && !(cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION) ) {
			vec3_t		angles;
			float backlerp = 1.0f - cg.lerpfrac;
			int		timeDelta;

			for( i = 0; i < 3; i++ )
				org[i] = cg.predictedOrigin[i] - backlerp * cg.predictionError[i];

			// smooth out stair climbing
			timeDelta = cg.realTime - cg.predictedStepTime;
			if ( timeDelta < PREDICTED_STEP_TIME ) {
				org[2] -= cg.predictedStep 
					* (PREDICTED_STEP_TIME - timeDelta) / PREDICTED_STEP_TIME;
			}

			// use refdef view angles on the model
			angles[YAW] = cg.refdef.viewangles[YAW];
			angles[PITCH] = 0;
			angles[ROLL] = 0;
			AnglesToAxis( angles, cent->ent.axis );
		} else
			VectorCopy( cent->ent.origin, org );

		// offset it some units back
		VectorMA( org, -24, cent->ent.axis[0], org );
		VectorCopy( org, cent->ent.origin);
		VectorCopy( org, cent->ent.oldorigin );
		VectorCopy( org, cent->ent.lightingOrigin );
	}
	
	// transform animation values into frames, and set up old-current poses pair
	CG_PModelAnimToFrame( pmodel, &pmodel->anim );

	// lerp old-current poses pair into interpolated pose
	CG_LerpBoneposes( pmodel->skel,
		pmodel->curboneposes, pmodel->oldboneposes,
		centBoneposes[cent->current.number].lerpboneposes,
		1.0f - pmodel->anim.backlerp );

	// relink interpolated pose into ent
	cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes;

	// add skeleton effects (pose is unmounted yet)
	if( !(cent->effects & EF_CORPSE) ) 
	{
		// apply interpolated LOWER angles to entity
		for( j = 0; j < 3; j++ )
			tmpangles[j] = LerpAngle( pmodel->oldangles[LOWER][j], pmodel->angles[LOWER][j], cg.lerpfrac );
		
		AnglesToAxis( tmpangles, cent->ent.axis );
		
		// apply UPPER and HEAD angles to rotator bones
		for( i=1; i<PMODEL_PARTS; i++ )	
		{
			if( pmodel->pmodelinfo->numRotators[i] )
			{
				// lerp rotation and divide angles by the number of rotation bones
				for( j = 0; j < 3; j++ ){
					tmpangles[j] = LerpAngle( pmodel->oldangles[i][j], pmodel->angles[i][j], cg.lerpfrac );
					tmpangles[j] /= pmodel->pmodelinfo->numRotators[i];
				}
				for( j=0; j<pmodel->pmodelinfo->numRotators[i]; j++ )
					CG_RotateBonePose( tmpangles, &cent->ent.boneposes[pmodel->pmodelinfo->rotator[i][j]] );
			}
		}
	}

	// finish (mount) pose. Now it's the final skeleton just as it's drawn.
	CG_TransformBoneposes( centBoneposes[cent->current.number].skel, centBoneposes[cent->current.number].lerpboneposes, centBoneposes[cent->current.number].lerpboneposes);

	cent->ent.backlerp = 0.0f;
	cent->ent.frame = cent->ent.oldframe = 0;//frame fields are not used with external poses

	// Add playermodel ent
	cent->ent.scale = 1.0f;
	cent->ent.rtype = RT_MODEL;
	cent->ent.customShader = NULL;
	cent->ent.model = pmodel->pmodelinfo->model;
	cent->ent.customSkin = pmodel->skin;

	Vector4Copy( cent->color, cent->ent.color );
	CG_AddEntityToScene( &cent->ent );
	if( !cent->ent.model )
		return;

	CG_PModel_AddFlag( cent );
	CG_AddCentityOutLineEffect( cent );
	CG_AddShellEffects( &cent->ent, cent->effects );
	CG_AddColorShell( &cent->ent, cent->renderfx );
	CG_AddHeadIcon( cent );
	if( cg_showPlayerTrails->value )
		CG_AddLinearTrail( cent, cg_showPlayerTrails->value );
#ifdef CGAMEGETLIGHTORIGIN
	if( !(cent->ent.flags & RF_NOSHADOW) )
		CG_AllocShadeBox( cent->current.number, cent->ent.origin, playerbox_stand_mins, playerbox_stand_maxs, NULL );
#endif

	// add teleporter sfx if needed
	CG_PModel_SpawnTeleportEffect( cent );

#ifdef CRAP_DEBUG_BOX	//jal private test
	if(!(cent->effects & EF_CORPSE))
		CG_DrawTestBox( cent->ent.origin, playerbox_stand_mins, playerbox_stand_maxs );
#endif

	// add weapon model
	if( cent->current.weapon && CG_GrabTag( &tag_weapon, &cent->ent, "tag_weapon" ) )
		CG_AddWeaponOnTag( &cent->ent, &tag_weapon, &pmodel->pweapon, cent->effects | EF_OUTLINE, &pmodel->projectionSource );
}

