//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "matsys_controls/mdlpanel.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "materialsystem/imesh.h"
#include "vgui/IVGui.h"
#include "tier1/KeyValues.h"
#include "vgui_controls/Frame.h"
#include "tier1/convar.h"
#include "tier0/dbg.h"
#include "tier1/fmtstr.h"
#include "istudiorender.h"
#include "matsys_controls/matsyscontrols.h"
#include "vcollide.h"
#include "vcollide_parse.h"
#include "bone_setup.h"
#include "vphysics_interface.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

using namespace vgui;

DECLARE_BUILD_FACTORY( CMDLPanel );

static const int THUMBNAIL_SAFE_ZONE_SIZE = 512;
static const int THUMBNAIL_SAFE_ZONE_HEIGHT = 92;
static const float THUMBNAIL_SAFE_ZONE_HEIGHT_SCALE = (float)THUMBNAIL_SAFE_ZONE_HEIGHT / THUMBNAIL_SAFE_ZONE_SIZE;

//-----------------------------------------------------------------------------
// Purpose: Keeps a global clock to autoplay sequences to run from
//			Also deals with speedScale changes
//-----------------------------------------------------------------------------
float GetAutoPlayTime( void )
{
	static int g_prevTicks;
	static float g_time;

	int ticks = Plat_MSTime();

	// limit delta so that float time doesn't overflow
	if (g_prevTicks == 0)
	{
		g_prevTicks = ticks;
	}

	g_time += ( ticks - g_prevTicks ) / 1000.0f;
	g_prevTicks = ticks;

	return g_time;
}


//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CMDLPanel::CMDLPanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName )
{
	SetVisible( true );

	// Used to poll input
	vgui::ivgui()->AddTickSignal( GetVPanel() );

	// Deal with the default cubemap
	ITexture *pCubemapTexture = vgui::MaterialSystem()->FindTexture( "editor/cubemap", NULL, true );
	m_DefaultEnvCubemap.Init( pCubemapTexture );
	pCubemapTexture = vgui::MaterialSystem()->FindTexture( "editor/cubemap.hdr", NULL, true );
	m_DefaultHDREnvCubemap.Init( pCubemapTexture );

	SetIdentityMatrix( m_RootMDL.m_MDLToWorld );
	m_bDrawCollisionModel = false;
	m_bWireFrame = false;
	m_bGroundGrid = false;
	m_bLockView = false;
	m_bLookAtCamera = true;
	m_bThumbnailSafeZone = false;
	m_nNumSequenceLayers = 0;
	ResetAnimationEventState( &m_EventState );
}

CMDLPanel::~CMDLPanel()
{
	m_aMergeMDLs.Purge();
	m_DefaultEnvCubemap.Shutdown( );
	m_DefaultHDREnvCubemap.Shutdown();
}


//-----------------------------------------------------------------------------
// Scheme settings
//-----------------------------------------------------------------------------
void CMDLPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );
	SetBackgroundColor( GetBgColor() );
	SetBorder( pScheme->GetBorder( "MenuBorder") );
}


//-----------------------------------------------------------------------------
// Rendering options
//-----------------------------------------------------------------------------
void CMDLPanel::SetCollsionModel( bool bVisible )
{
	m_bDrawCollisionModel = bVisible;
}

void CMDLPanel::SetGroundGrid( bool bVisible )
{
	m_bGroundGrid = bVisible;
}

void CMDLPanel::SetWireFrame( bool bVisible )
{
	m_bWireFrame = bVisible;
}

void CMDLPanel::SetLockView( bool bLocked )
{
	m_bLockView = bLocked;
}

void CMDLPanel::SetLookAtCamera( bool bLookAtCamera )
{
	m_bLookAtCamera = bLookAtCamera;
}

void CMDLPanel::SetIgnoreDoubleClick( bool bState )
{
	m_bIgnoreDoubleClick = bState;
}

void CMDLPanel::SetThumbnailSafeZone( bool bVisible )
{
	m_bThumbnailSafeZone = bVisible;
}

//-----------------------------------------------------------------------------
// Stores the clip
//-----------------------------------------------------------------------------
void CMDLPanel::SetMDL( MDLHandle_t handle, void *pProxyData )
{
	m_RootMDL.m_MDL.SetMDL( handle );
	m_RootMDL.m_MDL.m_pProxyData = pProxyData;

	Vector vecMins, vecMaxs;
	GetMDLBoundingBox( &vecMins, &vecMaxs, handle, m_RootMDL.m_MDL.m_nSequence );

	m_RootMDL.m_MDL.m_bWorldSpaceViewTarget = false;
	m_RootMDL.m_MDL.m_vecViewTarget.Init( 100.0f, 0.0f, vecMaxs.z );

	m_RootMDL.m_flCycleStartTime = 0.f;

	// Set the pose parameters to the default for the mdl
	SetPoseParameters( NULL, 0 );
	
	// Clear any sequence layers
	SetSequenceLayers( NULL, 0 );

	ResetAnimationEventState( &m_EventState );
}

//-----------------------------------------------------------------------------
// An MDL was selected
//-----------------------------------------------------------------------------
void CMDLPanel::SetMDL( const char *pMDLName, void *pProxyData )
{
	MDLHandle_t hMDLFindResult = vgui::MDLCache()->FindMDL( pMDLName );
	MDLHandle_t hMDL = pMDLName ? hMDLFindResult : MDLHANDLE_INVALID;
	if ( vgui::MDLCache()->IsErrorModel( hMDL ) )
	{
		hMDL = MDLHANDLE_INVALID;
	}

	SetMDL( hMDL, pProxyData );

	// FindMDL takes a reference and the the CMDL will also hold a reference for as long as it sticks around. Release the FindMDL reference.
	int nRef = vgui::MDLCache()->Release( hMDLFindResult );
	(void)nRef; // Avoid unreferenced variable warning
	AssertMsg( hMDL == MDLHANDLE_INVALID || nRef > 0, "CMDLPanel::SetMDL referenced a model that has a zero ref count." );
}

//-----------------------------------------------------------------------------
// Purpose: Returns a model bounding box.
//-----------------------------------------------------------------------------
bool CMDLPanel::GetBoundingBox( Vector &vecBoundsMin, Vector &vecBoundsMax )
{
	// Check to see if we have a valid model to look at.
	if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
		return false;

	GetMDLBoundingBox( &vecBoundsMin, &vecBoundsMax, m_RootMDL.m_MDL.GetMDL(), m_RootMDL.m_MDL.m_nSequence );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Returns a more accurate bounding sphere
//-----------------------------------------------------------------------------
bool CMDLPanel::GetBoundingSphere( Vector &vecCenter, float &flRadius )
{
	// Check to see if we have a valid model to look at.
	if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
		return false;

	Vector vecEngineCenter;
	GetMDLBoundingSphere( &vecEngineCenter, &flRadius, m_RootMDL.m_MDL.GetMDL(), m_RootMDL.m_MDL.m_nSequence );
	VectorTransform( vecEngineCenter, m_RootMDL.m_MDLToWorld, vecCenter );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMDLPanel::SetModelAnglesAndPosition(  const QAngle &angRot, const Vector &vecPos )
{
	SetIdentityMatrix( m_RootMDL.m_MDLToWorld );
	AngleMatrix( angRot, vecPos, m_RootMDL.m_MDLToWorld );
}

//-----------------------------------------------------------------------------
// Sets the camera to look at the model
//-----------------------------------------------------------------------------
void CMDLPanel::LookAtMDL()
{
	// Check to see if we have a valid model to look at.
	if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
		return;

	if ( m_bLockView )
		return;

	float flRadius;
	Vector vecCenter;
	GetBoundingSphere( vecCenter, flRadius );
	LookAt( vecCenter, flRadius );
}


//-----------------------------------------------------------------------------
// FIXME: This should be moved into studiorender
//-----------------------------------------------------------------------------
static ConVar	r_showenvcubemap( "r_showenvcubemap", "0", FCVAR_CHEAT );
static ConVar	r_eyegloss		( "r_eyegloss", "1", FCVAR_ARCHIVE ); // wet eyes
static ConVar	r_eyemove		( "r_eyemove", "1", FCVAR_ARCHIVE ); // look around
static ConVar	r_eyeshift_x	( "r_eyeshift_x", "0", FCVAR_ARCHIVE ); // eye X position
static ConVar	r_eyeshift_y	( "r_eyeshift_y", "0", FCVAR_ARCHIVE ); // eye Y position
static ConVar	r_eyeshift_z	( "r_eyeshift_z", "0", FCVAR_ARCHIVE ); // eye Z position
static ConVar	r_eyesize		( "r_eyesize", "0", FCVAR_ARCHIVE ); // adjustment to iris textures
static ConVar	mat_softwareskin( "mat_softwareskin", "0", FCVAR_CHEAT );
static ConVar	r_nohw			( "r_nohw", "0", FCVAR_CHEAT );
static ConVar	r_nosw			( "r_nosw", "0", FCVAR_CHEAT );
static ConVar	r_teeth			( "r_teeth", "1" );
static ConVar	r_drawentities	( "r_drawentities", "1", FCVAR_CHEAT );
static ConVar	r_flex			( "r_flex", "1" );
static ConVar	r_eyes			( "r_eyes", "1" );
static ConVar	r_skin			( "r_skin","0", FCVAR_CHEAT );
static ConVar	r_maxmodeldecal ( "r_maxmodeldecal", "50" );
static ConVar	r_modelwireframedecal ( "r_modelwireframedecal", "0", FCVAR_CHEAT );
static ConVar	mat_normals		( "mat_normals", "0", FCVAR_CHEAT );
static ConVar	r_eyeglintlodpixels ( "r_eyeglintlodpixels", "0", FCVAR_CHEAT );
static ConVar	r_rootlod		( "r_rootlod", "0" );

static StudioRenderConfig_t s_StudioRenderConfig;

void CMDLPanel::UpdateStudioRenderConfig( void )
{
	memset( &s_StudioRenderConfig, 0, sizeof(s_StudioRenderConfig) );

	s_StudioRenderConfig.bEyeMove = !!r_eyemove.GetInt();
	s_StudioRenderConfig.fEyeShiftX = r_eyeshift_x.GetFloat();
	s_StudioRenderConfig.fEyeShiftY = r_eyeshift_y.GetFloat();
	s_StudioRenderConfig.fEyeShiftZ = r_eyeshift_z.GetFloat();
	s_StudioRenderConfig.fEyeSize = r_eyesize.GetFloat();	
	if( mat_softwareskin.GetInt() || m_bWireFrame )
	{
		s_StudioRenderConfig.bSoftwareSkin = true;
	}
	else
	{
		s_StudioRenderConfig.bSoftwareSkin = false;
	}
	s_StudioRenderConfig.bNoHardware = !!r_nohw.GetInt();
	s_StudioRenderConfig.bNoSoftware = !!r_nosw.GetInt();
	s_StudioRenderConfig.bTeeth = !!r_teeth.GetInt();
	s_StudioRenderConfig.drawEntities = r_drawentities.GetInt();
	s_StudioRenderConfig.bFlex = !!r_flex.GetInt();
	s_StudioRenderConfig.bEyes = !!r_eyes.GetInt();
	s_StudioRenderConfig.bWireframe = m_bWireFrame;
	s_StudioRenderConfig.bDrawNormals = mat_normals.GetBool();
	s_StudioRenderConfig.skin = r_skin.GetInt();
	s_StudioRenderConfig.maxDecalsPerModel = r_maxmodeldecal.GetInt();
	s_StudioRenderConfig.bWireframeDecals = r_modelwireframedecal.GetInt() != 0;
	
	s_StudioRenderConfig.fullbright = false;
	s_StudioRenderConfig.bSoftwareLighting = false;

	s_StudioRenderConfig.bShowEnvCubemapOnly = r_showenvcubemap.GetInt() ? true : false;
	s_StudioRenderConfig.fEyeGlintPixelWidthLODThreshold = r_eyeglintlodpixels.GetFloat();

	StudioRender()->UpdateConfig( s_StudioRenderConfig );
}

void CMDLPanel::DrawCollisionModel()
{
	vcollide_t *pCollide = MDLCache()->GetVCollide( m_RootMDL.m_MDL.GetMDL() );

	if ( !pCollide || pCollide->solidCount <= 0 )
		return;
	
	static color32 color = {255,0,0,0};

	IVPhysicsKeyParser *pParser = g_pPhysicsCollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
	CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache );

	matrix3x4_t pBoneToWorld[MAXSTUDIOBONES];
	m_RootMDL.m_MDL.SetUpBones( m_RootMDL.m_MDLToWorld, MAXSTUDIOBONES, pBoneToWorld );

	// PERFORMANCE: Just parse the script each frame.  It's fast enough for tools.  If you need
	// this to go faster then cache off the bone index mapping in an array like HLMV does
	while ( !pParser->Finished() )
	{
		const char *pBlock = pParser->GetCurrentBlockName();
		if ( !stricmp( pBlock, "solid" ) )
		{
			solid_t solid;

			pParser->ParseSolid( &solid, NULL );
			int boneIndex = Studio_BoneIndexByName( &studioHdr, solid.name );
			Vector *outVerts;
			int vertCount = g_pPhysicsCollision->CreateDebugMesh( pCollide->solids[solid.index], &outVerts );

			if ( vertCount )
			{
				CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
				pRenderContext->CullMode( MATERIAL_CULLMODE_CCW );
				// NOTE: assumes these have been set up already by the model render code
				// So this is a little bit of a back door to a cache of the bones
				// this code wouldn't work unless you draw the model this frame before calling
				// this routine.  CMDLPanel always does this, but it's worth noting.
				// A better solution would be to move the ragdoll visulization into the CDmeMdl
				// and either draw it there or make it queryable and query/draw here.
				matrix3x4_t xform;
				SetIdentityMatrix( xform );
				if ( boneIndex >= 0 )
				{
					MatrixCopy( pBoneToWorld[ boneIndex ], xform );
				}
				IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_Wireframe );

				CMeshBuilder meshBuilder;
				meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, vertCount/3 );

				for ( int j = 0; j < vertCount; j++ )
				{
					Vector out;
					VectorTransform( outVerts[j].Base(), xform, out.Base() );
					meshBuilder.Position3fv( out.Base() );
					meshBuilder.Color4ub( color.r, color.g, color.b, color.a );
					meshBuilder.TexCoord2f( 0, 0, 0 );
					meshBuilder.AdvanceVertex();
				}
				meshBuilder.End();
				pMesh->Draw();
			}

			g_pPhysicsCollision->DestroyDebugMesh( vertCount, outVerts );
		}
		else
		{
			pParser->SkipBlock();
		}
	}
	g_pPhysicsCollision->VPhysicsKeyParserDestroy( pParser );
}

void CMDLPanel::SetupRenderState( int nDisplayWidth, int nDisplayHeight )
{
	int iWidth = nDisplayWidth;
	int iHeight = nDisplayHeight;
	if ( m_bThumbnailSafeZone )
	{
		iWidth = THUMBNAIL_SAFE_ZONE_SIZE;
		iHeight = THUMBNAIL_SAFE_ZONE_SIZE;
	}
	BaseClass::SetupRenderState( iWidth, iHeight );
}

//-----------------------------------------------------------------------------
// paint it!
//-----------------------------------------------------------------------------
void CMDLPanel::OnPaint3D()
{
	if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
		return;

	// FIXME: Move this call into DrawModel in StudioRender
	StudioRenderConfig_t oldStudioRenderConfig;
	StudioRender()->GetCurrentConfig( oldStudioRenderConfig );

	UpdateStudioRenderConfig();

	CMatRenderContextPtr pRenderContext( vgui::MaterialSystem() );
	if ( vgui::MaterialSystemHardwareConfig()->GetHDRType() == HDR_TYPE_NONE )
	{
		ITexture *pMyCube = HasLightProbe() ? GetLightProbeCubemap( false ) : m_DefaultEnvCubemap;
		pRenderContext->BindLocalCubemap( pMyCube );
	}
	else
	{
		ITexture *pMyCube = HasLightProbe() ? GetLightProbeCubemap( true ) : m_DefaultHDREnvCubemap;
		pRenderContext->BindLocalCubemap( pMyCube );
	}

	PrePaint3D( pRenderContext );

	if ( m_bGroundGrid )
	{
		DrawGrid();
	}

	if ( m_bLookAtCamera )
	{
		matrix3x4_t worldToCamera;
		ComputeCameraTransform( &worldToCamera );

		Vector vecPosition;
		MatrixGetColumn( worldToCamera, 3, vecPosition );
		m_RootMDL.m_MDL.m_bWorldSpaceViewTarget = true;
		m_RootMDL.m_MDL.m_vecViewTarget = vecPosition;
	}

	// Draw the MDL
	CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache );

	SetupFlexWeights();

	matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( studioHdr.numbones() );
	m_RootMDL.m_MDL.SetUpBones( m_RootMDL.m_MDLToWorld, studioHdr.numbones(), pBoneToWorld, m_PoseParameters, m_SequenceLayers, m_nNumSequenceLayers );
	g_pStudioRender->UnlockBoneMatrices();

	IMaterial* pOverrideMaterial = GetOverrideMaterial( m_RootMDL.m_MDL.GetMDL() );
	if ( pOverrideMaterial != NULL )
		g_pStudioRender->ForcedMaterialOverride( pOverrideMaterial );

	m_RootMDL.m_MDL.Draw( m_RootMDL.m_MDLToWorld, pBoneToWorld );

	if ( pOverrideMaterial != NULL )
		g_pStudioRender->ForcedMaterialOverride( NULL );

	pOverrideMaterial = NULL;

	// Draw the merge MDLs.
	matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES];
	int nMergeCount = m_aMergeMDLs.Count();
	for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge )
	{
		if ( m_aMergeMDLs[iMerge].m_bDisabled )
			continue;

		// Get the merge studio header.
		studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( m_aMergeMDLs[iMerge].m_MDL.GetMDL() );
		matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0];

		// If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because
		// it'll crash trying to pull data from the missing header.
		if ( pStudioHdr != NULL )
		{
			CStudioHdr mergeHdr( pStudioHdr, g_pMDLCache );
			m_aMergeMDLs[iMerge].m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, &studioHdr, pBoneToWorld, m_RootMDL.m_MDLToWorld );		

			pOverrideMaterial = GetOverrideMaterial( m_aMergeMDLs[iMerge].m_MDL.GetMDL() );
			if ( pOverrideMaterial != NULL ) 
				g_pStudioRender->ForcedMaterialOverride( pOverrideMaterial );

			m_aMergeMDLs[iMerge].m_MDL.Draw( m_aMergeMDLs[iMerge].m_MDLToWorld, pMergeBoneToWorld );

			if ( pOverrideMaterial != NULL )
				g_pStudioRender->ForcedMaterialOverride( NULL );

			// Notify of model render
			RenderingMergedModel( pRenderContext, &mergeHdr, m_aMergeMDLs[iMerge].m_MDL.GetMDL(), pMergeBoneToWorld );
		}
	}

	RenderingRootModel( pRenderContext, &studioHdr, m_RootMDL.m_MDL.GetMDL(), pBoneToWorld );

	PostPaint3D( pRenderContext );

	if ( m_bDrawCollisionModel )
	{
		DrawCollisionModel();
	}

	pRenderContext->Flush();
	StudioRender()->UpdateConfig( oldStudioRenderConfig );
}


//-----------------------------------------------------------------------------
// Sets the current LOD
//-----------------------------------------------------------------------------
void CMDLPanel::SetLOD( int nLOD )
{
	m_RootMDL.m_MDL.m_nLOD = nLOD;

	int nMergeCount = m_aMergeMDLs.Count();
	for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge )
	{
		m_aMergeMDLs[iMerge].m_MDL.m_nLOD = nLOD;
	}
}


//-----------------------------------------------------------------------------
// Sets the current sequence
//-----------------------------------------------------------------------------
void CMDLPanel::SetSequence( int nSequence, bool bResetSequence )
{
	m_RootMDL.m_MDL.m_nSequence = nSequence;

	if ( bResetSequence )
	{
		m_RootMDL.m_flCycleStartTime = GetAutoPlayTime();
	}
}


//-----------------------------------------------------------------------------
// Set the current pose parameters. If NULL the pose parameters will be reset
// to the default values.
//-----------------------------------------------------------------------------
void CMDLPanel::SetPoseParameters( const float *pPoseParameters, int nCount )
{
	if ( pPoseParameters )
	{
		int nParameters = MIN( MAXSTUDIOPOSEPARAM, nCount );
		for ( int iParam = 0; iParam < nParameters; ++iParam )
		{
			m_PoseParameters[ iParam ] = pPoseParameters[ iParam ];
		}
	}
	else if ( m_RootMDL.m_MDL.GetMDL() != MDLHANDLE_INVALID )
	{
		CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache );
		Studio_CalcDefaultPoseParameters( &studioHdr, m_PoseParameters, MAXSTUDIOPOSEPARAM );
	}
}


//-----------------------------------------------------------------------------
// Set a pose parameter by name
//-----------------------------------------------------------------------------
bool CMDLPanel::SetPoseParameterByName( const char *pszName, float fValue )
{
	CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache );
	int nPoseCount = studioHdr.GetNumPoseParameters();

	for ( int i = 0; i < nPoseCount; ++i )
	{
		const mstudioposeparamdesc_t &Pose = studioHdr.pPoseParameter( i );
		if ( V_strcasecmp( pszName, Pose.pszName() ) == 0 )
		{
			m_PoseParameters[ i ] = fValue;
			return true;
		}
	}
	return false;
}


//-----------------------------------------------------------------------------
// Set the overlay sequence layers
//-----------------------------------------------------------------------------
void CMDLPanel::SetSequenceLayers( const MDLSquenceLayer_t *pSequenceLayers, int nCount )
{
	if ( pSequenceLayers )
	{
		m_nNumSequenceLayers = MIN( MAX_SEQUENCE_LAYERS, nCount );
		for ( int iLayer = 0; iLayer < m_nNumSequenceLayers; ++iLayer )
		{
			m_SequenceLayers[ iLayer ] = pSequenceLayers[ iLayer ];
			ResetAnimationEventState( &m_SequenceLayerEventState[ iLayer ] );
		}
	}
	else
	{
		m_nNumSequenceLayers = 0;
		V_memset( m_SequenceLayers, 0, sizeof( m_SequenceLayers ) );
	}
}

//-----------------------------------------------------------------------------
// Set the current skin
//-----------------------------------------------------------------------------
void CMDLPanel::SetSkin( int nSkin )
{
	m_RootMDL.m_MDL.m_nSkin = nSkin;
}

//-----------------------------------------------------------------------------
// called when we're ticked...
//-----------------------------------------------------------------------------
void CMDLPanel::OnTick()
{
	BaseClass::OnTick();
	if ( m_RootMDL.m_MDL.GetMDL() != MDLHANDLE_INVALID )
	{
		m_RootMDL.m_MDL.m_flTime = ( GetAutoPlayTime() - m_RootMDL.m_flCycleStartTime );
	
		DoAnimationEvents();
	}
}

void CMDLPanel::Paint()
{
	BaseClass::Paint();

	if ( m_bThumbnailSafeZone )
	{
		int iWidth, iHeight;
		GetSize( iWidth, iHeight );

		CMatRenderContextPtr pRenderContext( vgui::MaterialSystem() );

		IMaterial *safezone = materials->FindMaterial( "vgui/thumbnails_safezone", TEXTURE_GROUP_VGUI, true );
		if ( safezone )
		{
			safezone->IncrementReferenceCount();
		}

		int screenposx = 0;
		int screenposy = 0;
		LocalToScreen( screenposx, screenposy );

		int iScaledHeight = THUMBNAIL_SAFE_ZONE_HEIGHT_SCALE * iHeight;
		int iRemappedHeight = RemapVal( iScaledHeight, 0, THUMBNAIL_SAFE_ZONE_HEIGHT, 0, iScaledHeight );

		pRenderContext->DrawScreenSpaceRectangle( safezone, screenposx, screenposy, iWidth, iRemappedHeight,
			0, 0,
			THUMBNAIL_SAFE_ZONE_SIZE, THUMBNAIL_SAFE_ZONE_HEIGHT,
			THUMBNAIL_SAFE_ZONE_SIZE, THUMBNAIL_SAFE_ZONE_SIZE );

		screenposx = 0;
		screenposy = iHeight - iRemappedHeight;
		LocalToScreen( screenposx, screenposy );
		pRenderContext->DrawScreenSpaceRectangle( safezone, screenposx, screenposy, iWidth, iRemappedHeight,
			0, 0,
			THUMBNAIL_SAFE_ZONE_SIZE, THUMBNAIL_SAFE_ZONE_HEIGHT,
			THUMBNAIL_SAFE_ZONE_SIZE, THUMBNAIL_SAFE_ZONE_SIZE );

		if ( safezone )
		{
			safezone->DecrementReferenceCount();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMDLPanel::DoAnimationEvents()
{
	CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache );

	// If we don't have any sequences, don't do anything
	if ( studioHdr.GetNumSeq() < 1 )
	{
		Assert( studioHdr.GetNumSeq() >= 1 );
		return;
	}

	DoAnimationEvents( &studioHdr, m_RootMDL.m_MDL.m_nSequence, m_RootMDL.m_MDL.m_flTime, false, &m_EventState );

	for ( int i = 0; i < m_nNumSequenceLayers; ++i )
	{
		float flTime = m_RootMDL.m_MDL.m_flTime - m_SequenceLayers[ i ].m_flCycleBeganAt;
		//Plat_DebugString( CFmtStr("Animation: time = %f, started = %f, delta = %f\n",m_RootMDL.m_MDL.m_flTime,m_SequenceLayers[ i ].m_flCycleBeganAt,flTime ) );
		DoAnimationEvents( &studioHdr, m_SequenceLayers[ i ].m_nSequenceIndex, flTime, m_SequenceLayers[ i ].m_bNoLoop, &m_SequenceLayerEventState[ i ] );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMDLPanel::DoAnimationEvents( CStudioHdr *pStudioHdr, int nSeqNum, float flTime, bool bNoLoop, MDLAnimEventState_t *pEventState )
{
	if ( nSeqNum < 0 || nSeqNum >= pStudioHdr->GetNumSeq() )
	{
		return;
	}

	mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSeqNum );
	if ( seqdesc.numevents == 0 )
	{
		return;
	}

	mstudioevent_t *pevent = seqdesc.pEvent( 0 );

	int nFrameCount = Studio_MaxFrame( pStudioHdr, nSeqNum, m_PoseParameters );
	if ( nFrameCount == 0 )
	{
		nFrameCount = 1;
	}
	float flEventCycle = ( flTime * m_RootMDL.m_MDL.m_flPlaybackRate ) / nFrameCount;
	//Plat_DebugString( CFmtStr("Event cycle: %f, playback rate: %f, frame count: %d\n", flEventCycle, m_RootMDL.m_MDL.m_flPlaybackRate, nFrameCount ) );
	if ( bNoLoop )
	{
		flEventCycle = MIN(flEventCycle, 1.0f);
	}
	else
	{
		flEventCycle -= (int)(flEventCycle);
	}

	if ( pEventState->m_nEventSequence != nSeqNum )
	{
		pEventState->m_nEventSequence = nSeqNum;
		flEventCycle = 0.0f;
		pEventState->m_flPrevEventCycle = -0.01f; // back up to get 0'th frame animations
	}

	if ( flEventCycle == pEventState->m_flPrevEventCycle )
	{
		return;
	}

	// check for looping
	BOOL bLooped = (flEventCycle < pEventState->m_flPrevEventCycle);

	// This makes sure events that occur at the end of a sequence occur are
	// sent before events that occur at the beginning of a sequence.
	if (bLooped)
	{
		for (int i = 0; i < (int)seqdesc.numevents; i++)
		{
			if ( pevent[i].cycle <= pEventState->m_flPrevEventCycle )
				continue;

			FireEvent( pevent[ i ].pszEventName(), pevent[ i ].pszOptions() );
		}

		// Necessary to get the next loop working
		pEventState->m_flPrevEventCycle = -0.01f;
	}

	for (int i = 0; i < (int)seqdesc.numevents; i++)
	{
		if ( (pevent[i].cycle > pEventState->m_flPrevEventCycle && pevent[i].cycle <= flEventCycle) )
		{
			FireEvent( pevent[ i ].pszEventName(), pevent[ i ].pszOptions() );
		}
	}

	pEventState->m_flPrevEventCycle = flEventCycle;
}

void CMDLPanel::FireEvent( const char *pszEventName, const char *pszEventOptions )
{
	KeyValues* pKVEvent = new KeyValues( "AnimEvent" );
	pKVEvent->SetString( "name", pszEventName );
	pKVEvent->SetString( "options", pszEventOptions );
	PostActionSignal( pKVEvent );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMDLPanel::ResetAnimationEventState( MDLAnimEventState_t *pEventState )
{
	pEventState->m_nEventSequence = -1;
	pEventState->m_flPrevEventCycle = -0.01f;
}

//-----------------------------------------------------------------------------
// input
//-----------------------------------------------------------------------------
void CMDLPanel::OnMouseDoublePressed( vgui::MouseCode code )
{
	if ( m_bIgnoreDoubleClick )
		return;

	float flRadius;
	Vector vecCenter;
	GetBoundingSphere( vecCenter, flRadius );
	LookAt( vecCenter, flRadius );

	BaseClass::OnMouseDoublePressed( code );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMDLPanel::SetMergeMDL( MDLHandle_t handle, void *pProxyData, int nSkin /*= -1 */ )
{
	// Verify that we have a root model to merge to.
	if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
		return;

	int iIndex = m_aMergeMDLs.AddToTail();
	if ( !m_aMergeMDLs.IsValidIndex( iIndex ) )
		return;

	m_aMergeMDLs[iIndex].m_MDL.SetMDL( handle );

	if ( nSkin != -1 )
	{
		m_aMergeMDLs[iIndex].m_MDL.m_nSkin = nSkin;
	}

	m_aMergeMDLs[iIndex].m_MDL.m_nLOD = m_RootMDL.m_MDL.m_nLOD;

	m_aMergeMDLs[iIndex].m_MDL.m_pProxyData = pProxyData;
	SetIdentityMatrix( m_aMergeMDLs[iIndex].m_MDLToWorld );

	m_aMergeMDLs[iIndex].m_bDisabled = false;

	// Need to invalidate the layout so the panel will adjust is LookAt for the new model.
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
MDLHandle_t CMDLPanel::SetMergeMDL( const char *pMDLName, void *pProxyData, int nSkin /*= -1 */ )
{
	MDLHandle_t hMDLFindResult = vgui::MDLCache()->FindMDL( pMDLName );
	MDLHandle_t hMDL = pMDLName ? hMDLFindResult : MDLHANDLE_INVALID;
	if ( vgui::MDLCache()->IsErrorModel( hMDL ) )
	{
		hMDL = MDLHANDLE_INVALID;
	}

	SetMergeMDL( hMDL, pProxyData, nSkin );

	// FindMDL takes a reference and the the CMDL will also hold a reference for as long as it sticks around. Release the FindMDL reference.
	int nRef = vgui::MDLCache()->Release( hMDLFindResult );
	(void)nRef; // Avoid unreferenced variable warning
	AssertMsg( hMDL == MDLHANDLE_INVALID || nRef > 0, "CMDLPanel::SetMergeMDL referenced a model that has a zero ref count." );

	return hMDL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CMDLPanel::GetMergeMDLIndex( void *pProxyData )
{
	int nMergeCount = m_aMergeMDLs.Count();
	for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge )
	{
		if ( m_aMergeMDLs[iMerge].m_MDL.m_pProxyData == pProxyData )
			return iMerge;
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CMDLPanel::GetMergeMDLIndex( MDLHandle_t handle )
{
	int nMergeCount = m_aMergeMDLs.Count();
	for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge )
	{
		if ( m_aMergeMDLs[iMerge].m_MDL.GetMDL() == handle )
			return iMerge;
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMDL *CMDLPanel::GetMergeMDL( MDLHandle_t handle )
{
	int nMergeCount = m_aMergeMDLs.Count();
	for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge )
	{
		if ( m_aMergeMDLs[iMerge].m_MDL.GetMDL() == handle )
			return (&m_aMergeMDLs[iMerge].m_MDL);
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMDLPanel::ClearMergeMDLs( void )
{
	m_aMergeMDLs.Purge();
}