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

#include "matsys_controls/potterywheelpanel.h"
#include "matsys_controls/manipulator.h"
#include "vgui/ISystem.h"
#include "vgui/Cursor.h"
#include "vgui/IVGui.h"
#include "vgui/ISurface.h"
#include "vgui/IInput.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "dmxloader/dmxelement.h"
#include "vgui_controls/Frame.h"
#include "convar.h"
#include "tier0/dbg.h"
#include "matsys_controls/matsyscontrols.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialsystem.h"
#include "istudiorender.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "tier2/renderutils.h"
#include "tier1/KeyValues.h"
#include "materialsystem/imesh.h"

#include "inputsystem/iinputsystem.h"

#include "renderparm.h"

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


using namespace vgui;


//-----------------------------------------------------------------------------
// Translation manipulator
//-----------------------------------------------------------------------------
class CTranslationManipulator : public CTransformManipulator
{
public:
	CTranslationManipulator( matrix3x4_t *pTransform );

	// Methods of IManipulator
	virtual void OnMousePressed( vgui::MouseCode code, int x, int y );
	virtual void OnCursorMoved( int x, int y );

protected:
	int m_lastx, m_lasty;
};


CTranslationManipulator::CTranslationManipulator( matrix3x4_t *pTransform ) : CTransformManipulator( pTransform )
{
	m_lastx = m_lasty = 0;
}

void CTranslationManipulator::OnMousePressed( vgui::MouseCode code, int x, int y )
{
	m_lasty = y;
	m_lastx = x;
}

void CTranslationManipulator::OnCursorMoved( int x, int y )
{
	if ( !m_pTransform )
		return;

	Vector vPosition;
	QAngle quakeEuler;
	MatrixAngles( *m_pTransform, quakeEuler, vPosition );

	Vector forward, right, up;
	AngleVectors( quakeEuler, &forward, &right, &up );

	int dy = y - m_lasty;
	int dx = x - m_lastx;

	right *= -0.2f * dx;
	up *= 0.2f * dy;
	vPosition += up + right;

	m_lastx = x;
	m_lasty = y;

	PositionMatrix( vPosition, *m_pTransform );
}


//-----------------------------------------------------------------------------
// Zoom manipulator
//-----------------------------------------------------------------------------
class CZoomManipulator : public CBaseManipulator
{
public:
	CZoomManipulator( float *pDistance );

	// Methods of IManipulator
	virtual void OnMousePressed( vgui::MouseCode code, int x, int y );
	virtual void OnCursorMoved( int x, int y );

protected:
	int m_lasty;
	float *m_pDistance;
};

CZoomManipulator::CZoomManipulator( float *pDistance )
{
	m_lasty = 0;
	m_pDistance = pDistance;
}

void CZoomManipulator::OnMousePressed( vgui::MouseCode code, int x, int y )
{
	m_lasty = y;
}

void CZoomManipulator::OnCursorMoved( int x, int y )
{
	float delta  = 0.2f * ( y - m_lasty );
	m_lasty = y;
	*m_pDistance *= pow( 1.01f, delta );
}


//-----------------------------------------------------------------------------
// Rotation manipulator
//-----------------------------------------------------------------------------
class CRotationManipulator : public CTransformManipulator
{
public:
	CRotationManipulator( matrix3x4_t *pTransform );

	// Inherited from IManipulator
	virtual void OnMousePressed( vgui::MouseCode code, int x, int y );
	virtual void OnCursorMoved( int x, int y );
	virtual void UpdateTransform();

			void	UpdateFromMatrix( void );

private:
	int		m_lastx, m_lasty;
	float	m_altitude, m_azimuth, m_roll;
	bool	m_bDoRoll;
};


CRotationManipulator::CRotationManipulator( matrix3x4_t *pTransform ) : CTransformManipulator( pTransform )
{
	m_lastx = m_lasty = 0;
	m_altitude = M_PI/6;
	m_azimuth = -3*M_PI/4;
	m_roll = 0.0f;
	m_bDoRoll = false;
	UpdateTransform();
}

void CRotationManipulator::OnMousePressed( vgui::MouseCode code, int x, int y )
{
	if ( input()->IsKeyDown( KEY_LALT ) || input()->IsKeyDown( KEY_RALT ) )
	{
		m_bDoRoll = true;
	}
	else
	{
		m_bDoRoll = false;
	}
	m_lasty = y;
	m_lastx = x;
}

void CRotationManipulator::OnCursorMoved( int x, int y )
{
	if ( m_bDoRoll )
	{
		m_roll += 0.002f * ( m_lastx - x );
	}
	else
	{
		m_azimuth  += 0.002f * ( m_lastx - x );
		m_altitude -= 0.002f * ( m_lasty - y );
		m_altitude = max( (float)-M_PI/2, min( (float)M_PI/2, m_altitude ) );
	}

	m_lastx = x;
	m_lasty = y;

	UpdateTransform();
}

void CRotationManipulator::UpdateTransform()
{
	if ( !m_pTransform )
		return;

	QAngle angles( RAD2DEG( m_altitude ), RAD2DEG( m_azimuth ), RAD2DEG( m_roll ) );
	Vector vecPosition;
	MatrixGetColumn( *m_pTransform, 3, vecPosition );
	AngleMatrix( angles, vecPosition, *m_pTransform );
}

void CRotationManipulator::UpdateFromMatrix( void )
{
	if ( !m_pTransform )
		return;

	QAngle	angDir;
	Vector	vecPos;

	MatrixAngles( *m_pTransform, angDir, vecPos );

	m_altitude = DEG2RAD( angDir.x );
	m_azimuth = DEG2RAD( angDir.y );
	m_roll = DEG2RAD( angDir.z );
}



//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CPotteryWheelPanel::CPotteryWheelPanel( vgui::Panel *pParent, const char *pName ) :
	BaseClass( pParent, pName ), 
	m_pCameraRotate( NULL ), 
	m_pCameraTranslate( NULL ),
	m_pCameraZoom( NULL ),
	m_pLightManip( NULL ),
	m_pCurrentManip( NULL ),
	m_nCaptureMouseCode( vgui::MouseCode( -1 ) ),
	m_xoffset( 0 ), m_yoffset( 0 ),
	m_bRenderToTexture( true )
{
	m_bHasLightProbe = false;

	SetPaintBackgroundEnabled( false );
	SetPaintBorderEnabled( false );
	m_ClearColor.SetColor( 76, 88, 68, 255 );

	SetIdentityMatrix( m_CameraPivot );

	CreateDefaultLights();

	m_nManipStartX = m_nManipStartY = 0;

	m_vecCameraOffset.Init( 100.0f, 0.0f, 0.0f );
		
	m_Camera.m_flZNear = 3.0f;
	m_Camera.m_flZFar = 16384.0f * 1.73205080757f;
	m_Camera.m_flFOV = 30.0f;

	m_pCameraRotate = new CRotationManipulator( &m_CameraPivot );
	m_pCameraTranslate = new CTranslationManipulator( &m_CameraPivot );
	m_pCameraZoom = new CZoomManipulator( &m_vecCameraOffset.x );

	KeyValues *pMaterialKeys = new KeyValues( "Wireframe", "$model", "1" );

	pMaterialKeys->SetString( "$vertexcolor", "1" );
	m_Wireframe.Init( "potterywheelpanelwireframe", pMaterialKeys );

	SetKeyBoardInputEnabled( true );
	UpdateCameraTransform();
}

void CPotteryWheelPanel::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	KeyValues *pLights = inResourceData->FindKey( "lights" );
	if ( pLights )
	{
		ParseLightsFromKV( pLights );
	}
}

void CPotteryWheelPanel::Init( int x, int y, int wide, int tall )
{
	BaseClass::Init( x, y, wide, tall );

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


CPotteryWheelPanel::~CPotteryWheelPanel()
{
	m_Wireframe.Shutdown();
	m_LightProbeBackground.Shutdown();
	m_LightProbeHDRBackground.Shutdown();
	m_LightProbeCubemap.Shutdown();
	m_LightProbeHDRCubemap.Shutdown();

	if ( m_pCameraRotate )
	{
		delete m_pCameraRotate;
		m_pCameraRotate = NULL;
	}

	if ( m_pCameraZoom )
	{
		delete m_pCameraZoom;
		m_pCameraZoom = NULL;
	}

	if ( m_pCameraTranslate )
	{
		delete m_pCameraTranslate;
		m_pCameraTranslate = NULL;
	}

	DestroyLights();
}

void CPotteryWheelPanel::CreateDefaultLights()
{
	for ( int i = 0; i < 6; ++i )
	{
		m_vecAmbientCube[i].Init( 0.4f, 0.4f, 0.4f, 1.0f );
	}

	memset( &m_Lights[0].m_Desc, 0, sizeof(LightDesc_t) );
	SetIdentityMatrix( m_Lights[0].m_LightToWorld );
	m_Lights[0].m_Desc.m_Type = MATERIAL_LIGHT_DIRECTIONAL;
	m_Lights[0].m_Desc.m_Color.Init( 1.0f, 1.0f, 1.0f );
	m_Lights[0].m_Desc.m_Direction.Init( 0.0f, 0.0f, -1.0f );
	m_Lights[0].m_Desc.m_Range=0.0;
	m_Lights[0].m_Desc.m_Attenuation0 = 1.0;
	m_Lights[0].m_Desc.m_Attenuation1 = 0;
	m_Lights[0].m_Desc.m_Attenuation2 = 0;
	m_Lights[0].m_Desc.RecalculateDerivedValues();
	m_nLightCount = 1;

	m_pLightManip = new CPotteryWheelManip( &m_Lights[0].m_LightToWorld );
}


void CPotteryWheelPanel::DestroyLights()
{
	if ( m_pLightManip )
	{
		delete m_pLightManip;
		m_pLightManip = NULL;
	}

	m_nLightCount = 0;
}


void StringToFloatArray( float *pVector, int count, const char *pString )
{
	char *pstr, *pfront, tempString[128];
	int	j;

	Q_strncpy( tempString, pString, sizeof(tempString) );
	pstr = pfront = tempString;

	for ( j = 0; j < count; j++ )			// lifted from pr_edict.c
	{
		pVector[j] = atof( pfront );

		// skip any leading whitespace
		while ( *pstr && *pstr <= ' ' )
			pstr++;

		// skip to next whitespace
		while ( *pstr && *pstr > ' ' )
			pstr++;

		if (!*pstr)
			break;

		pstr++;
		pfront = pstr;
	}
	for ( j++; j < count; j++ )
	{
		pVector[j] = 0;
	}
}

void StringToVector( float *pVector, const char *pString )
{
	StringToFloatArray( pVector, 3, pString );
}


//-----------------------------------------------------------------------------
// Sets initialize lights from KeyValues
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::ParseLightsFromKV( KeyValues *pLightsKV )
{
	int nLightCount = 0;
	FOR_EACH_SUBKEY( pLightsKV, pLocalLight )
	{
		Assert( nLightCount < MAX_LIGHT_COUNT );
		if ( nLightCount >= MAX_LIGHT_COUNT )
			break;
			
		LightDesc_t *pDesc = &m_Lights[nLightCount].m_Desc;
		const char *pType = pLocalLight->GetString( "name" );
		Vector vecColor;
		StringToVector( vecColor.Base(), pLocalLight->GetString( "color" ) );

		if ( !Q_stricmp( pType, "directional" ) )
		{
			Vector vecDirection;
			StringToVector( vecDirection.Base(), pLocalLight->GetString( "direction" ) );
			pDesc->InitDirectional( vecDirection.Normalized(), vecColor ); 
			++nLightCount;
			continue;
		}

		if ( !Q_stricmp( pType, "point" ) )
		{
			Vector vecAtten;
			StringToVector( vecAtten.Base(), pLocalLight->GetString( "attenuation" ) );
			Vector vecOrigin;
			StringToVector( vecOrigin.Base(), pLocalLight->GetString( "origin" ) );
			pDesc->InitPoint( vecOrigin, vecColor );
			pDesc->m_Attenuation0 = vecAtten.x;
			pDesc->m_Attenuation1 = vecAtten.y;
			pDesc->m_Attenuation2 = vecAtten.z;
			pDesc->m_Range = pLocalLight->GetFloat( "maxDistance" );
			pDesc->RecalculateDerivedValues();
			++nLightCount;
			continue;
		}

		if ( !Q_stricmp( pType, "spot" ) )
		{
			Vector vecAtten;
			StringToVector( vecAtten.Base(), pLocalLight->GetString( "attenuation" ) );
			Vector vecOrigin;
			StringToVector( vecOrigin.Base(), pLocalLight->GetString( "origin" ) );
			pDesc->InitSpot( vecOrigin, vecColor, vec3_origin,
							pLocalLight->GetFloat( "inner_cone_angle" ),
							pLocalLight->GetFloat( "outer_cone_angle" ) );

			Vector vecDirection;
			StringToVector( vecDirection.Base(), pLocalLight->GetString( "direction" ) );
			pDesc->m_Direction = vecDirection.Normalized();
			pDesc->m_Attenuation0 = vecAtten.x;
			pDesc->m_Attenuation1 = vecAtten.y;
			pDesc->m_Attenuation2 = vecAtten.z;
			pDesc->m_Range = pLocalLight->GetFloat( "maxDistance" );
			pDesc->m_Falloff = pLocalLight->GetFloat( "exponent" );
			pDesc->RecalculateDerivedValues();
			++nLightCount;
			continue;
		}

		AssertMsg1( 0, "Failed to initialize light with type '%s'", pType );
	}

	AssertMsg( nLightCount > 0, "Must specify at least one valid light" );

	m_nLightCount = nLightCount;
}


//-----------------------------------------------------------------------------
// Sets the background color
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetBackgroundColor( int r, int g, int b )
{
	m_ClearColor.SetColor( r, g, b, 255 );
}

void CPotteryWheelPanel::SetBackgroundColor( const Color& c )
{
	m_ClearColor = c;
}

const Color& CPotteryWheelPanel::GetBackgroundColor() const
{
	return m_ClearColor;
}


//-----------------------------------------------------------------------------
// Light probe
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetLightProbe( CDmxElement *pLightProbe )
{
	m_LightProbeBackground.Shutdown();
	m_LightProbeHDRBackground.Shutdown();
	m_LightProbeCubemap.Shutdown();
	m_LightProbeHDRCubemap.Shutdown();

	DestroyLights();

	m_bHasLightProbe = ( pLightProbe != NULL );
	if ( !m_bHasLightProbe )
	{
		CreateDefaultLights();
		return;
	}

	const char *pCubemap = pLightProbe->GetValueString( "cubemap" );
	m_LightProbeCubemap.Init( pCubemap, TEXTURE_GROUP_OTHER );

	const char *pCubemapHDR = pLightProbe->HasAttribute( "cubemapHdr" ) ? pLightProbe->GetValueString( "cubemapHdr" ) : pCubemap;
	m_LightProbeHDRCubemap.Init( pCubemapHDR, TEXTURE_GROUP_OTHER );

	KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
	pVMTKeyValues->SetInt( "$ignorez", 1 );
	pVMTKeyValues->SetString( "$envmap", pCubemap );
	pVMTKeyValues->SetInt( "$no_fullbright", 1 );
	pVMTKeyValues->SetInt( "$nocull", 1 );
	m_LightProbeBackground.Init( "SPWP_LightProbeBackground", pVMTKeyValues );
	m_LightProbeBackground->Refresh();

	pVMTKeyValues = new KeyValues( "UnlitGeneric" );
	pVMTKeyValues->SetInt( "$ignorez", 1 );
	pVMTKeyValues->SetString( "$envmap", pCubemapHDR );
	pVMTKeyValues->SetInt( "$no_fullbright", 1 );
	pVMTKeyValues->SetInt( "$nocull", 1 );
	m_LightProbeHDRBackground.Init( "SPWP_LightProbeBackground_HDR", pVMTKeyValues );
	m_LightProbeHDRBackground->Refresh();

	const CUtlVector< Vector >& ambientCube = pLightProbe->GetArray<Vector>( "ambientCube" );
	if ( ambientCube.Count() == 6 )
	{
		for ( int i = 0; i < 6; ++i )
		{
			m_vecAmbientCube[i].Init( ambientCube[i].x, ambientCube[i].y, ambientCube[i].z, 0.0f );
		}
	}

	const CUtlVector< CDmxElement* >& localLights = pLightProbe->GetArray< CDmxElement* >( "localLights" );
	int nLightCount = localLights.Count();
	for ( int i = 0; i < nLightCount; ++i )
	{
		if ( m_nLightCount == MAX_LIGHT_COUNT )
			break;

		LightDesc_t *pDesc = &m_Lights[m_nLightCount].m_Desc;
		CDmxElement *pLocalLight = localLights[ i ];
		const char *pType = pLocalLight->GetValueString( "name" );
		const Vector& vecColor = pLocalLight->GetValue<Vector>( "color" );

		if ( !Q_stricmp( pType, "directional" ) )
		{
			pDesc->InitDirectional( pLocalLight->GetValue<Vector>( "direction" ), vecColor ); 
			++m_nLightCount;
			continue;
		}

		if ( !Q_stricmp( pType, "point" ) )
		{
			const Vector& vecAtten = pLocalLight->GetValue<Vector>( "attenuation" );
			pDesc->InitPoint( pLocalLight->GetValue<Vector>( "origin" ), vecColor );
			pDesc->m_Attenuation0 = vecAtten.x;
			pDesc->m_Attenuation1 = vecAtten.y;
			pDesc->m_Attenuation2 = vecAtten.z;
			pDesc->m_Range = pLocalLight->GetValue<float>( "maxDistance" );
			pDesc->RecalculateDerivedValues();
			++m_nLightCount;
			continue;
		}

		if ( !Q_stricmp( pType, "spot" ) )
		{
			const Vector& vecAtten = pLocalLight->GetValue<Vector>( "attenuation" );
			pDesc->InitSpot( pLocalLight->GetValue<Vector>( "origin" ), vecColor, vec3_origin,
				RAD2DEG ( pLocalLight->GetValue<float>( "theta" ) ), 
				RAD2DEG ( pLocalLight->GetValue<float>( "phi" ) ) );

			pDesc->m_Direction = pLocalLight->GetValue<Vector>( "direction" );
			pDesc->m_Attenuation0 = vecAtten.x;
			pDesc->m_Attenuation1 = vecAtten.y;
			pDesc->m_Attenuation2 = vecAtten.z;
			pDesc->m_Range = pLocalLight->GetValue<float>( "maxDistance" );
			pDesc->m_Falloff = pLocalLight->GetValue<float>( "exponent" );
			pDesc->RecalculateDerivedValues();
			++m_nLightCount;
			continue;
		}
	}

	if ( nLightCount > 0 )
	{
		m_pLightManip = new CPotteryWheelManip( &m_Lights[0].m_LightToWorld );
	}
}

bool CPotteryWheelPanel::HasLightProbe() const
{
	return m_bHasLightProbe;
}

ITexture *CPotteryWheelPanel::GetLightProbeCubemap( bool bHDR )
{
	if ( !m_bHasLightProbe )
		return NULL;

	return bHDR ? m_LightProbeHDRCubemap : m_LightProbeCubemap;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CPotteryWheelPanel::GetCameraFOV( void )
{
	return m_Camera.m_flFOV;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetCameraFOV( float flFOV )
{
	m_Camera.m_flFOV = flFOV;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetCameraOffset( const Vector &vecOffset )
{
	m_vecCameraOffset = vecOffset;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::GetCameraOffset( Vector &vecOffset )
{
	vecOffset = m_vecCameraOffset;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetCameraPositionAndAngles( const Vector &vecPos, const QAngle &angDir, bool syncManipulators )
{
	SetIdentityMatrix( m_CameraPivot );
	AngleMatrix( angDir, vecPos, m_CameraPivot );

	UpdateCameraTransform();
	if ( syncManipulators )
	{
		SyncManipulation();
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::GetCameraPositionAndAngles( Vector &vecPos, QAngle &angDir )
{
	MatrixAngles( m_CameraPivot, angDir, vecPos );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::ResetCameraPivot( void )
{
	SetIdentityMatrix( m_CameraPivot );
}

//-----------------------------------------------------------------------------
// Sets the camera to look at the the thing we're spinning around
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::LookAt( float flRadius )
{
	// Compute the distance to the camera for the object based on its
	// radius and fov.

	// since tan( fov/2 ) = f/d
	// cos( fov/2 ) = r / r' where r = sphere radius, r' = perp distance from sphere center to max extent of camera
	// d/f = r'/d' where d' is distance of camera to sphere
	// d' = r' / tan( fov/2 ) * r' = r / ( cos (fov/2) * tan( fov/2 ) ) = r / sin( fov/2 )
	float flFOVx = m_Camera.m_flFOV;

	// Compute fov/2 in radians
	flFOVx *= M_PI / 360.0f;

	// Compute an effective fov	based on the aspect ratio 
	// if the height is smaller than the width
	int w, h;
	GetSize( w, h );
	if ( h < w )
	{
		flFOVx = atan( h * tan( flFOVx ) / w );
	}

	m_vecCameraOffset.x = -( flRadius / sin( flFOVx ) );
	UpdateCameraTransform();
}


void CPotteryWheelPanel::LookAt( const Vector &vecCenter, float flRadius )
{
	MatrixSetColumn( vecCenter, 3, m_CameraPivot );
	LookAt( flRadius );
}

	
//-----------------------------------------------------------------------------
// Sets up render state in the material system for rendering
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetupRenderState( int nDisplayWidth, int nDisplayHeight )
{
	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );

	VMatrix view, projection;
	ComputeViewMatrix( &view, m_Camera );
	ComputeProjectionMatrix( &projection, m_Camera, nDisplayWidth, nDisplayHeight );

	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->LoadIdentity( );

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->LoadMatrix( view );

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->LoadMatrix( projection );

	LightDesc_t *pDesc = (LightDesc_t*)stackalloc( m_nLightCount * sizeof(LightDesc_t) );
	for ( int i = 0; i < m_nLightCount; ++i )
	{
		pDesc[i] = m_Lights[i].m_Desc;
		VectorTransform( m_Lights[i].m_Desc.m_Position, m_Lights[i].m_LightToWorld, pDesc->m_Position );
		VectorRotate( m_Lights[i].m_Desc.m_Direction, m_Lights[i].m_LightToWorld, pDesc->m_Direction );
		VectorNormalize( pDesc->m_Direction );
		pRenderContext->SetLight( i, pDesc[i] );
	}

	LightDesc_t desc;
	desc.m_Type = MATERIAL_LIGHT_DISABLE;
	int nMaxLightCount = g_pMaterialSystemHardwareConfig->MaxNumLights();
	for ( int i = m_nLightCount; i < nMaxLightCount; ++i )
	{
		pRenderContext->SetLight( i, desc );
	}

	pRenderContext->SetAmbientLightCube( m_vecAmbientCube );

	// FIXME: Remove this! This should automatically happen in DrawModel
	// in studiorender.
	if ( !g_pStudioRender )
		return;

	VMatrix worldToCamera;
	MatrixInverseTR( view, worldToCamera );
	Vector vecOrigin, vecRight, vecUp, vecForward;
	MatrixGetColumn( worldToCamera, 0, &vecRight );
	MatrixGetColumn( worldToCamera, 1, &vecUp );
	MatrixGetColumn( worldToCamera, 2, &vecForward );
	MatrixGetColumn( worldToCamera, 3, &vecOrigin );
	g_pStudioRender->SetViewState( vecOrigin, vecRight, vecUp, vecForward );

	g_pStudioRender->SetLocalLights( m_nLightCount, pDesc );
	g_pStudioRender->SetAmbientLightColors( m_vecAmbientCube );
}


//-----------------------------------------------------------------------------
// Compute the camera world position
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::UpdateCameraTransform( )
{
	// Set up the render state for the camera + light
	matrix3x4_t offset, worldToCamera;
	SetIdentityMatrix( offset );
	MatrixSetColumn( m_vecCameraOffset, 3, offset );
	ConcatTransforms( m_CameraPivot, offset, worldToCamera );
	MatrixAngles( worldToCamera, m_Camera.m_angles, m_Camera.m_origin );
}

void CPotteryWheelPanel::ComputeCameraTransform( matrix3x4_t *pWorldToCamera )
{
	AngleMatrix( m_Camera.m_angles, m_Camera.m_origin, *pWorldToCamera );
}


//-----------------------------------------------------------------------------
// Computes the position in the panel of a particular 3D point
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::ComputePanelPosition( const Vector &vecPosition, Vector2D *pPanelPos )
{ 
	int w, h;
	GetSize( w, h );

	matrix3x4_t worldToCamera;
	ComputeCameraTransform( &worldToCamera );
	MatrixAngles( worldToCamera, m_Camera.m_angles, m_Camera.m_origin );
	ComputeScreenSpacePosition( pPanelPos, vecPosition, m_Camera, w, h );
}


//-----------------------------------------------------------------------------
// Utility method to draw a grid at the 'ground'
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::DrawGrid()
{
	matrix3x4_t transform;
	CMatRenderContextPtr pRenderContext( MaterialSystem() );
	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->LoadIdentity( );

	pRenderContext->Bind( m_Wireframe );

	IMesh *pMesh = pRenderContext->GetDynamicMesh();

	int nGridDim = 10;
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_LINES, 2 * nGridDim + 2 );

	float bounds = 100.0f;
	float delta = 2 * bounds / nGridDim;
	for ( int i = 0; i < nGridDim + 1; ++i )
	{
		float xy = -bounds + delta * i;

		meshBuilder.Position3f( xy, -bounds, 0 );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();
		meshBuilder.Position3f( xy, bounds, 0 );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( -bounds, xy, 0 );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();
		meshBuilder.Position3f( bounds, xy, 0 );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();
	}

	meshBuilder.End();
	pMesh->Draw();
}


//-----------------------------------------------------------------------------
// paint it!
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::Paint()
{
	int iWidth, iHeight;
	GetSize( iWidth, iHeight );

	int screenw, screenh;
	vgui::surface()->GetScreenSize( screenw, screenh );

	int windowposx = 0, windowposy = 0;
	GetPos( windowposx, windowposy );

	int windowposright = windowposx + iWidth;
	int windowposbottom = windowposy + iHeight;

	if ( windowposright >= screenw )
	{
		iWidth -= ( windowposright - screenw );
	}
	if ( windowposbottom >= screenh )
	{
		iHeight -= ( windowposbottom - screenh );
	}

	int startx = 0, starty = 0;
	if( windowposx < 0 )
	{
		startx = -windowposx;
		iWidth -= startx;
	}
	if ( windowposy < 0 )
	{
		starty = -windowposy;
		iHeight -= starty;
	}

	int w, h;
	GetSize( w, h );
	vgui::MatSystemSurface()->Begin3DPaint( 0, 0, w, h, m_bRenderToTexture );

	if ( m_pCurrentManip )
	{
		m_pCurrentManip->SetViewportSize( iWidth, iHeight );
	}

	// Set up the render state for the camera + light
	SetupRenderState( iWidth, iHeight );

	CMatRenderContextPtr pRenderContext( vgui::MaterialSystem() );

	if ( m_bUseParentBG && GetParent() )
	{
		Color bgCol = GetParent()->GetBgColor();
		pRenderContext->ClearColor4ub( bgCol.r(), bgCol.g(), bgCol.b(), bgCol.a() );
	}
	else
	{
		pRenderContext->ClearColor4ub( m_ClearColor.r(), m_ClearColor.g(), m_ClearColor.b(), m_ClearColor.a() ); 
	}
	pRenderContext->ClearBuffers( m_bRenderToTexture, true );

	pRenderContext->CullMode( MATERIAL_CULLMODE_CCW );
	pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false );

	if ( HasLightProbe() )
	{
		IMaterial *pMaterial = ( vgui::MaterialSystemHardwareConfig()->GetHDRType() == HDR_TYPE_NONE ) ?
			m_LightProbeBackground : m_LightProbeHDRBackground;

		RenderBox( m_Camera.m_origin, vec3_angle, Vector( -100, -100, -100 ), Vector( 100, 100, 100 ),
			Color( 255, 255, 255, 255 ), pMaterial, true );
	}

	OnPaint3D();

	pRenderContext->CullMode( MATERIAL_CULLMODE_CW );

	vgui::MatSystemSurface()->End3DPaint( );
}


//-----------------------------------------------------------------------------
// called when we're ticked...
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::OnTick()
{
	BaseClass::OnTick();
	if ( m_pCurrentManip )
	{
		m_pCurrentManip->OnTick();
		UpdateCameraTransform();
	}
}


//-----------------------------------------------------------------------------
// input
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::OnKeyCodePressed(KeyCode code)
{
	if ( m_pCurrentManip )
	{
		switch( code )
		{
		case KEY_RSHIFT:
		case KEY_LSHIFT:
			// start translate mode
			AcceptManipulation( false );
			EnterManipulationMode( CAMERA_TRANSLATE, false );
			break;

		case KEY_RCONTROL:
		case KEY_LCONTROL:
			// start light mode
			AcceptManipulation( false );
			EnterManipulationMode( LIGHT_MODE, false );
			break;
		}
	}

	BaseClass::OnKeyCodePressed( code );
}



//-----------------------------------------------------------------------------
// Purpose: soaks up any remaining messages
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::OnKeyCodeReleased(KeyCode code)
{
	if ( m_pCurrentManip )
	{
		switch( code )
		{
		case KEY_RSHIFT:
		case KEY_LSHIFT:
		case KEY_RCONTROL:
		case KEY_LCONTROL:
			{
				// stop manipulation mode
				AcceptManipulation( false );
				switch ( m_nCaptureMouseCode )
				{
				default:
				case MOUSE_LEFT:
					EnterManipulationMode( CAMERA_ROTATE, false );
					break;

				case MOUSE_MIDDLE:
					EnterManipulationMode( CAMERA_TRANSLATE, false );
					break;

				case MOUSE_RIGHT:
					EnterManipulationMode( CAMERA_ZOOM, false );
					break;
				}
			}
			break;
		}
	}
	BaseClass::OnKeyCodeReleased( code );
}

void CPotteryWheelPanel::OnMousePressed( vgui::MouseCode code )
{
	if ( m_pCurrentManip )
		return;

	RequestFocus();

	if ( input()->IsKeyDown( KEY_RSHIFT ) || input()->IsKeyDown( KEY_LSHIFT ) )
	{
		EnterManipulationMode( CAMERA_TRANSLATE, true, code );
	}
	else if ( input()->IsKeyDown( KEY_RCONTROL ) || input()->IsKeyDown( KEY_LCONTROL ) )
	{
		EnterManipulationMode( LIGHT_MODE, true, code );
	}
	else
	{
		switch ( code )
		{
		case MOUSE_LEFT:
			EnterManipulationMode( CAMERA_ROTATE, true, code );
			break;

		case MOUSE_MIDDLE:
			EnterManipulationMode( CAMERA_TRANSLATE, true, code );
			break;

		case MOUSE_RIGHT:
			EnterManipulationMode( CAMERA_ZOOM, true, code );
			break;
		}
	}

	BaseClass::OnMousePressed( code );
}

void CPotteryWheelPanel::OnMouseReleased( vgui::MouseCode code )
{
	int x, y;
	input()->GetCursorPos( x, y );
	ScreenToLocal( x, y );

	AcceptManipulation();
	
	BaseClass::OnMouseReleased( code );
}

void CPotteryWheelPanel::OnCursorMoved( int x, int y )
{
	if ( m_pCurrentManip )
	{
		if ( WarpMouse( x, y ) )
		{
			m_pCurrentManip->OnCursorMoved( x, y );
		}
	}

	BaseClass::OnCursorMoved( x, y );
}

void CPotteryWheelPanel::OnMouseWheeled( int delta )
{
	if ( m_pCurrentManip )
	{
		m_pCurrentManip->OnMouseWheeled( delta );
	}

	BaseClass::OnMouseWheeled( delta );
}


void CPotteryWheelPanel::EnterManipulationMode( ManipulationMode_t manipMode, bool bMouseCapture, vgui::MouseCode mouseCode /* = -1 */ )
{
	switch ( manipMode )
	{
	case CAMERA_ROTATE:
		m_pCurrentManip = m_pCameraRotate;
		break;

	case CAMERA_TRANSLATE:
		m_pCurrentManip = m_pCameraTranslate;
		break;

	case CAMERA_ZOOM:
		m_pCurrentManip = m_pCameraZoom;
		break;

	case LIGHT_MODE:
		m_pCurrentManip = m_pLightManip;
		break;
	}

	if ( !m_pCurrentManip )
		return;

	m_pCurrentManip->OnBeginManipulation();

	m_xoffset = m_yoffset = 0;

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

	if ( bMouseCapture )
	{
		input()->GetCursorPos( m_nManipStartX, m_nManipStartY );
		EnableMouseCapture( true, mouseCode );

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

	m_pCurrentManip->OnMousePressed( mouseCode, x, y );
}

void CPotteryWheelPanel::AcceptManipulation( bool bReleaseMouseCapture )
{
	if ( m_pCurrentManip )
	{
		m_pCurrentManip->OnAcceptManipulation();

		if ( bReleaseMouseCapture )
		{
			EnableMouseCapture( false );
			input()->SetCursorPos( m_nManipStartX, m_nManipStartY );
		}

		m_pCurrentManip = NULL;
	}
}

void CPotteryWheelPanel::CancelManipulation()
{
	if ( m_pCurrentManip )
	{
		m_pCurrentManip->OnCancelManipulation();

		EnableMouseCapture( false );
		input()->SetCursorPos( m_nManipStartX, m_nManipStartY );

		m_pCurrentManip = NULL;
	}
	
}

void CPotteryWheelPanel::ApplyManipulation()
{
	if ( dynamic_cast< CRotationManipulator * >( m_pCameraRotate ) )
	{
		dynamic_cast< CRotationManipulator * >( m_pCameraRotate )->UpdateTransform();
	}
	UpdateCameraTransform();
}

void CPotteryWheelPanel::SyncManipulation()
{
	if ( dynamic_cast< CRotationManipulator * >( m_pCameraRotate ) )
	{
		dynamic_cast< CRotationManipulator * >( m_pCameraRotate )->UpdateFromMatrix();
	}
}

void CPotteryWheelPanel::OnMouseCaptureLost()
{
	SetCursor( vgui::dc_arrow );
	m_nCaptureMouseCode = vgui::MouseCode( -1 );
}

void CPotteryWheelPanel::EnableMouseCapture( bool enable, vgui::MouseCode mouseCode /* = -1 */ )
{
	if ( enable )
	{
		m_nCaptureMouseCode = mouseCode;
		SetCursor( vgui::dc_none );
		input()->SetMouseCaptureEx( GetVPanel(), m_nCaptureMouseCode );
	}
	else
	{
		m_nCaptureMouseCode = vgui::MouseCode( -1 );
		input()->SetMouseCapture( (VPANEL)0 );
		SetCursor( vgui::dc_arrow );
	}
}

bool CPotteryWheelPanel::WarpMouse( int &x, int &y )
{
	// Re-force capture if it was lost...
	if ( input()->GetMouseCapture() != GetVPanel() )
	{
		input()->GetCursorPos( m_nManipStartX, m_nManipStartY );
		EnableMouseCapture( true, m_nCaptureMouseCode );
	}

	int width, height;
	GetSize( width, height );

	int centerx = width / 2;
	int centery = height / 2;

	// skip this event
	if ( x == centerx && y == centery )
		return false; 

	int xpos = centerx;
	int ypos = centery;
	LocalToScreen( xpos, ypos );

#if defined( DX_TO_GL_ABSTRACTION )
	//
	// Really reset the cursor to the center for the PotteryWheel Control
	//
	// In TF2's edit loadout dialog there is a character model that you can rotate
	// around using the mouse.  This control resets the cursor to the center of the window
	// after each mouse move.  Except the input()->SetCursorPos results (after a lot of redirection) to
	// vgui/matsurface/Cursor.cpp function CursorSetPos but it has a (needed) test to not move the 
	// cursor if it's currently hidden. Rather than change all the levels between here and there
	// to support a flag, we are just jumping to the chase and directly calling the inputsystem
	// SetCursorPosition on OpenGL platforms
	//
	g_pInputSystem->SetCursorPosition( xpos, ypos );
#else
	input()->SetCursorPos( xpos, ypos );
#endif

	int dx = x - centerx;
	int dy = y - centery;

	x += m_xoffset;
	y += m_yoffset;

	m_xoffset += dx;
	m_yoffset += dy;

	return true;
}
