//====== Copyright © 1996-2005, 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 "shaderapi/ishaderapi.h"
#include "view_shared.h"
#include "ivrenderview.h"
#include "game/client/irendercaptureconfiguration.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 SetDefaultValues();

private:
	int m_lastx, m_lasty;
	float m_altitude, m_azimuth;
};


CRotationManipulator::CRotationManipulator( matrix3x4_t *pTransform ) : CTransformManipulator( pTransform )
{
	SetDefaultValues();
}

void CRotationManipulator::SetDefaultValues()
{
	m_lastx = m_lasty = 0;
	m_altitude = M_PI/6;
	m_azimuth = -3*M_PI/4;
	UpdateTransform();
}

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

void CRotationManipulator::OnCursorMoved( int x, int y )
{
	m_azimuth  += 0.002f * ( m_lastx - x );
	m_altitude -= 0.002f * ( m_lasty - y );
	m_altitude = MAX( -M_PI/2, MIN( 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 ), 0 );
	Vector vecPosition;
	MatrixGetColumn( *m_pTransform, 3, vecPosition );
	AngleMatrix( angles, vecPosition, *m_pTransform );
}

//-----------------------------------------------------------------------------
// 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_bParentMouseNotify( false )
{
	m_bHasLightProbe = false;
	m_bSetupRenderStateDelayed = false;
	m_bRender3DSupersampled = false;
	m_bInRender3dForRenderCapture = false;
	m_pvRenderingWithFlashlightConfiguration = NULL;

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

	SetIdentityMatrix( m_CameraPivot );

	CreateDefaultLights();

	m_nManipStartX = m_nManipStartY = 0;

	m_vecCameraOffset.Init( 100.0f, 0.0f, 0.0f );
		
	m_Camera.Init( Vector( 0, 0, 0 ), QAngle( 0, 0, 0 ), 3.0f, 16384.0f * 1.73205080757f, 30.0f, 1.0f );
	
	m_pCameraRotate = new CRotationManipulator( &m_CameraPivot );
	m_pCameraRotate->SetDefaultValues();
	m_pCameraTranslate = new CTranslationManipulator( &m_CameraPivot );
	m_pCameraZoom = new CZoomManipulator( &m_vecCameraOffset.x );

	SetKeyBoardInputEnabled( true );
	UpdateCameraTransform();

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

CPotteryWheelPanel::~CPotteryWheelPanel()
{
	delete m_pCameraRotate;
	delete m_pCameraZoom;
	delete m_pCameraTranslate;
	DestroyLights();
}

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

	SetIdentityMatrix( m_LightToWorld[0] );
	m_LightingState.m_pLocalLightDesc[0].m_Type = MATERIAL_LIGHT_DIRECTIONAL;
	m_LightingState.m_pLocalLightDesc[0].m_Color.Init( 1.0f, 1.0f, 1.0f );
	m_LightingState.m_pLocalLightDesc[0].m_Direction.Init( 0.0f, 0.0f, -1.0f );
	m_LightingState.m_pLocalLightDesc[0].m_Range=0.0;
	m_LightingState.m_pLocalLightDesc[0].m_Attenuation0 = 1.0;
	m_LightingState.m_pLocalLightDesc[0].m_Attenuation1 = 0;
	m_LightingState.m_pLocalLightDesc[0].m_Attenuation2 = 0;
	m_LightingState.m_pLocalLightDesc[0].RecalculateDerivedValues();
	m_LightingState.m_nLocalLightCount = 1;

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

void CPotteryWheelPanel::UpdateDirectionalLight( int idx, const Color& color, const Vector& direction )
{
	if ( idx >= 0 && idx < m_LightingState.m_nLocalLightCount )
	{
		// Update the existing light
		SetIdentityMatrix( m_LightToWorld[idx] );
		m_LightingState.m_pLocalLightDesc[idx].m_Type = MATERIAL_LIGHT_DIRECTIONAL;
		m_LightingState.m_pLocalLightDesc[idx].m_Color.Init( color.r() / 255.0f, color.g() / 255.0f, color.b() / 255.0f );
		m_LightingState.m_pLocalLightDesc[idx].m_Direction.Init( direction.x, direction.y, direction.z );
		m_LightingState.m_pLocalLightDesc[idx].m_Range=0.0;
		m_LightingState.m_pLocalLightDesc[idx].m_Attenuation0 = 1.0;
		m_LightingState.m_pLocalLightDesc[idx].m_Attenuation1 = 0;
		m_LightingState.m_pLocalLightDesc[idx].m_Attenuation2 = 0;
		m_LightingState.m_pLocalLightDesc[idx].RecalculateDerivedValues();
	}
	else
	{
		AddDirectionalLight( color, direction );
	}
}

void CPotteryWheelPanel::ClearDirectionalLights()
{
	m_LightingState.m_nLocalLightCount = 0;
}

void CPotteryWheelPanel::AddDirectionalLight( const Color& color, const Vector& direction )
{
	if ( m_LightingState.m_nLocalLightCount < MATERIAL_MAX_LIGHT_COUNT )
	{
		SetIdentityMatrix( m_LightToWorld[m_LightingState.m_nLocalLightCount] );
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Type = MATERIAL_LIGHT_DIRECTIONAL;
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Color.Init( color.r() / 255.0f, color.g() / 255.0f, color.b() / 255.0f );
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Direction.Init( direction.x, direction.y, direction.z );
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Range=0.0;
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Attenuation0 = 1.0;
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Attenuation1 = 0;
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].m_Attenuation2 = 0;
		m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount].RecalculateDerivedValues();
		m_LightingState.m_nLocalLightCount++;
	}
}

void CPotteryWheelPanel::SetLightAmbient( const Vector& ambient )
{
	for ( int i = 0; i < 6; ++i )
	{
		m_LightingState.m_vecAmbientCube[i].Init( ambient.x, ambient.y, ambient.z );
	}
}

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

	m_LightingState.m_nLocalLightCount = 0;
}


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

void CPotteryWheelPanel::SetGridColor( int r, int g, int b )
{
	m_GridColor.SetColor( r, g, b, 255 );
}

//-----------------------------------------------------------------------------
// 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_LightingState.m_vecAmbientCube[i].Init( ambientCube[i].x, ambientCube[i].y, ambientCube[i].z );
		}
	}

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

		LightDesc_t *pDesc = &m_LightingState.m_pLocalLightDesc[m_LightingState.m_nLocalLightCount];
		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_LightingState.m_nLocalLightCount;
			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_LightingState.m_nLocalLightCount;
			continue;
		}

		if ( !Q_stricmp( pType, "spot" ) )
		{
			const Vector& vecAtten = pLocalLight->GetValue<Vector>( "attenuation" );
			pDesc->InitSpot( pLocalLight->GetValue<Vector>( "origin" ), vecColor, vec3_origin,
				0.5f * RAD2DEG ( pLocalLight->GetValue<float>( "theta" ) ), 
				0.5f * 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_LightingState.m_nLocalLightCount;
			continue;
		}
	}

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

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_flFOVX;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetCameraFOV( float flFOV )
{
	m_Camera.m_flFOVX = 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 )
{
	m_Camera.m_origin = vecPos;
	m_Camera.m_angles = angDir;
}

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

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

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::ResetView( void )
{
	SetIdentityMatrix( m_CameraPivot );
	m_vecCameraOffset.Init( 100.0f, 0.0f, 0.0f );
	m_pCameraRotate->SetDefaultValues();
	UpdateCameraTransform();
}

//-----------------------------------------------------------------------------
// 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_flFOVX;

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

	// Transform light based on manipulator
	MaterialLightingState_t state = m_LightingState;
	for ( int i = 0; i < state.m_nLocalLightCount; ++i )
	{
		const LightDesc_t &srcDesc = m_LightingState.m_pLocalLightDesc[ i ];
		LightDesc_t &destDesc = state.m_pLocalLightDesc[ i ];
		VectorTransform( srcDesc.m_Position, m_LightToWorld[i], destDesc.m_Position );
		VectorRotate( srcDesc.m_Direction, m_LightToWorld[i], destDesc.m_Direction );
		VectorNormalize( destDesc.m_Direction );
	}

	pRenderContext->SetLightingState( state );

	// 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( state.m_nLocalLightCount, state.m_pLocalLightDesc );
	g_pStudioRender->SetAmbientLightColors( state.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 );
}


IMaterial * CPotteryWheelPanel::GetWireframeMaterial()
{
	if ( !m_Wireframe.IsValid() )
	{
		KeyValues *pMaterialKeys = new KeyValues( "Wireframe", "$model", "1" );
		pMaterialKeys->SetString( "$vertexcolor", "1" );
		m_Wireframe.Init( "potterywheelpanelwireframe", pMaterialKeys );
	}
	return m_Wireframe;
}


//-----------------------------------------------------------------------------
// 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( GetWireframeMaterial() );

	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( m_GridColor.r(), m_GridColor.g(), m_GridColor.b(), m_GridColor.a());
		meshBuilder.AdvanceVertex();
		meshBuilder.Position3f( xy, bounds, 0 );
		meshBuilder.Color4ub( m_GridColor.r(), m_GridColor.g(), m_GridColor.b(), m_GridColor.a());
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( -bounds, xy, 0 );
		meshBuilder.Color4ub( m_GridColor.r(), m_GridColor.g(), m_GridColor.b(), m_GridColor.a());
		meshBuilder.AdvanceVertex();
		meshBuilder.Position3f( bounds, xy, 0 );
		meshBuilder.Color4ub( m_GridColor.r(), m_GridColor.g(), m_GridColor.b(), m_GridColor.a());
		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;
	}

	if( iWidth < 0 || iHeight < 0 )
	{
		return;
	}

	if ( GetRenderingWithFlashlightConfiguration() )
	{
		RenderCapture();
	}

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

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

	// Set up the render state for the camera + light
	m_nRenderWidth = iWidth;
	m_nRenderHeight = iHeight;
	if ( !m_bSetupRenderStateDelayed )
		SetupRenderState( iWidth, iHeight );

	CMatRenderContextPtr pRenderContext( vgui::MaterialSystem() );
	
	PIXEVENT( pRenderContext, "CPotteryWheelPanel::Paint" );

	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( true, true );

	pRenderContext->CullMode( MATERIAL_CULLMODE_CCW );

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

void CPotteryWheelPanel::EnableRenderingWithFlashlight( void *pvConfiguration )
{
	m_pvRenderingWithFlashlightConfiguration = pvConfiguration;
}

void CPotteryWheelPanel::RenderCapture()
{
	if ( !GetRenderingWithFlashlightConfiguration() )
		return;

	CRenderCaptureConfigurationState *pCfg = reinterpret_cast< CRenderCaptureConfigurationState * >( GetRenderingWithFlashlightConfiguration() );

	m_nRenderWidth = pCfg->m_pFlashlightDepthTexture->GetActualWidth();
	m_nRenderHeight = pCfg->m_pFlashlightDepthTexture->GetActualHeight();
	if ( !m_bSetupRenderStateDelayed )
		SetupRenderState( m_nRenderWidth, m_nRenderHeight );

	CMatRenderContextPtr pRenderContext( vgui::MaterialSystem() );
	PIXEVENT( pRenderContext, "CPotteryWheelPanel::RenderCapture" );

	CViewSetup view2d;
	view2d.x				= 0;
	view2d.y				= 0;
	view2d.width			= m_nRenderWidth;
	view2d.height			= m_nRenderHeight;
	pCfg->m_pIVRenderView->Push3DView( pRenderContext, view2d, VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH, pCfg->m_pDummyColorBufferTexture, NULL, pCfg->m_pFlashlightDepthTexture );

	pRenderContext->ClearColor4ub( 0, 0, 0, 0 );
	pRenderContext->ClearBuffers( true, true, false );

	m_bInRender3dForRenderCapture = true;
	pRenderContext->CullMode(MATERIAL_CULLMODE_CW);
	g_pStudioRender->ForcedMaterialOverride( NULL, OVERRIDE_DEPTH_WRITE );

	// Don't draw the 3D scene w/ stencil
	ShaderStencilState_t state;
	pRenderContext->SetStencilState( state );

	OnPaint3D();

	g_pStudioRender->ForcedMaterialOverride( NULL );
	pRenderContext->CullMode(MATERIAL_CULLMODE_CCW);
	m_bInRender3dForRenderCapture = false;

	pCfg->m_pIVRenderView->PopView( pRenderContext, NULL );
	pRenderContext->Flush();
}


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

//-----------------------------------------------------------------------------
// Set whether the parent panel should be notified of mouse actions
//-----------------------------------------------------------------------------
void CPotteryWheelPanel::SetParentMouseNotify( bool bParentMouseNotify )
{
	m_bParentMouseNotify = bParentMouseNotify;
}

//-----------------------------------------------------------------------------
// 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::OnMouseDoublePressed( vgui::MouseCode code )
{
	BaseClass::OnMouseDoublePressed( code );

	if( m_bParentMouseNotify && GetParent() )
	{
		GetParent()->OnMouseDoublePressed(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 );

	if( m_bParentMouseNotify && GetParent() )
	{
		GetParent()->OnMousePressed(code);
	}
}

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

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

	if( m_bParentMouseNotify && GetParent() )
	{
		GetParent()->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 );

	if( m_bParentMouseNotify && GetParent() )
	{
		GetParent()->OnCursorMoved(x,y);
	}
}

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

	BaseClass::OnMouseWheeled( delta );

	if( m_bParentMouseNotify && GetParent() )
	{
		GetParent()->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::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( NULL );
		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 );
	input()->SetCursorPos( xpos, ypos );

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

	x += m_xoffset;
	y += m_yoffset;

	m_xoffset += dx;
	m_yoffset += dy;

	return true;
}