/*
   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_ResetPModels
//================
void CG_ResetPModels( void )
{
	int i;

	for( i = 0; i < MAX_EDICTS; i++ )
	{
		memset( &cg_entPModels[i].anim, 0, sizeof( animationinfo_t ) );
		memset( &cg_entPModels[i].pweapon, 0, sizeof( pweapon_t ) );
	}
	memset( &vweap, 0, sizeof( vweap ) );
}

//================
//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->fps = 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[0] )
			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[0] )  //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[0] ) 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->fps = 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[0] ) break; //Error (fixme)

				if( !Q_stricmp( token, "upper" ) )
				{
					token = COM_ParseExt( &ptr, qfalse );
					if( !token[0] ) break; //Error (fixme)
					CG_ParseRotationBone( pmodelinfo, token, UPPER );
				}
				else if( !Q_stricmp( token, "head" ) )
				{
					token = COM_ParseExt( &ptr, qfalse );
					if( !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[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[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[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_ANIMS )
					break;
			}
		}
	}

	CG_Free( buf );

	//it must contain at least as many animations as a Q3 script to be valid
	if( counter < PMODEL_MAX_ANIMS )
	{
		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_TAUNT];
		//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;
	}

	// validate frames inside skeleton range
	{
		cgs_skeleton_t *skel;
		skel = CG_SkeletonForModel( pmodelinfo->model );
		for( i = 0; i < counter; ++i )
		{
			clamp( pmodelinfo->firstframe[i], 0, skel->numFrames - 1 );
			clamp( pmodelinfo->lastframe[i], 0, skel->numFrames - 1 );
		}
	}

	// store root bones of animations
	rootanims[LOWER] = -1;
	for( i = LOWER; i < PMODEL_PARTS; i++ )
		pmodelinfo->rootanims[i] = rootanims[i];

	//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 );
	}

	return qtrue;
}

//================
//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 );
	if( cgs.pure && !trap_FS_IsPureFile( scratch ) )
		return qfalse;

	pmodelinfo->model = CG_RegisterModel( scratch );
	if( !trap_R_SkeletalGetNumBones( pmodelinfo->model, NULL ) )
	{                                                          // pmodels only accept skeletal models
		pmodelinfo->model = NULL;
		return qfalse;
	}

	//load animations script
	if( pmodelinfo->model )
	{
		Q_snprintfz( anim_filename, sizeof( anim_filename ), "%s/animation.cfg", filename );
		if( !cgs.pure || trap_FS_IsPureFile( anim_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 );
	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] * ent->scale, dest->axis[i], ent->origin );

	VectorCopy( ent->origin, ent->origin2 );
	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] * ent->scale, dest->axis[i], ent->origin );

	VectorCopy( ent->origin, ent->origin2 );
	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 view weapon
	if( ISVIEWERENTITY( entnum ) && !cg.view.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
//===============
static void CG_AddQuadShell( entity_t *ent )
{
	entity_t shell;

	shell = *ent;
	shell.customSkin = NULL;

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

	shell.renderfx |= ( RF_FULLBRIGHT|RF_NOSHADOW );
	//	shell.renderfx |= (ent->renderfx & RF_OCCLUSIONTEST);
	shell.outlineHeight = 0;

	CG_AddEntityToScene( &shell );
}

//===============
//CG_AddShellShell
//===============
static void CG_AddShellShell( entity_t *ent )
{
	entity_t shell;

	shell = *ent;
	shell.customSkin = NULL;

	if( shell.renderfx & RF_WEAPONMODEL )
		shell.customShader = trap_R_RegisterPic( "powerups/warshell" );
	else
		shell.customShader = trap_R_RegisterPic( "powerups/warshell_weapon" );

	shell.renderfx |= ( RF_FULLBRIGHT|RF_NOSHADOW );
	//	shell.renderfx |= (ent->renderfx & RF_OCCLUSIONTEST);
	shell.outlineHeight = 0;

	CG_AddEntityToScene( &shell );
}

//===============
//CG_AddRaceGhostShell
//===============
static void CG_AddRaceGhostShell( entity_t *ent )
{
	entity_t shell;
	float alpha = cg_raceGhostsAlpha->value;

	clamp( alpha, 0, 1.0 );

	shell = *ent;
	shell.customSkin = NULL;

	if( shell.renderfx & RF_WEAPONMODEL )
		return;

	shell.customShader = CG_MediaShader( cgs.media.shaderRaceGhostEffect );
	shell.renderfx |= ( RF_FULLBRIGHT|RF_NOSHADOW );
	//	shell.renderfx |= (ent->renderfx & RF_OCCLUSIONTEST);
	shell.outlineHeight = 0;

	shell.color[0] *= alpha;
	shell.color[1] *= alpha;
	shell.color[2] *= alpha;
	shell.color[3] = 255 * alpha;

	CG_AddEntityToScene( &shell );
}

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

	if( effects & EF_QUAD )
		CG_AddQuadShell( ent );
	else if( effects & EF_SHELL )
		CG_AddShellShell( ent );
	else if( effects & EF_RACEGHOST )
		CG_AddRaceGhostShell( 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->renderfx & RF_VIEWERMODEL )
		return;

	if( !( renderfx & ( RF_COLORSHELL_GREEN | RF_COLORSHELL_RED | RF_COLORSHELL_BLUE ) ) )
		return;

	shell = *ent;
	shell.customSkin = NULL;

	if( renderfx & RF_COLORSHELL_RED )
		shadelight[0] = 1.0;
	if( renderfx & RF_COLORSHELL_GREEN )
		shadelight[1] = 1.0;
	if( renderfx & RF_COLORSHELL_BLUE )
		shadelight[2] = 1.0;

	for( i = 0; i < 4; i++ )
		shell.shaderRGBA[i] = shadelight[i] * 255;

	if( ent->renderfx & RF_WEAPONMODEL )
		return; //fixme: try the shell shader for viewweapon, or build a good one

	shell.customShader = CG_MediaShader( cgs.media.shaderShellEffect );
	shell.renderfx |= ( RF_FULLBRIGHT|RF_NOSHADOW );
	//	shell.renderfx |= (ent->renderfx & RF_OCCLUSIONTEST);
	shell.outlineHeight = 0;

	CG_AddEntityToScene( &shell ); //vicskmblend
}

//===============
//CG_SetOutlineColor
//===============
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_OutlineDistanceForEntity
//===============
static float CG_OutlineDistanceForEntity( entity_t *e )
{
	float dist;
	vec3_t dir;

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

	if( !( e->renderfx & RF_WEAPONMODEL ) )
	{
		if( DotProduct( dir, cg.view.axis[FORWARD] ) < 0 )
			return 0;
	}

	return dist;
}

//===============
//CG_OutlineScaleForDist
//===============
static float CG_OutlineScaleForDist( entity_t *e, float scale )
{
	float dist;

	dist = CG_OutlineDistanceForEntity( e );
	if( !dist )
		return 0;
	dist *= scale;

	if( dist < 64 || ( e->renderfx & RF_WEAPONMODEL ) )
		return 0.14f;
	if( dist < 128 )
		return 0.30f;
	if( dist < 256 )
		return 0.42f;
	if( dist < 512 )
		return 0.56f;
	return 0.70f;
}

//===============
//CG_AddColoredOutLineEffect
//===============
void CG_AddColoredOutLineEffect( entity_t *ent, int effects, qbyte r, qbyte g, qbyte b, qbyte a )
{
	float scale;
	qbyte *RGBA;

	if( !cg_outlineModels->integer || !( effects & EF_OUTLINE ) || ( ent->renderfx & RF_VIEWERMODEL ) )
	{
		ent->outlineHeight = 0;
		return;
	}

	if( effects & ( EF_QUAD|EF_SHELL ) )
	{
		scale = CG_OutlineScaleForDist( ent, 4.0f );
	}
	else
	{
		scale = CG_OutlineScaleForDist( ent, 1.0f );
	}

	if( !scale )
	{
		ent->outlineHeight = 0;
		return;
	}

	ent->outlineHeight = scale;
	RGBA = ent->outlineRGBA;

	if( effects & EF_QUAD )
	{
		RGBA[0] = ( qbyte )( 255 );
		RGBA[1] = ( qbyte )( 255 );
		RGBA[2] = ( qbyte )( 0 );
		RGBA[3] = ( qbyte )( 255 );
	}
	else if( effects & EF_SHELL )
	{
		RGBA[0] = ( qbyte )( 125 );
		RGBA[1] = ( qbyte )( 200 );
		RGBA[2] = ( qbyte )( 255 );
		RGBA[3] = ( qbyte )( 255 );
	}
	else
	{
		RGBA[0] = ( qbyte )( r );
		RGBA[1] = ( qbyte )( g );
		RGBA[2] = ( qbyte )( b );
		RGBA[3] = ( qbyte )( a );
	}
}

//===============
//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] );
}

//===============
//CG_PModel_AddFlag
//===============
static void CG_PModel_AddFlag( centity_t *cent )
{
	int flag_team;

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

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

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

	if( cent->effects & EF_BUSYICON )
	{
		iconShader = CG_MediaShader( cgs.media.shaderChatBalloon );
		radius = 12;
		upoffset = 2;
	}
	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 = 12;
		upoffset = 0;
	}

	// add the current active icon
	if( iconShader != NULL )
	{
		memset( &balloon, 0, sizeof( entity_t ) );
		balloon.rtype = RT_SPRITE;
		balloon.model = NULL;
		balloon.renderfx = RF_NOSHADOW;
		balloon.radius = radius;
		balloon.customShader = iconShader;
		balloon.scale = 1.0f;
		Matrix_Identity( balloon.axis );
		if( CG_GrabTag( &tag_head, &cent->ent, "tag_head" ) )
		{
			balloon.origin[0] = tag_head.origin[0];
			balloon.origin[1] = tag_head.origin[1];
			balloon.origin[2] = tag_head.origin[2] + balloon.radius + upoffset;
			VectorCopy( balloon.origin, balloon.origin2 );
			CG_PlaceModelOnTag( &balloon, &cent->ent, &tag_head );
		}
		else
		{
			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.origin2 );
		}

		trap_R_AddEntityToScene( &balloon );
	}
}


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

/*
   ===============
   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;

	//interpolate
	if( cg.time < anim->nextframetime )
	{
		anim->lerpFrac = (double)( cg.time - anim->prevframetime )/(double)( anim->nextframetime - anim->prevframetime );
		clamp( anim->lerpFrac, 0, 1.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;
		}
	}

	//updated frametime
	anim->prevframetime = cg.time;
	anim->nextframetime = (unsigned int)( (double)cg.time + 1000.0/(double)pmodelinfo->fps );
	anim->lerpFrac = 0.0f;
}

//===============
//CG_ClearEventAnimations
//  Called from CG_PModelUpdateState if the new state has a teleported bit.
//  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_TAUNT ) )
			continue;

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

//==============
//CG_PModel_StartShootEffect
//start fire animations, flashes, etc
//==============
void CG_PModel_StartShootEffect( int entNum )
{
	centity_t *cent;

	if( !ISVIEWERENTITY( entNum ) || cg.view.thirdperson )
	{                                                  // not in first person view
		cent = &cg_entities[entNum];
		if( cent->current.type == ET_PLAYER )
		{
			pmodel_t *pmodel = &cg_entPModels[entNum];

			//activate weapon flash & barrel
			if( cent->current.weapon == WEAP_GUNBLADE )
			{                                 // gunblade is special
				if( cent->current.effects & EF_STRONG_WEAPON )
				{
					if( cg_weaponFlashes->integer )
						pmodel->pweapon.flashtime = (unsigned int)( (double)cg.time + 1000.0/(double)pmodel->pmodelinfo->fps );
				}
				else
				{
					pmodel->pweapon.barreltime = (unsigned int)( (double)cg.time + 1000.0/(double)pmodel->pmodelinfo->fps );
				}
			}
			else
			{
				if( cg_weaponFlashes->integer )
				{
					pmodel->pweapon.flashtime = (unsigned int)( (double)cg.time + 1000.0/(double)pmodel->pmodelinfo->fps );
				}
				pmodel->pweapon.barreltime = (unsigned int)( (double)cg.time + 1000.0/(double)pmodel->pmodelinfo->fps );
			}

			//add animation to the player model
			CG_AddPModelAnimation( entNum, 0, TORSO_ATTACK1, 0, EVENT_CHANNEL );
		}
	}
}


//======================================================================
//							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 );
}

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

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

	pmodel = &cg_entPModels[cent->current.number];
	pmodel->pmodelinfo = CG_PModelForCentity( cent );
	pmodel->skin = CG_SkinForCentity( cent );
	CG_SetPlayerColor( cent );
	if( cg_raceGhosts->integer && !ISVIEWERENTITY( cent->current.number ) && ( cg.frame.playerState.stats[STAT_GAMETYPE] == GAMETYPE_RACE ) )
	{
		cent->effects &= ~EF_OUTLINE;
		cent->effects |= EF_RACEGHOST;
	}
	else
	{
		cent->effects |= EF_OUTLINE; // add always EF_OUTLINE to players.
	}

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

	// make sure al poses have their memory space
	cent->skel = CG_SkeletonForModel( pmodel->pmodelinfo->model );
	if( !cent->skel ) CG_Error( "CG_PlayerModelEntityNewState: ET_PLAYER without a skeleton\n" );

	//update parts rotation angles
	for( i = LOWER; i < PMODEL_PARTS; i++ )
		VectorCopy( pmodel->angles[i], pmodel->oldangles[i] );

	if( cent->current.type != ET_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;
	}

	//Spawning (teleported bit) forces nobacklerp and the interruption of EVENT_CHANNEL animations
	if( cent->current.teleported )
	{
		CG_ClearEventAnimations( cent->current.number );
		for( i = LOWER; i < PMODEL_PARTS; i++ )
			VectorCopy( pmodel->angles[i], pmodel->oldangles[i] );
	}

	if( cent->current.type == ET_CORPSE )
	{                                   // corpses use server side animations
		frame = cent->current.frame;
	}
	else
	{  // find out the base animation from the entity state
		int i, count;

		// smooth a velocity vector between the last snaps

		VectorSubtract( cent->current.origin, cent->prev.origin, cent->animVelocity );
		cent->animVelocity[2] = 0;
		VectorScale( cent->animVelocity, 1000.0f/(float)( cg.frame.serverTime - cg.oldFrame.serverTime ), cent->animVelocity );

		cent->lastVelocities[cg.frame.serverFrame&3][0] = (int)cent->animVelocity[0];
		cent->lastVelocities[cg.frame.serverFrame&3][1] = (int)cent->animVelocity[1];
		cent->lastVelocities[cg.frame.serverFrame&3][2] = (int)cent->animVelocity[2];
		cent->lastVelocities[cg.frame.serverFrame&3][3] = cg.frame.serverFrame;

		count = 0;
		VectorClear( cent->animVelocity );
		for( i = cg.frame.serverFrame; ( i >= 0 ) && ( count < 3 ) && ( i == cent->lastVelocities[i&3][3] ); i-- )
		{
			count++;
			cent->animVelocity[0] += cent->lastVelocities[i&3][0];
			cent->animVelocity[1] += cent->lastVelocities[i&3][1];
			cent->animVelocity[2] += cent->lastVelocities[i&3][2];
		}

		VectorScale( cent->animVelocity, 1.0f/(float)count, cent->animVelocity );
		frame = GS_UpdateBaseAnims( &cent->current, cent->animVelocity );
	}

	//filter unchanged animations
	newanim[LOWER] = ( frame&0x3F ) * ( ( frame &0x3F ) != ( cent->lastAnims&0x3F ) );
	newanim[UPPER] = ( frame>>6 &0x3F ) * ( ( frame>>6 &0x3F ) != ( cent->lastAnims>>6 &0x3F ) );
	newanim[HEAD] = ( frame>>12 &0xF ) * ( ( frame>>12 &0xF ) != ( cent->lastAnims>>12 &0xF ) );

	cent->lastAnims = frame;

	CG_AddPModelAnimation( cent->current.number, newanim[LOWER], newanim[UPPER], newanim[HEAD], BASIC_CHANNEL );
	if( cent->current.teleported )
		CG_PModelFixOldAnimationMiss( cent->current.number );
}


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

static bonepose_t blendpose[SKM_MAX_BONES];

//===============
//CG_AddPModelEnt
//===============
void CG_AddPModel( centity_t *cent )
{
	int i, j;
	pmodel_t *pmodel;
	vec3_t tmpangles;
	orientation_t tag_weapon;
	int rootanim;

	pmodel = &cg_entPModels[cent->current.number];

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

	if( cent->ent.renderfx & RF_VIEWERMODEL && !( cent->renderfx & RF_NOSHADOW ) )
	{
		vec3_t org;
		if( cg.view.playerPrediction )
		{
			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.view.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
		if( cg_shadows->integer == 1 )
			VectorMA( org, -24, cent->ent.axis[0], org );
		VectorCopy( org, cent->ent.origin );
		VectorCopy( org, cent->ent.origin2 );
		VectorCopy( org, cent->ent.lightingOrigin );
		VectorCopy( org, cg.lightingOrigin );
	}

	// transform animation values into frames, and set up old-current poses pair
	CG_PModelAnimToFrame( pmodel, &pmodel->anim );

	// register temp boneposes for this skeleton
	if( !cent->skel ) CG_Error( "CG_PlayerModelEntityAddToScene: ET_PLAYER without a skeleton\n" );
	cent->ent.boneposes = CG_RegisterTemporaryExternalBoneposes( cent->skel );
	cent->ent.oldboneposes = cent->ent.boneposes;

	// fill base pose with lower animation already interpolated
	CG_LerpBoneposes( cent->skel, pmodel->anim.oldframe[LOWER], pmodel->anim.frame[LOWER], cent->ent.boneposes, pmodel->anim.lerpFrac );

	// create an interpolated pose of the animation to be blent
	CG_LerpBoneposes( cent->skel, pmodel->anim.oldframe[UPPER], pmodel->anim.frame[UPPER], blendpose, pmodel->anim.lerpFrac );

	// blend it into base pose
	rootanim = pmodel->pmodelinfo->rootanims[UPPER];
	CG_RecurseBlendSkeletalBone( blendpose, cent->ent.boneposes, CG_BoneNodeFromNum( cent->skel, rootanim ), 1.0f );

	// add skeleton effects (pose is unmounted yet)
	if( cent->current.type != ET_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( cent->skel, cent->ent.boneposes, cent->ent.boneposes );

	// Vic: Hack in frame numbers to aid frustum culling
	cent->ent.backlerp = 1.0 - cg.lerpfrac;
	cent->ent.frame = pmodel->anim.frame[LOWER];
	cent->ent.oldframe = pmodel->anim.oldframe[LOWER];

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

	Vector4Copy( cent->color, cent->ent.shaderRGBA );
	if( !( cent->effects & EF_RACEGHOST ) )
	{
		CG_AddCentityOutLineEffect( cent );
		CG_AddEntityToScene( &cent->ent );
	}
	if( !cent->ent.model )
		return;

	CG_PModel_AddFlag( 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.renderfx & RF_NOSHADOW ) && ( cg_shadows->integer == 1 ) )
		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->current.type != ET_CORPSE )
		CG_DrawTestBox( cent->ent.origin, playerbox_stand_mins, playerbox_stand_maxs, vec3_origin );
#endif

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