//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Contains 2D clipping routines
//
// $Revision: $
// $NoKeywords: $
//=============================================================================//

#include <vgui/ISurface.h>
#include "Clip2D.h"
#include "tier0/dbg.h"
#include "utlvector.h"
#if defined( _X360 )
#include "materialsystem/imaterialsystem.h"
#endif

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

//-----------------------------------------------------------------------------
// Stretch texture to fit window ( before scissoring )
//-----------------------------------------------------------------------------
static bool	g_bStretchTexture = false;


//-----------------------------------------------------------------------------
// Max # of vertices for clipping
//-----------------------------------------------------------------------------
enum
{
	VGUI_VERTEX_TEMP_COUNT = 48,
};

//-----------------------------------------------------------------------------
// For simulated scissor tests...
//-----------------------------------------------------------------------------
struct ScissorRect_t
{
	int left;
	int top;
	int right;
	int bottom;
};

static ScissorRect_t g_ScissorRect;
static bool	g_bScissor = false;
static bool g_bFullScreenScissor = false;

//-----------------------------------------------------------------------------
// Enable/disable scissoring...
//-----------------------------------------------------------------------------
void EnableScissor( bool enable )
{
	g_bScissor = enable;
}


void SetScissorRect( int left, int top, int right, int bottom )
{
	// Check for a valid rectangle...
	Assert( left <= right );
	Assert( top <= bottom );

	if ( g_ScissorRect.left == left && g_ScissorRect.right == right &&
		 g_ScissorRect.top == top && g_ScissorRect.bottom == bottom )
		 return;

	g_ScissorRect.left   = left;
	g_ScissorRect.top    = top;
	g_ScissorRect.right  = right;
	g_ScissorRect.bottom = bottom;

#if defined( _X360 )
	// no reason to waste cpu on full screen scissor, gpu does it
	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	int vx, vy, vw, vh;
	pRenderContext->GetViewport( vx, vy, vw, vh );
	g_bFullScreenScissor = (left <= vx && top <= vy && right >= vw && bottom >= vh );
#endif
}

void GetScissorRect( int &left, int &top, int &right, int &bottom, bool &enabled )
{
	left = g_ScissorRect.left;
	top = g_ScissorRect.top;
	right = g_ScissorRect.right;
	bottom = g_ScissorRect.bottom;
	enabled = g_bScissor;
}


//-----------------------------------------------------------------------------
// Used to clip the shadow decals
//-----------------------------------------------------------------------------
struct PolygonClipState_t
{
	int m_CurrVert;
	int	m_TempCount;
	int	m_ClipCount;
	vgui::Vertex_t	m_pTempVertices[VGUI_VERTEX_TEMP_COUNT];
	vgui::Vertex_t*	m_ppClipVertices[2][VGUI_VERTEX_TEMP_COUNT];
};


//-----------------------------------------------------------------------------
// Clipping methods for 2D
//-----------------------------------------------------------------------------
class CClipTop
{
public:
	static inline bool Inside( vgui::Vertex_t const& vert )					
	{ 
		return vert.m_Position.y >= g_ScissorRect.top;
	}
	static inline float Clip( const Vector2D& one, const Vector2D& two )	
	{ 
		return (g_ScissorRect.top - one.y) / (two.y - one.y);
	}
};

class CClipLeft
{
public:
	static inline bool Inside( vgui::Vertex_t const& vert )					
	{ 
		return vert.m_Position.x >= g_ScissorRect.left;
	}
	static inline float Clip( const Vector2D& one, const Vector2D& two )	
	{ 
		return (one.x - g_ScissorRect.left) / (one.x - two.x);
	}
};

class CClipRight
{
public:
	static inline bool Inside( vgui::Vertex_t const& vert )					
	{
		return vert.m_Position.x < g_ScissorRect.right;
	}
	static inline float Clip( const Vector2D& one, const Vector2D& two )	
	{
		return (g_ScissorRect.right - one.x) / (two.x - one.x);
	}
};

class CClipBottom
{
public:
	static inline bool Inside( vgui::Vertex_t const& vert )					
	{
		return vert.m_Position.y < g_ScissorRect.bottom;
	}
	static inline float Clip( const Vector2D& one, const Vector2D& two )	
	{
		return (one.y - g_ScissorRect.bottom) / (one.y - two.y);
	}
};

template <class Clipper>
static inline void Intersect( const vgui::Vertex_t& start, const vgui::Vertex_t& end, vgui::Vertex_t* pOut, Clipper& clipper )
{
	// Clip to the scissor rectangle
	float t = Clipper::Clip( start.m_Position, end.m_Position );
	Vector2DLerp( start.m_Position, end.m_Position, t, pOut->m_Position );
	Vector2DLerp( start.m_TexCoord, end.m_TexCoord, t, pOut->m_TexCoord );
}


//-----------------------------------------------------------------------------
// Clips a line segment to a single plane
//-----------------------------------------------------------------------------
template< class Clipper >
bool ClipLineToPlane( Clipper &clipper, const vgui::Vertex_t *pInVerts, vgui::Vertex_t* pOutVerts )
{
	bool startInside = Clipper::Inside( pInVerts[0] );
	bool endInside = Clipper::Inside( pInVerts[1] );

	// Cull
	if (!startInside && !endInside)
		return false;

	if (startInside && endInside)
	{
		pOutVerts[0] = pInVerts[0];
		pOutVerts[1] = pInVerts[1];
	}
	else
	{
		int inIndex = startInside ? 0 : 1;
		pOutVerts[inIndex] = pInVerts[inIndex];
		Intersect( pInVerts[0], pInVerts[1], &pOutVerts[1 - inIndex], clipper ); 
	}

	return true;
}


//-----------------------------------------------------------------------------
// Clips a line segment to the current scissor rectangle
//-----------------------------------------------------------------------------
bool ClipLine( const vgui::Vertex_t *pInVerts, vgui::Vertex_t* pOutVerts )
{
	if ( g_bScissor && !g_bFullScreenScissor )
	{
		// Clippers...
		CClipTop top;
		CClipBottom bottom;
		CClipLeft left;
		CClipRight right;

		// Sutherland-hodgman clip, not particularly efficient but that's ok for now
		vgui::Vertex_t tempVerts[2];
		if (!ClipLineToPlane( top, pInVerts, tempVerts ))
			return false;
		if (!ClipLineToPlane( bottom, tempVerts, pOutVerts ))
			return false;
		if (!ClipLineToPlane( left, pOutVerts, tempVerts ))
			return false;
		if (!ClipLineToPlane( right, tempVerts, pOutVerts ))
			return false;

		return true;
	}
	else
	{
		pOutVerts[0] = pInVerts[0];
		pOutVerts[1] = pInVerts[1];
		return true;
	}
}


//-----------------------------------------------------------------------------
// Methods associated with clipping 2D polygons
//-----------------------------------------------------------------------------
struct ScreenClipState_t
{
	int m_iCurrVert;
	int	m_iTempCount;
	int	m_iClipCount;
	CUtlVector<vgui::Vertex_t>	m_pTempVertices;
	CUtlVector<vgui::Vertex_t*>	m_ppClipVertices[2];
};


template <class Clipper>
static void ScreenClip( ScreenClipState_t& clip, Clipper& clipper )
{
	if (clip.m_iClipCount < 3)
		return;

	// Ye Olde Sutherland-Hodgman clipping algorithm
	int numOutVerts = 0;
	vgui::Vertex_t** pSrcVert = clip.m_ppClipVertices[clip.m_iCurrVert].Base();
	vgui::Vertex_t** pDestVert = clip.m_ppClipVertices[!clip.m_iCurrVert].Base();

	int numVerts = clip.m_iClipCount;
	vgui::Vertex_t* pStart = pSrcVert[numVerts-1];
	bool startInside = Clipper::Inside( *pStart );
	for (int i = 0; i < numVerts; ++i)
	{
		vgui::Vertex_t* pEnd = pSrcVert[i];
		bool endInside = Clipper::Inside( *pEnd );
		if (endInside)
		{
			if (!startInside)
			{
				// Started outside, ended inside, need to clip the edge
				Assert( clip.m_iTempCount <= clip.m_pTempVertices.Count() ); 

				// Allocate a new clipped vertex 
				pDestVert[numOutVerts] = &clip.m_pTempVertices[clip.m_iTempCount++];

				// Clip the edge to the clip plane
				Intersect( *pStart, *pEnd, pDestVert[numOutVerts], clipper ); 
				++numOutVerts;
			}
			pDestVert[numOutVerts++] = pEnd;
		}
		else
		{
			if (startInside)
			{
				// Started inside, ended outside, need to clip the edge
				Assert( clip.m_iTempCount <= clip.m_pTempVertices.Count() ); 

				// Allocate a new clipped vertex 
				pDestVert[numOutVerts] = &clip.m_pTempVertices[clip.m_iTempCount++];

				// Clip the edge to the clip plane
				Intersect( *pStart, *pEnd, pDestVert[numOutVerts], clipper ); 
				++numOutVerts;
			}
		}
		pStart = pEnd;
		startInside = endInside;
	}

	// Switch source lists
	clip.m_iCurrVert = 1 - clip.m_iCurrVert;
	clip.m_iClipCount = numOutVerts;
}


//-----------------------------------------------------------------------------
// Clips a polygon to the screen area
//-----------------------------------------------------------------------------
int ClipPolygon( int iCount, vgui::Vertex_t *pVerts, int iTranslateX, int iTranslateY, vgui::Vertex_t ***pppOutVertex ) 
{
	static ScreenClipState_t clip;

	// Allocate enough room in the clip state...
	// Having no reallocations during clipping 
	clip.m_pTempVertices.EnsureCount( iCount * 4 );
	clip.m_ppClipVertices[0].EnsureCount( iCount * 4 );
	clip.m_ppClipVertices[1].EnsureCount( iCount * 4 );

	// Copy the initial verts in...
	for (int i = 0; i < iCount; ++i)
	{
		// NOTE: This only works because we EnsuredCount above
		clip.m_pTempVertices[i] = pVerts[i];
		clip.m_pTempVertices[i].m_Position.x += iTranslateX;
		clip.m_pTempVertices[i].m_Position.y += iTranslateY;
		clip.m_ppClipVertices[0][i] = &clip.m_pTempVertices[i];
	}

	if ( !g_bScissor || g_bFullScreenScissor )
	{
		Assert(pppOutVertex);
		*pppOutVertex = clip.m_ppClipVertices[0].Base();
		return iCount;
	}

	clip.m_iClipCount = iCount;
	clip.m_iTempCount = iCount;
	clip.m_iCurrVert = 0;

	// Clippers...
	CClipTop top;
	CClipBottom bottom;
	CClipLeft left;
	CClipRight right;

	// Sutherland-hodgman clip
	ScreenClip( clip, top );
	ScreenClip( clip, bottom );
	ScreenClip( clip, left );
	ScreenClip( clip, right );

	if (clip.m_iClipCount < 3)
		return 0;

	// Return a pointer to the array of clipped vertices...
	Assert(pppOutVertex);
	*pppOutVertex = clip.m_ppClipVertices[clip.m_iCurrVert].Base();
	return clip.m_iClipCount;
}


//-----------------------------------------------------------------------------
// Purpose: Used for clipping, produces an interpolated texture coordinate
//-----------------------------------------------------------------------------
inline float InterpTCoord(float val, float mins, float maxs, float tMin, float tMax)
{
	float flPercent;
	if (mins != maxs)
		flPercent = (float)(val - mins) / (maxs - mins);
	else
		flPercent = 0.5f;
	return tMin + (tMax - tMin) * flPercent;
}


//-----------------------------------------------------------------------------
// Purpose: Does a scissor clip of the input rectangle.  
// Returns false if it is completely clipped off.
//-----------------------------------------------------------------------------
bool ClipRect( const vgui::Vertex_t &inUL, const vgui::Vertex_t &inLR, 
			   vgui::Vertex_t *pOutUL, vgui::Vertex_t *pOutLR )
{
	// Check for a valid rectangle...
//	Assert( inUL.m_Position.x <= inLR.m_Position.x );
//	Assert( inUL.m_Position.y <= inLR.m_Position.y );
 
	if ( IsX360() && ( !g_bScissor || g_bFullScreenScissor || 
		( inUL.m_Position.x >= g_ScissorRect.left && inLR.m_Position.x <= g_ScissorRect.right && inUL.m_Position.y >= g_ScissorRect.top && inLR.m_Position.y <= g_ScissorRect.bottom ) ) )
	{
		// clipping is not needed
		// either full screen, and hw will do it or rect is inscribed, and operation is meaningless
		*pOutUL = inUL;
		*pOutLR = inLR;
		return true;
	}

	if ( g_bScissor )
	{
		// Pick whichever left side is larger
		if (g_ScissorRect.left > inUL.m_Position.x)
			pOutUL->m_Position.x = g_ScissorRect.left;
		else
			pOutUL->m_Position.x = inUL.m_Position.x;

		// Pick whichever right side is smaller
		if (g_ScissorRect.right <= inLR.m_Position.x)
			pOutLR->m_Position.x = g_ScissorRect.right;
		else
			pOutLR->m_Position.x = inLR.m_Position.x;

		// Pick whichever top side is larger
		if (g_ScissorRect.top > inUL.m_Position.y)
			pOutUL->m_Position.y = g_ScissorRect.top;
		else
			pOutUL->m_Position.y = inUL.m_Position.y;

		// Pick whichever bottom side is smaller
		if (g_ScissorRect.bottom <= inLR.m_Position.y)
			pOutLR->m_Position.y = g_ScissorRect.bottom;
		else
			pOutLR->m_Position.y = inLR.m_Position.y;

		// Check for non-intersecting
		if ( (pOutUL->m_Position.x > pOutLR->m_Position.x) ||
			 (pOutUL->m_Position.y > pOutLR->m_Position.y) )
		{
			return false;
		}

		if ( !g_bStretchTexture )
		{
			pOutUL->m_TexCoord.x = InterpTCoord(pOutUL->m_Position.x, 
				inUL.m_Position.x, inLR.m_Position.x, inUL.m_TexCoord.x, inLR.m_TexCoord.x);
			pOutLR->m_TexCoord.x = InterpTCoord(pOutLR->m_Position.x,  
				inUL.m_Position.x, inLR.m_Position.x, inUL.m_TexCoord.x, inLR.m_TexCoord.x);

			pOutUL->m_TexCoord.y = InterpTCoord(pOutUL->m_Position.y, 
				inUL.m_Position.y, inLR.m_Position.y, inUL.m_TexCoord.y, inLR.m_TexCoord.y);
			pOutLR->m_TexCoord.y = InterpTCoord(pOutLR->m_Position.y,  
				inUL.m_Position.y, inLR.m_Position.y, inUL.m_TexCoord.y, inLR.m_TexCoord.y);
		}
		else
		{
			// FIXME, this isn't right
			pOutUL->m_TexCoord = inUL.m_TexCoord;
			pOutLR->m_TexCoord = inLR.m_TexCoord;
		}
	}
	else
	{
		*pOutUL = inUL;
		*pOutLR = inLR;
	}

	return true;
}