//===== Copyright © 1996-2005, 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 _GAMECONSOLE #define DEFAULT_MIN_OCCLUDER_AREA 70.0f #else #define DEFAULT_MIN_OCCLUDER_AREA 5.0f #endif #define DEFAULT_MAX_OCCLUDEE_AREA 5.0f #ifdef _GAMECONSOLE #define DEFAULT_OCCLUSION_STATE "0" #else #define DEFAULT_OCCLUSION_STATE "1" #endif // Used by ViewData ring buffer #define OCCLUSION_SYSTEM_VIEWDATA_MAX 32 ConVar r_visocclusion( "r_visocclusion", "0", FCVAR_CHEAT, "Activate/deactivate wireframe rendering of what the occlusion system is doing." ); ConVar r_occlusion( "r_occlusion", DEFAULT_OCCLUSION_STATE, 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 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 DEDICATED 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 DEDICATED 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 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 m_OrigSortIndices; CUtlVector 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 DEDICATED 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 DEDICATED 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 int GetViewId() const; virtual bool IsOccluded( int occlusionViewId, 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; }; struct ViewData_t { 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; }; // Recomputes the edge list for occluders void RecomputeOccluderEdgeList( ViewData_t& viewData ); // Is the point inside the near plane? bool IsPointInsideNearPlane( const ViewData_t& viewData, const Vector &vecPos ) const; void IntersectWithNearPlane( const ViewData_t& viewData, const Vector &vecStart, const Vector &vecEnd, Vector &outPos ) const; // Clips a polygon to the near clip plane int ClipPolygonToNearPlane( const ViewData_t& viewData, Vector **ppVertices, int nVertexCount, Vector **ppOutVerts, bool *pClipped ) const; // Project world-space verts + add into the edge list void AddPolygonToEdgeList( const ViewData_t& viewData, 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 ViewData_t& viewData, 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 int m_nCurrentViewId; // ring buffer ViewData_t m_viewData[OCCLUSION_SYSTEM_VIEWDATA_MAX]; 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_nCurrentViewId = 0; for (int i = 0; i < OCCLUSION_SYSTEM_VIEWDATA_MAX; ++i) { m_viewData[i].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 ViewData_t& viewData, const Vector &vecPos ) const { return DotProduct( vecPos, viewData.m_NearClipPlane.normal ) >= viewData.m_NearClipPlane.dist; } inline void COcclusionSystem::IntersectWithNearPlane( const ViewData_t& viewData, const Vector &vecStart, const Vector &vecEnd, Vector &outPos ) const { Vector vecDir; VectorSubtract( vecEnd, vecStart, vecDir ); float t = IntersectRayWithPlane( vecStart, vecDir, viewData.m_NearClipPlane.normal, viewData.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( const ViewData_t& viewData, 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( viewData, *pStart ); for ( int i = 0; i < nVertexCount; ++i ) { Vector* pEnd = ppVertices[ i ]; bool bEndInside = IsPointInsideNearPlane( viewData, *pEnd ); if (bEndInside) { if (!bStartInside) { // Started outside, ended inside, need to clip the edge ppOutVerts[nOutVertCount] = &s_TempVertMemory[ nNewVertCount++ ]; IntersectWithNearPlane( viewData, *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( viewData, *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 ViewData_t& viewData, 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 * viewData.m_flXProjScale; pScreenSpacePlane->normal.y = cameraSpacePlane.normal.y * ooD * viewData.m_flYProjScale; pScreenSpacePlane->normal.z = 1; pScreenSpacePlane->dist = cameraSpacePlane.normal.z * ooD * viewData.m_flProjDistScale + viewData.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( const ViewData_t& viewData, 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( viewData.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( ViewData_t& viewData ) { #ifndef DEDICATED if ( !viewData.m_bEdgeListDirty ) return; // Tracker 17772: If building cubemaps can end up calling into here w/o GetBaseLocalClient().pAreaBits setup yet, oh well. if ( !GetBaseLocalClient().m_bAreaBitsValid && ( CommandLine()->FindParm( "-buildcubemaps" ) || CommandLine()->FindParm( "-buildmodelforworld" ) ) ) return; viewData.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 ( GetBaseLocalClient().m_chAreaBits && (GetBaseLocalClient().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, viewData.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( viewData, ppSurfVerts, nVertexCount, ppClipVerts, &bClipped ); Assert( nClipCount <= ( nVertexCount * 2 ) ); if ( nClipCount < 3 ) continue; cplane_t projectionSpacePlane; cplane_t cameraSpacePlane; MatrixTransformPlane( viewData.m_WorldToCamera, surfPlane, cameraSpacePlane ); ComputeScreenSpacePlane( viewData, 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( viewData, 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 ); #endif } //----------------------------------------------------------------------------- // 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; } for ( int i = 0; i < OCCLUSION_SYSTEM_VIEWDATA_MAX; ++i ) { m_viewData[i].m_bEdgeListDirty = true; } } void COcclusionSystem::SetView( const Vector &vecCameraPos, float flFOV, const VMatrix &worldToCamera, const VMatrix &cameraToProjection, const VPlane &nearClipPlane ) { m_nCurrentViewId = ( m_nCurrentViewId + 1 ) % OCCLUSION_SYSTEM_VIEWDATA_MAX; ViewData_t* pActiveView = &m_viewData[m_nCurrentViewId]; pActiveView->m_vecCameraPosition = vecCameraPos; pActiveView->m_WorldToCamera = worldToCamera; // See ComputeScreenSpacePlane() for the use of these constants pActiveView->m_flXProjScale = -cameraToProjection[2][3] / cameraToProjection[0][0]; pActiveView->m_flYProjScale = -cameraToProjection[2][3] / cameraToProjection[1][1]; pActiveView->m_flProjDistScale = -cameraToProjection[2][3]; pActiveView->m_flProjDistOffset = -cameraToProjection[2][2]; MatrixMultiply( cameraToProjection, worldToCamera, pActiveView->m_WorldToProjection ); pActiveView->m_NearClipPlane.normal = nearClipPlane.m_Normal; pActiveView->m_NearClipPlane.dist = nearClipPlane.m_Dist; pActiveView->m_NearClipPlane.type = 3; pActiveView->m_bEdgeListDirty = true; pActiveView->m_flNearPlaneDist = -( DotProduct( vecCameraPos, pActiveView->m_NearClipPlane.normal ) - pActiveView->m_NearClipPlane.dist ); // Due to FP precision issues this value can sometimes drop slightly below 0.0f (during CSM shadow rendering). Assert( pActiveView->m_flNearPlaneDist > -0.125f ); pActiveView->m_flNearPlaneDist = MAX( pActiveView->m_flNearPlaneDist, 0.0f ); pActiveView->m_flFOVFactor = pActiveView->m_flNearPlaneDist * tan( flFOV * 0.5f * M_PI / 180.0f ); pActiveView->m_flFOVFactor = pActiveView->m_flNearPlaneDist / pActiveView->m_flFOVFactor; pActiveView->m_flFOVFactor *= pActiveView->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; } } } int COcclusionSystem::GetViewId() const { return m_nCurrentViewId; } //----------------------------------------------------------------------------- // 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( int occlusionViewId, const Vector &vecAbsMins, const Vector &vecAbsMaxs ) { if ( r_occlusion.GetInt() == 0 ) return false; if ( occlusionViewId < 0 || occlusionViewId >= OCCLUSION_SYSTEM_VIEWDATA_MAX ) return false; ViewData_t* pActiveView = &m_viewData[occlusionViewId]; VPROF_BUDGET( "COcclusionSystem::IsOccluded", VPROF_BUDGETGROUP_OCCLUSION ); // @MULTICORE (toml 9/11/2006): need to eliminate this mutex static CThreadFastMutex mutex; AUTO_LOCK( mutex ); RecomputeOccluderEdgeList( *pActiveView ); // 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 -= pActiveView->m_vecCameraPosition; float flDist = DotProduct( pActiveView->m_NearClipPlane.normal, vecCenter ); if (flDist <= 0.0f) return false; flDist += pActiveView->m_flNearPlaneDist; Vector vecSize; VectorSubtract( vecAbsMaxs, vecAbsMins, vecSize ); float flRadiusSq = DotProduct( vecSize, vecSize ) * 0.25f; float flScreenArea = pActiveView->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( pActiveView->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 * pActiveView->m_WorldToProjection[0][0], vecSize.x * pActiveView->m_WorldToProjection[1][0], vecSize.x * pActiveView->m_WorldToProjection[2][0], vecSize.x * pActiveView->m_WorldToProjection[3][0] ); vecDeltaProj[1].Init( vecSize.y * pActiveView->m_WorldToProjection[0][1], vecSize.y * pActiveView->m_WorldToProjection[1][1], vecSize.y * pActiveView->m_WorldToProjection[2][1], vecSize.y * pActiveView->m_WorldToProjection[3][1] ); vecDeltaProj[2].Init( vecSize.z * pActiveView->m_WorldToProjection[0][2], vecSize.z * pActiveView->m_WorldToProjection[1][2], vecSize.z * pActiveView->m_WorldToProjection[2][2], vecSize.z * pActiveView->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], pActiveView->m_vecCameraPosition, vecDelta[0] ); VectorSubtract( pActiveView->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( pActiveView->m_WorldToCamera, nDim, flSign, flPlaneDist, cameraSpacePlane ); ComputeScreenSpacePlane( *pActiveView, 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 DEDICATED 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(); }