//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// $Header: $
// $NoKeywords: $
//
// This file contains code to perform dynamic occlusion in the engine
//=============================================================================

#include "IOcclusionSystem.h"
#include "vector.h"
#include "UtlSortVector.h"
#include "utllinkedlist.h"
#include "utlvector.h"
#include "collisionutils.h"
#include "gl_model_private.h"
#include "gl_matsysiface.h"
#include "glquake.h"
#include "materialsystem/imesh.h"
#include "tier0/vprof.h"


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

// Uncomment this if you want to get a whole bunch of paranoid error checking
//#define DEBUG_OCCLUSION_SYSTEM 1


//-----------------------------------------------------------------------------
// Used to visualizes what the occlusion system is doing.
//-----------------------------------------------------------------------------
static ConVar r_visocclusion( "r_visocclusion", "0", 0, "Activate/deactivate wireframe rendering of what the occlusion system is doing." );
static ConVar r_occlusion( "r_occlusion", "1", 0, "Activate/deactivate the occlusion system." );
static ConVar r_occludermincount( "r_occludermincount", "6", 0, "At least this many occluders will be used, no matter how big they are." );
static ConVar r_occlusionspew( "r_occlusionspew", "0", 0, "Activate/deactivates spew about what the occlusion system is doing." );
static ConVar r_occluderminarea( "r_occluderminarea", "5", 0, "Prevents this occluder from being used if it takes up less than X% of the screen." );
static ConVar r_occludeemaxarea( "r_occludeemaxarea", "5", 0, "Prevents occlusion testing for entities that take up more than X% of the screen." );


#ifdef DEBUG_OCCLUSION_SYSTEM

static ConVar r_occtest( "r_occtest", "0" );

// Set this in the debugger to activate debugging spew
bool s_bSpew = false;

#endif // DEBUG_OCCLUSION_SYSTEM


//-----------------------------------------------------------------------------
//
// Edge list that's fast to iterate over, fast to insert into
//
//-----------------------------------------------------------------------------
class CWingedEdgeList
{
public:
	struct WingedEdge_t
	{
		Vector m_vecPosition;		// of the upper point in y, measured in screen space
		Vector m_vecPositionEnd;	// of the lower point in y, measured in screen space
		float m_flDxDy;				// Change in x per unit in y.
		float m_flOODy;
		float m_flX;
		short m_nLeaveSurfID;		// Unique index of the surface this is a part of
		short m_nEnterSurfID;		// Unique index of the surface this is a part of
		WingedEdge_t *m_pPrevActiveEdge;
		WingedEdge_t *m_pNextActiveEdge;
	};

public:
	CWingedEdgeList();

	// Clears out the edge list
	void Clear();

	// Iteration
	int EdgeCount() const;
	WingedEdge_t &WingedEdge( int i );

	// Adds an edge
	int AddEdge( );
	int AddEdge( const Vector &vecStartVert, const Vector &vecEndVert, int nLeaveSurfID, int nEnterSurfID );

	// Adds a surface
	int AddSurface( const cplane_t &plane );

	// Does this edge list occlude another winged edge list?
	bool IsOccludingEdgeList( CWingedEdgeList &testList );

	// Renders the winged edge list
	void Visualize( unsigned char *pColor );

	// Checks consistency of the edge list...
	void CheckConsistency();

private:
	struct Surface_t
	{
		cplane_t m_Plane;			// measured in projection space
	};

private:
	// Active edges...
	WingedEdge_t *FirstActiveEdge( );
	WingedEdge_t *LastActiveEdge( );
	bool AtListEnd( const WingedEdge_t* pEdge ) const;
	bool AtListStart( const WingedEdge_t* pEdge ) const;
	void LinkActiveEdgeAfter( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge );
	void UnlinkActiveEdge( WingedEdge_t *pEdge );

	// Used to insert an edge into the active edge list
	bool IsEdgeXGreater( const WingedEdge_t *pEdge1, const WingedEdge_t *pEdge2 );

	// Clears the active edge list
	void ResetActiveEdgeList();

	// Spew active edge list
	void SpewActiveEdgeList( float y, bool bHex = false );

	// Inserts an edge into the active edge list, sorted by X
	void InsertActiveEdge( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge );

	// Returns true if this active edge list occludes another active edge list
	bool IsOccludingActiveEdgeList( CWingedEdgeList &testList, float y );

	// Advances the X values of the active edge list, with no reordering
	bool AdvanceActiveEdgeList( float flCurrY );

	// Advance the active edge list until a particular X value is reached.
	WingedEdge_t *AdvanceActiveEdgeListToX( WingedEdge_t *pEdge, float x );

	// Returns the z value of a surface given and x,y coordinate
	float ComputeZValue( const Surface_t *pSurface, float x, float y ) const;

	// Returns the next time in Y the edge list will undergo a change
	float NextDiscontinuity() const;

private:
	// Active Edge list...
	WingedEdge_t m_StartTerminal;
	WingedEdge_t m_EndTerminal;

	// Back surface...
	Surface_t m_BackSurface;

	// Next discontinuity..
	float m_flNextDiscontinuity;
	int m_nCurrentEdgeIndex;

	CUtlVector< WingedEdge_t > m_WingedEdges;
	CUtlVector< Surface_t > m_Surfaces;
};



//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CWingedEdgeList::CWingedEdgeList() : m_WingedEdges( 0, 64 )
{
	m_StartTerminal.m_vecPosition.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
	m_StartTerminal.m_vecPositionEnd.Init( -FLT_MAX, FLT_MAX, -FLT_MAX );
	m_StartTerminal.m_nLeaveSurfID = -1;
	m_StartTerminal.m_nEnterSurfID = -1;
	m_StartTerminal.m_pPrevActiveEdge = NULL;
	m_StartTerminal.m_pNextActiveEdge = NULL;
	m_StartTerminal.m_flDxDy = 0.0f;
	m_StartTerminal.m_flOODy = 0.0f;
	m_StartTerminal.m_flX = -FLT_MAX;

	m_EndTerminal.m_vecPosition.Init( FLT_MAX, -FLT_MAX, -FLT_MAX );
	m_EndTerminal.m_vecPositionEnd.Init( FLT_MAX, FLT_MAX, -FLT_MAX );
	m_EndTerminal.m_nLeaveSurfID = -1;
	m_EndTerminal.m_nEnterSurfID = -1;
	m_EndTerminal.m_pPrevActiveEdge = NULL;
	m_EndTerminal.m_pNextActiveEdge = NULL;
	m_EndTerminal.m_flDxDy = 0.0f;
	m_EndTerminal.m_flOODy = 0.0f;
	m_EndTerminal.m_flX = FLT_MAX;

	m_BackSurface.m_Plane.normal.Init( 0, 0, 1 );
	m_BackSurface.m_Plane.dist = FLT_MAX;
}


//-----------------------------------------------------------------------------
// Renders the winged edge list for debugging
//-----------------------------------------------------------------------------
void CWingedEdgeList::Clear()
{
	m_WingedEdges.RemoveAll();
	m_Surfaces.RemoveAll();
}


//-----------------------------------------------------------------------------
// Iterate over the winged edges
//-----------------------------------------------------------------------------
inline int CWingedEdgeList::EdgeCount() const 
{ 
	return m_WingedEdges.Count(); 
}

inline CWingedEdgeList::WingedEdge_t &CWingedEdgeList::WingedEdge( int i )
{
	return m_WingedEdges[i];
}


//-----------------------------------------------------------------------------
// Adds new edges
//-----------------------------------------------------------------------------
inline int CWingedEdgeList::AddEdge( )
{
	int i = m_WingedEdges.AddToTail();

	WingedEdge_t &newEdge = m_WingedEdges[i];
	newEdge.m_pPrevActiveEdge = NULL;
	newEdge.m_pNextActiveEdge = NULL;

	return i;
}

int CWingedEdgeList::AddEdge( const Vector &vecStartVert, const Vector &vecEndVert, int nLeaveSurfID, int nEnterSurfID )
{
	// This is true if we've clipped to the near clip plane
	Assert( (vecStartVert.z >= 0.0) && (vecEndVert.z >= 0.0) );

	// Don't bother adding edges with dy == 0
	float dy;
	dy = vecEndVert.y - vecStartVert.y;
	if (dy == 0.0f)
		return -1;

	int i = m_WingedEdges.AddToTail();
	WingedEdge_t &newEdge = m_WingedEdges[i];

	newEdge.m_flOODy = 1.0f / dy;
	newEdge.m_nLeaveSurfID = nLeaveSurfID;
	newEdge.m_nEnterSurfID = nEnterSurfID;
	newEdge.m_vecPosition = vecStartVert;
	newEdge.m_vecPositionEnd = vecEndVert;
	newEdge.m_pPrevActiveEdge = NULL;
	newEdge.m_pNextActiveEdge = NULL;
	newEdge.m_flDxDy = (vecEndVert.x - vecStartVert.x) * newEdge.m_flOODy;

	return i;
}


//-----------------------------------------------------------------------------
// Adds new surfaces
//-----------------------------------------------------------------------------
int CWingedEdgeList::AddSurface( const cplane_t &plane )
{
	int i = m_Surfaces.AddToTail();
	m_Surfaces[i].m_Plane = plane;
	return i;
}


//-----------------------------------------------------------------------------
// Active edges...
//-----------------------------------------------------------------------------
inline CWingedEdgeList::WingedEdge_t *CWingedEdgeList::FirstActiveEdge( )
{ 
	return m_StartTerminal.m_pNextActiveEdge; 
}

inline CWingedEdgeList::WingedEdge_t *CWingedEdgeList::LastActiveEdge( )
{ 
	return m_EndTerminal.m_pPrevActiveEdge; 
}

inline bool CWingedEdgeList::AtListEnd( const WingedEdge_t* pEdge ) const
{ 
	return pEdge == &m_EndTerminal; 
}

inline bool CWingedEdgeList::AtListStart( const WingedEdge_t* pEdge ) const
{ 
	return pEdge == &m_StartTerminal; 
}

inline void CWingedEdgeList::LinkActiveEdgeAfter( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge )
{
	pInsertEdge->m_pNextActiveEdge = pPrevEdge->m_pNextActiveEdge;
	pInsertEdge->m_pPrevActiveEdge = pPrevEdge;
	pInsertEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pInsertEdge;
	pPrevEdge->m_pNextActiveEdge = pInsertEdge;
}

inline void CWingedEdgeList::UnlinkActiveEdge( WingedEdge_t *pEdge )
{
	pEdge->m_pPrevActiveEdge->m_pNextActiveEdge = pEdge->m_pNextActiveEdge;
	pEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pEdge->m_pPrevActiveEdge;

#ifdef _DEBUG
	pEdge->m_pPrevActiveEdge = pEdge->m_pNextActiveEdge = NULL;
#endif
}


//-----------------------------------------------------------------------------
// Checks consistency of the edge list...
//-----------------------------------------------------------------------------
void CWingedEdgeList::CheckConsistency()
{
	float flLastY = -FLT_MAX;
	float flLastX = -FLT_MAX;
	float flLastDxDy = 0;

	int nEdgeCount = EdgeCount();
	for ( int i = 0; i < nEdgeCount; ++i )
	{
		WingedEdge_t *pEdge = &WingedEdge(i);
		Assert( pEdge->m_vecPosition.y >= flLastY );
		if ( pEdge->m_vecPosition.y == flLastY )
		{
			Assert( pEdge->m_vecPosition.x >= flLastX );
			if ( pEdge->m_vecPosition.x == flLastX )
			{
				Assert( pEdge->m_flDxDy >= flLastDxDy );
			}
		}

		flLastX = pEdge->m_vecPosition.x;
		flLastY = pEdge->m_vecPosition.y;
		flLastDxDy = pEdge->m_flDxDy;
	}

	ResetActiveEdgeList();
	float flCurrentY = NextDiscontinuity();
	AdvanceActiveEdgeList( flCurrentY );
	while ( flCurrentY != FLT_MAX )
	{
		// Make sure all edges have correct Xs + enter + leave surfaces..
		int nCurrentSurfID = -1;
		float flX = -FLT_MAX;
		WingedEdge_t *pCurEdge = FirstActiveEdge();
		while ( !AtListEnd( pCurEdge ) )
		{
			Assert( pCurEdge->m_nLeaveSurfID == nCurrentSurfID );
			Assert( pCurEdge->m_flX >= flX );
			Assert( pCurEdge->m_nLeaveSurfID != pCurEdge->m_nEnterSurfID );
			nCurrentSurfID = pCurEdge->m_nEnterSurfID;
			flX = pCurEdge->m_flX;
			pCurEdge = pCurEdge->m_pNextActiveEdge;
		}
//		Assert( nCurrentSurfID == -1 );

		flCurrentY = NextDiscontinuity();
		AdvanceActiveEdgeList( flCurrentY );
	}
}


//-----------------------------------------------------------------------------
// Returns the z value of a surface given and x,y coordinate
//-----------------------------------------------------------------------------
inline float CWingedEdgeList::ComputeZValue( const Surface_t *pSurface, float x, float y ) const
{
	const cplane_t &plane = pSurface->m_Plane;
	Assert( plane.normal.z == 1.0f );
	return plane.dist - plane.normal.x * x - plane.normal.y * y; 
}


//-----------------------------------------------------------------------------
// Used to insert an edge into the active edge list, sorted by X
// If Xs match, sort by Dx/Dy
//-----------------------------------------------------------------------------
inline bool CWingedEdgeList::IsEdgeXGreater( const WingedEdge_t *pEdge1, const WingedEdge_t *pEdge2 )
{
	float flDelta = pEdge1->m_flX - pEdge2->m_flX;
	if ( flDelta > 0 )
		return true;

	if ( flDelta < 0 )
		return false;

	// NOTE: Using > instead of >= means coincident edges won't continually swap places
	return pEdge1->m_flDxDy > pEdge2->m_flDxDy;
}


//-----------------------------------------------------------------------------
// Inserts an edge into the active edge list, sorted by X
//-----------------------------------------------------------------------------
inline void CWingedEdgeList::InsertActiveEdge( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge )
{
	while( !AtListStart(pPrevEdge) && IsEdgeXGreater( pPrevEdge, pInsertEdge ) )
	{
		pPrevEdge = pPrevEdge->m_pPrevActiveEdge;
	}
	LinkActiveEdgeAfter( pPrevEdge, pInsertEdge );
}


//-----------------------------------------------------------------------------
// Clears the active edge list
//-----------------------------------------------------------------------------
void CWingedEdgeList::ResetActiveEdgeList()
{
	// This shouldn't be called unless we're about to do active edge checking
	Assert( EdgeCount() );

	m_nCurrentEdgeIndex = 0;

	// Don't bother with edges below the screen edge
	m_flNextDiscontinuity = WingedEdge( 0 ).m_vecPosition.y;
	m_flNextDiscontinuity = max( m_flNextDiscontinuity, -1.0f );

	m_StartTerminal.m_pNextActiveEdge = &m_EndTerminal;
	m_EndTerminal.m_pPrevActiveEdge = &m_StartTerminal;
	Assert( m_StartTerminal.m_pPrevActiveEdge == NULL );
	Assert( m_EndTerminal.m_pNextActiveEdge == NULL );
}


//-----------------------------------------------------------------------------
// Spew active edge list
//-----------------------------------------------------------------------------
void CWingedEdgeList::SpewActiveEdgeList( float y, bool bHex )
{
	WingedEdge_t *pEdge = FirstActiveEdge();
	Msg( "%.3f : ", y );
	while ( !AtListEnd( pEdge ) )
	{
		if (!bHex)
		{
			Msg( "(%d %.3f [%d/%d]) ", (int)(pEdge - m_WingedEdges.Base()), pEdge->m_flX, pEdge->m_nLeaveSurfID, pEdge->m_nEnterSurfID );
		}
		else
		{
			Msg( "(%d %X [%d/%d]) ", (int)(pEdge - m_WingedEdges.Base()), *(int*)&pEdge->m_flX, pEdge->m_nLeaveSurfID, pEdge->m_nEnterSurfID );
		}
		pEdge = pEdge->m_pNextActiveEdge;
	}
	Msg( "\n" );
}


//-----------------------------------------------------------------------------
// Returns the next time in Y the edge list will undergo a change
//-----------------------------------------------------------------------------
inline float CWingedEdgeList::NextDiscontinuity() const
{
	return m_flNextDiscontinuity;
}


//-----------------------------------------------------------------------------
// Advances the X values of the active edge list, with no reordering
//-----------------------------------------------------------------------------
bool CWingedEdgeList::AdvanceActiveEdgeList( float flCurrY )
{
	// Reordering is unnecessary because the winged edges are guaranteed to be non-overlapping
	m_flNextDiscontinuity = FLT_MAX;

	// Advance all edges until the current Y; we don't need to re-order *any* edges.
	WingedEdge_t *pCurEdge;
	WingedEdge_t *pNextEdge;
	for ( pCurEdge = FirstActiveEdge(); !AtListEnd( pCurEdge ); pCurEdge = pNextEdge )
	{
		pNextEdge = pCurEdge->m_pNextActiveEdge;

		if ( pCurEdge->m_vecPositionEnd.y <= flCurrY )
		{
			UnlinkActiveEdge( pCurEdge );
			continue;
		}

		pCurEdge->m_flX = pCurEdge->m_vecPosition.x + (flCurrY - pCurEdge->m_vecPosition.y) * pCurEdge->m_flDxDy;
		if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity )
		{
			m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y;
		}
	}

	int nEdgeCount = EdgeCount();
	if ( m_nCurrentEdgeIndex == nEdgeCount )
		return (m_flNextDiscontinuity != FLT_MAX);

	pCurEdge = &WingedEdge( m_nCurrentEdgeIndex );

	// Add new edges, computing the x + z coordinates at the requested y value
	while ( pCurEdge->m_vecPosition.y <= flCurrY )
	{
		// This is necessary because of our initial skip up to y == -1.0f
		if ( pCurEdge->m_vecPositionEnd.y > flCurrY )
		{
			float flDy = flCurrY - pCurEdge->m_vecPosition.y; 
			pCurEdge->m_flX = pCurEdge->m_vecPosition.x + flDy * pCurEdge->m_flDxDy;
			if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity )
			{
				m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y;
			}

			// Now re-insert in the list, sorted by X
			InsertActiveEdge( LastActiveEdge(), pCurEdge );
		}

		if ( ++m_nCurrentEdgeIndex == nEdgeCount )
			return (m_flNextDiscontinuity != FLT_MAX);

		pCurEdge = &WingedEdge( m_nCurrentEdgeIndex );
	}

	// The next edge in y will also present a discontinuity
	if ( pCurEdge->m_vecPosition.y < m_flNextDiscontinuity )
	{
		m_flNextDiscontinuity = pCurEdge->m_vecPosition.y;
	}

	return (m_flNextDiscontinuity != FLT_MAX);
}


//-----------------------------------------------------------------------------
// Advance the active edge list until a particular X value is reached.
//-----------------------------------------------------------------------------
inline CWingedEdgeList::WingedEdge_t *CWingedEdgeList::AdvanceActiveEdgeListToX( WingedEdge_t *pEdge, float x )
{
	// <= is necessary because we always want to point *after* the edge
	while( pEdge->m_flX <= x )
	{
		pEdge = pEdge->m_pNextActiveEdge;
	}
	return pEdge;
}


//-----------------------------------------------------------------------------
// Returns true if this active edge list occludes another active edge list
//-----------------------------------------------------------------------------
bool CWingedEdgeList::IsOccludingActiveEdgeList( CWingedEdgeList &testList, float y )
{
	WingedEdge_t *pTestEdge = testList.FirstActiveEdge();
	pTestEdge = AdvanceActiveEdgeListToX( pTestEdge, -1.0f );
	float flNextTestX = pTestEdge->m_flX;

	// If the occludee is off screen, it's occluded
	if ( flNextTestX >= 1.0f )
		return true;

	WingedEdge_t *pOccluderEdge = FirstActiveEdge();
	pOccluderEdge = AdvanceActiveEdgeListToX( pOccluderEdge, flNextTestX );

	Surface_t *pTestSurf = (pTestEdge->m_nEnterSurfID >= 0) ? &testList.m_Surfaces[pTestEdge->m_nEnterSurfID] : &m_BackSurface;

	// Use the leave surface because we know the occluder has been advanced *beyond* the test surf X.
	Surface_t *pOccluderSurf = (pOccluderEdge->m_nLeaveSurfID >= 0) ? &m_Surfaces[pOccluderEdge->m_nLeaveSurfID] : &m_BackSurface;

	float flCurrentX = flNextTestX;
	float flNextOccluderX = pOccluderEdge->m_flX;
	flNextTestX = pTestEdge->m_flX;

	while ( true )
	{
		// Is the occludee in front of the occluder? No dice!
		float flTestOOz = ComputeZValue( pTestSurf, flCurrentX, y );
		float flOccluderOOz = ComputeZValue( pOccluderSurf, flCurrentX, y );
		if ( flTestOOz < flOccluderOOz )
			return false;

		// We're done if there's no more occludees
		if ( flNextTestX == FLT_MAX )
			return true;

		// We're done if there's no more occluders
		if ( flNextOccluderX == FLT_MAX )
			return false;

		if ( flNextTestX <= flNextOccluderX )
		{
			flCurrentX = flNextTestX;
			pTestEdge = pTestEdge->m_pNextActiveEdge;
			pTestSurf = (pTestEdge->m_nEnterSurfID >= 0) ? &testList.m_Surfaces[pTestEdge->m_nEnterSurfID] : &m_BackSurface;
			flNextTestX = pTestEdge->m_flX;
		}
		else
		{
			flCurrentX = flNextOccluderX;
			pOccluderEdge = pOccluderEdge->m_pNextActiveEdge;
			pOccluderSurf = (pOccluderEdge->m_nLeaveSurfID >= 0) ? &m_Surfaces[pOccluderEdge->m_nLeaveSurfID] : &m_BackSurface;
			flNextOccluderX = pOccluderEdge->m_flX;
		}
	}
}


//-----------------------------------------------------------------------------
// Does this edge list occlude another winged edge list?
//-----------------------------------------------------------------------------
bool CWingedEdgeList::IsOccludingEdgeList( CWingedEdgeList &testList )
{
#ifdef DEBUG_OCCLUSION_SYSTEM
	testList.CheckConsistency();
	CheckConsistency();
#endif

	// Did all the edges get culled for some reason? Then it's occluded
	if ( testList.EdgeCount() == 0 )
		return true;

	testList.ResetActiveEdgeList();
	ResetActiveEdgeList();

	// What we're going to do is look for the first discontinuities we can find
	// in both edge lists. Then, at each discontinuity, we must check the 
	// active edge lists against each other and see if the occluders always
	// block the occludees...
	float flCurrentY = testList.NextDiscontinuity();

	// The edge list for the occluder must completely obscure the occludee...
	// If, then, the first occluder edge starts *below* the first occludee edge, it doesn't occlude.
	if ( flCurrentY < NextDiscontinuity() )
		return false;

	// If we start outside the screen bounds, then it's occluded!
	if ( flCurrentY >= 1.0f )
		return true;

	testList.AdvanceActiveEdgeList( flCurrentY );
	AdvanceActiveEdgeList( flCurrentY );

	while ( true )
	{
		if ( !IsOccludingActiveEdgeList( testList, flCurrentY ) )
			return false;

		float flTestY = testList.NextDiscontinuity();
		float flOccluderY = NextDiscontinuity();
		flCurrentY = min( flTestY, flOccluderY );

		// If we got outside the screen bounds, then it's occluded!
		if ( flCurrentY >= 1.0f )
			return true;

		// If the occludee list is empty, then it's occluded! 
		if ( !testList.AdvanceActiveEdgeList( flCurrentY ) )
			return true;
		
		// If the occluder list is empty, then the occludee is not occluded!
		if ( !AdvanceActiveEdgeList( flCurrentY ) )
			return false;
	}
}

//-----------------------------------------------------------------------------
// Renders the winged edge list for debugging
//-----------------------------------------------------------------------------
void CWingedEdgeList::Visualize( unsigned char *pColor )
{
#ifndef SWDS
	if ( !r_visocclusion.GetInt() )
		return;

	materialSystemInterface->MatrixMode( MATERIAL_MODEL );
	materialSystemInterface->PushMatrix();
	materialSystemInterface->LoadIdentity();

	materialSystemInterface->MatrixMode( MATERIAL_VIEW );
	materialSystemInterface->PushMatrix();
	materialSystemInterface->LoadIdentity();

	materialSystemInterface->MatrixMode( MATERIAL_PROJECTION );
	materialSystemInterface->PushMatrix();
	materialSystemInterface->LoadIdentity();

	materialSystemInterface->Bind( g_pMaterialWireframeVertexColorIgnoreZ );

	IMesh *pMesh = materialSystemInterface->GetDynamicMesh( );
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_LINES, m_WingedEdges.Count() );

	int i;
	for ( i = m_WingedEdges.Count(); --i >= 0; )
	{
		meshBuilder.Position3fv( m_WingedEdges[i].m_vecPosition.Base() );
		meshBuilder.Color4ubv( pColor );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3fv( m_WingedEdges[i].m_vecPositionEnd.Base() );
#ifdef DEBUG_OCCLUSION_SYSTEM
		meshBuilder.Color4ub( 0, 0, 255, 255 );
#else
		meshBuilder.Color4ubv( pColor );
#endif
		meshBuilder.AdvanceVertex();
	}

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

	materialSystemInterface->MatrixMode( MATERIAL_MODEL );
	materialSystemInterface->PopMatrix();

	materialSystemInterface->MatrixMode( MATERIAL_VIEW );
	materialSystemInterface->PopMatrix();

	materialSystemInterface->MatrixMode( MATERIAL_PROJECTION );
	materialSystemInterface->PopMatrix();
#endif
}


//-----------------------------------------------------------------------------
// Edge list that's fast to iterate over, fast to insert into
//-----------------------------------------------------------------------------
class CEdgeList
{
public:
	struct Edge_t
	{
		Vector m_vecPosition;		// of the upper point in y, measured in screen space
		Vector m_vecPositionEnd;	// of the lower point in y, measured in screen space
		float m_flDxDy;				// Change in x per unit in y.
		float m_flOODy;
		float m_flX;
		int   m_nSurfID;			// Unique index of the surface this is a part of

		// Active edge list
		Edge_t *m_pPrevActiveEdge;
		Edge_t *m_pNextActiveEdge;
	};

public:
	CEdgeList();

	// Insertion
	void AddEdge( Vector **ppEdgeVertices, int nSurfID );

	// Surface ID management
	int AddSurface( const cplane_t &plane );
	void SetSurfaceArea( int nSurfID, float flArea );

	// Removal
	void RemoveAll();

	// Visualization
	void Visualize( unsigned char *pColor );

	// Access
	int EdgeCount() const;
	int ActualEdgeCount() const;
	const Edge_t &EdgeFromSortIndex( int nSortIndex ) const;
	Edge_t &EdgeFromSortIndex( int nSortIndex );

	// Is the test edge list occluded by this edge list
	bool IsOccludingEdgeList( CEdgeList &testList );

	// Reduces the active occlusion edge list to the bare minimum set of edges
	void ReduceActiveList( CWingedEdgeList &newEdgeList );

	// Removal of small occluders
	void CullSmallOccluders();

private:
	struct Surface_t
	{
		cplane_t m_Plane;		// measured in projection space
		float m_flOOz;
		Surface_t *m_pPrevSurface;
		Surface_t *m_pNextSurface;
		int m_nSurfID;
		float m_flArea;			// Area in screen space
	};

	struct ReduceInfo_t
	{
		short m_hEdge;
		short m_nWingedEdge;
		const Edge_t *m_pEdge;
	};

	struct EdgeCrossingInfo_t
	{
		Edge_t *m_pFirstCrossing;
		Edge_t *m_pLastCrossing;
	};

	typedef CUtlVector<Edge_t> EdgeList_t;
	 
private:
	// Gets an edge
	const Edge_t &Edge( int nIndex ) const;

	// Active edges...
	const Edge_t *FirstActiveEdge( ) const;
	Edge_t *FirstActiveEdge( );
	const Edge_t *LastActiveEdge( ) const;
	Edge_t *LastActiveEdge( );
	bool AtListEnd( const Edge_t* pEdge ) const;
	bool AtListStart( const Edge_t* pEdge ) const;
	void LinkActiveEdgeAfter( Edge_t *pPrevEdge, Edge_t *pInsertEdge );
	void UnlinkActiveEdge( Edge_t *pEdge );

	// Surface list
	Surface_t* TopSurface();
	bool AtSurfListEnd( const Surface_t* pSurface ) const;
	void CleanupCurrentSurfaceList();

	// Active edge list
	void ResetActiveEdgeList();
	float NextDiscontinuity() const;

	// Clears the current scan line
	float ClearCurrentSurfaceList();

	// Returns the z value of a surface given and x,y coordinate
	float ComputeZValue( const Surface_t *pSurface, float x, float y ) const;

	// Computes a point at a specified y value along an edge
	void ComputePointAlongEdge( const Edge_t *pEdge, int nSurfID, float y, Vector *pPoint ) const;

	// Inserts an edge into the active edge list, sorted by X
	void InsertActiveEdge( Edge_t *pPrevEdge, Edge_t *pInsertEdge );

	// Used to insert an edge into the active edge list
	bool IsEdgeXGreater( const Edge_t *pEdge1, const Edge_t *pEdge2 );

	// Reduces the active edge list into a subset of ones we truly care about
	void ReduceActiveEdgeList( CWingedEdgeList &newEdgeList, float flMinY, float flMaxY );

	// Discovers the first edge crossing discontinuity
	float LocateEdgeCrossingDiscontinuity( float flNextY, EdgeCrossingInfo_t *pInfo );

	// Generates a list of surfaces on the current scan line
	void UpdateCurrentSurfaceZValues( float x, float y );

	// Intoruces a single new edge
	void IntroduceSingleActiveEdge( const Edge_t *pEdge, float flCurrY );

	// Returns true if pTestSurf is closer (lower z value)
	bool IsSurfaceBehind( Surface_t *pTestSurf, Surface_t *pSurf );

	// Advances the X values of the active edge list, with no reordering
	void AdvanceActiveEdgeList( float flNextY );

	void IntroduceNewActiveEdges( float y );
	void ReorderActiveEdgeList( const EdgeCrossingInfo_t &info );

	// Debugging spew
	void SpewActiveEdgeList( float y, bool bHex = false );

	// Checks consistency of the edge list...
	void CheckConsistency();

	static bool EdgeLessFunc( const unsigned short& src1, const unsigned short& src2, void *pCtx );
	static int SurfCompare( const void *elem1, const void *elem2 );

private:
	// Used to sort surfaces by screen area
	static Surface_t *s_pSortSurfaces;

	// List of all edges
	EdgeList_t m_Edges;
	CUtlSortVector<unsigned short> m_OrigSortIndices;
	CUtlVector<unsigned short> m_SortIndices;
	Edge_t m_StartTerminal;
	Edge_t m_EndTerminal;

	// Surfaces
	CUtlVector< Surface_t > m_Surfaces;
	CUtlVector< int > m_SurfaceSort;
	Surface_t m_StartSurfTerminal;
	Surface_t m_EndSurfTerminal;

	// Active edges
	int m_nCurrentEdgeIndex;
	float m_flNextDiscontinuity;

	// List of edges on the current Y scan-line
	Edge_t *m_pCurrentActiveEdge;

	// Last X on the current scan line
	float m_flLastX;

	// Reduce list
	ReduceInfo_t *m_pNewReduceInfo;
	ReduceInfo_t *m_pPrevReduceInfo;
	int m_nNewReduceCount;
	int m_nPrevReduceCount;
};

		
//-----------------------------------------------------------------------------
// Used to sort the edge list
//-----------------------------------------------------------------------------
bool CEdgeList::EdgeLessFunc( const unsigned short& src1, const unsigned short& src2, void *pCtx )
{
	EdgeList_t *pEdgeList = (EdgeList_t*)pCtx;

	const Edge_t &e1 = pEdgeList->Element(src1);
	const Edge_t &e2 = pEdgeList->Element(src2);

	if ( e1.m_vecPosition.y < e2.m_vecPosition.y )
		return true;

	if ( e1.m_vecPosition.y > e2.m_vecPosition.y )
		return false;

	if ( e1.m_vecPosition.x < e2.m_vecPosition.x )
		return true;

	if ( e1.m_vecPosition.x > e2.m_vecPosition.x )
		return false;

	// This makes it so that if two edges start on the same point,
	// the leftmost edge is always selected
	return ( e1.m_flDxDy <= e2.m_flDxDy );
}


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CEdgeList::CEdgeList() : m_Edges( 0, 32 ), m_OrigSortIndices( CEdgeList::EdgeLessFunc, 0, 32 )
{
	m_OrigSortIndices.SetLessContext( &m_Edges );

	m_StartTerminal.m_vecPosition.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
	m_StartTerminal.m_vecPositionEnd.Init( -FLT_MAX, FLT_MAX, -FLT_MAX );
	m_StartTerminal.m_nSurfID = -1;
	m_StartTerminal.m_pPrevActiveEdge = NULL;
	m_StartTerminal.m_pNextActiveEdge = NULL;
	m_StartTerminal.m_flDxDy = 0.0f;
	m_StartTerminal.m_flOODy = 0.0f;
	m_StartTerminal.m_flX = -FLT_MAX;

	m_EndTerminal.m_vecPosition.Init( FLT_MAX, -FLT_MAX, -FLT_MAX );
	m_EndTerminal.m_vecPositionEnd.Init( FLT_MAX, FLT_MAX, -FLT_MAX );
	m_EndTerminal.m_nSurfID = -1;
	m_EndTerminal.m_pPrevActiveEdge = NULL;
	m_EndTerminal.m_pNextActiveEdge = NULL;
	m_EndTerminal.m_flDxDy = 0.0f;
	m_EndTerminal.m_flOODy = 0.0f;
	m_EndTerminal.m_flX = FLT_MAX;

	m_StartSurfTerminal.m_flOOz = -FLT_MAX;
	m_StartSurfTerminal.m_Plane.normal.Init( 0, 0, 1 );
	m_StartSurfTerminal.m_Plane.dist = -FLT_MAX;
	m_StartSurfTerminal.m_nSurfID = -1;
	m_StartSurfTerminal.m_pNextSurface = NULL;
	m_StartSurfTerminal.m_pPrevSurface = NULL;

	m_EndSurfTerminal.m_flOOz = FLT_MAX;
	m_EndSurfTerminal.m_Plane.normal.Init( 0, 0, 1 );
	m_EndSurfTerminal.m_Plane.dist = FLT_MAX;
	m_EndSurfTerminal.m_nSurfID = -1;
	m_EndSurfTerminal.m_pNextSurface = NULL;
	m_EndSurfTerminal.m_pPrevSurface = NULL;
}


//-----------------------------------------------------------------------------
// iteration
//-----------------------------------------------------------------------------
inline int CEdgeList::EdgeCount() const
{
	return m_Edges.Count();
}

inline int CEdgeList::ActualEdgeCount() const
{
	return m_SortIndices.Count();
}

inline const CEdgeList::Edge_t &CEdgeList::EdgeFromSortIndex( int nSortIndex ) const
{
	return m_Edges[ m_SortIndices[nSortIndex] ];
}

inline CEdgeList::Edge_t &CEdgeList::EdgeFromSortIndex( int nSortIndex )
{
	return m_Edges[ m_SortIndices[nSortIndex] ];
}

inline const CEdgeList::Edge_t &CEdgeList::Edge( int nIndex ) const
{
	return m_Edges[ nIndex ];
}


//-----------------------------------------------------------------------------
// Active edges...
//-----------------------------------------------------------------------------
inline const CEdgeList::Edge_t *CEdgeList::FirstActiveEdge( ) const
{ 
	return m_StartTerminal.m_pNextActiveEdge; 
}

inline CEdgeList::Edge_t *CEdgeList::FirstActiveEdge( )
{ 
	return m_StartTerminal.m_pNextActiveEdge; 
}

inline const CEdgeList::Edge_t *CEdgeList::LastActiveEdge( ) const
{ 
	return m_EndTerminal.m_pPrevActiveEdge; 
}

inline CEdgeList::Edge_t *CEdgeList::LastActiveEdge( )
{ 
	return m_EndTerminal.m_pPrevActiveEdge; 
}

inline bool CEdgeList::AtListEnd( const Edge_t* pEdge ) const
{ 
	return pEdge == &m_EndTerminal; 
}

inline bool CEdgeList::AtListStart( const Edge_t* pEdge ) const
{ 
	return pEdge == &m_StartTerminal; 
}

inline void CEdgeList::LinkActiveEdgeAfter( Edge_t *pPrevEdge, Edge_t *pInsertEdge )
{
	pInsertEdge->m_pNextActiveEdge = pPrevEdge->m_pNextActiveEdge;
	pInsertEdge->m_pPrevActiveEdge = pPrevEdge;
	pInsertEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pInsertEdge;
	pPrevEdge->m_pNextActiveEdge = pInsertEdge;
}

inline void CEdgeList::UnlinkActiveEdge( Edge_t *pEdge )
{
	pEdge->m_pPrevActiveEdge->m_pNextActiveEdge = pEdge->m_pNextActiveEdge;
	pEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pEdge->m_pPrevActiveEdge;

#ifdef _DEBUG
	pEdge->m_pPrevActiveEdge = pEdge->m_pNextActiveEdge = NULL;
#endif
}


//-----------------------------------------------------------------------------
// Surface list
//-----------------------------------------------------------------------------
inline CEdgeList::Surface_t* CEdgeList::TopSurface()
{
	return m_StartSurfTerminal.m_pNextSurface;
}

inline bool CEdgeList::AtSurfListEnd( const Surface_t* pSurface ) const
{
	return pSurface == &m_EndSurfTerminal;
}

void CEdgeList::CleanupCurrentSurfaceList()
{
	Surface_t *pSurf = TopSurface();
	while ( !AtSurfListEnd(pSurf) )
	{
		Surface_t *pNext = pSurf->m_pNextSurface;
		pSurf->m_pPrevSurface = pSurf->m_pNextSurface = NULL;
		pSurf = pNext;
	}
}

inline void CEdgeList::SetSurfaceArea( int nSurfID, float flArea )
{
	m_Surfaces[nSurfID].m_flArea = flArea;
}


//-----------------------------------------------------------------------------
// Returns the z value of a surface given and x,y coordinate
//-----------------------------------------------------------------------------
inline float CEdgeList::ComputeZValue( const Surface_t *pSurface, float x, float y ) const
{
	const cplane_t &plane = pSurface->m_Plane;
	Assert( plane.normal.z == 1.0f );
	return plane.dist - plane.normal.x * x - plane.normal.y * y; 
}


//-----------------------------------------------------------------------------
// Computes a point at a specified y value along an edge
//-----------------------------------------------------------------------------
inline void CEdgeList::ComputePointAlongEdge( const Edge_t *pEdge, int nSurfID, float y, Vector *pPoint ) const
{
	Assert( (y >= pEdge->m_vecPosition.y) && (y <= pEdge->m_vecPositionEnd.y) );

	float t;
	t = (y - pEdge->m_vecPosition.y) * pEdge->m_flOODy;
	pPoint->x = pEdge->m_vecPosition.x + ( pEdge->m_vecPositionEnd.x - pEdge->m_vecPosition.x ) * t;
	pPoint->y = y;
	pPoint->z = ComputeZValue( &m_Surfaces[nSurfID], pPoint->x, y );
}


//-----------------------------------------------------------------------------
// Surface ID management
//-----------------------------------------------------------------------------
int CEdgeList::AddSurface( const cplane_t &plane )
{
	int nIndex = m_Surfaces.AddToTail();

	Surface_t &surf = m_Surfaces[nIndex];
	surf.m_flOOz = 0.0f;
	surf.m_Plane = plane;
	surf.m_pNextSurface = NULL;
	surf.m_pPrevSurface = NULL;
	surf.m_nSurfID = nIndex;

	m_SurfaceSort.AddToTail(nIndex);

	return nIndex;
}


//-----------------------------------------------------------------------------
// Insertion
//-----------------------------------------------------------------------------
void CEdgeList::AddEdge( Vector **ppEdgeVertices, int nSurfID )
{
	int nMinIndex = ( ppEdgeVertices[0]->y >= ppEdgeVertices[1]->y );

	const Vector &vecStartVert = *(ppEdgeVertices[ nMinIndex ]);
	const Vector &vecEndVert = *(ppEdgeVertices[ 1 - nMinIndex ]);

	// This is true if we've clipped to the near clip plane
	Assert( (vecStartVert.z >= 0.0f) && (vecEndVert.z >= 0.0f) );

	// Don't bother adding edges with dy == 0
	float dy = vecEndVert.y - vecStartVert.y;
	if (dy == 0.0f)
		return;

	int i = m_Edges.AddToTail();
	Edge_t &newEdge = m_Edges[i];

	newEdge.m_flOODy = 1.0f / dy;
	newEdge.m_vecPosition = vecStartVert;
	newEdge.m_vecPositionEnd = vecEndVert;
	newEdge.m_nSurfID = nSurfID;
	newEdge.m_flDxDy = (vecEndVert.x - vecStartVert.x) * newEdge.m_flOODy;
	newEdge.m_pPrevActiveEdge = NULL;
	newEdge.m_pNextActiveEdge = NULL;

	// Insert it into the sorted list
	m_OrigSortIndices.Insert( i );
}


//-----------------------------------------------------------------------------
// Used to sort the surfaces
//-----------------------------------------------------------------------------
CEdgeList::Surface_t *CEdgeList::s_pSortSurfaces = NULL;
int CEdgeList::SurfCompare( const void *elem1, const void *elem2 )
{
	int nSurfID1 = *(int*)elem1;
	float flArea1 = s_pSortSurfaces[nSurfID1].m_flArea;

	int nSurfID2 = *(int*)elem2;
	float flArea2 = s_pSortSurfaces[nSurfID2].m_flArea;

	if (flArea1 > flArea2)
		return -1;
	if (flArea1 < flArea2)
		return 1;
	return 0;
}


//-----------------------------------------------------------------------------
// Removal of small occluders
//-----------------------------------------------------------------------------
void CEdgeList::CullSmallOccluders()
{	 
	// Cull out all surfaces with too small of a screen area...
	// Sort the surfaces by screen area, in descending order
	int nSurfCount = m_Surfaces.Count();
	s_pSortSurfaces = m_Surfaces.Base();
	qsort( m_SurfaceSort.Base(), nSurfCount, sizeof(int), SurfCompare );

	// We're going to keep the greater of r_occludermin + All surfaces with a screen area >= r_occluderarea
	int nMinSurfaces = r_occludermincount.GetInt();

	// The *2 here is because surf areas are 2x bigger than actual
	float flMinScreenArea = r_occluderminarea.GetFloat() * 0.02f;
	bool *bUseSurface = (bool*)stackalloc( nSurfCount * sizeof(bool) );
	memset( bUseSurface, 0, nSurfCount * sizeof(bool) );
	
	int i;
	for ( i = 0; i < nSurfCount; ++i )
	{
		int nSurfID = m_SurfaceSort[i];
		if (( m_Surfaces[ nSurfID ].m_flArea < flMinScreenArea ) && (i >= nMinSurfaces ))
			break;
		bUseSurface[nSurfID] = true;
	}

	int nEdgeCount = m_OrigSortIndices.Count();
	m_SortIndices.RemoveAll();
	m_SortIndices.EnsureCapacity( nEdgeCount );
	for( i = 0; i < nEdgeCount; ++i )
	{
		int nEdgeIndex = m_OrigSortIndices[i];
		if ( bUseSurface[ m_Edges[ nEdgeIndex ].m_nSurfID ] )
		{
			m_SortIndices.AddToTail( nEdgeIndex ); 
		}
	}
}


//-----------------------------------------------------------------------------
// Removal
//-----------------------------------------------------------------------------
void CEdgeList::RemoveAll()
{
	m_Edges.RemoveAll();
	m_SortIndices.RemoveAll();
	m_OrigSortIndices.RemoveAll();
	m_Surfaces.RemoveAll();
	m_SurfaceSort.RemoveAll();
}


//-----------------------------------------------------------------------------
// Active edge list
//-----------------------------------------------------------------------------
void CEdgeList::ResetActiveEdgeList()
{
	// This shouldn't be called unless we're about to do active edge checking
	Assert( ActualEdgeCount() );

	m_nCurrentEdgeIndex = 0;
	m_flNextDiscontinuity = EdgeFromSortIndex( 0 ).m_vecPosition.y;

	m_StartTerminal.m_pNextActiveEdge = &m_EndTerminal;
	m_EndTerminal.m_pPrevActiveEdge = &m_StartTerminal;
	Assert( m_StartTerminal.m_pPrevActiveEdge == NULL );
	Assert( m_EndTerminal.m_pNextActiveEdge == NULL );
}


//-----------------------------------------------------------------------------
// Returns the next time in Y the edge list will undergo a change
//-----------------------------------------------------------------------------
inline float CEdgeList::NextDiscontinuity() const
{
	return m_flNextDiscontinuity;
}


//-----------------------------------------------------------------------------
// Used to insert an edge into the active edge list, sorted by X
// If Xs match, sort by Dx/Dy
//-----------------------------------------------------------------------------
inline bool CEdgeList::IsEdgeXGreater( const Edge_t *pEdge1, const Edge_t *pEdge2 )
{
	float flDelta = pEdge1->m_flX - pEdge2->m_flX;
	if ( flDelta > 0 )
		return true;

	if ( flDelta < 0 )
		return false;

	// NOTE: Using > instead of >= means coincident edges won't continually swap places
	return pEdge1->m_flDxDy > pEdge2->m_flDxDy;
}


//-----------------------------------------------------------------------------
// Inserts an edge into the active edge list, sorted by X
//-----------------------------------------------------------------------------
inline void CEdgeList::InsertActiveEdge( Edge_t *pPrevEdge, Edge_t *pInsertEdge )
{
	while( !AtListStart(pPrevEdge) && IsEdgeXGreater( pPrevEdge, pInsertEdge ) )
	{
		pPrevEdge = pPrevEdge->m_pPrevActiveEdge;
	}
	LinkActiveEdgeAfter( pPrevEdge, pInsertEdge );
}


//-----------------------------------------------------------------------------
// Clears the current scan line
//-----------------------------------------------------------------------------
float CEdgeList::ClearCurrentSurfaceList()
{
	m_pCurrentActiveEdge = FirstActiveEdge();
	m_flLastX = m_pCurrentActiveEdge->m_flX;
	m_StartSurfTerminal.m_pNextSurface = &m_EndSurfTerminal;
	m_EndSurfTerminal.m_pPrevSurface = &m_StartSurfTerminal;
	return m_flLastX;
}


//-----------------------------------------------------------------------------
// Generates a list of surfaces on the current scan line
//-----------------------------------------------------------------------------
inline void CEdgeList::UpdateCurrentSurfaceZValues( float x, float y )
{
	// Update the z values of all active surfaces
	for ( Surface_t *pSurf = TopSurface(); !AtSurfListEnd( pSurf ); pSurf = pSurf->m_pNextSurface )
	{
		// NOTE: As long as we assume no interpenetrating surfaces,
		// we don't need to re-sort by ooz here.
		pSurf->m_flOOz = ComputeZValue( pSurf, x, y );
	}
}


//-----------------------------------------------------------------------------
// Returns true if pTestSurf is closer (lower z value)
//-----------------------------------------------------------------------------
inline bool CEdgeList::IsSurfaceBehind( Surface_t *pTestSurf, Surface_t *pSurf )
{
	if ( pTestSurf->m_flOOz - pSurf->m_flOOz <= -1e-6 )
		return true;
	if ( pTestSurf->m_flOOz - pSurf->m_flOOz >= 1e-6 )
		return false;

	// If they're nearly equal, then the thing that's approaching the screen
	// more quickly as we ascend in y is closer
	return ( pTestSurf->m_Plane.normal.y >= pSurf->m_Plane.normal.y );
}


//-----------------------------------------------------------------------------
// Introduces a single new edge
//-----------------------------------------------------------------------------
void CEdgeList::IntroduceSingleActiveEdge( const Edge_t *pEdge, float flCurrY )
{
	Surface_t *pCurrentSurf = &m_Surfaces[ pEdge->m_nSurfID ];
	if ( !pCurrentSurf->m_pNextSurface )
	{
		pCurrentSurf->m_flOOz = ComputeZValue( pCurrentSurf, pEdge->m_flX, flCurrY );

		// Determine where to insert the surface into the surface list...
		// Insert it so that the surface list is sorted by OOz
		Surface_t *pNextSurface = TopSurface();
		while( IsSurfaceBehind( pNextSurface, pCurrentSurf ) )
		{
			pNextSurface = pNextSurface->m_pNextSurface;
		}
		pCurrentSurf->m_pNextSurface = pNextSurface;
		pCurrentSurf->m_pPrevSurface = pNextSurface->m_pPrevSurface;
		pNextSurface->m_pPrevSurface = pCurrentSurf;
		pCurrentSurf->m_pPrevSurface->m_pNextSurface = pCurrentSurf;
	}
	else
	{
		// This means this edge is associated with a surface
		// already in the current surface list
		// In this case, simply remove the surface from the surface list
		pCurrentSurf->m_pNextSurface->m_pPrevSurface = pCurrentSurf->m_pPrevSurface;
		pCurrentSurf->m_pPrevSurface->m_pNextSurface = pCurrentSurf->m_pNextSurface;
		pCurrentSurf->m_pPrevSurface = pCurrentSurf->m_pNextSurface = NULL;
	}
}


//-----------------------------------------------------------------------------
// Reduces the active occlusion edge list to the bare minimum set of edges
//-----------------------------------------------------------------------------
void CEdgeList::IntroduceNewActiveEdges( float y )
{
	int nEdgeCount = ActualEdgeCount();
	if ( m_nCurrentEdgeIndex == nEdgeCount )
		return;

	Edge_t *pCurEdge = &EdgeFromSortIndex( m_nCurrentEdgeIndex );

	// Add new edges, computing the x + z coordinates at the requested y value
	while ( pCurEdge->m_vecPosition.y <= y )
	{
		// This is necessary because of our initial skip up to y == -1.0f
		if (pCurEdge->m_vecPositionEnd.y > y)
		{
			float flDy = y - pCurEdge->m_vecPosition.y; 
			pCurEdge->m_flX = pCurEdge->m_vecPosition.x + flDy * pCurEdge->m_flDxDy;
			if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity )
			{
				m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y;
			}

			// Now re-insert in the list, sorted by X
			InsertActiveEdge( LastActiveEdge(), pCurEdge );
		}

		if ( ++m_nCurrentEdgeIndex == nEdgeCount )
			return;

		pCurEdge = &EdgeFromSortIndex( m_nCurrentEdgeIndex );
	}

	// The next edge in y will also present a discontinuity
	if ( pCurEdge->m_vecPosition.y < m_flNextDiscontinuity )
	{
		m_flNextDiscontinuity = pCurEdge->m_vecPosition.y;
	}
}


//-----------------------------------------------------------------------------
// Reduces the active edge list into a subset of ones we truly care about
//-----------------------------------------------------------------------------
void CEdgeList::ReduceActiveEdgeList( CWingedEdgeList &wingedEdgeList, float flMinY, float flMaxY )
{
	// Surface lists should be empty
	int i;

#ifdef DEBUG_OCCLUSION_SYSTEM
	for ( i = m_Surfaces.Count(); --i >= 0; )
	{
		Assert( m_Surfaces[i].m_pNextSurface == NULL );
	}
#endif

	int nLeaveSurfID = -1;
	const Edge_t *pCurEdge = FirstActiveEdge();
	const Edge_t *pNextEdge;

	// NOTE: This algorithm depends on the fact that the active edge
	// list is not only sorted by ascending X, but also because edges
	// that land on the same X value are sorted by ascending dy/dx
	float flPrevX = pCurEdge->m_flX; 

	for ( ; !AtListEnd( pCurEdge ); pCurEdge = pNextEdge ) 
	{
		if ( pCurEdge->m_flX != flPrevX )
		{
			UpdateCurrentSurfaceZValues( pCurEdge->m_flX, flMinY );
		}

		IntroduceSingleActiveEdge( pCurEdge, flMinY );

		flPrevX = pCurEdge->m_flX;

		// If we have coincident edges, we have to introduce them at the same time...
		pNextEdge = pCurEdge->m_pNextActiveEdge;
		if ( (flPrevX == pNextEdge->m_flX) && (pCurEdge->m_flDxDy == pNextEdge->m_flDxDy) )
			continue;

		// If there's more than one overlapping surface at this point,
		// we can eliminate some edges.
		int nEnterSurfID = TopSurface()->m_nSurfID;

		// No change in the top surface? No edges needed...
		if ( nLeaveSurfID == nEnterSurfID )
			continue;

		Assert( ( nLeaveSurfID != -1 ) || ( nEnterSurfID != -1 )  );

		int nEdgeSurfID = ( nEnterSurfID != -1 ) ? nEnterSurfID : nLeaveSurfID;

		// Seam up edges...
		for ( i = m_nPrevReduceCount; --i >= 0; )
		{
			CWingedEdgeList::WingedEdge_t &testEdge = wingedEdgeList.WingedEdge( m_pPrevReduceInfo[i].m_nWingedEdge );
			if (( testEdge.m_nLeaveSurfID != nLeaveSurfID ) || ( testEdge.m_nEnterSurfID != nEnterSurfID ))
				continue;

			if ( ( testEdge.m_flDxDy != pCurEdge->m_flDxDy) || ( fabs( testEdge.m_vecPositionEnd.x - pCurEdge->m_flX ) >= 1e-3 ) )
				continue;

			ComputePointAlongEdge( m_pPrevReduceInfo[i].m_pEdge, nEdgeSurfID, flMaxY, &testEdge.m_vecPositionEnd );
			
			// Don't try to seam up edges that end on this line...
			if ( pCurEdge->m_vecPositionEnd.y > flMaxY )
			{
				ReduceInfo_t *pNewEdge = &m_pNewReduceInfo[ m_nNewReduceCount ];
				++m_nNewReduceCount;
				pNewEdge->m_pEdge = m_pPrevReduceInfo[i].m_pEdge;
				pNewEdge->m_nWingedEdge = m_pPrevReduceInfo[i].m_nWingedEdge;
			}
			break;
		}

		// This edge didn't exist on the previous y discontinuity line
		// We'll need to make a new one
		if ( i < 0 )
		{
			i = wingedEdgeList.AddEdge();
			CWingedEdgeList::WingedEdge_t &newWingedEdge = wingedEdgeList.WingedEdge(i);
			newWingedEdge.m_nLeaveSurfID = nLeaveSurfID;
			newWingedEdge.m_nEnterSurfID = nEnterSurfID;
			newWingedEdge.m_flDxDy = pCurEdge->m_flDxDy;
			ComputePointAlongEdge( pCurEdge, nEdgeSurfID, flMinY, &newWingedEdge.m_vecPosition ); 
			ComputePointAlongEdge( pCurEdge, nEdgeSurfID, flMaxY, &newWingedEdge.m_vecPositionEnd ); 
			
			// Enforce sort order...
			// Required because we're computing the x position here, which can introduce error.
			if ( i != 0 )
			{
				CWingedEdgeList::WingedEdge_t &prevWingedEdge = wingedEdgeList.WingedEdge(i - 1);
				if ( newWingedEdge.m_vecPosition.y == prevWingedEdge.m_vecPosition.y )
				{
					if ( newWingedEdge.m_vecPosition.x < prevWingedEdge.m_vecPosition.x ) 
					{
						newWingedEdge.m_vecPosition.x = prevWingedEdge.m_vecPosition.x;
					}
				}
			}

			// Don't try to seam up edges that end on this line...
			if ( pCurEdge->m_vecPositionEnd.y > flMaxY )
			{
				ReduceInfo_t *pNewEdge = &m_pNewReduceInfo[ m_nNewReduceCount ];
				++m_nNewReduceCount;
				pNewEdge->m_pEdge = pCurEdge;
				pNewEdge->m_nWingedEdge = i;
			}

#ifdef DEBUG_OCCLUSION_SYSTEM
			wingedEdgeList.CheckConsistency();
#endif
		}

		nLeaveSurfID = nEnterSurfID;
	}

	Assert( nLeaveSurfID == -1 );

//		Msg("\n");

}


//-----------------------------------------------------------------------------
// Discovers the first edge crossing discontinuity
//-----------------------------------------------------------------------------
float CEdgeList::LocateEdgeCrossingDiscontinuity( float flNextY, EdgeCrossingInfo_t *pInfo )
{
	float flCurrX = -FLT_MAX;
	float flNextX = -FLT_MAX;
	float flCurrY = flNextY;

	Vector2D vecDelta, vecIntersection;
	
	pInfo->m_pFirstCrossing = NULL;
	pInfo->m_pLastCrossing = NULL;
	Edge_t *pCurEdge;
	for ( pCurEdge = FirstActiveEdge(); !AtListEnd(pCurEdge); flCurrX = flNextX, pCurEdge = pCurEdge->m_pNextActiveEdge )
	{
		// Don't take into account edges that end on the current line
		Assert( pCurEdge->m_vecPositionEnd.y >= flCurrY );

		flNextX = pCurEdge->m_vecPosition.x + (flCurrY - pCurEdge->m_vecPosition.y) * pCurEdge->m_flDxDy;

		// Look for an X-crossing... This check helps for nearly co-linear lines
		// NOTE: You might think this would crash since it could dereference a NULL
		// pointer the first time through the loop, but it never hits that check since the
		// first X test is guaranteed to pass
		Edge_t *pPrevEdge = pCurEdge->m_pPrevActiveEdge;
		if ( ( flNextX > flCurrX ) || ( pPrevEdge->m_flDxDy <= pCurEdge->m_flDxDy ) )
			continue;

		// This test is necessary to not capture edges that meet at a point...
		if ( pPrevEdge->m_vecPositionEnd == pCurEdge->m_vecPositionEnd )
			continue;

		Assert( pPrevEdge->m_flDxDy != pCurEdge->m_flDxDy );

		// Found one! Let's find the intersection of these two
		// edges and up the Y discontinuity to that point.
		// We'll solve this by doing an intersection of point + plane in 2D...
		// For the line, we'll use the previous line where
		// P = Pop + D * t, Pop = prevEdge.m_vecPosition, D = [dx dy] = [(dx/dy) 1]
		// For the plane, we'll use the current line where
		// N * P = d
		// Normal is perpendicular to the line, therefore N = [-dy dx] = [-1 (dx/dy)]
		// d = DotProduct( N, edge.m_vecPosition ) = N dot Pon
		// So, the t that solve the equation is given by t = (d - N dot Pop) / (N dot D)
		// Or, t = (N dot Pon - N dot Pop) / (N dot D)
		// t = (N dot (Pon - Pop)) / (N dot D)

		float flDenominator = 1.0f / (-pPrevEdge->m_flDxDy + pCurEdge->m_flDxDy);
		Vector2DSubtract( pCurEdge->m_vecPosition.AsVector2D(), pPrevEdge->m_vecPosition.AsVector2D(), vecDelta );
		float flNumerator = - vecDelta.x + pCurEdge->m_flDxDy * vecDelta.y;
		float t = flNumerator * flDenominator;
		float flYCrossing = pPrevEdge->m_vecPosition.y + t;
		
		// Precision errors...
		if ( flYCrossing >= flNextY )
			continue;

		flNextX = pPrevEdge->m_vecPosition.x + t * pPrevEdge->m_flDxDy;
		pInfo->m_pLastCrossing = pCurEdge;

		// This check is necessary to deal with the case where more than 2 edges meet at the same point
		if ( flCurrY != flYCrossing )
		{
			pInfo->m_pFirstCrossing = pPrevEdge;
			flCurrY	= flYCrossing;
		}
	}

	return flCurrY;
}


//-----------------------------------------------------------------------------
// Advances the X values of the active edge list, with no reordering
//-----------------------------------------------------------------------------
void CEdgeList::AdvanceActiveEdgeList( float flCurrY )
{
	m_flNextDiscontinuity = FLT_MAX;

	// Advance all edges until the current Y; we don't need to re-order *any* edges.
	Edge_t *pCurEdge;
	Edge_t *pNextEdge;
	float flPrevX = -FLT_MAX;
	for ( pCurEdge = FirstActiveEdge(); !AtListEnd( pCurEdge ); pCurEdge = pNextEdge )
	{
		pNextEdge = pCurEdge->m_pNextActiveEdge;

		if ( pCurEdge->m_vecPositionEnd.y <= flCurrY )
		{
			UnlinkActiveEdge( pCurEdge );
			continue;
		}

		pCurEdge->m_flX = pCurEdge->m_vecPosition.x + (flCurrY - pCurEdge->m_vecPosition.y) * pCurEdge->m_flDxDy;

		// Eliminate precision errors by guaranteeing sort ordering...
		if ( pCurEdge->m_flX < flPrevX )
		{
			pCurEdge->m_flX = flPrevX;
		}
		else
		{
			flPrevX = pCurEdge->m_flX;
		}

		if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity )
		{
			m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y;
		}
	}
}


//-----------------------------------------------------------------------------
// Reorders the active edge list based on where edge crossings occur
//-----------------------------------------------------------------------------
void CEdgeList::ReorderActiveEdgeList( const EdgeCrossingInfo_t &info )
{
	// Re-order the list where the edge crossing occurred.
	// For all edges that passed through the exact same point, we need only
	// reverse the order of those edges. At the same time, slam the X value of each
	// crossing edge to reduce precision errors
	Edge_t *pFirstCrossing = info.m_pFirstCrossing;
	Edge_t *pLastCrossing = info.m_pLastCrossing;

	// First, bring shared (or nearly shared) edges into the crossing list...
	while( pFirstCrossing->m_pPrevActiveEdge->m_flX == pFirstCrossing->m_flX )
	{
		pFirstCrossing = pFirstCrossing->m_pPrevActiveEdge;
	}

	// Slam all x values to be the same to avoid precision errors...
	// This guarantees that this crossing at least will occur
	float flXCrossing = pFirstCrossing->m_flX;
	float flLastXCrossing = pLastCrossing->m_flX;
	pLastCrossing->m_flX = flXCrossing;
	while( pLastCrossing->m_pNextActiveEdge->m_flX == flLastXCrossing )
	{
		pLastCrossing = pLastCrossing->m_pNextActiveEdge;
		pLastCrossing->m_flX = flXCrossing;
	}

	// Now re-insert everything to take into account other edges which may well have 
	// crossed on this line
	Edge_t *pEdge;
	Edge_t *pNextEdge;
	for( pEdge = FirstActiveEdge(); !AtListEnd(pEdge); pEdge = pNextEdge )
	{
		pNextEdge = pEdge->m_pNextActiveEdge;
		Edge_t *pPrevEdge = pEdge->m_pPrevActiveEdge;
		if ( pPrevEdge->m_flX == pEdge->m_flX )
		{
			UnlinkActiveEdge( pEdge );
			InsertActiveEdge( pPrevEdge, pEdge );
		}
	}
}


//-----------------------------------------------------------------------------
// Reduces the active occlusion edge list to the bare minimum set of edges
//-----------------------------------------------------------------------------
void CEdgeList::SpewActiveEdgeList( float y, bool bHex)
{
	Edge_t *pEdge = FirstActiveEdge();
	Msg( "%.3f : ", y );
	while ( !AtListEnd( pEdge ) )
	{
		if (!bHex)
		{
			Msg( "(%d %.3f [%d]) ", (int)(pEdge - m_Edges.Base()), pEdge->m_flX, pEdge->m_nSurfID );
		}
		else
		{
			Msg( "(%d %X [%d]) ", (int)(pEdge - m_Edges.Base()), *(int*)&pEdge->m_flX, pEdge->m_nSurfID );
		}
		pEdge = pEdge->m_pNextActiveEdge;
	}
	Msg( "\n" );
}


//-----------------------------------------------------------------------------
// Checks consistency of the edge list...
//-----------------------------------------------------------------------------
void CEdgeList::CheckConsistency()
{
	Edge_t *pEdge = FirstActiveEdge();
	while( !AtListEnd( pEdge ) )
	{
		Edge_t *pPrevEdge = pEdge->m_pPrevActiveEdge;
		Assert( pEdge->m_flX >= pPrevEdge->m_flX );
		if ( pEdge->m_flX == pPrevEdge->m_flX )
		{
			// End point check necessary because of precision errors
			Assert( (pEdge->m_flDxDy >= pPrevEdge->m_flDxDy) || (pEdge->m_vecPositionEnd == pPrevEdge->m_vecPositionEnd) );
		}

		pEdge = pEdge->m_pNextActiveEdge;
	}
}


//-----------------------------------------------------------------------------
// Reduces the active occlusion edge list to the bare minimum set of edges
//-----------------------------------------------------------------------------
void CEdgeList::ReduceActiveList( CWingedEdgeList &newEdgeList )
{
	int nEdgeCount = ActualEdgeCount();
	if ( nEdgeCount == 0 )
		return;

	// Copy the surfaces over
	int nCount = m_Surfaces.Count();
//	newEdgeList.m_Surfaces.EnsureCapacity( nCount );
	for ( int i = 0; i < nCount; ++i )
	{
		newEdgeList.AddSurface( m_Surfaces[i].m_Plane );
	}

	EdgeCrossingInfo_t edgeCrossingInfo;
	ReduceInfo_t *pBuf[2];
	pBuf[0] = (ReduceInfo_t*)stackalloc( nEdgeCount * sizeof(ReduceInfo_t) );
	pBuf[1] = (ReduceInfo_t*)stackalloc( nEdgeCount * sizeof(ReduceInfo_t) );
	m_nPrevReduceCount = m_nNewReduceCount = 0;
	int nIndex = 0;

	ResetActiveEdgeList();
	ClearCurrentSurfaceList();

	// We can skip everything up to y = -1.0f; since that's offscreen
	float flPrevY = NextDiscontinuity();
	flPrevY = max( -1.0f, flPrevY );

	m_flNextDiscontinuity = FLT_MAX;
	IntroduceNewActiveEdges( flPrevY );

	bool bDone = false;
	while( !bDone )
	{
		// Don't immediately progress to the next discontinuity if there are edge crossings.
		float flNextY = LocateEdgeCrossingDiscontinuity( NextDiscontinuity(), &edgeCrossingInfo );

#ifdef  DEBUG_OCCLUSION_SYSTEM
		if ( s_bSpew )
		{
			// Debugging spew
			SpewActiveEdgeList( flPrevY );
		}
#endif

		// Reduce the active edge list
		m_pNewReduceInfo = pBuf[1 - nIndex];
		m_pPrevReduceInfo = pBuf[nIndex];
		m_nPrevReduceCount = m_nNewReduceCount;
		m_nNewReduceCount = 0;

		if (flNextY >= 1.0f)
		{
			flNextY = 1.0f;
			bDone = true;
		}

		ReduceActiveEdgeList( newEdgeList, flPrevY, flNextY );
		flPrevY = flNextY;

		// Advance the active edge list, with no resorting necessary!!
		AdvanceActiveEdgeList( flNextY );

		// If we had an edge crossing, re-order the edges. Otherwise introduce new active edges
		if ( edgeCrossingInfo.m_pFirstCrossing == NULL )
		{
			IntroduceNewActiveEdges( flNextY );

			// Keep advancing the active edge list until it's got no more discontinuities
			if ( NextDiscontinuity() == FLT_MAX )
				return;
		}
		else
		{
			ReorderActiveEdgeList( edgeCrossingInfo );
			
			// The next edge in y will also present a discontinuity
			if ( m_nCurrentEdgeIndex < nEdgeCount )
			{
				float flNextEdgeY = EdgeFromSortIndex( m_nCurrentEdgeIndex ).m_vecPosition.y;
				if ( flNextEdgeY < m_flNextDiscontinuity )
				{
					m_flNextDiscontinuity = flNextEdgeY;
				}
			}
		}

#ifdef DEBUG_OCCLUSION_SYSTEM
		CheckConsistency();
#endif

		nIndex = 1 - nIndex;
	}
}


//-----------------------------------------------------------------------------
// Used to debug the occlusion system
//-----------------------------------------------------------------------------
void CEdgeList::Visualize( unsigned char *pColor )
{
#ifndef SWDS
	if ( !r_visocclusion.GetInt() )
		return;

	materialSystemInterface->MatrixMode( MATERIAL_MODEL );
	materialSystemInterface->PushMatrix();
	materialSystemInterface->LoadIdentity();

	materialSystemInterface->MatrixMode( MATERIAL_VIEW );
	materialSystemInterface->PushMatrix();
	materialSystemInterface->LoadIdentity();

	materialSystemInterface->MatrixMode( MATERIAL_PROJECTION );
	materialSystemInterface->PushMatrix();
	materialSystemInterface->LoadIdentity();

	materialSystemInterface->Bind( g_pMaterialWireframeVertexColorIgnoreZ );

	IMesh *pMesh = materialSystemInterface->GetDynamicMesh( );
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_LINES, m_Edges.Count() );

	int i;
	for ( i = m_Edges.Count(); --i >= 0; )
	{
		meshBuilder.Position3fv( m_Edges[i].m_vecPosition.Base() );
		meshBuilder.Color4ubv( pColor );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3fv( m_Edges[i].m_vecPositionEnd.Base() );
#ifdef DEBUG_OCCLUSION_SYSTEM
		meshBuilder.Color4ub( 0, 0, 255, 255 );
#else
		meshBuilder.Color4ubv( pColor );
#endif
		meshBuilder.AdvanceVertex();
	}

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

	materialSystemInterface->MatrixMode( MATERIAL_MODEL );
	materialSystemInterface->PopMatrix();

	materialSystemInterface->MatrixMode( MATERIAL_VIEW );
	materialSystemInterface->PopMatrix();

	materialSystemInterface->MatrixMode( MATERIAL_PROJECTION );
	materialSystemInterface->PopMatrix();
#endif
}



//-----------------------------------------------------------------------------
// Implementation of IOcclusionSystem
//-----------------------------------------------------------------------------
class COcclusionSystem : public IOcclusionSystem
{
public:
	COcclusionSystem();
	~COcclusionSystem();

	// Inherited from IOcclusionSystem
	virtual void ActivateOccluder( int nOccluderIndex, bool bActive );
	virtual void SetView( const Vector &vecCameraPos, float flFOV, const VMatrix &worldToCamera, const VMatrix &cameraToProjection, const VPlane &nearClipPlane );
	virtual bool IsOccluded( const Vector &vecAbsMins, const Vector &vecAbsMaxs );

private:
	struct AxisAlignedPlane_t
	{
		int m_nAxis;
		float m_flSign;
		float m_flDist;
	};

	// Recomputes the edge list for occluders
	void RecomputeOccluderEdgeList();

	// Is the point inside the near plane?
	bool IsPointInsideNearPlane( const Vector &vecPos ) const;
	void IntersectWithNearPlane( const Vector &vecStart, const Vector &vecEnd, Vector &outPos ) const;

	// Clips a polygon to the near clip plane
	int ClipPolygonToNearPlane( Vector **ppVertices, int nVertexCount, Vector **ppOutVerts, bool *pClipped ) const;

	// Project world-space verts + add into the edge list
	void AddPolygonToEdgeList( CEdgeList &edgeList, Vector **ppPolygon, int nCount, int nSurfID, bool bClipped );

	// Computes the plane equation of a polygon in screen space from a camera-space plane
	void ComputeScreenSpacePlane( const cplane_t &cameraSpacePlane, cplane_t *pScreenSpacePlane );

	// Used to clip the screen space polygons to the screen
	void ResetClipTempVerts();
	int ClipPolygonToAxisAlignedPlane( Vector **ppVertices, int nVertexCount, 
								const AxisAlignedPlane_t &plane, Vector **ppOutVerts ) const;

	// Is the point within an axis-aligned plane?
	bool IsPointInsideAAPlane( const Vector &vecPos, const AxisAlignedPlane_t &plane ) const;
	void IntersectWithAAPlane( const Vector &vecStart, const Vector &vecEnd, const AxisAlignedPlane_t &plane, Vector &outPos ) const;

	// Stitches up clipped vertices
	void StitchClippedVertices( Vector *pVertices, int nCount );

private:
	// Per-frame information
	bool m_bEdgeListDirty;
	VMatrix m_WorldToProjection;
	VMatrix m_WorldToCamera;
	float m_flXProjScale;
	float m_flYProjScale;
	float m_flProjDistScale;
	float m_flProjDistOffset;
	Vector m_vecCameraPosition;		// in world space
	cplane_t m_NearClipPlane;
	float m_flNearPlaneDist;
	float m_flFOVFactor;
	CEdgeList m_EdgeList;
	CWingedEdgeList m_WingedEdgeList;
	CUtlVector< Vector > m_ClippedVerts;

	// Stats
	int m_nTests;
	int m_nOccluded;
};

static COcclusionSystem g_OcclusionSystem;



//-----------------------------------------------------------------------------
// Singleton accessor
//-----------------------------------------------------------------------------
IOcclusionSystem *OcclusionSystem()
{
	return &g_OcclusionSystem;
}


//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
COcclusionSystem::COcclusionSystem() : m_ClippedVerts( 0, 64 )
{
	m_bEdgeListDirty = false;
	m_nTests = 0;
	m_nOccluded = 0;
}

COcclusionSystem::~COcclusionSystem()
{
}


//-----------------------------------------------------------------------------
// Is the point within the near plane?
//-----------------------------------------------------------------------------
inline bool COcclusionSystem::IsPointInsideNearPlane( const Vector &vecPos ) const
{
	return DotProduct( vecPos, m_NearClipPlane.normal ) >= m_NearClipPlane.dist;
}

inline void COcclusionSystem::IntersectWithNearPlane( const Vector &vecStart, const Vector &vecEnd, Vector &outPos ) const
{
	Vector vecDir;
	VectorSubtract( vecEnd, vecStart, vecDir );
	float t = IntersectRayWithPlane( vecStart, vecDir, m_NearClipPlane.normal, m_NearClipPlane.dist );
	VectorLerp( vecStart, vecEnd, t, outPos );
}


//-----------------------------------------------------------------------------
// Clips a surface to the near clip plane
// FIXME: This blows: a *third* S-H clipper in the engine! All because the
// vertex formats are different owing to different goals of the 3 clippers
//-----------------------------------------------------------------------------
static Vector s_TempVertMemory[256];

int COcclusionSystem::ClipPolygonToNearPlane( Vector **ppVertices, int nVertexCount, Vector **ppOutVerts, bool *pClipped ) const
{
	*pClipped = false;

	if ( nVertexCount < 3 )
		return 0;

	// Ye Olde Sutherland-Hodgman clipping algorithm
	int nOutVertCount = 0;
	int nNewVertCount = 0;

	Vector* pStart = ppVertices[ nVertexCount - 1 ];
	bool bStartInside = IsPointInsideNearPlane( *pStart );
	for ( int i = 0; i < nVertexCount; ++i )
	{
		Vector* pEnd = ppVertices[ i ];
		bool bEndInside = IsPointInsideNearPlane( *pEnd );
		if (bEndInside)
		{
			if (!bStartInside)
			{
				// Started outside, ended inside, need to clip the edge
				ppOutVerts[nOutVertCount] = &s_TempVertMemory[ nNewVertCount++ ];
				IntersectWithNearPlane( *pStart, *pEnd, *ppOutVerts[nOutVertCount] ); 
				++nOutVertCount;
				*pClipped = true;
			}
			ppOutVerts[nOutVertCount++] = pEnd;
		}
		else
		{
			if (bStartInside)
			{
				// Started inside, ended outside, need to clip the edge
				ppOutVerts[nOutVertCount] = &s_TempVertMemory[ nNewVertCount++ ];
				IntersectWithNearPlane( *pStart, *pEnd, *ppOutVerts[nOutVertCount] ); 
				++nOutVertCount;
				*pClipped = true;
			}
		}
		pStart = pEnd;
		bStartInside = bEndInside;
	}

	return nOutVertCount;
}


//-----------------------------------------------------------------------------
// Is the point within an axis-aligned plane?
//-----------------------------------------------------------------------------
inline bool COcclusionSystem::IsPointInsideAAPlane( const Vector &vecPos, const AxisAlignedPlane_t &plane ) const
{
	return vecPos[plane.m_nAxis] * plane.m_flSign >= plane.m_flDist;
}

inline void COcclusionSystem::IntersectWithAAPlane( const Vector &vecStart, const Vector &vecEnd, const AxisAlignedPlane_t &plane, Vector &outPos ) const
{
	float t = IntersectRayWithAAPlane( vecStart, vecEnd, plane.m_nAxis, plane.m_flSign, plane.m_flDist );
	VectorLerp( vecStart, vecEnd, t, outPos );
}


//-----------------------------------------------------------------------------
// Clips a surface to the edges of the screen (axis-aligned planes)
//-----------------------------------------------------------------------------
static int s_nTempVertCount = 0;

void COcclusionSystem::ResetClipTempVerts()
{
	s_nTempVertCount = 0;
}

int COcclusionSystem::ClipPolygonToAxisAlignedPlane( Vector **ppVertices, int nVertexCount, 
							const AxisAlignedPlane_t &plane, Vector **ppOutVerts ) const
{
	// Ye Olde Sutherland-Hodgman clipping algorithm
	int nOutVertCount = 0;

	Vector* pStart = ppVertices[ nVertexCount - 1 ];
	bool bStartInside = IsPointInsideAAPlane( *pStart, plane );
	for ( int i = 0; i < nVertexCount; ++i )
	{
		Vector* pEnd = ppVertices[ i ];
		bool bEndInside = IsPointInsideAAPlane( *pEnd, plane );
		if (bEndInside)
		{
			if (!bStartInside)
			{
				// Started outside, ended inside, need to clip the edge
				ppOutVerts[nOutVertCount] = &s_TempVertMemory[ s_nTempVertCount++ ];
				IntersectWithAAPlane( *pStart, *pEnd, plane, *ppOutVerts[nOutVertCount] ); 
				++nOutVertCount;
			}
			ppOutVerts[nOutVertCount++] = pEnd;
		}
		else
		{
			if (bStartInside)
			{
				// Started inside, ended outside, need to clip the edge
				ppOutVerts[nOutVertCount] = &s_TempVertMemory[ s_nTempVertCount++ ];
				IntersectWithAAPlane( *pStart, *pEnd, plane, *ppOutVerts[nOutVertCount] ); 
				++nOutVertCount;
			}
		}
		pStart = pEnd;
		bStartInside = bEndInside;
	}

	return nOutVertCount;
}


//-----------------------------------------------------------------------------
// Computes the plane equation of a polygon in screen space from a world-space plane
//-----------------------------------------------------------------------------
void COcclusionSystem::ComputeScreenSpacePlane( const cplane_t &cameraSpacePlane, cplane_t *pScreenSpacePlane )
{
	// Here's how this is computed:
	// If the *camera* space plane is Ax+By+Cz = D, 
	// and xs = -(xf) * x/z, ys = -(yf) y/z, zs = - (zc + zf * ooz)
	// Then x = -xs * z / xf, y = -ys * z / yf, ooz = -(zs + zc) / zf
	// So - A * xs * z / xf - B * ys * z / yf + C * z = D
	// - A xs / xf - B ys / yf + C = D * ooz
	// (A/D) xs/xf + (B/D) ys/yf + ooz = (C/D)
	// (A/D) xs/xf + (B/D) ys/yf - (zs + zc) / zf = (C/D)
	// -(A/D) xs/xf - (B/D) ys/yf + (zs + zc) / zf = -(C/D)
	// -zf * (A/D) xs/xf - zf * (B/D) ys/yf + zs = -zf * (C/D) - zc
	// Let A' = -zf/xf*(A/D), B' = -zf/yf*(B/D), D' =  -zf * (C/D) - zc
	// A' xs + B' ys + zs = D'	is the screen space plane equation
	
	float ooD = (cameraSpacePlane.dist != 0) ? (1.0f / cameraSpacePlane.dist) : 0.0f;
	pScreenSpacePlane->normal.x = cameraSpacePlane.normal.x * ooD * m_flXProjScale;
	pScreenSpacePlane->normal.y = cameraSpacePlane.normal.y * ooD * m_flYProjScale;
	pScreenSpacePlane->normal.z = 1;
	pScreenSpacePlane->dist = cameraSpacePlane.normal.z * ooD * m_flProjDistScale + m_flProjDistOffset;
}



//-----------------------------------------------------------------------------
// Stitches up clipped vertices
//-----------------------------------------------------------------------------
void COcclusionSystem::StitchClippedVertices( Vector *pVertices, int nCount )
{
	for ( int i = 0; i < nCount; ++i )
	{
		// Only stitch ones that have been clipped by the near clip plane
		if ( fabs( pVertices[i].z ) > 1e-3 )
			continue;

		int j;
		for ( j = m_ClippedVerts.Count(); --j >= 0; )
		{
			if ( VectorsAreEqual( pVertices[i], m_ClippedVerts[j], 1e-3 ) )
			{
				pVertices[i] = m_ClippedVerts[j];
				break;
			}
		}

		if ( j < 0 )
		{
			// No match found...
			m_ClippedVerts.AddToTail( pVertices[i] );
		}
	}
}

	
//-----------------------------------------------------------------------------
// Project world-space verts + add into the edge list
//-----------------------------------------------------------------------------
void COcclusionSystem::AddPolygonToEdgeList( CEdgeList &edgeList, Vector **ppPolygon, int nCount, int nSurfID, bool bClipped )
{
	// Transform the verts into projection space
	// Transform into projection space (extra logic here is to simply guarantee that we project each vert exactly once)
	int nMaxClipVerts = (nCount * 4);
	int nClipCount, nClipCount1;
	Vector **ppClipVertex = (Vector**)stackalloc( nMaxClipVerts * sizeof(Vector*) );
	Vector **ppClipVertex1 = (Vector**)stackalloc( nMaxClipVerts * sizeof(Vector*) );
	Vector *pVecProjectedVertex = (Vector*)stackalloc( nCount * sizeof(Vector) );

	int k;
	for ( k = 0; k < nCount; ++k )
	{
		Vector3DMultiplyPositionProjective( m_WorldToProjection, *(ppPolygon[k]), pVecProjectedVertex[k] );

		// Clamp needed to avoid precision problems.
//		if ( pVecProjectedVertex[k].z < 0.0f )
//			pVecProjectedVertex[k].z = 0.0f;
		pVecProjectedVertex[k].z *= (pVecProjectedVertex[k].z > 0.0f);
		ppClipVertex[k] = &pVecProjectedVertex[k];
	}

	// Clip vertices to the screen in x,y...
	AxisAlignedPlane_t aaPlane;
	aaPlane.m_nAxis = 0;
	aaPlane.m_flDist = -1;
	aaPlane.m_flSign = -1;
	nClipCount = nCount;

	ResetClipTempVerts();

	nClipCount1 = ClipPolygonToAxisAlignedPlane( ppClipVertex, nClipCount, aaPlane, ppClipVertex1 );
	if ( nClipCount1 < 3 )
		return;
	Assert( nClipCount1 < nMaxClipVerts );

	aaPlane.m_flSign = 1;
	nClipCount = ClipPolygonToAxisAlignedPlane( ppClipVertex1, nClipCount1, aaPlane, ppClipVertex );
	if ( nClipCount < 3 )
		return;
	Assert( nClipCount < nMaxClipVerts );

	aaPlane.m_nAxis = 1;
	nClipCount1 = ClipPolygonToAxisAlignedPlane( ppClipVertex, nClipCount, aaPlane, ppClipVertex1 );
	if ( nClipCount1 < 3 )
		return;
	Assert( nClipCount1 < nMaxClipVerts );

	aaPlane.m_flSign = -1;
	nClipCount = ClipPolygonToAxisAlignedPlane( ppClipVertex1, nClipCount1, aaPlane, ppClipVertex );
	if ( nClipCount < 3 )
		return;
	Assert( nClipCount < nMaxClipVerts );

	// Compute the screen area...
	float flScreenArea = 0.0f;
	int nLastClipVert = nClipCount - 1;
	for ( k = 1; k < nLastClipVert; ++k )
	{
		// Using area times two simply because it's faster...
		float flTriArea = TriArea2DTimesTwo( (*ppClipVertex[0]), (*ppClipVertex[k]), (*ppClipVertex[k+1]) );
		Assert( flTriArea <= 1e-6 );
		flScreenArea += -flTriArea;
	}
	edgeList.SetSurfaceArea( nSurfID, flScreenArea );

	// If there's a clipped vertex, attempt to seam up with other edges...
	if ( bClipped )
	{
		StitchClippedVertices( pVecProjectedVertex, nCount );
	}

	// Add in the edges of the *unclipped* polygon: to avoid precision errors
	Vector *ppEdgeVertices[2];
	int nLastVert = nCount - 1;
	ppEdgeVertices[ 1 ] = &pVecProjectedVertex[ nLastVert ];
	for ( k = 0; k < nLastVert; ++k )
	{
		ppEdgeVertices[ k & 0x1 ] = &pVecProjectedVertex[ k ];
		edgeList.AddEdge( ppEdgeVertices, nSurfID );
	}
	ppEdgeVertices[ nLastVert & 0x1 ] = &pVecProjectedVertex[ nLastVert ];
	edgeList.AddEdge( ppEdgeVertices, nSurfID );
}


//-----------------------------------------------------------------------------
// Recomputes the occluder edge list
//-----------------------------------------------------------------------------
void COcclusionSystem::RecomputeOccluderEdgeList()
{
	if ( !m_bEdgeListDirty )
		return;

	m_bEdgeListDirty = false;
	m_EdgeList.RemoveAll();
	m_WingedEdgeList.Clear();
	m_ClippedVerts.RemoveAll();

	mvertex_t *pVertices = host_state.worldmodel->brush.vertexes;
	int *pIndices = host_state.worldmodel->brush.occludervertindices;
	doccluderdata_t *pOccluders = host_state.worldmodel->brush.occluders;

	int i, j, k;
	for ( i = host_state.worldmodel->brush.numoccluders ; --i >= 0; )
	{
		if ( pOccluders[i].flags & OCCLUDER_FLAGS_INACTIVE )
			continue;

		int nSurfID = pOccluders[i].firstpoly;
		int nSurfCount = pOccluders[i].polycount;
		for ( j = 0; j < nSurfCount; ++j, ++nSurfID )
		{
			doccluderpolydata_t *pSurf = &host_state.worldmodel->brush.occluderpolys[nSurfID];

			int nFirstVertexIndex = pSurf->firstvertexindex;
			int nVertexCount = pSurf->vertexcount;

			// If the surface is backfacing, blow it off...
			const cplane_t &surfPlane = host_state.worldmodel->brush.planes[ pSurf->planenum ];
			if ( DotProduct( surfPlane.normal, m_vecCameraPosition ) <= surfPlane.dist )
				continue;

			// Clip to the near plane (has to be done in world space)
			Vector **ppSurfVerts = (Vector**)stackalloc( ( nVertexCount ) * sizeof(Vector*) );
			Vector **ppClipVerts = (Vector**)stackalloc( ( nVertexCount * 2 ) * sizeof(Vector*) );
			for ( k = 0; k < nVertexCount; ++k )
			{
				int nVertIndex = pIndices[nFirstVertexIndex + k];
				ppSurfVerts[k] = &( pVertices[nVertIndex].position );
			}

			bool bClipped;
			int nClipCount = ClipPolygonToNearPlane( ppSurfVerts, nVertexCount, ppClipVerts, &bClipped );
			Assert( nClipCount <= ( nVertexCount * 2 ) );
			if ( nClipCount < 3 )
				continue;

			cplane_t projectionSpacePlane;
			cplane_t cameraSpacePlane;
			MatrixTransformPlane( m_WorldToCamera, surfPlane, cameraSpacePlane );
			ComputeScreenSpacePlane( cameraSpacePlane, &projectionSpacePlane ); 
			int nEdgeSurfID = m_EdgeList.AddSurface( projectionSpacePlane );

			// Transform into projection space (extra logic here is to simply guarantee that we project each vert exactly once)
			AddPolygonToEdgeList( m_EdgeList, ppClipVerts, nClipCount, nEdgeSurfID, bClipped );
		}
	}

	m_EdgeList.CullSmallOccluders();
	m_EdgeList.ReduceActiveList( m_WingedEdgeList ); 
//	Msg("Edge count %d -> %d\n", m_EdgeList.EdgeCount(), m_WingedEdgeList.EdgeCount() );

	unsigned char color[4] = { 255, 255, 255, 255 };
	m_WingedEdgeList.Visualize( color );
}


//-----------------------------------------------------------------------------
// Occluder list management
//-----------------------------------------------------------------------------
void COcclusionSystem::ActivateOccluder( int nOccluderIndex, bool bActive )
{
	if ( ( nOccluderIndex >= host_state.worldmodel->brush.numoccluders ) || ( nOccluderIndex < 0 ) )
		return;

	if ( bActive )
	{
		host_state.worldmodel->brush.occluders[nOccluderIndex].flags &= ~OCCLUDER_FLAGS_INACTIVE;
	}
	else
	{
		host_state.worldmodel->brush.occluders[nOccluderIndex].flags |= OCCLUDER_FLAGS_INACTIVE;
	}

	m_bEdgeListDirty = true;
}


void COcclusionSystem::SetView( const Vector &vecCameraPos, float flFOV, const VMatrix &worldToCamera, 
			const VMatrix &cameraToProjection, const VPlane &nearClipPlane )
{
	m_vecCameraPosition = vecCameraPos;
	m_WorldToCamera = worldToCamera;

	// See ComputeScreenSpacePlane() for the use of these constants
	m_flXProjScale = -cameraToProjection[2][3] / cameraToProjection[0][0];
	m_flYProjScale = -cameraToProjection[2][3] / cameraToProjection[1][1];
	m_flProjDistScale = -cameraToProjection[2][3];
	m_flProjDistOffset = -cameraToProjection[2][2];
	MatrixMultiply( cameraToProjection, worldToCamera, m_WorldToProjection );
	m_NearClipPlane.normal = nearClipPlane.m_Normal;
	m_NearClipPlane.dist = nearClipPlane.m_Dist;
	m_NearClipPlane.type = 3;
	m_bEdgeListDirty = true;
	m_flNearPlaneDist = -( DotProduct( vecCameraPos, m_NearClipPlane.normal ) - m_NearClipPlane.dist );
	Assert( m_flNearPlaneDist > 0.0f );
	m_flFOVFactor = m_flNearPlaneDist * tan( flFOV * 0.5f * M_PI / 180.0f );
	m_flFOVFactor = m_flNearPlaneDist / m_flFOVFactor;
	m_flFOVFactor *= m_flFOVFactor;

	if ( r_occlusionspew.GetInt() )
	{
		if ( m_nTests )
		{
			float flPercent = 100.0f * ((float)m_nOccluded / (float)m_nTests);
			Msg("Occl %.2f (%d/%d)\n", flPercent, m_nOccluded, m_nTests );
			m_nTests = 0;
			m_nOccluded = 0;
		}
	}
}


//-----------------------------------------------------------------------------
// Used to build the quads to test for occlusion
//-----------------------------------------------------------------------------
static int s_pFaceIndices[6][4] =
{
	{ 0, 4, 6, 2 },		// -x
	{ 1, 3, 7, 5 },		// +x
	{ 0, 1, 5, 4 },		// -y
	{ 2, 6, 7, 3 },		// +y
	{ 0, 2, 3, 1 },		// -z
	{ 4, 5, 7, 6 },		// +z
};

static int s_pSourceIndices[8] = 
{
	-1, 0, 0, 1, 0, 1, 2, 3  
};

static int s_pDeltaIndices[8] = 
{
	-1, 0, 1, 1, 2, 2, 2, 2
};

static unsigned char s_VisualizationColor[2][4] = 
{ 
	{ 255, 0, 0, 255 }, 
	{ 0, 255, 0, 255 } 
};

struct EdgeInfo_t
{
	unsigned char m_nVert[2];
	unsigned char m_nFace[2];
	int m_nTestCount;
	int m_nMinVert;
};

// NOTE: The face indices here have to very carefully ordered for the algorithm
// to work. They must be ordered so that vert0 -> vert1 is clockwise
// for the first face listed and vert1 -> vert0 is clockwise for the 2nd face listed
static EdgeInfo_t s_pEdges[12] = 
{
	{ 0, 1, 2, 4, 0, 0 },		// 0: Edge between -y + -z
	{ 2, 0, 0, 4, 0, 0 },		// 1: Edge between -x + -z
	{ 1, 3, 1, 4, 0, 0 },		// 2: Edge between +x + -z
	{ 3, 2, 3, 4, 0, 0 },		// 3: Edge between +y + -z
	{ 0, 4, 0, 2, 0, 0 },		// 4: Edge between -x + -y
	{ 5, 1, 1, 2, 0, 0 },		// 5: Edge between +x + -y
	{ 6, 2, 0, 3, 0, 0 },		// 6: Edge between -x + +y
	{ 3, 7, 1, 3, 0, 0 },		// 7: Edge between +x + +y
	{ 5, 4, 2, 5, 0, 0 },		// 8: Edge between -y + +z
	{ 4, 6, 0, 5, 0, 0 },		// 9: Edge between -x + +z
	{ 7, 5, 1, 5, 0, 0 },		// 10:Edge between +x + +z
	{ 6, 7, 3, 5, 0, 0 },		// 11:Edge between +y + +z
};

static int s_pFaceEdges[6][4] = 
{
	{ 4, 9, 6, 1 },
	{ 2, 7, 10, 5 },
	{ 0, 5, 8, 4 },
	{ 6, 11, 7, 3 },
	{ 1, 3, 2, 0 },
	{ 8, 10, 11, 9 },
};


//-----------------------------------------------------------------------------
// Occlusion checks
//-----------------------------------------------------------------------------
static CWingedEdgeList s_WingedTestEdgeList;

static bool WingedEdgeLessFunc( const int& src1, const int& src2, void *pCtx )
{
	Vector *pVertices = (Vector*)pCtx;

	EdgeInfo_t *pEdge1 = &s_pEdges[ src1 ];
	EdgeInfo_t *pEdge2 = &s_pEdges[ src2 ];

	Vector *pV1 = &pVertices[ pEdge1->m_nVert[ pEdge1->m_nMinVert ] ];
	Vector *pV2 = &pVertices[ pEdge2->m_nVert[ pEdge2->m_nMinVert ] ];

	if (pV1->y < pV2->y)
		return true;
	if (pV1->y > pV2->y)
		return false;
	if (pV1->x < pV2->x)
		return true;
	if (pV1->x > pV2->x)
		return false;

	// This is the same as the following line:
//	return (pEdge1->m_flDxDy <= pEdge2->m_flDxDy);
	Vector2D dEdge1, dEdge2;
	Vector2DSubtract( pVertices[ pEdge1->m_nVert[ 1 - pEdge1->m_nMinVert ] ].AsVector2D(), pV1->AsVector2D(), dEdge1 );
	Vector2DSubtract( pVertices[ pEdge2->m_nVert[ 1 - pEdge2->m_nMinVert ] ].AsVector2D(), pV2->AsVector2D(), dEdge2 );
	Assert( dEdge1.y >= 0.0f );
	Assert( dEdge2.y >= 0.0f );

	return dEdge1.x * dEdge2.y <= dEdge1.y * dEdge2.x;
}

bool COcclusionSystem::IsOccluded( const Vector &vecAbsMins, const Vector &vecAbsMaxs )
{
	if ( r_occlusion.GetInt() == 0 )
		return false;

	VPROF_BUDGET( "COcclusionSystem::IsOccluded", "Occlusion" );

	RecomputeOccluderEdgeList();

	// No occluders? Then the edge list isn't occluded
	if ( m_WingedEdgeList.EdgeCount() == 0 )
		return false;

	// Don't occlude things that have large screen area
	// Use a super cheap but inaccurate screen area computation
	Vector vecCenter;
	VectorAdd( vecAbsMaxs, vecAbsMins, vecCenter );
	vecCenter *= 0.5f;

	vecCenter -= m_vecCameraPosition;
	float flDist = DotProduct( m_NearClipPlane.normal, vecCenter );
	if (flDist <= 0.0f)
		return false;

	flDist += m_flNearPlaneDist;

	Vector vecSize;
	VectorSubtract( vecAbsMaxs, vecAbsMins, vecSize );
	float flRadiusSq = DotProduct( vecSize, vecSize ) * 0.25f;

	float flScreenArea = m_flFOVFactor * flRadiusSq / (flDist * flDist);
	float flMaxSize = r_occludeemaxarea.GetFloat() * 0.01f;
	if (flScreenArea >= flMaxSize)
		return false;

	++m_nTests;

	// Clear out its state
	s_WingedTestEdgeList.Clear();

	// NOTE: This assumes that frustum culling has already occurred on this object
	// If that were not the case, we'd need to add a little extra into this 
	// (probably a single plane test, which tests if the box is wholly behind the camera )

	// Convert the bbox into a max of 3 quads...
	const Vector *pCornerVert[2] = { &vecAbsMins, &vecAbsMaxs };

	// Compute the 8 box verts, and transform them into projective space...
	// NOTE: We'd want to project them *after* the plane test if there were
	// no frustum culling.
	int i;
	Vector pVecProjectedVertex[8];

	// NOTE: The code immediately below is an optimized version of this loop
	// The optimization takes advantage of the fact that the verts are all
	// axis aligned.
//	Vector vecBoxVertex;
//	for ( i = 0; i < 8; ++i )
//	{
//		vecBoxVertex.x = pCornerVert[ (i & 0x1) ]->x;
//		vecBoxVertex.y = pCornerVert[ (i & 0x2) >> 1 ]->y;
//		vecBoxVertex.z = pCornerVert[ (i & 0x4) >> 2 ]->z;
//		Vector3DMultiplyPositionProjective( m_WorldToProjection, vecBoxVertex, pVecProjectedVertex[ i ] );
//		if ( pVecProjectedVertex[ i ].z <= 0.0f )
//			return false;
//	}

	Vector4D vecProjVert[8];
	Vector4D vecDeltaProj[3];
	Vector4D vecAbsMins4D( vecAbsMins.x, vecAbsMins.y, vecAbsMins.z, 1.0f );
	Vector4DMultiply( m_WorldToProjection, vecAbsMins4D, vecProjVert[0] );
	if ( vecProjVert[0].w <= 0.0f )
		return false;
	float flOOW = 1.0f / vecProjVert[0].w;

	vecDeltaProj[0].Init( vecSize.x * m_WorldToProjection[0][0], vecSize.x * m_WorldToProjection[1][0],	vecSize.x * m_WorldToProjection[2][0], vecSize.x * m_WorldToProjection[3][0] );
	vecDeltaProj[1].Init( vecSize.y * m_WorldToProjection[0][1], vecSize.y * m_WorldToProjection[1][1],	vecSize.y * m_WorldToProjection[2][1], vecSize.y * m_WorldToProjection[3][1] );
	vecDeltaProj[2].Init( vecSize.z * m_WorldToProjection[0][2], vecSize.z * m_WorldToProjection[1][2],	vecSize.z * m_WorldToProjection[2][2], vecSize.z * m_WorldToProjection[3][2] );

	pVecProjectedVertex[0].Init( vecProjVert[0].x * flOOW, vecProjVert[0].y * flOOW, vecProjVert[0].z * flOOW ); 
	if ( pVecProjectedVertex[0].z <= 0.0f )
		return false;

	for ( i = 1; i < 8; ++i )
	{
		int nIndex = s_pSourceIndices[i];
		int nDelta = s_pDeltaIndices[i];
		Vector4DAdd( vecProjVert[nIndex], vecDeltaProj[nDelta], vecProjVert[i] );
		if ( vecProjVert[ i ].w <= 0.0f )
			return false;
		flOOW = 1.0f / vecProjVert[i].w;
		pVecProjectedVertex[ i ].Init( vecProjVert[i].x * flOOW, vecProjVert[i].y * flOOW, vecProjVert[i].z * flOOW ); 
		if ( pVecProjectedVertex[ i ].z <= 0.0f )
			return false;
	}

	// Precompute stuff needed by the loop over faces below
	float pSign[2] = { -1, 1 };
	Vector vecDelta[2];
	VectorSubtract( *pCornerVert[0], m_vecCameraPosition, vecDelta[0] );
	VectorSubtract( m_vecCameraPosition, *pCornerVert[1], vecDelta[1] );

	// Determine which faces + edges are visible...
	int pSurfInd[6];
	for ( i = 0; i < 6; ++i )
	{
		int nDim = ( i >> 1 );
		int nInd = i & 0x1;

		// Try to backface cull each of the 6 box faces
		if ( vecDelta[nInd][nDim] <= 0.0f )
		{
			pSurfInd[i] = -1;
			continue;
		}

		cplane_t cameraSpacePlane, projectionSpacePlane;
		float flSign = pSign[nInd];
		float flPlaneDist = (*pCornerVert[nInd])[ nDim ] * flSign;
		MatrixTransformAxisAlignedPlane( m_WorldToCamera, nDim, flSign, flPlaneDist, cameraSpacePlane );
		ComputeScreenSpacePlane( cameraSpacePlane, &projectionSpacePlane ); 
		int nSurfID = s_WingedTestEdgeList.AddSurface( projectionSpacePlane );
		pSurfInd[i] = nSurfID;

		// Mark edges as being used...
		int *pFaceEdges = s_pFaceEdges[i];
		s_pEdges[ *pFaceEdges++ ].m_nTestCount = m_nTests;
		s_pEdges[ *pFaceEdges++ ].m_nTestCount = m_nTests;
		s_pEdges[ *pFaceEdges++ ].m_nTestCount = m_nTests;
		s_pEdges[ *pFaceEdges ].m_nTestCount = m_nTests;
	}

	// Sort edges by minimum Y + dx/dy...
	int pEdgeSort[12];
	CUtlSortVector< int > edgeSort( WingedEdgeLessFunc, pEdgeSort, 12 );
	edgeSort.SetLessContext( pVecProjectedVertex );
	for ( i = 0; i < 12; ++i )
	{
		// Skip non-visible edges
		EdgeInfo_t *pEdge = &s_pEdges[i];
		if ( pEdge->m_nTestCount != m_nTests )
			continue;

		pEdge->m_nMinVert = ( pVecProjectedVertex[ pEdge->m_nVert[0] ].y >= pVecProjectedVertex[ pEdge->m_nVert[1] ].y );
		edgeSort.Insert( i );
	}

	// Now add them into the winged edge list, in sorted order...
	int nEdgeCount = edgeSort.Count();
	for ( i = 0; i < nEdgeCount; ++i )
	{
		EdgeInfo_t *pEdge = &s_pEdges[edgeSort[i]];

		// The enter + leave ids depend entirely on which edge is further up
		// This works because the edges listed in s_pEdges show the edges as they
		// would be visited in *clockwise* order
		const Vector &startVert = pVecProjectedVertex[pEdge->m_nVert[pEdge->m_nMinVert]];
		const Vector &endVert = pVecProjectedVertex[pEdge->m_nVert[1 - pEdge->m_nMinVert]];
		int nLeaveSurfID = pSurfInd[ pEdge->m_nFace[pEdge->m_nMinVert] ];
		int nEnterSurfID = pSurfInd[ pEdge->m_nFace[1 - pEdge->m_nMinVert] ];

		s_WingedTestEdgeList.AddEdge( startVert, endVert, nLeaveSurfID, nEnterSurfID );
	}

#ifdef DEBUG_OCCLUSION_SYSTEM
	s_WingedTestEdgeList.CheckConsistency();
#endif

	// Now let's see if this edge list is occluded or not..
	bool bOccluded = m_WingedEdgeList.IsOccludingEdgeList( s_WingedTestEdgeList );
	if (bOccluded)
		++m_nOccluded;

	s_WingedTestEdgeList.Visualize( s_VisualizationColor[bOccluded] );

	return bOccluded;
}

