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

#include "cbase.h"
#include "Portal_DynamicMeshRenderingUtils.h"
#include "iviewrender.h"

extern ConVar mat_wireframe;


int ClipPolyToPlane_LerpTexCoords( PortalMeshPoint_t *inVerts, int vertCount, PortalMeshPoint_t *outVerts, const Vector& normal, float dist, float fOnPlaneEpsilon )
{
	vec_t	*dists = (vec_t *)stackalloc( sizeof(vec_t) * vertCount * 4 ); //4x vertcount should cover all cases
	int		*sides = (int *)stackalloc( sizeof(int) * vertCount * 4 );
	int		counts[3];
	vec_t	dot;
	int		i, j;
	Vector	mid = vec3_origin;
	int		outCount;

	counts[0] = counts[1] = counts[2] = 0;

	// determine sides for each point
	for ( i = 0; i < vertCount; i++ )
	{
		dot = DotProduct( inVerts[i].vWorldSpacePosition, normal) - dist;
		dists[i] = dot;
		if ( dot > fOnPlaneEpsilon )
			sides[i] = SIDE_FRONT;
		else if ( dot < -fOnPlaneEpsilon )
			sides[i] = SIDE_BACK;
		else
			sides[i] = SIDE_ON;
		counts[sides[i]]++;
	}
	sides[i] = sides[0];
	dists[i] = dists[0];

	if (!counts[0])
		return 0;

	if (!counts[1])
	{
		// Copy to output verts
		//for ( i = 0; i < vertCount; i++ )
		memcpy( outVerts, inVerts, sizeof( PortalMeshPoint_t ) * vertCount );
		return vertCount;
	}

	outCount = 0;
	for ( i = 0; i < vertCount; i++ )
	{
		if (sides[i] == SIDE_ON)
		{
			memcpy( &outVerts[outCount], &inVerts[i], sizeof( PortalMeshPoint_t ) );
			++outCount;
			continue;
		}
		if (sides[i] == SIDE_FRONT)
		{
			memcpy( &outVerts[outCount], &inVerts[i], sizeof( PortalMeshPoint_t ) );
			++outCount;
		}
		if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
			continue;

		Vector& p1 = inVerts[i].vWorldSpacePosition;
		

		// generate a split point
		int i2 = (i+1)%vertCount;
		Vector& p2 = inVerts[i2].vWorldSpacePosition;
		
		dot = dists[i] / (dists[i]-dists[i+1]);
		for (j=0 ; j<3 ; j++)
		{	
			mid[j] = p1[j] + dot*(p2[j]-p1[j]);
		}

		VectorCopy (mid, outVerts[outCount].vWorldSpacePosition);
		
		outVerts[outCount].texCoord.x = inVerts[i].texCoord.x + dot*(inVerts[i2].texCoord.x - inVerts[i].texCoord.x);
		outVerts[outCount].texCoord.y = inVerts[i].texCoord.y + dot*(inVerts[i2].texCoord.y - inVerts[i].texCoord.y);
		
		++outCount;
	}

	return outCount;
}

void RenderPortalMeshConvexPolygon( PortalMeshPoint_t *pVerts, int iVertCount, const IMaterial *pMaterial, void *pBind )
{
	CMatRenderContextPtr pRenderContext( materials );
	pRenderContext->Bind( (IMaterial *)pMaterial, pBind );

	//PortalMeshPoint_t *pMidVerts = (PortalMeshPoint_t *)stackalloc( sizeof( PortalMeshPoint_t ) * iVertCount );

	CMeshBuilder meshBuilder;
	IMesh* pMesh = pRenderContext->GetDynamicMesh( true );
	meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, iVertCount - 2 );

	//any convex polygon can be rendered with a triangle strip by starting at a vertex and alternating vertices from each side
	int iForwardCounter = 0;
	int iReverseCounter = iVertCount - 1; //guaranteed to be >= 2 to start

	do
	{
		PortalMeshPoint_t *pVertex = &pVerts[iForwardCounter];
		meshBuilder.Position3fv( &pVertex->vWorldSpacePosition.x );
		meshBuilder.TexCoord2fv( 0, &pVertex->texCoord.x );
		meshBuilder.AdvanceVertex();
		++iForwardCounter;

		if( iForwardCounter > iReverseCounter )
			break;

		pVertex = &pVerts[iReverseCounter];
		meshBuilder.Position3fv( &pVertex->vWorldSpacePosition.x );
		meshBuilder.TexCoord2fv( 0, &pVertex->texCoord.x );
		meshBuilder.AdvanceVertex();
		--iReverseCounter;
	} while( iForwardCounter <= iReverseCounter );

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


void Clip_And_Render_Convex_Polygon( PortalMeshPoint_t *pVerts, int iVertCount, const IMaterial *pMaterial, void *pBind )
{
	PortalMeshPoint_t *pInVerts = (PortalMeshPoint_t *)stackalloc( iVertCount * 4 * sizeof( PortalMeshPoint_t ) ); //really only should need 2x points, but I'm paranoid
	PortalMeshPoint_t *pOutVerts = (PortalMeshPoint_t *)stackalloc( iVertCount * 4 * sizeof( PortalMeshPoint_t ) );
	PortalMeshPoint_t *pTempVerts;


	//clip by the viewing frustum
	{
		VPlane *pFrustum = view->GetFrustum();
		
		//clip by first plane and put output into pInVerts
		iVertCount = ClipPolyToPlane_LerpTexCoords( pVerts, iVertCount, pInVerts, pFrustum[0].m_Normal, pFrustum[0].m_Dist, 0.01f );

		//clip by other planes and flipflop in and out pointers
		for( int i = 1; i != FRUSTUM_NUMPLANES; ++i )
		{
			if( iVertCount < 3 )
				return; //nothing to draw

			iVertCount = ClipPolyToPlane_LerpTexCoords( pInVerts, iVertCount, pOutVerts, pFrustum[i].m_Normal, pFrustum[i].m_Dist, 0.01f );
			pTempVerts = pInVerts; pInVerts = pOutVerts; pOutVerts = pTempVerts; //swap vertex pointers
		}

		if( iVertCount < 3 )
			return; //nothing to draw
	}

	CMatRenderContextPtr pRenderContext( materials );
	
	RenderPortalMeshConvexPolygon( pOutVerts, iVertCount, pMaterial, pBind );
	if( mat_wireframe.GetBool() )
		RenderPortalMeshConvexPolygon( pOutVerts, iVertCount, materials->FindMaterial( "shadertest/wireframe", TEXTURE_GROUP_CLIENT_EFFECTS, false ), pBind );

	stackfree( pOutVerts );
	stackfree( pInVerts );
}