//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// updates:
// 1-4-99	fixed file texture load and file read bug

////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "StudioModel.h"
#include "vphysics/constraints.h"
#include "physmesh.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imaterial.h"
#include "ViewerSettings.h"
#include "bone_setup.h"
#include "UtlMemory.h"
#include "mxtk/mx.h"
#include "filesystem.h"
#include "IStudioRender.h"
#include "materialsystem/IMaterialSystemHardwareConfig.h"
#include "MDLViewer.h"
#include "optimize.h"

extern char g_appTitle[];
Vector *StudioModel::m_AmbientLightColors;

#pragma warning( disable : 4244 ) // double to float


static StudioModel g_studioModel;
static StudioModel *g_pActiveModel;

// Expose it to the rest of the app
StudioModel *g_pStudioModel = &g_studioModel;
StudioModel *g_pStudioExtraModel[4];

StudioModel::StudioModel()
{
	m_MDLHandle = MDLHANDLE_INVALID;
	ClearLookTargets();
}

void StudioModel::Init()
{
	m_AmbientLightColors = new Vector[g_pStudioRender->GetNumAmbientLightSamples()];
	
	// JasonM & garymcthack - should really only do this once a frame and at init time.
	UpdateStudioRenderConfig( g_viewerSettings.renderMode == RM_WIREFRAME, false,
							  g_viewerSettings.showNormals,
							  g_viewerSettings.showTangentFrame );
}

void StudioModel::Shutdown( void )
{
	g_pStudioModel->FreeModel( false );
	delete [] m_AmbientLightColors;
}

void StudioModel::SetCurrentModel()
{
	// track the correct model
	g_pActiveModel = this;
}

void StudioModel::ReleaseStudioModel()
{
	SaveViewerSettings( g_pStudioModel->GetFileName(), g_pStudioModel );
	g_pStudioModel->FreeModel( true ); 
}

void StudioModel::RestoreStudioModel()
{
	// should view settings be loaded before the model is loaded?
	if ( g_pStudioModel->LoadModel( g_pStudioModel->m_pModelName ) )
	{
		g_pStudioModel->PostLoadModel( g_pStudioModel->m_pModelName );
	}
}



//-----------------------------------------------------------------------------
// Purpose: Frees the model data and releases textures from OpenGL.
//-----------------------------------------------------------------------------
void StudioModel::FreeModel( bool bReleasing )
{
	if ( m_pStudioHdr )
	{
		delete m_pStudioHdr;
		m_pStudioHdr = NULL;
	}

	if ( m_MDLHandle != MDLHANDLE_INVALID )
	{
		g_pMDLCache->Release( m_MDLHandle );
		m_MDLHandle = MDLHANDLE_INVALID;
	}

	if ( !bReleasing )
	{
		if (m_pModelName)
		{
			delete[] m_pModelName;
			m_pModelName = NULL;
		}
	}

	m_SurfaceProps.Purge();

	DestroyPhysics( m_pPhysics );
	m_pPhysics = NULL;
}

void *StudioModel::operator new( size_t stAllocateBlock )
{
	// call into engine to get memory
	Assert( stAllocateBlock != 0 );
	return calloc( 1, stAllocateBlock );
}

void StudioModel::operator delete( void *pMem )
{
#ifdef _DEBUG
	// set the memory to a known value
	int size = _msize( pMem );
	memset( pMem, 0xcd, size );
#endif

	// get the engine to free the memory
	free( pMem );
}

bool StudioModel::LoadModel( const char *pModelName )
{
	MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );

	if (!pModelName)
		return 0;

	// In the case of restore, m_pModelName == modelname
	if (m_pModelName != pModelName)
	{
		// Copy over the model name; we'll need it later...
		if (m_pModelName)
		{
			delete[] m_pModelName;
		}
		m_pModelName = new char[Q_strlen(pModelName) + 1];
		strcpy( m_pModelName, pModelName );
	}

	m_MDLHandle = g_pMDLCache->FindMDL( pModelName );

	// allocate a pool for a studiohdr cache
	if (m_pStudioHdr != NULL)
	{
		delete m_pStudioHdr;
	}
	m_pStudioHdr = new CStudioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache );

	// manadatory to access correct verts
	SetCurrentModel();

	m_pPhysics = LoadPhysics( m_MDLHandle );

	// Copy over all of the hitboxes; we may add and remove elements
	m_HitboxSets.RemoveAll();

	CStudioHdr *pStudioHdr = GetStudioHdr();

	int i;
	for ( int s = 0; s < pStudioHdr->numhitboxsets(); s++ )
	{
		mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( s );
		if ( !set )
			continue;

		m_HitboxSets.AddToTail();

		for ( i = 0; i < set->numhitboxes; ++i )
		{
			mstudiobbox_t *pHit = set->pHitbox(i);
			int nIndex = m_HitboxSets[ s ].AddToTail( );
			m_HitboxSets[s][nIndex] = *pHit;
		}

		// Set the name
		hbsetname_s *n = &m_HitboxSetNames[ m_HitboxSetNames.AddToTail() ];
		strcpy( n->name, set->pszName() );
	}

	// Copy over all of the surface props; we may change them...
	for ( i = 0; i < pStudioHdr->numbones(); ++i )
	{
		mstudiobone_t* pBone = pStudioHdr->pBone(i);

		CUtlSymbol prop( pBone->pszSurfaceProp() );
		m_SurfaceProps.AddToTail( prop );
	}

	m_physPreviewBone = -1;

	bool forceOpaque = (pStudioHdr->flags() & STUDIOHDR_FLAGS_FORCE_OPAQUE) != 0;
	bool vertexLit = false;
	m_bIsTransparent = false;
	m_bHasProxy = false;

	studiohwdata_t *pHardwareData = g_pMDLCache->GetHardwareData( m_MDLHandle );
	if ( !pHardwareData )
	{
		Assert( 0 );
		return false;
	}

	for( int lodID = pHardwareData->m_RootLOD; lodID < pHardwareData->m_NumLODs; lodID++ )
	{
		studioloddata_t *pLODData = &pHardwareData->m_pLODs[lodID];
		for ( i = 0; i < pLODData->numMaterials; ++i )
		{
			if (pLODData->ppMaterials[i]->IsVertexLit())
			{
				vertexLit = true;
			}
			if ((!forceOpaque) && pLODData->ppMaterials[i]->IsTranslucent())
			{
				m_bIsTransparent = true;
				//Msg("Translucent material %s for model %s\n", pLODData->ppMaterials[i]->GetName(), pStudioHdr->name );
			}
			if (pLODData->ppMaterials[i]->HasProxy())
			{
				m_bHasProxy = true;
			}
		}
	}

	return true;
}



bool StudioModel::PostLoadModel( const char *modelname )
{
	MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );

	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (pStudioHdr == NULL)
		return false;

	SetSequence (0);
	SetController (0, 0.0f);
	SetController (1, 0.0f);
	SetController (2, 0.0f);
	SetController (3, 0.0f);
	SetBlendTime( DEFAULT_BLEND_TIME );
	// SetHeadTurn( 1.0f );  // FIXME:!!!

	int n;
	for (n = 0; n < pStudioHdr->numbodyparts(); n++)
	{
		SetBodygroup (n, 0);
	}

	SetSkin (0);

/*
	Vector mins, maxs;
	ExtractBbox (mins, maxs);
	if (mins[2] < 5.0f)
		m_origin[2] = -mins[2];
*/
	return true;
}


//------------------------------------------------------------------------------
// Returns true if the model has at least one body part with model data, false if not.
//------------------------------------------------------------------------------
bool StudioModel::HasModel()
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return false;
		
	for ( int i = 0; i < pStudioHdr->numbodyparts(); i++ )
	{
		if ( pStudioHdr->pBodypart(i)->nummodels )
		{
			return true;
		}
	}

	return false;
}


////////////////////////////////////////////////////////////////////////


int StudioModel::GetSequence( )
{
	return m_sequence;
}

int StudioModel::SetSequence( int iSequence )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0;

	if (iSequence < 0)
		return 0;

	if (iSequence > pStudioHdr->GetNumSeq())
		return m_sequence;

	m_prevsequence = m_sequence;
	m_sequence = iSequence;
	m_cycle = 0;
	m_sequencetime = 0.0;

	return m_sequence;
}

void StudioModel::ClearOverlaysSequences( void )
{
	ClearAnimationLayers( );
	memset( m_Layer, 0, sizeof( m_Layer ) );
}

void StudioModel::ClearAnimationLayers( void )
{
	m_iActiveLayers = 0;
}

int	StudioModel::GetNewAnimationLayer( int iPriority )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0;

	if ( m_iActiveLayers >= MAXSTUDIOANIMLAYERS )
	{
		Assert( 0 );
		return MAXSTUDIOANIMLAYERS - 1;
	}

	m_Layer[m_iActiveLayers].m_priority = iPriority;

	return m_iActiveLayers++;
}

int StudioModel::SetOverlaySequence( int iLayer, int iSequence, float flWeight )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0;

	if (iSequence < 0)
		return 0;

	if (iLayer < 0 || iLayer >= MAXSTUDIOANIMLAYERS)
	{
		Assert(0);
		return 0;
	}

	if (iSequence > pStudioHdr->GetNumSeq())
		return m_Layer[iLayer].m_sequence;

	m_Layer[iLayer].m_sequence = iSequence;
	m_Layer[iLayer].m_weight = flWeight;
	m_Layer[iLayer].m_playbackrate = 1.0;

	return iSequence;
}

float StudioModel::SetOverlayRate( int iLayer, float flCycle, float flPlaybackRate )
{
	if (iLayer >= 0 && iLayer < MAXSTUDIOANIMLAYERS)
	{
		m_Layer[iLayer].m_cycle = flCycle;
		m_Layer[iLayer].m_playbackrate = flPlaybackRate;
	}
	return flCycle;
}


int StudioModel::GetOverlaySequence( int iLayer )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return -1;

	if (iLayer < 0 || iLayer >= MAXSTUDIOANIMLAYERS)
	{
		Assert(0);
		return 0;
	}

	return m_Layer[iLayer].m_sequence;
}


float StudioModel::GetOverlaySequenceWeight( int iLayer )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return -1;

	if (iLayer < 0 || iLayer >= MAXSTUDIOANIMLAYERS)
	{
		Assert(0);
		return 0;
	}

	return m_Layer[iLayer].m_weight;
}


int StudioModel::LookupSequence( const char *szSequence )
{
	int i;

	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return -1;

	for (i = 0; i < pStudioHdr->GetNumSeq(); i++)
	{
		if (!stricmp( szSequence, pStudioHdr->pSeqdesc( i ).pszLabel() ))
		{
			return i;
		}
	}
	return -1;
}

int StudioModel::LookupActivity( const char *szActivity )
{
	int i;

	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return -1;

	for (i = 0; i < pStudioHdr->GetNumSeq(); i++)
	{
		if (!stricmp( szActivity, pStudioHdr->pSeqdesc( i ).pszActivityName() ))
		{
			return i;
		}
	}
	return -1;
}

int StudioModel::SetSequence( const char *szSequence )
{
	return SetSequence( LookupSequence( szSequence ) );
}

void StudioModel::StartBlending( void )
{
	// Switch back to old sequence ( this will oscillate between this one and the last one )
	SetSequence( m_prevsequence );
}

void StudioModel::SetBlendTime( float blendtime )
{
	if ( blendtime > 0.0f )
	{
		m_blendtime = blendtime;
	}
}

float StudioModel::GetTransitionAmount( void )
{
	if ( g_viewerSettings.blendSequenceChanges &&
		m_sequencetime < m_blendtime && m_prevsequence != m_sequence )
	{
		float s;
		s = ( m_sequencetime / m_blendtime );
		return s;
	}

	return 0.0f;
}

LocalFlexController_t StudioModel::LookupFlexController( char *szName )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return LocalFlexController_t(0);

	for (LocalFlexController_t iFlex = LocalFlexController_t(0); iFlex < pStudioHdr->numflexcontrollers(); iFlex++)
	{
		if (stricmp( szName, pStudioHdr->pFlexcontroller( iFlex )->pszName() ) == 0)
		{
			return iFlex;
		}
	}
	return LocalFlexController_t(-1);
}


void StudioModel::SetFlexController( char *szName, float flValue )
{
	SetFlexController( LookupFlexController( szName ), flValue );
}

void StudioModel::SetFlexController( LocalFlexController_t iFlex, float flValue )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return;

	if (iFlex >= 0 && iFlex < pStudioHdr->numflexcontrollers())
	{
		mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller(iFlex);

		if (pflex->min != pflex->max)
		{
			flValue = (flValue - pflex->min) / (pflex->max - pflex->min);
		}
		m_flexweight[iFlex] = clamp( flValue, 0.0f, 1.0f );
	}
}


void StudioModel::SetFlexControllerRaw( LocalFlexController_t iFlex, float flValue )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return;

	if (iFlex >= 0 && iFlex < pStudioHdr->numflexcontrollers())
	{
//		mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller(iFlex);
		m_flexweight[iFlex] = clamp( flValue, 0.0f, 1.0f );
	}
}

float StudioModel::GetFlexController( char *szName )
{
	return GetFlexController( LookupFlexController( szName ) );
}

float StudioModel::GetFlexController( LocalFlexController_t iFlex )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0.0f;

	if (iFlex >= 0 && iFlex < pStudioHdr->numflexcontrollers())
	{
		mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller(iFlex);

		float flValue = m_flexweight[iFlex];

		if (pflex->min != pflex->max)
		{
			flValue = flValue * (pflex->max - pflex->min) + pflex->min;
		}
		return flValue;
	}
	return 0.0;
}


float StudioModel::GetFlexControllerRaw( LocalFlexController_t iFlex )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0.0f;

	if (iFlex >= 0 && iFlex < pStudioHdr->numflexcontrollers())
	{
//		mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller(iFlex);
		return m_flexweight[iFlex];
	}
	return 0.0;
}

int StudioModel::GetNumLODs() const
{
	return g_pStudioRender->GetNumLODs( *GetHardwareData() );
}

float StudioModel::GetLODSwitchValue( int lod ) const
{
	return g_pStudioRender->GetLODSwitchValue( *GetHardwareData(), lod );
}

void StudioModel::SetLODSwitchValue( int lod, float switchValue )
{
	g_pStudioRender->SetLODSwitchValue( *GetHardwareData(), lod, switchValue );
}

void StudioModel::ExtractBbox( Vector &mins, Vector &maxs )
{
	studiohdr_t *pStudioHdr = GetStudioRenderHdr();
	if ( !pStudioHdr )
		return;

	// look for hull
	if ( ((Vector)pStudioHdr->hull_min).Length() != 0 )
	{
		mins = pStudioHdr->hull_min;
		maxs = pStudioHdr->hull_max;
	}
	// look for view clip
	else if (((Vector)pStudioHdr->view_bbmin).Length() != 0)
	{
		mins = pStudioHdr->view_bbmin;
		maxs = pStudioHdr->view_bbmax;
	}
	else
	{
		mstudioseqdesc_t &pseqdesc = pStudioHdr->pSeqdesc( m_sequence );

		mins = pseqdesc.bbmin;
		maxs = pseqdesc.bbmax;
	}
}



void StudioModel::GetSequenceInfo( int iSequence, float *pflFrameRate, float *pflGroundSpeed )
{
	float t = GetDuration( iSequence );

	if (t > 0)
	{
		*pflFrameRate = 1.0 / t;
	}
	else
	{
		*pflFrameRate = 1.0;
	}
	*pflGroundSpeed = GetGroundSpeed( iSequence );
}

void StudioModel::GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed )
{
	GetSequenceInfo( m_sequence, pflFrameRate, pflGroundSpeed );
}

float StudioModel::GetFPS( int iSequence )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0.0f;

	return Studio_FPS( pStudioHdr, iSequence, m_poseparameter );
}

float StudioModel::GetFPS( void )
{
	return GetFPS( m_sequence );
}

float StudioModel::GetDuration( int iSequence )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return 0.0f;

	return Studio_Duration( pStudioHdr, iSequence, m_poseparameter );
}


int StudioModel::GetNumFrames( int iSequence )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr || iSequence < 0 || iSequence >= pStudioHdr->GetNumSeq() )
	{
		return 1;
	}

	return Studio_MaxFrame( pStudioHdr, iSequence, m_poseparameter );
}

static int GetSequenceFlags( CStudioHdr *pstudiohdr, int sequence )
{
	if ( !pstudiohdr || 
		sequence < 0 || 
		sequence >= pstudiohdr->GetNumSeq() )
	{
		return 0;
	}

	mstudioseqdesc_t &seqdesc = pstudiohdr->pSeqdesc( sequence );

	return seqdesc.flags;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : iSequence - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool StudioModel::GetSequenceLoops( int iSequence )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return false;

	int flags = GetSequenceFlags( pStudioHdr, iSequence );
	bool looping = flags & STUDIO_LOOPING ? true : false;
	return looping;
}

float StudioModel::GetDuration( )
{
	return GetDuration( m_sequence );
}


void StudioModel::GetMovement( float prevcycle[5], Vector &vecPos, QAngle &vecAngles )
{
	vecPos.Init();
	vecAngles.Init();

	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return;

  	// assume that changes < -0.5 are loops....
  	if (m_cycle - prevcycle[0] < -0.5)
  	{
  		prevcycle[0] = prevcycle[0] - 1.0;
  	}

	Studio_SeqMovement( pStudioHdr, m_sequence, prevcycle[0], m_cycle, m_poseparameter, vecPos, vecAngles );
	prevcycle[0] = m_cycle;

	int i;
	for (i = 0; i < 4; i++)
	{
		Vector vecTmp;
		QAngle angTmp;

  		if (m_Layer[i].m_cycle - prevcycle[i+1] < -0.5)
  		{
  			prevcycle[i+1] = prevcycle[i+1] - 1.0;
  		}

		if (m_Layer[i].m_weight > 0.0)
		{
			vecTmp.Init();
			angTmp.Init();
			if (Studio_SeqMovement( pStudioHdr, m_Layer[i].m_sequence, prevcycle[i+1], m_Layer[i].m_cycle, m_poseparameter, vecTmp, angTmp ))
			{
				vecPos = vecPos * ( 1.0 - m_Layer[i].m_weight ) + vecTmp * m_Layer[i].m_weight;
			}
		}
		prevcycle[i+1] = m_Layer[i].m_cycle;
	}

	return;
}


void StudioModel::GetMovement( int iSequence, float prevCycle, float nextCycle, Vector &vecPos, QAngle &vecAngles )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
	{
		vecPos.Init();
		vecAngles.Init();
		return;
	}

	// FIXME: this doesn't consider layers
	Studio_SeqMovement( pStudioHdr, iSequence, prevCycle, nextCycle, m_poseparameter, vecPos, vecAngles );

	return;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the ground speed of the specifed sequence.
//-----------------------------------------------------------------------------
float StudioModel::GetGroundSpeed( int iSequence )
{
	Vector vecMove;
	QAngle vecAngles;
	GetMovement( iSequence, 0, 1, vecMove, vecAngles );

	float t = GetDuration( iSequence );

	float flGroundSpeed = 0;
	if (t > 0)
	{
		flGroundSpeed = vecMove.Length() / t;
	}

	return flGroundSpeed;
}



//-----------------------------------------------------------------------------
// Purpose: Returns the ground speed of the current sequence.
//-----------------------------------------------------------------------------
float StudioModel::GetGroundSpeed( void )
{
	return GetGroundSpeed( m_sequence );
}


//-----------------------------------------------------------------------------
// Purpose: Returns the ground speed of the current sequence.
//-----------------------------------------------------------------------------
float StudioModel::GetCurrentVelocity( void )
{
	Vector vecVelocity;

	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (pStudioHdr && Studio_SeqVelocity( pStudioHdr, m_sequence, m_cycle, m_poseparameter, vecVelocity ))
	{
		return vecVelocity.Length();
	}
	return 0;
}


//-----------------------------------------------------------------------------
// Purpose: Returns the the sequence should be hidden or not
//-----------------------------------------------------------------------------
bool StudioModel::IsHidden( int iSequence )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (pStudioHdr->pSeqdesc( iSequence ).flags & STUDIO_HIDDEN)
		return true;

	return false;
}



void StudioModel::GetSeqAnims( int iSequence, mstudioanimdesc_t *panim[4], float *weight )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return;

	mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence );
	Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, m_poseparameter, panim, weight );
}

void StudioModel::GetSeqAnims( mstudioanimdesc_t *panim[4], float *weight )
{
	GetSeqAnims( m_sequence, panim, weight );
}


float StudioModel::SetController( int iController, float flValue )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return 0.0f;

	return Studio_SetController( pStudioHdr, iController, flValue, m_controller[iController] );
}



int	StudioModel::LookupPoseParameter( char const *szName )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return false;

	for (int iParameter = 0; iParameter < pStudioHdr->GetNumPoseParameters(); iParameter++)
	{
		if (stricmp( szName, pStudioHdr->pPoseParameter( iParameter ).pszName() ) == 0)
		{
			return iParameter;
		}
	}
	return -1;
}

float StudioModel::SetPoseParameter( char const *szName, float flValue )
{
	return SetPoseParameter( LookupPoseParameter( szName ), flValue );
}

float StudioModel::SetPoseParameter( int iParameter, float flValue )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return 0.0f;

	return Studio_SetPoseParameter( pStudioHdr, iParameter, flValue, m_poseparameter[iParameter] );
}

float StudioModel::GetPoseParameter( char const *szName )
{
	return GetPoseParameter( LookupPoseParameter( szName ) );
}

float* StudioModel::GetPoseParameters()
{
	return m_poseparameter;
}

float StudioModel::GetPoseParameter( int iParameter )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return 0.0f;

	return Studio_GetPoseParameter( pStudioHdr, iParameter, m_poseparameter[iParameter] );
}

bool StudioModel::GetPoseParameterRange( int iParameter, float *pflMin, float *pflMax )
{
	*pflMin = 0;
	*pflMax = 0;

	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return false;

	if (iParameter < 0 || iParameter >= pStudioHdr->GetNumPoseParameters())
		return false;

	const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( iParameter );

	*pflMin = Pose.start;
	*pflMax = Pose.end;

	return true;
}

int StudioModel::LookupAttachment( char const *szName )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if ( !pStudioHdr )
		return -1;

	for (int i = 0; i < pStudioHdr->GetNumAttachments(); i++)
	{
		if (stricmp( pStudioHdr->pAttachment( i ).pszName(), szName ) == 0)
		{
			return i;
		}
	}
	return -1;
}



int StudioModel::SetBodygroup( int iGroup, int iValue )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return 0;

	if (iGroup > pStudioHdr->numbodyparts())
		return -1;

	mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( iGroup );

	int iCurrent = (m_bodynum / pbodypart->base) % pbodypart->nummodels;

	if (iValue >= pbodypart->nummodels)
		return iCurrent;

	m_bodynum = (m_bodynum - (iCurrent * pbodypart->base) + (iValue * pbodypart->base));

	return iValue;
}


int StudioModel::SetSkin( int iValue )
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return 0;

	if (iValue >= pStudioHdr->numskinfamilies())
	{
		return m_skinnum;
	}

	m_skinnum = iValue;

	return iValue;
}



void StudioModel::scaleMeshes (float scale)
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return;

	int i, j, k;

	// manadatory to access correct verts
	SetCurrentModel();

	// scale verts
	int tmp = m_bodynum;
	for (i = 0; i < pStudioHdr->numbodyparts(); i++)
	{
		mstudiobodyparts_t *pbodypart = pStudioHdr->pBodypart( i );
		for (j = 0; j < pbodypart->nummodels; j++)
		{
			SetBodygroup (i, j);
			SetupModel (i);

			const mstudio_modelvertexdata_t *vertData = m_pmodel->GetVertexData();
			Assert( vertData ); // This can only return NULL on X360 for now

			for (k = 0; k < m_pmodel->numvertices; k++)
			{
				*vertData->Position(k) *= scale;
			}
		}
	}

	m_bodynum = tmp;

	// scale complex hitboxes
	int hitboxset = g_MDLViewer->GetCurrentHitboxSet();

	mstudiobbox_t *pbboxes = pStudioHdr->pHitbox( 0, hitboxset );
	for (i = 0; i < pStudioHdr->iHitboxCount( hitboxset ); i++)
	{
		VectorScale (pbboxes[i].bbmin, scale, pbboxes[i].bbmin);
		VectorScale (pbboxes[i].bbmax, scale, pbboxes[i].bbmax);
	}

	// scale bounding boxes
	for (i = 0; i < pStudioHdr->GetNumSeq(); i++)
	{
		mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( i );
		Vector tmp;

		tmp = seqdesc.bbmin;
		VectorScale( tmp, scale, tmp );
		seqdesc.bbmin = tmp;

		tmp = seqdesc.bbmax;
		VectorScale( tmp, scale, tmp );
		seqdesc.bbmax = tmp;

	}

	// maybe scale exeposition, pivots, attachments
}



void StudioModel::scaleBones (float scale)
{
	CStudioHdr *pStudioHdr = GetStudioHdr();
	if (!pStudioHdr)
		return;

	mstudiobone_t *pbones = pStudioHdr->pBone( 0 );
	for (int i = 0; i < pStudioHdr->numbones(); i++)
	{
		pbones[i].pos *= scale;
		pbones[i].posscale *= scale;
	}	
}

int	StudioModel::Physics_GetBoneCount( void )
{
	return m_pPhysics->Count();
}


const char *StudioModel::Physics_GetBoneName( int index ) 
{ 
	CPhysmesh *pmesh = m_pPhysics->GetMesh( index );

	if ( !pmesh )
		return NULL;

	return pmesh->m_boneName;
}


void StudioModel::Physics_GetData( int boneIndex, hlmvsolid_t *psolid, constraint_ragdollparams_t *pConstraint ) const
{
	CPhysmesh *pMesh = m_pPhysics->GetMesh( boneIndex );
	
	if ( !pMesh )
		return;

	if ( psolid )
	{
		memcpy( psolid, &pMesh->m_solid, sizeof(*psolid) );
	}

	if ( pConstraint )
	{
		*pConstraint = pMesh->m_constraint;
	}
}

void StudioModel::Physics_SetData( int boneIndex, const hlmvsolid_t *psolid, const constraint_ragdollparams_t *pConstraint )
{
	CPhysmesh *pMesh = m_pPhysics->GetMesh( boneIndex );
	
	if ( !pMesh )
		return;

	if ( psolid )
	{
		memcpy( &pMesh->m_solid, psolid, sizeof(*psolid) );
	}

	if ( pConstraint )
	{
		pMesh->m_constraint = *pConstraint;
	}
}


float StudioModel::Physics_GetMass( void )
{
	return m_pPhysics->GetMass();
}

void StudioModel::Physics_SetMass( float mass )
{
	m_physMass = mass;
}


char *StudioModel::Physics_DumpQC( void )
{
	return m_pPhysics->DumpQC();
}

const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData )
{
	Assert( pModelData == NULL );
	Assert( g_pActiveModel );

	return g_pStudioDataCache->CacheVertexData( g_pActiveModel->GetStudioRenderHdr() );
}


//-----------------------------------------------------------------------------
// FIXME: This trashy glue code is really not acceptable. Figure out a way of making it unnecessary.
//-----------------------------------------------------------------------------
const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const
{
	MDLHandle_t handle = g_pMDLCache->FindMDL( pModelName );
	*cache = (void*)handle;
	return g_pMDLCache->GetStudioHdr( handle );
}

virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const
{
	return g_pMDLCache->GetVirtualModel( (MDLHandle_t)virtualModel );
}

byte *studiohdr_t::GetAnimBlock( int i ) const
{
	return g_pMDLCache->GetAnimBlock( (MDLHandle_t)virtualModel, i );
}

int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const
{
	return g_pMDLCache->GetAutoplayList( (MDLHandle_t)virtualModel, pOut );
}

const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const
{
	return g_pMDLCache->GetStudioHdr( (MDLHandle_t)cache );
}
