summaryrefslogtreecommitdiff
path: root/engine/OcclusionSystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/OcclusionSystem.cpp')
-rw-r--r--engine/OcclusionSystem.cpp2957
1 files changed, 2957 insertions, 0 deletions
diff --git a/engine/OcclusionSystem.cpp b/engine/OcclusionSystem.cpp
new file mode 100644
index 0000000..8649163
--- /dev/null
+++ b/engine/OcclusionSystem.cpp
@@ -0,0 +1,2957 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+#include "IOcclusionSystem.h"
+#include "mathlib/vector.h"
+#include "UtlSortVector.h"
+#include "utllinkedlist.h"
+#include "utlvector.h"
+#include "collisionutils.h"
+#include "filesystem.h"
+#include "gl_model_private.h"
+#include "gl_matsysiface.h"
+#include "client.h"
+#include "gl_shader.h"
+#include "materialsystem/imesh.h"
+#include "tier0/vprof.h"
+#include "tier0/icommandline.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.
+//-----------------------------------------------------------------------------
+#ifdef _X360
+#define DEFAULT_MIN_OCCLUDER_AREA 70.0f
+#else
+#define DEFAULT_MIN_OCCLUDER_AREA 5.0f
+#endif
+#define DEFAULT_MAX_OCCLUDEE_AREA 5.0f
+
+
+ConVar r_visocclusion( "r_visocclusion", "0", FCVAR_CHEAT, "Activate/deactivate wireframe rendering of what the occlusion system is doing." );
+ConVar r_occlusion( "r_occlusion", "1", 0, "Activate/deactivate the occlusion system." );
+static ConVar r_occludermincount( "r_occludermincount", "0", 0, "At least this many occluders will be used, no matter how big they are." );
+static ConVar r_occlusionspew( "r_occlusionspew", "0", FCVAR_CHEAT, "Activate/deactivates spew about what the occlusion system is doing." );
+ConVar r_occluderminarea( "r_occluderminarea", "0", 0, "Prevents this occluder from being used if it takes up less than X% of the screen. 0 means use whatever the level said to use." );
+ConVar r_occludeemaxarea( "r_occludeemaxarea", "0", 0, "Prevents occlusion testing for entities that take up more than X% of the screen. 0 means use whatever the level said to use." );
+
+#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
+
+
+//-----------------------------------------------------------------------------
+// Visualization
+//-----------------------------------------------------------------------------
+struct EdgeVisualizationInfo_t
+{
+ Vector m_vecPoint[2];
+ unsigned char m_pColor[4];
+};
+
+//-----------------------------------------------------------------------------
+// Queued up rendering
+//-----------------------------------------------------------------------------
+static CUtlVector<EdgeVisualizationInfo_t> g_EdgeVisualization;
+
+
+//-----------------------------------------------------------------------------
+//
+// 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 );
+
+ // Queues up stuff to visualize
+ void QueueVisualization( unsigned char *pColor );
+
+ // 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();
+
+ // If the occludee is off screen, it's occluded
+ if ( pTestEdge->m_flX >= 1.0f )
+ return true;
+
+ pTestEdge = AdvanceActiveEdgeListToX( pTestEdge, -1.0f );
+
+ // If all occludee edges have x values <= -1.0f, it's occluded
+ if ( testList.AtListEnd( pTestEdge ) )
+ return true;
+
+ // Start at the first edge whose x value is <= -1.0f
+ // if the occludee goes off the left side of the screen.
+ float flNextTestX = pTestEdge->m_flX;
+ if ( !testList.AtListStart( pTestEdge->m_pPrevActiveEdge ) )
+ {
+ // In this case, we should be on a span crossing from x <= -1.0f to x > 1.0f.
+ // Do the first occlusion test at x = -1.0f.
+ Assert( pTestEdge->m_flX > -1.0f );
+ pTestEdge = pTestEdge->m_pPrevActiveEdge;
+ Assert( pTestEdge->m_flX <= -1.0f );
+ flNextTestX = -1.0f;
+ }
+
+ 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_pNextActiveEdge->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;
+ if ( pTestEdge->m_nEnterSurfID >= 0 )
+ {
+ pTestSurf = &testList.m_Surfaces[pTestEdge->m_nEnterSurfID];
+ }
+ else
+ {
+ pTestSurf = (pTestEdge->m_nLeaveSurfID >= 0) ? &testList.m_Surfaces[pTestEdge->m_nLeaveSurfID] : &m_BackSurface;
+ }
+ flNextTestX = pTestEdge->m_pNextActiveEdge->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;
+
+ // If we got outside the screen bounds, then it's occluded!
+ if ( flCurrentY >= 1.0f )
+ return true;
+
+ float flTestY = testList.NextDiscontinuity();
+ float flOccluderY = NextDiscontinuity();
+ flCurrentY = min( flTestY, flOccluderY );
+
+ // NOTE: This check here is to help occlusion @ the top of the screen
+ // We cut the occluders off at y = 1.0 + epsilon, which means there's
+ // not necessarily a discontinuity at y == 1.0. We need to create a discontinuity
+ // there so that the occluder edges are still being used.
+ if ( flCurrentY > 1.0f )
+ {
+ flCurrentY = 1.0f;
+ }
+
+ // 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;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Queues up stuff to visualize
+//-----------------------------------------------------------------------------
+void CWingedEdgeList::QueueVisualization( unsigned char *pColor )
+{
+#ifndef SWDS
+ if ( !r_visocclusion.GetInt() )
+ return;
+
+ int nFirst = g_EdgeVisualization.AddMultipleToTail( m_WingedEdges.Count() );
+ for ( int i = m_WingedEdges.Count(); --i >= 0; )
+ {
+ WingedEdge_t *pEdge = &m_WingedEdges[i];
+ EdgeVisualizationInfo_t &info = g_EdgeVisualization[nFirst + i];
+ info.m_vecPoint[0] = pEdge->m_vecPosition;
+ info.m_vecPoint[1] = pEdge->m_vecPositionEnd;
+ *(int*)(info.m_pColor) = *(int*)pColor;
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Renders the winged edge list for debugging
+//-----------------------------------------------------------------------------
+void CWingedEdgeList::Visualize( unsigned char *pColor )
+{
+#ifndef SWDS
+ if ( !r_visocclusion.GetInt() )
+ return;
+
+ CMatRenderContextPtr pRenderContext( materials );
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->Bind( g_pMaterialWireframeVertexColorIgnoreZ );
+
+ IMesh *pMesh = pRenderContext->GetDynamicMesh( );
+ CMeshBuilder meshBuilder;
+ meshBuilder.Begin( pMesh, MATERIAL_LINES, m_WingedEdges.Count() );
+
+ int i;
+ int nCount = m_WingedEdges.Count();
+ for ( i = nCount; --i >= 0; )
+ {
+ WingedEdge_t *pEdge = &m_WingedEdges[i];
+ meshBuilder.Position3fv( pEdge->m_vecPosition.Base() );
+ meshBuilder.Color4ubv( pColor );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3fv( pEdge->m_vecPositionEnd.Base() );
+#ifdef DEBUG_OCCLUSION_SYSTEM
+ meshBuilder.Color4ub( 0, 0, 255, 255 );
+#else
+ meshBuilder.Color4ubv( pColor );
+#endif
+ meshBuilder.AdvanceVertex();
+ }
+
+ meshBuilder.End();
+ pMesh->Draw();
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->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 QueueVisualization( unsigned char *pColor );
+ 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;
+ };
+
+ enum
+ {
+ MAX_EDGE_CROSSINGS = 64
+ };
+
+ 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, float flPrevY, int &nCount, Edge_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( int nCount, Edge_t **ppInfo );
+
+ // Debugging spew
+ void SpewActiveEdgeList( float y, bool bHex = false );
+
+ // Checks consistency of the edge list...
+ void CheckConsistency();
+
+ class EdgeLess
+ {
+ public:
+ bool Less( const unsigned short& src1, const unsigned short& src2, void *pCtx );
+ };
+
+ static int __cdecl 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, EdgeLess > 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::EdgeLess::Less( 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( 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 __cdecl 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;
+ if ( flMinScreenArea == 0.0f )
+ {
+ flMinScreenArea = OcclusionSystem()->MinOccluderArea() * 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;
+ }
+
+ MEM_ALLOC_CREDIT();
+
+ 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, float flPrevY, int &nCount, Edge_t **ppInfo )
+{
+ nCount = 0;
+ float flCurrX = -FLT_MAX;
+ float flNextX = -FLT_MAX;
+ float flCurrY = flNextY;
+
+ Vector2D vecDelta, vecIntersection;
+
+ 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...
+ // NOTE: The optimizer unfortunately causes this test to not return ==
+ // if the bitpattern of flYCrossing and flNextY are the exact same, because it's
+ // doing the test with the 80bit fp registers. flYCrossing is still sitting in the register
+ // from the computation on the line above, but flNextY isn't. Therefore it returns not equal.
+ // That's why I have to do the explicit bitpattern check.
+ if ( ( flYCrossing >= flNextY ) || ( *(int*)&flYCrossing == *(int*)&flNextY ) )
+ continue;
+
+ if ( flYCrossing < flPrevY )
+ {
+ flYCrossing = flPrevY;
+ }
+
+ // If we advanced in Y, then reset the edge crossings
+ if ( flCurrY != flYCrossing )
+ {
+ flCurrY = flYCrossing;
+ nCount = 0;
+ }
+
+ Assert( nCount < MAX_EDGE_CROSSINGS );
+ flNextX = pPrevEdge->m_vecPosition.x + t * pPrevEdge->m_flDxDy;
+ ppInfo[nCount++] = pCurEdge;
+ }
+ 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( int nCount, Edge_t **ppCrossings )
+{
+ int nCurCrossing = 0;
+ while ( nCurCrossing < nCount )
+ {
+ // 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 *pCurCrossing = ppCrossings[nCurCrossing++];
+ Edge_t *pFirstCrossing = pCurCrossing->m_pPrevActiveEdge;
+
+ // First, bring shared (or nearly shared) edges into the crossing list...
+ while ( pFirstCrossing->m_pPrevActiveEdge->m_flX == pFirstCrossing->m_flX )
+ {
+ pFirstCrossing = pFirstCrossing->m_pPrevActiveEdge;
+ }
+
+ // Find the last crossing...
+ Edge_t *pLastCrossing = pCurCrossing->m_pNextActiveEdge;
+ Edge_t *pPrevCrossing = pCurCrossing;
+ while ( true )
+ {
+ if ( (nCurCrossing < nCount) && (pLastCrossing == ppCrossings[nCurCrossing]) )
+ {
+ pPrevCrossing = pLastCrossing;
+ pLastCrossing = pLastCrossing->m_pNextActiveEdge;
+ ++nCurCrossing;
+ continue;
+ }
+
+ if ( pPrevCrossing->m_flX != pLastCrossing->m_flX )
+ break;
+
+ pLastCrossing = pLastCrossing->m_pNextActiveEdge;
+ }
+
+ // This should always be true, since there's always an edge at FLT_MAX.
+ Assert( pLastCrossing );
+
+ // 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;
+ for ( Edge_t *pCrossing = pFirstCrossing->m_pNextActiveEdge; pCrossing != pLastCrossing; pCrossing = pCrossing->m_pNextActiveEdge )
+ {
+ pCrossing->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 );
+ }
+
+ Edge_t *pEdgeCrossings[MAX_EDGE_CROSSINGS];
+ 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 = fpmax( -1.0f, flPrevY );
+
+ m_flNextDiscontinuity = FLT_MAX;
+ IntroduceNewActiveEdges( flPrevY );
+
+ int nEdgeCrossingCount = 0;
+ bool bDone = false;
+ while( !bDone )
+ {
+ // Don't immediately progress to the next discontinuity if there are edge crossings.
+ float flNextY = LocateEdgeCrossingDiscontinuity( NextDiscontinuity(), flPrevY, nEdgeCrossingCount, pEdgeCrossings );
+
+#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;
+
+ // Add a small epsilon so we occlude things on the top edge at y = 1.0
+ if (flNextY >= 1.001f)
+ {
+ flNextY = 1.001f;
+ 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 ( !nEdgeCrossingCount )
+ {
+ IntroduceNewActiveEdges( flNextY );
+
+ // Keep advancing the active edge list until it's got no more discontinuities
+ if ( NextDiscontinuity() == FLT_MAX )
+ return;
+ }
+ else
+ {
+ ReorderActiveEdgeList( nEdgeCrossingCount, pEdgeCrossings );
+
+ // 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::QueueVisualization( unsigned char *pColor )
+{
+#ifndef SWDS
+ if ( !r_visocclusion.GetInt() )
+ return;
+
+ int nFirst = g_EdgeVisualization.AddMultipleToTail( m_Edges.Count() );
+ for ( int i = m_Edges.Count(); --i >= 0; )
+ {
+ EdgeVisualizationInfo_t &info = g_EdgeVisualization[nFirst + i];
+ info.m_vecPoint[0] = m_Edges[i].m_vecPosition;
+ info.m_vecPoint[1] = m_Edges[i].m_vecPositionEnd;
+ *(int*)(info.m_pColor) = *(int*)pColor;
+ }
+#endif
+}
+
+
+void CEdgeList::Visualize( unsigned char *pColor )
+{
+#ifndef SWDS
+ if ( !r_visocclusion.GetInt() )
+ return;
+
+ CMatRenderContextPtr pRenderContext( materials );
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->Bind( g_pMaterialWireframeVertexColorIgnoreZ );
+
+ IMesh *pMesh = pRenderContext->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();
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->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 );
+ virtual void SetOcclusionParameters( float flMaxOccludeeArea, float flMinOccluderArea );
+ virtual float MinOccluderArea() const;
+ virtual void DrawDebugOverlays();
+
+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;
+
+ float m_flMaxOccludeeArea;
+ float m_flMinOccluderArea;
+
+ // 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;
+ m_flMinOccluderArea = DEFAULT_MIN_OCCLUDER_AREA;
+ m_flMaxOccludeeArea = DEFAULT_MAX_OCCLUDEE_AREA;
+}
+
+COcclusionSystem::~COcclusionSystem()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Occlusion parameters?
+//-----------------------------------------------------------------------------
+void COcclusionSystem::SetOcclusionParameters( float flMaxOccludeeArea, float flMinOccluderArea )
+{
+ m_flMaxOccludeeArea = (flMaxOccludeeArea ? flMaxOccludeeArea : DEFAULT_MAX_OCCLUDEE_AREA) * 0.01f;
+ m_flMinOccluderArea = (flMinOccluderArea ? flMinOccluderArea : DEFAULT_MIN_OCCLUDER_AREA);
+}
+
+float COcclusionSystem::MinOccluderArea() const
+{
+ return m_flMinOccluderArea;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 )
+ {
+ MEM_ALLOC_CREDIT();
+ // 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-3 );
+ if ( flTriArea < 0 )
+ {
+ 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;
+
+ // Tracker 17772: If building cubemaps can end up calling into here w/o cl.pAreaBits setup yet, oh well.
+ if ( !cl.m_bAreaBitsValid && CommandLine()->FindParm( "-buildcubemaps" ) )
+ return;
+
+ m_bEdgeListDirty = false;
+ m_EdgeList.RemoveAll();
+ m_WingedEdgeList.Clear();
+ m_ClippedVerts.RemoveAll();
+
+ mvertex_t *pVertices = host_state.worldbrush->vertexes;
+ int *pIndices = host_state.worldbrush->occludervertindices;
+ doccluderdata_t *pOccluders = host_state.worldbrush->occluders;
+
+ int i, j, k;
+ for ( i = host_state.worldbrush->numoccluders ; --i >= 0; )
+ {
+ if ( pOccluders[i].flags & OCCLUDER_FLAGS_INACTIVE )
+ continue;
+
+ // Skip the occluder if it's in a disconnected area
+ if ( cl.m_chAreaBits &&
+ (cl.m_chAreaBits[pOccluders[i].area >> 3] & (1 << ( pOccluders[i].area & 0x7 )) ) == 0 )
+ continue;
+
+ int nSurfID = pOccluders[i].firstpoly;
+ int nSurfCount = pOccluders[i].polycount;
+ for ( j = 0; j < nSurfCount; ++j, ++nSurfID )
+ {
+ doccluderpolydata_t *pSurf = &host_state.worldbrush->occluderpolys[nSurfID];
+
+ int nFirstVertexIndex = pSurf->firstvertexindex;
+ int nVertexCount = pSurf->vertexcount;
+
+ // If the surface is backfacing, blow it off...
+ const cplane_t &surfPlane = host_state.worldbrush->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() );
+
+ // Draw the occluders
+ unsigned char color[4] = { 255, 255, 255, 255 };
+ m_WingedEdgeList.QueueVisualization( color );
+}
+
+
+//-----------------------------------------------------------------------------
+// Occluder list management
+//-----------------------------------------------------------------------------
+void COcclusionSystem::ActivateOccluder( int nOccluderIndex, bool bActive )
+{
+ if ( ( nOccluderIndex >= host_state.worldbrush->numoccluders ) || ( nOccluderIndex < 0 ) )
+ return;
+
+ if ( bActive )
+ {
+ host_state.worldbrush->occluders[nOccluderIndex].flags &= ~OCCLUDER_FLAGS_INACTIVE;
+ }
+ else
+ {
+ host_state.worldbrush->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;
+
+class WingedEdgeLessFunc
+{
+public:
+ bool Less( 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", VPROF_BUDGETGROUP_OCCLUSION );
+
+ // @MULTICORE (toml 9/11/2006): need to eliminate this mutex
+ static CThreadFastMutex mutex;
+ AUTO_LOCK( mutex );
+
+ 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 ( flMaxSize == 0.0f )
+ {
+ flMaxSize = m_flMaxOccludeeArea;
+ }
+ if (flScreenArea >= flMaxSize)
+ return false;
+
+ // 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...
+ ++m_nTests;
+ 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[0] ].m_nTestCount = m_nTests;
+ s_pEdges[ pFaceEdges[1] ].m_nTestCount = m_nTests;
+ s_pEdges[ pFaceEdges[2] ].m_nTestCount = m_nTests;
+ s_pEdges[ pFaceEdges[3] ].m_nTestCount = m_nTests;
+ }
+
+ // Sort edges by minimum Y + dx/dy...
+ int pEdgeSort[12];
+ CUtlSortVector< int, WingedEdgeLessFunc > edgeSort( 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.QueueVisualization( s_VisualizationColor[bOccluded] );
+
+ return bOccluded;
+}
+
+
+//-----------------------------------------------------------------------------
+// Used to debug the occlusion system
+//-----------------------------------------------------------------------------
+void VisualizeQueuedEdges( )
+{
+#ifndef SWDS
+ if ( !g_EdgeVisualization.Count() )
+ return;
+
+ CMatRenderContextPtr pRenderContext( materials );
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->Bind( g_pMaterialWireframeVertexColorIgnoreZ );
+
+ IMesh *pMesh = pRenderContext->GetDynamicMesh( );
+ CMeshBuilder meshBuilder;
+ meshBuilder.Begin( pMesh, MATERIAL_LINES, g_EdgeVisualization.Count() );
+
+ int i;
+ for ( i = g_EdgeVisualization.Count(); --i >= 0; )
+ {
+ EdgeVisualizationInfo_t &info = g_EdgeVisualization[i];
+
+ meshBuilder.Position3fv( info.m_vecPoint[0].Base() );
+ meshBuilder.Color4ubv( info.m_pColor );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Position3fv( info.m_vecPoint[1].Base() );
+#ifdef DEBUG_OCCLUSION_SYSTEM
+ meshBuilder.Color4ub( 0, 0, 255, 255 );
+#else
+ meshBuilder.Color4ubv( info.m_pColor );
+#endif
+ meshBuilder.AdvanceVertex();
+ }
+
+ meshBuilder.End();
+ pMesh->Draw();
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PopMatrix();
+
+ g_EdgeVisualization.RemoveAll();
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Render debugging overlay
+//-----------------------------------------------------------------------------
+void COcclusionSystem::DrawDebugOverlays()
+{
+ // Draw the occludees
+ VisualizeQueuedEdges();
+}