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

#include "cbase.h"
#include "basemodel_panel.h"
#include "activitylist.h"
#include "animation.h"
#include "vgui/IInput.h"
#include "matsys_controls/manipulator.h"

using namespace vgui;
DECLARE_BUILD_FACTORY( CBaseModelPanel );

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseModelPanel::CBaseModelPanel( vgui::Panel *pParent, const char *pName ): BaseClass( pParent, pName )
{
	m_bForcePos = false;
	m_bMousePressed = false;
	m_bAllowRotation = false;
	m_bAllowFullManipulation = false;
	m_bApplyManipulators = false;
	m_bForcedCameraPosition = false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseModelPanel::~CBaseModelPanel()
{
}

//-----------------------------------------------------------------------------
// Purpose: Load in the model portion of the panel's resource file.
//-----------------------------------------------------------------------------
void CBaseModelPanel::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	// Set whether we render to texture
	m_bRenderToTexture = inResourceData->GetBool( "render_texture", true );

	// Grab and set the camera FOV.
	float flFOV = GetCameraFOV();
	m_BMPResData.m_flFOV = inResourceData->GetInt( "fov", flFOV );
	SetCameraFOV( m_BMPResData.m_flFOV );

	// Do we allow rotation on these panels.
	m_bAllowRotation = inResourceData->GetBool( "allow_rot", false );

	// Do we allow full manipulation on these panels.
	m_bAllowFullManipulation = inResourceData->GetBool( "allow_manip", false );

	// Parse our resource file and apply all necessary updates to the MDL.
 	for ( KeyValues *pData = inResourceData->GetFirstSubKey() ; pData != NULL ; pData = pData->GetNextKey() )
 	{
 		if ( !Q_stricmp( pData->GetName(), "model" ) )
 		{
 			ParseModelResInfo( pData );
 		}
 	}

	SetMouseInputEnabled( m_bAllowFullManipulation || m_bAllowRotation );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::ParseModelResInfo( KeyValues *inResourceData )
{
	m_bForcePos = ( inResourceData->GetInt( "force_pos", 0 ) == 1 );
	m_BMPResData.m_pszModelName = ReadAndAllocStringValue( inResourceData, "modelname" );
	m_BMPResData.m_pszModelName_HWM = ReadAndAllocStringValue( inResourceData, "modelname_hwm" );
	m_BMPResData.m_pszVCD = ReadAndAllocStringValue( inResourceData, "vcd" );
	m_BMPResData.m_angModelPoseRot.Init( inResourceData->GetFloat( "angles_x", 0.0f ), inResourceData->GetFloat( "angles_y", 0.0f ), inResourceData->GetFloat( "angles_z", 0.0f ) );
	m_BMPResData.m_vecOriginOffset.Init( inResourceData->GetFloat( "origin_x", 110.0 ), inResourceData->GetFloat( "origin_y", 5.0 ), inResourceData->GetFloat( "origin_z", 5.0 ) );
	m_BMPResData.m_vecFramedOriginOffset.Init( inResourceData->GetFloat( "frame_origin_x", 110.0 ), inResourceData->GetFloat( "frame_origin_y", 5.0 ), inResourceData->GetFloat( "frame_origin_z", 5.0 ) );
	m_BMPResData.m_vecViewportOffset.Init();
	m_BMPResData.m_nSkin = inResourceData->GetInt( "skin", -1 );
	m_BMPResData.m_bUseSpotlight = ( inResourceData->GetInt( "spotlight", 0 ) == 1 );

	m_angPlayer = m_BMPResData.m_angModelPoseRot;
	m_vecPlayerPos = m_BMPResData.m_vecOriginOffset;

	for ( KeyValues *pData = inResourceData->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
	{
		if ( !Q_stricmp( pData->GetName(), "animation" ) )
		{
			ParseModelAnimInfo( pData );
		}
		else if ( !Q_stricmp( pData->GetName(), "attached_model" ) )
		{
			ParseModelAttachInfo( pData );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::ParseModelAnimInfo( KeyValues *inResourceData )
{
	if ( !inResourceData )
		return;

	int iAnim = m_BMPResData.m_aAnimations.AddToTail();
	if ( iAnim == m_BMPResData.m_aAnimations.InvalidIndex() )
		return;

	m_BMPResData.m_aAnimations[iAnim].m_pszName = ReadAndAllocStringValue( inResourceData, "name" );
	m_BMPResData.m_aAnimations[iAnim].m_pszSequence = ReadAndAllocStringValue( inResourceData, "sequence" );
	m_BMPResData.m_aAnimations[iAnim].m_pszActivity = ReadAndAllocStringValue( inResourceData, "activity" );
	m_BMPResData.m_aAnimations[iAnim].m_bDefault = ( inResourceData->GetInt( "default", 0 ) == 1 );

	for ( KeyValues *pAnimData = inResourceData->GetFirstSubKey(); pAnimData != NULL; pAnimData = pAnimData->GetNextKey() )
	{
		if ( !Q_stricmp( pAnimData->GetName(), "pose_parameters" ) )
		{
			m_BMPResData.m_aAnimations[iAnim].m_pPoseParameters = pAnimData->MakeCopy();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::ParseModelAttachInfo( KeyValues *inResourceData )
{
	if ( !inResourceData )
		return;

	int iAttach = m_BMPResData.m_aAttachModels.AddToTail();
	if ( iAttach == m_BMPResData.m_aAttachModels.InvalidIndex() )
		return;

	m_BMPResData.m_aAttachModels[iAttach].m_pszModelName = ReadAndAllocStringValue( inResourceData, "modelname" );
	m_BMPResData.m_aAttachModels[iAttach].m_nSkin = inResourceData->GetInt( "skin", -1 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::SetupModelDefaults( void )
{
	SetupModelAnimDefaults();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::SetupModelAnimDefaults( void )
{
	// Set the move_x parameter so the run activity works
	SetPoseParameterByName( "move_x", 1.0f );

	// Verify that we have animations for this model.
	int nAnimCount = m_BMPResData.m_aAnimations.Count();
	if ( nAnimCount == 0 )
		return;

	// Find the default animation if one exists.
	int iIndex = FindDefaultAnim();
	if ( iIndex == -1 )
		return;

	SetModelAnim( iIndex );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseModelPanel::FindDefaultAnim( void )
{
	int iIndex = -1;

	int nAnimCount = m_BMPResData.m_aAnimations.Count();
	for ( int iAnim = 0; iAnim < nAnimCount; ++iAnim )
	{
		if ( m_BMPResData.m_aAnimations[iAnim].m_bDefault )
			return iAnim;
	}

	return iIndex;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseModelPanel::FindAnimByName( const char *pszName )
{
	int iIndex = -1;
	if ( !pszName )
		return iIndex;
	
	int nAnimCount = m_BMPResData.m_aAnimations.Count();
	for ( int iAnim = 0; iAnim < nAnimCount; ++iAnim )
	{
		if ( !Q_stricmp( m_BMPResData.m_aAnimations[iAnim].m_pszName, pszName ) )
			return iAnim;
	}

	return iIndex;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseModelPanel::FindSequenceFromActivity( CStudioHdr *pStudioHdr, const char *pszActivity )
{
	if ( !pStudioHdr )
		return -1;

	for ( int iSeq = 0; iSeq < pStudioHdr->GetNumSeq(); ++iSeq )
	{
		mstudioseqdesc_t &seqDesc = pStudioHdr->pSeqdesc( iSeq );
		if ( !V_stricmp( seqDesc.pszActivityName(), pszActivity ) )
		{
			return iSeq;
		}
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::SetModelAnim( int iAnim )
{
	int nAnimCount = m_BMPResData.m_aAnimations.Count();
	if ( nAnimCount == 0 || !m_BMPResData.m_aAnimations.IsValidIndex( iAnim ) )
		return;

	MDLCACHE_CRITICAL_SECTION();

	// Get the studio header of the root model.
	studiohdr_t *pStudioHdr = m_RootMDL.m_MDL.GetStudioHdr();
	if ( !pStudioHdr )
		return;

	CStudioHdr studioHdr( pStudioHdr, g_pMDLCache );

	// Do we have an activity or a sequence?
	int iSequence = ACT_INVALID;
	if ( m_BMPResData.m_aAnimations[iAnim].m_pszActivity && m_BMPResData.m_aAnimations[iAnim].m_pszActivity[0] )
	{
		iSequence = FindSequenceFromActivity( &studioHdr, m_BMPResData.m_aAnimations[iAnim].m_pszActivity );
	}
	else if ( m_BMPResData.m_aAnimations[iAnim].m_pszSequence && m_BMPResData.m_aAnimations[iAnim].m_pszSequence[0] )
	{
		iSequence = LookupSequence( &studioHdr, m_BMPResData.m_aAnimations[iAnim].m_pszSequence );
	}

	if ( iSequence != ACT_INVALID )
	{
		SetSequence( iSequence );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::SetMDL( MDLHandle_t handle, void *pProxyData )
{
	MDLCACHE_CRITICAL_SECTION();
	studiohdr_t *pHdr = g_pMDLCache->GetStudioHdr( handle );

	if ( pHdr )
	{
		// SetMDL will cause the base CMdl code to set our localtoglobal indices if they aren't set.
		// We set them up here so that they're left alone by that code.
		CStudioHdr studioHdr( pHdr, g_pMDLCache );
		if (studioHdr.numflexcontrollers() > 0 && studioHdr.pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1)
		{
			for (LocalFlexController_t i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
			{
				int j = C_BaseFlex::AddGlobalFlexController( studioHdr.pFlexcontroller( i )->pszName() );
				studioHdr.pFlexcontroller( i )->localToGlobal = j;
			}
		}
	}
	else 
	{
		handle = MDLHANDLE_INVALID;
	}

	// Clear our current sequence
	SetSequence( ACT_IDLE );

	BaseClass::SetMDL( handle, pProxyData );

	SetupModelDefaults();

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::SetModelAnglesAndPosition( const QAngle &angRot, const Vector &vecPos )
{
	BaseClass::SetModelAnglesAndPosition( angRot, vecPos );

	// Cache
	m_vecPlayerPos = vecPos;
	m_angPlayer = angRot;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::SetMDL( const char *pMDLName, void *pProxyData )
{
	BaseClass::SetMDL( pMDLName, pProxyData );

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseModelPanel::PerformLayout()
{
	BaseClass::PerformLayout();

	if ( m_bForcedCameraPosition )
	{
		return;
	}

	if ( m_bAllowFullManipulation )
	{
		// Set this to true if you want to keep the current rotation when changing models or poses
		const bool bPreserveManipulation = false;

		// Need to look at the target so we can rotate around it
		const Vector kVecFocalPoint( 0.0f, 0.0f, 60.0f );
		ResetCameraPivot();
		SetCameraOffset( -(m_vecPlayerPos + kVecFocalPoint) );
		SetCameraPositionAndAngles( kVecFocalPoint, vec3_angle, !bPreserveManipulation );

		// We want to move the player to the origin and facing the correct way,
		// but don't clobber m_angPlayer and m_vecPlayerPos, so use BaseClass.
		BaseClass::SetModelAnglesAndPosition( m_angPlayer, vec3_origin );

		// Once a manual transform has been done we want to apply it
		if ( m_bApplyManipulators )
		{
			ApplyManipulation();
		}
		else
		{
			SyncManipulation();
		}
		return;
	}

	if ( m_bForcePos )
	{
		ResetCameraPivot();
		SetCameraOffset( Vector( 0.0f, 0.0f, 0.0f ) );
		SetCameraPositionAndAngles( vec3_origin, vec3_angle );
		SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos );
	}

	// Center and fill the frame with the model?
	if ( m_bStartFramed )
	{
		Vector vecBoundsMin, vecBoundsMax;
		if ( GetBoundingBox( vecBoundsMin, vecBoundsMax ) )
		{
			LookAtBounds( vecBoundsMin, vecBoundsMax );
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::OnKeyCodePressed ( vgui::KeyCode code )
{
	if ( m_bAllowFullManipulation )
	{
		BaseClass::OnKeyCodePressed( code );
		return;
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::OnKeyCodeReleased( vgui::KeyCode code )
{
	if ( m_bAllowFullManipulation )
	{
		BaseClass::OnKeyCodeReleased( code );
		return;
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::OnMousePressed ( vgui::MouseCode code )
{
	if ( m_bAllowFullManipulation )
	{
		BaseClass::OnMousePressed( code );
		return;
	}

	if ( !m_bAllowRotation )
		return;

	RequestFocus();

	EnableMouseCapture( true, code );

	// Warp the mouse to the center of the screen
	int width, height;
	GetSize( width, height );
	int x = width / 2;
	int y = height / 2;

	int xpos = x;
	int ypos = y;
	LocalToScreen( xpos, ypos );
	input()->SetCursorPos( xpos, ypos );

	m_nManipStartX = xpos;
	m_nManipStartY = ypos;

	m_bMousePressed = true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::OnMouseReleased( vgui::MouseCode code )
{
	if ( m_bAllowFullManipulation )
	{
		BaseClass::OnMouseReleased( code );
		return;
	}

	if ( !m_bAllowRotation )
		return;

	EnableMouseCapture( false );
	m_bMousePressed = false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::OnCursorMoved( int x, int y )
{
	if ( m_bAllowFullManipulation )
	{
		if ( m_pCurrentManip )
		{
			m_bApplyManipulators = true;
		}
		BaseClass::OnCursorMoved( x, y );
		return;
	}

	if ( !m_bAllowRotation )
		return;

	if ( m_bMousePressed )
	{
		WarpMouse( x, y );
		int xpos, ypos;
		input()->GetCursorPos( xpos, ypos );

		// Only want the x delta.
		float flDelta = xpos - m_nManipStartX;

		// Apply the delta and rotate the player.
		RotateYaw( flDelta );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::RotateYaw( float flDelta )
{
	m_angPlayer.y += flDelta;
	if ( m_angPlayer.y > 360.0f )
	{
		m_angPlayer.y = m_angPlayer.y - 360.0f;
	}
	else if ( m_angPlayer.y < -360.0f )
	{
		m_angPlayer.y = m_angPlayer.y + 360.0f;
	}

	SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CBaseModelPanel::GetPlayerPos() const
{
	return m_vecPlayerPos;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
QAngle CBaseModelPanel::GetPlayerAngles() const
{
	return m_angPlayer;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseModelPanel::OnMouseWheeled( int delta )
{
	if ( m_bAllowFullManipulation )
	{
		BaseClass::OnMouseWheeled( delta );
		return;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Set the camera to a distance that allows the object to fill the model panel.
//-----------------------------------------------------------------------------
void CBaseModelPanel::LookAtBounds( const Vector &vecBoundsMin, const Vector &vecBoundsMax )
{
	// Get the model space render bounds.
	Vector vecMin = vecBoundsMin;
	Vector vecMax = vecBoundsMax;
	Vector vecCenter = ( vecMax + vecMin ) * 0.5f;
	vecMin -= vecCenter;
	vecMax -= vecCenter;

	// Get the bounds points and transform them by the desired model panel rotation.
	Vector aBoundsPoints[8];
	aBoundsPoints[0].Init( vecMax.x, vecMax.y, vecMax.z ); 
	aBoundsPoints[1].Init( vecMin.x, vecMax.y, vecMax.z ); 
	aBoundsPoints[2].Init( vecMax.x, vecMin.y, vecMax.z ); 
	aBoundsPoints[3].Init( vecMin.x, vecMin.y, vecMax.z ); 
	aBoundsPoints[4].Init( vecMax.x, vecMax.y, vecMin.z ); 
	aBoundsPoints[5].Init( vecMin.x, vecMax.y, vecMin.z ); 
	aBoundsPoints[6].Init( vecMax.x, vecMin.y, vecMin.z ); 
	aBoundsPoints[7].Init( vecMin.x, vecMin.y, vecMin.z ); 

	// Translated center point (offset from camera center).
	Vector vecTranslateCenter = -vecCenter;

	// Build the rotation matrix.
	matrix3x4_t matRotation;
	AngleMatrix( m_BMPResData.m_angModelPoseRot, matRotation );

	Vector aXFormPoints[8];
	for ( int iPoint = 0; iPoint < 8; ++iPoint )
	{
		VectorTransform( aBoundsPoints[iPoint], matRotation, aXFormPoints[iPoint] );
	}

	Vector vecXFormCenter;
	VectorTransform( -vecTranslateCenter, matRotation, vecXFormCenter );

	int w, h;
	GetSize( w, h );
	float flW = (float)w;
	float flH = (float)h;

	float flFOVx = DEG2RAD( m_BMPResData.m_flFOV * 0.5f );
	float flFOVy = CalcFovY( ( m_BMPResData.m_flFOV * 0.5f ), flW/flH );
	flFOVy = DEG2RAD( flFOVy );

	float flTanFOVx = tan( flFOVx );
	float flTanFOVy = tan( flFOVy );

	// Find the max value of x, y, or z
	Vector2D dist[8];
	float flDist = 0.0f;
	for ( int iPoint = 0; iPoint < 8; ++iPoint )
	{
		float flDistY = fabs( aXFormPoints[iPoint].y / flTanFOVx ) - aXFormPoints[iPoint].x;
		float flDistZ = fabs( aXFormPoints[iPoint].z / flTanFOVy ) - aXFormPoints[iPoint].x;
		dist[iPoint].x = flDistY;
		dist[iPoint].y = flDistZ;
		float flTestDist = MAX( flDistZ, flDistY );
		flDist = MAX( flDist, flTestDist );
	}

	// Screen space points.
	Vector2D aScreenPoints[8];
	Vector aCameraPoints[8];
	for ( int iPoint = 0; iPoint < 8; ++iPoint )
	{
		aCameraPoints[iPoint] = aXFormPoints[iPoint];
		aCameraPoints[iPoint].x += flDist;

		aScreenPoints[iPoint].x = aCameraPoints[iPoint].y / ( flTanFOVx * aCameraPoints[iPoint].x );
		aScreenPoints[iPoint].y = aCameraPoints[iPoint].z / ( flTanFOVy * aCameraPoints[iPoint].x );

		aScreenPoints[iPoint].x = ( aScreenPoints[iPoint].x * 0.5f + 0.5f ) * flW;
		aScreenPoints[iPoint].y = ( aScreenPoints[iPoint].y * 0.5f + 0.5f ) * flH;
	}

	// Find the min/max and center of the 2D bounding box of the object.
	Vector2D vecScreenMin( 99999.0f, 99999.0f ), vecScreenMax( -99999.0f, -99999.0f );
	for ( int iPoint = 0; iPoint < 8; ++iPoint )
	{
		vecScreenMin.x = MIN( vecScreenMin.x, aScreenPoints[iPoint].x );
		vecScreenMin.y = MIN( vecScreenMin.y, aScreenPoints[iPoint].y );
		vecScreenMax.x = MAX( vecScreenMax.x, aScreenPoints[iPoint].x );
		vecScreenMax.y = MAX( vecScreenMax.y, aScreenPoints[iPoint].y );
	}

	// Offset the model to the be the correct distance away from the camera.
	Vector vecModelPos;
	vecModelPos.x = flDist - vecXFormCenter.x;
	vecModelPos.y = -vecXFormCenter.y;
	vecModelPos.z = -vecXFormCenter.z;
	SetModelAnglesAndPosition( m_BMPResData.m_angModelPoseRot, vecModelPos );
	m_vecPlayerPos = vecModelPos;

	// Back project to figure out the camera offset to center the model.
	Vector2D vecPanelCenter( ( flW * 0.5f ), ( flH * 0.5f ) );
	Vector2D vecScreenCenter = ( vecScreenMax + vecScreenMin ) * 0.5f;

	Vector2D vecPanelCenterCamera, vecScreenCenterCamera;
	vecPanelCenterCamera.x = ( ( vecPanelCenter.x / flW ) * 2.0f ) - 0.5f;
	vecPanelCenterCamera.y = ( ( vecPanelCenter.y / flH ) * 2.0f ) - 0.5f;
	vecPanelCenterCamera.x *= ( flTanFOVx * flDist );
	vecPanelCenterCamera.y *= ( flTanFOVy * flDist );
	vecScreenCenterCamera.x = ( ( vecScreenCenter.x / flW ) * 2.0f ) - 0.5f;
	vecScreenCenterCamera.y = ( ( vecScreenCenter.y / flH ) * 2.0f ) - 0.5f;
	vecScreenCenterCamera.x *= ( flTanFOVx * flDist );
	vecScreenCenterCamera.y *= ( flTanFOVy * flDist );

	Vector2D vecCameraOffset( 0.0f, 0.0f );
	vecCameraOffset.x = vecPanelCenterCamera.x - vecScreenCenterCamera.x;
	vecCameraOffset.y = vecPanelCenterCamera.y - vecScreenCenterCamera.y;

	// Clear the camera pivot and set position matrix.
	ResetCameraPivot();
	if (m_bAllowRotation )
	{
		vecCameraOffset.x = 0.0f;
	}
	SetCameraOffset( Vector( 0.0f, -vecCameraOffset.x, -vecCameraOffset.y ) );
	UpdateCameraTransform();
}

