You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2959 lines
93 KiB
2959 lines
93 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//===========================================================================// |
|
|
|
#include "IOcclusionSystem.h" |
|
#include "mathlib/vector.h" |
|
#include "UtlSortVector.h" |
|
#include "utllinkedlist.h" |
|
#include "utlvector.h" |
|
#include "collisionutils.h" |
|
#include "filesystem.h" |
|
#include "gl_model_private.h" |
|
#include "gl_matsysiface.h" |
|
#include "client.h" |
|
#include "gl_shader.h" |
|
#include "materialsystem/imesh.h" |
|
#include "tier0/vprof.h" |
|
#include "tier0/icommandline.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// Uncomment this if you want to get a whole bunch of paranoid error checking |
|
// #define DEBUG_OCCLUSION_SYSTEM 1 |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to visualizes what the occlusion system is doing. |
|
//----------------------------------------------------------------------------- |
|
#ifdef _X360 |
|
#define DEFAULT_MIN_OCCLUDER_AREA 70.0f |
|
#else |
|
#define DEFAULT_MIN_OCCLUDER_AREA 5.0f |
|
#endif |
|
#define DEFAULT_MAX_OCCLUDEE_AREA 5.0f |
|
|
|
|
|
ConVar r_visocclusion( "r_visocclusion", "0", FCVAR_CHEAT, "Activate/deactivate wireframe rendering of what the occlusion system is doing." ); |
|
ConVar r_occlusion( "r_occlusion", "1", 0, "Activate/deactivate the occlusion system." ); |
|
static ConVar r_occludermincount( "r_occludermincount", "0", 0, "At least this many occluders will be used, no matter how big they are." ); |
|
static ConVar r_occlusionspew( "r_occlusionspew", "0", FCVAR_CHEAT, "Activate/deactivates spew about what the occlusion system is doing." ); |
|
ConVar r_occluderminarea( "r_occluderminarea", "0", 0, "Prevents this occluder from being used if it takes up less than X% of the screen. 0 means use whatever the level said to use." ); |
|
ConVar r_occludeemaxarea( "r_occludeemaxarea", "0", 0, "Prevents occlusion testing for entities that take up more than X% of the screen. 0 means use whatever the level said to use." ); |
|
|
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
|
|
static ConVar r_occtest( "r_occtest", "0" ); |
|
|
|
// Set this in the debugger to activate debugging spew |
|
bool s_bSpew = false; |
|
|
|
#endif // DEBUG_OCCLUSION_SYSTEM |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Visualization |
|
//----------------------------------------------------------------------------- |
|
struct EdgeVisualizationInfo_t |
|
{ |
|
Vector m_vecPoint[2]; |
|
unsigned char m_pColor[4]; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Queued up rendering |
|
//----------------------------------------------------------------------------- |
|
static CUtlVector<EdgeVisualizationInfo_t> g_EdgeVisualization; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Edge list that's fast to iterate over, fast to insert into |
|
// |
|
//----------------------------------------------------------------------------- |
|
class CWingedEdgeList |
|
{ |
|
public: |
|
struct WingedEdge_t |
|
{ |
|
Vector m_vecPosition; // of the upper point in y, measured in screen space |
|
Vector m_vecPositionEnd; // of the lower point in y, measured in screen space |
|
float m_flDxDy; // Change in x per unit in y. |
|
float m_flOODy; |
|
float m_flX; |
|
short m_nLeaveSurfID; // Unique index of the surface this is a part of |
|
short m_nEnterSurfID; // Unique index of the surface this is a part of |
|
WingedEdge_t *m_pPrevActiveEdge; |
|
WingedEdge_t *m_pNextActiveEdge; |
|
}; |
|
|
|
public: |
|
CWingedEdgeList(); |
|
|
|
// Clears out the edge list |
|
void Clear(); |
|
|
|
// Iteration |
|
int EdgeCount() const; |
|
WingedEdge_t &WingedEdge( int i ); |
|
|
|
// Adds an edge |
|
int AddEdge( ); |
|
int AddEdge( const Vector &vecStartVert, const Vector &vecEndVert, int nLeaveSurfID, int nEnterSurfID ); |
|
|
|
// Adds a surface |
|
int AddSurface( const cplane_t &plane ); |
|
|
|
// Does this edge list occlude another winged edge list? |
|
bool IsOccludingEdgeList( CWingedEdgeList &testList ); |
|
|
|
// Queues up stuff to visualize |
|
void QueueVisualization( unsigned char *pColor ); |
|
|
|
// Renders the winged edge list |
|
void Visualize( unsigned char *pColor ); |
|
|
|
// Checks consistency of the edge list... |
|
void CheckConsistency(); |
|
|
|
private: |
|
struct Surface_t |
|
{ |
|
cplane_t m_Plane; // measured in projection space |
|
}; |
|
|
|
private: |
|
// Active edges... |
|
WingedEdge_t *FirstActiveEdge( ); |
|
WingedEdge_t *LastActiveEdge( ); |
|
bool AtListEnd( const WingedEdge_t* pEdge ) const; |
|
bool AtListStart( const WingedEdge_t* pEdge ) const; |
|
void LinkActiveEdgeAfter( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge ); |
|
void UnlinkActiveEdge( WingedEdge_t *pEdge ); |
|
|
|
// Used to insert an edge into the active edge list |
|
bool IsEdgeXGreater( const WingedEdge_t *pEdge1, const WingedEdge_t *pEdge2 ); |
|
|
|
// Clears the active edge list |
|
void ResetActiveEdgeList(); |
|
|
|
// Spew active edge list |
|
void SpewActiveEdgeList( float y, bool bHex = false ); |
|
|
|
// Inserts an edge into the active edge list, sorted by X |
|
void InsertActiveEdge( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge ); |
|
|
|
// Returns true if this active edge list occludes another active edge list |
|
bool IsOccludingActiveEdgeList( CWingedEdgeList &testList, float y ); |
|
|
|
// Advances the X values of the active edge list, with no reordering |
|
bool AdvanceActiveEdgeList( float flCurrY ); |
|
|
|
// Advance the active edge list until a particular X value is reached. |
|
WingedEdge_t *AdvanceActiveEdgeListToX( WingedEdge_t *pEdge, float x ); |
|
|
|
// Returns the z value of a surface given and x,y coordinate |
|
float ComputeZValue( const Surface_t *pSurface, float x, float y ) const; |
|
|
|
// Returns the next time in Y the edge list will undergo a change |
|
float NextDiscontinuity() const; |
|
|
|
private: |
|
// Active Edge list... |
|
WingedEdge_t m_StartTerminal; |
|
WingedEdge_t m_EndTerminal; |
|
|
|
// Back surface... |
|
Surface_t m_BackSurface; |
|
|
|
// Next discontinuity.. |
|
float m_flNextDiscontinuity; |
|
int m_nCurrentEdgeIndex; |
|
|
|
CUtlVector< WingedEdge_t > m_WingedEdges; |
|
CUtlVector< Surface_t > m_Surfaces; |
|
}; |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CWingedEdgeList::CWingedEdgeList() : m_WingedEdges( 0, 64 ) |
|
{ |
|
m_StartTerminal.m_vecPosition.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); |
|
m_StartTerminal.m_vecPositionEnd.Init( -FLT_MAX, FLT_MAX, -FLT_MAX ); |
|
m_StartTerminal.m_nLeaveSurfID = -1; |
|
m_StartTerminal.m_nEnterSurfID = -1; |
|
m_StartTerminal.m_pPrevActiveEdge = NULL; |
|
m_StartTerminal.m_pNextActiveEdge = NULL; |
|
m_StartTerminal.m_flDxDy = 0.0f; |
|
m_StartTerminal.m_flOODy = 0.0f; |
|
m_StartTerminal.m_flX = -FLT_MAX; |
|
|
|
m_EndTerminal.m_vecPosition.Init( FLT_MAX, -FLT_MAX, -FLT_MAX ); |
|
m_EndTerminal.m_vecPositionEnd.Init( FLT_MAX, FLT_MAX, -FLT_MAX ); |
|
m_EndTerminal.m_nLeaveSurfID = -1; |
|
m_EndTerminal.m_nEnterSurfID = -1; |
|
m_EndTerminal.m_pPrevActiveEdge = NULL; |
|
m_EndTerminal.m_pNextActiveEdge = NULL; |
|
m_EndTerminal.m_flDxDy = 0.0f; |
|
m_EndTerminal.m_flOODy = 0.0f; |
|
m_EndTerminal.m_flX = FLT_MAX; |
|
|
|
m_BackSurface.m_Plane.normal.Init( 0, 0, 1 ); |
|
m_BackSurface.m_Plane.dist = FLT_MAX; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Renders the winged edge list for debugging |
|
//----------------------------------------------------------------------------- |
|
void CWingedEdgeList::Clear() |
|
{ |
|
m_WingedEdges.RemoveAll(); |
|
m_Surfaces.RemoveAll(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Iterate over the winged edges |
|
//----------------------------------------------------------------------------- |
|
inline int CWingedEdgeList::EdgeCount() const |
|
{ |
|
return m_WingedEdges.Count(); |
|
} |
|
|
|
inline CWingedEdgeList::WingedEdge_t &CWingedEdgeList::WingedEdge( int i ) |
|
{ |
|
return m_WingedEdges[i]; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds new edges |
|
//----------------------------------------------------------------------------- |
|
inline int CWingedEdgeList::AddEdge( ) |
|
{ |
|
int i = m_WingedEdges.AddToTail(); |
|
|
|
WingedEdge_t &newEdge = m_WingedEdges[i]; |
|
newEdge.m_pPrevActiveEdge = NULL; |
|
newEdge.m_pNextActiveEdge = NULL; |
|
|
|
return i; |
|
} |
|
|
|
int CWingedEdgeList::AddEdge( const Vector &vecStartVert, const Vector &vecEndVert, int nLeaveSurfID, int nEnterSurfID ) |
|
{ |
|
// This is true if we've clipped to the near clip plane |
|
Assert( (vecStartVert.z >= 0.0) && (vecEndVert.z >= 0.0) ); |
|
|
|
// Don't bother adding edges with dy == 0 |
|
float dy; |
|
dy = vecEndVert.y - vecStartVert.y; |
|
if (dy == 0.0f) |
|
return -1; |
|
|
|
int i = m_WingedEdges.AddToTail(); |
|
WingedEdge_t &newEdge = m_WingedEdges[i]; |
|
|
|
newEdge.m_flOODy = 1.0f / dy; |
|
newEdge.m_nLeaveSurfID = nLeaveSurfID; |
|
newEdge.m_nEnterSurfID = nEnterSurfID; |
|
newEdge.m_vecPosition = vecStartVert; |
|
newEdge.m_vecPositionEnd = vecEndVert; |
|
newEdge.m_pPrevActiveEdge = NULL; |
|
newEdge.m_pNextActiveEdge = NULL; |
|
newEdge.m_flDxDy = (vecEndVert.x - vecStartVert.x) * newEdge.m_flOODy; |
|
|
|
return i; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds new surfaces |
|
//----------------------------------------------------------------------------- |
|
int CWingedEdgeList::AddSurface( const cplane_t &plane ) |
|
{ |
|
int i = m_Surfaces.AddToTail(); |
|
m_Surfaces[i].m_Plane = plane; |
|
return i; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Active edges... |
|
//----------------------------------------------------------------------------- |
|
inline CWingedEdgeList::WingedEdge_t *CWingedEdgeList::FirstActiveEdge( ) |
|
{ |
|
return m_StartTerminal.m_pNextActiveEdge; |
|
} |
|
|
|
inline CWingedEdgeList::WingedEdge_t *CWingedEdgeList::LastActiveEdge( ) |
|
{ |
|
return m_EndTerminal.m_pPrevActiveEdge; |
|
} |
|
|
|
inline bool CWingedEdgeList::AtListEnd( const WingedEdge_t* pEdge ) const |
|
{ |
|
return pEdge == &m_EndTerminal; |
|
} |
|
|
|
inline bool CWingedEdgeList::AtListStart( const WingedEdge_t* pEdge ) const |
|
{ |
|
return pEdge == &m_StartTerminal; |
|
} |
|
|
|
inline void CWingedEdgeList::LinkActiveEdgeAfter( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge ) |
|
{ |
|
pInsertEdge->m_pNextActiveEdge = pPrevEdge->m_pNextActiveEdge; |
|
pInsertEdge->m_pPrevActiveEdge = pPrevEdge; |
|
pInsertEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pInsertEdge; |
|
pPrevEdge->m_pNextActiveEdge = pInsertEdge; |
|
} |
|
|
|
inline void CWingedEdgeList::UnlinkActiveEdge( WingedEdge_t *pEdge ) |
|
{ |
|
pEdge->m_pPrevActiveEdge->m_pNextActiveEdge = pEdge->m_pNextActiveEdge; |
|
pEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pEdge->m_pPrevActiveEdge; |
|
|
|
#ifdef _DEBUG |
|
pEdge->m_pPrevActiveEdge = pEdge->m_pNextActiveEdge = NULL; |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Checks consistency of the edge list... |
|
//----------------------------------------------------------------------------- |
|
void CWingedEdgeList::CheckConsistency() |
|
{ |
|
float flLastY = -FLT_MAX; |
|
float flLastX = -FLT_MAX; |
|
float flLastDxDy = 0; |
|
|
|
int nEdgeCount = EdgeCount(); |
|
for ( int i = 0; i < nEdgeCount; ++i ) |
|
{ |
|
WingedEdge_t *pEdge = &WingedEdge(i); |
|
Assert( pEdge->m_vecPosition.y >= flLastY ); |
|
if ( pEdge->m_vecPosition.y == flLastY ) |
|
{ |
|
Assert( pEdge->m_vecPosition.x >= flLastX ); |
|
if ( pEdge->m_vecPosition.x == flLastX ) |
|
{ |
|
Assert( pEdge->m_flDxDy >= flLastDxDy ); |
|
} |
|
} |
|
|
|
flLastX = pEdge->m_vecPosition.x; |
|
flLastY = pEdge->m_vecPosition.y; |
|
flLastDxDy = pEdge->m_flDxDy; |
|
} |
|
|
|
ResetActiveEdgeList(); |
|
float flCurrentY = NextDiscontinuity(); |
|
AdvanceActiveEdgeList( flCurrentY ); |
|
while ( flCurrentY != FLT_MAX ) |
|
{ |
|
// Make sure all edges have correct Xs + enter + leave surfaces.. |
|
int nCurrentSurfID = -1; |
|
float flX = -FLT_MAX; |
|
WingedEdge_t *pCurEdge = FirstActiveEdge(); |
|
while ( !AtListEnd( pCurEdge ) ) |
|
{ |
|
Assert( pCurEdge->m_nLeaveSurfID == nCurrentSurfID ); |
|
Assert( pCurEdge->m_flX >= flX ); |
|
Assert( pCurEdge->m_nLeaveSurfID != pCurEdge->m_nEnterSurfID ); |
|
nCurrentSurfID = pCurEdge->m_nEnterSurfID; |
|
flX = pCurEdge->m_flX; |
|
pCurEdge = pCurEdge->m_pNextActiveEdge; |
|
} |
|
// Assert( nCurrentSurfID == -1 ); |
|
|
|
flCurrentY = NextDiscontinuity(); |
|
AdvanceActiveEdgeList( flCurrentY ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the z value of a surface given and x,y coordinate |
|
//----------------------------------------------------------------------------- |
|
inline float CWingedEdgeList::ComputeZValue( const Surface_t *pSurface, float x, float y ) const |
|
{ |
|
const cplane_t &plane = pSurface->m_Plane; |
|
Assert( plane.normal.z == 1.0f ); |
|
return plane.dist - plane.normal.x * x - plane.normal.y * y; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to insert an edge into the active edge list, sorted by X |
|
// If Xs match, sort by Dx/Dy |
|
//----------------------------------------------------------------------------- |
|
inline bool CWingedEdgeList::IsEdgeXGreater( const WingedEdge_t *pEdge1, const WingedEdge_t *pEdge2 ) |
|
{ |
|
float flDelta = pEdge1->m_flX - pEdge2->m_flX; |
|
if ( flDelta > 0 ) |
|
return true; |
|
|
|
if ( flDelta < 0 ) |
|
return false; |
|
|
|
// NOTE: Using > instead of >= means coincident edges won't continually swap places |
|
return pEdge1->m_flDxDy > pEdge2->m_flDxDy; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Inserts an edge into the active edge list, sorted by X |
|
//----------------------------------------------------------------------------- |
|
inline void CWingedEdgeList::InsertActiveEdge( WingedEdge_t *pPrevEdge, WingedEdge_t *pInsertEdge ) |
|
{ |
|
while( !AtListStart(pPrevEdge) && IsEdgeXGreater( pPrevEdge, pInsertEdge ) ) |
|
{ |
|
pPrevEdge = pPrevEdge->m_pPrevActiveEdge; |
|
} |
|
LinkActiveEdgeAfter( pPrevEdge, pInsertEdge ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Clears the active edge list |
|
//----------------------------------------------------------------------------- |
|
void CWingedEdgeList::ResetActiveEdgeList() |
|
{ |
|
// This shouldn't be called unless we're about to do active edge checking |
|
Assert( EdgeCount() ); |
|
|
|
m_nCurrentEdgeIndex = 0; |
|
|
|
// Don't bother with edges below the screen edge |
|
m_flNextDiscontinuity = WingedEdge( 0 ).m_vecPosition.y; |
|
m_flNextDiscontinuity = max( m_flNextDiscontinuity, -1.0f ); |
|
|
|
m_StartTerminal.m_pNextActiveEdge = &m_EndTerminal; |
|
m_EndTerminal.m_pPrevActiveEdge = &m_StartTerminal; |
|
Assert( m_StartTerminal.m_pPrevActiveEdge == NULL ); |
|
Assert( m_EndTerminal.m_pNextActiveEdge == NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Spew active edge list |
|
//----------------------------------------------------------------------------- |
|
void CWingedEdgeList::SpewActiveEdgeList( float y, bool bHex ) |
|
{ |
|
WingedEdge_t *pEdge = FirstActiveEdge(); |
|
Msg( "%.3f : ", y ); |
|
while ( !AtListEnd( pEdge ) ) |
|
{ |
|
if (!bHex) |
|
{ |
|
Msg( "(%d %.3f [%d/%d]) ", (int)(pEdge - m_WingedEdges.Base()), pEdge->m_flX, pEdge->m_nLeaveSurfID, pEdge->m_nEnterSurfID ); |
|
} |
|
else |
|
{ |
|
Msg( "(%d %X [%d/%d]) ", (int)(pEdge - m_WingedEdges.Base()), *(int*)&pEdge->m_flX, pEdge->m_nLeaveSurfID, pEdge->m_nEnterSurfID ); |
|
} |
|
pEdge = pEdge->m_pNextActiveEdge; |
|
} |
|
Msg( "\n" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the next time in Y the edge list will undergo a change |
|
//----------------------------------------------------------------------------- |
|
inline float CWingedEdgeList::NextDiscontinuity() const |
|
{ |
|
return m_flNextDiscontinuity; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Advances the X values of the active edge list, with no reordering |
|
//----------------------------------------------------------------------------- |
|
bool CWingedEdgeList::AdvanceActiveEdgeList( float flCurrY ) |
|
{ |
|
// Reordering is unnecessary because the winged edges are guaranteed to be non-overlapping |
|
m_flNextDiscontinuity = FLT_MAX; |
|
|
|
// Advance all edges until the current Y; we don't need to re-order *any* edges. |
|
WingedEdge_t *pCurEdge; |
|
WingedEdge_t *pNextEdge; |
|
for ( pCurEdge = FirstActiveEdge(); !AtListEnd( pCurEdge ); pCurEdge = pNextEdge ) |
|
{ |
|
pNextEdge = pCurEdge->m_pNextActiveEdge; |
|
|
|
if ( pCurEdge->m_vecPositionEnd.y <= flCurrY ) |
|
{ |
|
UnlinkActiveEdge( pCurEdge ); |
|
continue; |
|
} |
|
|
|
pCurEdge->m_flX = pCurEdge->m_vecPosition.x + (flCurrY - pCurEdge->m_vecPosition.y) * pCurEdge->m_flDxDy; |
|
if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y; |
|
} |
|
} |
|
|
|
int nEdgeCount = EdgeCount(); |
|
if ( m_nCurrentEdgeIndex == nEdgeCount ) |
|
return (m_flNextDiscontinuity != FLT_MAX); |
|
|
|
pCurEdge = &WingedEdge( m_nCurrentEdgeIndex ); |
|
|
|
// Add new edges, computing the x + z coordinates at the requested y value |
|
while ( pCurEdge->m_vecPosition.y <= flCurrY ) |
|
{ |
|
// This is necessary because of our initial skip up to y == -1.0f |
|
if ( pCurEdge->m_vecPositionEnd.y > flCurrY ) |
|
{ |
|
float flDy = flCurrY - pCurEdge->m_vecPosition.y; |
|
pCurEdge->m_flX = pCurEdge->m_vecPosition.x + flDy * pCurEdge->m_flDxDy; |
|
if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y; |
|
} |
|
|
|
// Now re-insert in the list, sorted by X |
|
InsertActiveEdge( LastActiveEdge(), pCurEdge ); |
|
} |
|
|
|
if ( ++m_nCurrentEdgeIndex == nEdgeCount ) |
|
return (m_flNextDiscontinuity != FLT_MAX); |
|
|
|
pCurEdge = &WingedEdge( m_nCurrentEdgeIndex ); |
|
} |
|
|
|
// The next edge in y will also present a discontinuity |
|
if ( pCurEdge->m_vecPosition.y < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = pCurEdge->m_vecPosition.y; |
|
} |
|
|
|
return (m_flNextDiscontinuity != FLT_MAX); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Advance the active edge list until a particular X value is reached. |
|
//----------------------------------------------------------------------------- |
|
inline CWingedEdgeList::WingedEdge_t *CWingedEdgeList::AdvanceActiveEdgeListToX( WingedEdge_t *pEdge, float x ) |
|
{ |
|
// <= is necessary because we always want to point *after* the edge |
|
while( pEdge->m_flX <= x ) |
|
{ |
|
pEdge = pEdge->m_pNextActiveEdge; |
|
} |
|
return pEdge; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true if this active edge list occludes another active edge list |
|
//----------------------------------------------------------------------------- |
|
bool CWingedEdgeList::IsOccludingActiveEdgeList( CWingedEdgeList &testList, float y ) |
|
{ |
|
WingedEdge_t *pTestEdge = testList.FirstActiveEdge(); |
|
|
|
// If the occludee is off screen, it's occluded |
|
if ( pTestEdge->m_flX >= 1.0f ) |
|
return true; |
|
|
|
pTestEdge = AdvanceActiveEdgeListToX( pTestEdge, -1.0f ); |
|
|
|
// If all occludee edges have x values <= -1.0f, it's occluded |
|
if ( testList.AtListEnd( pTestEdge ) ) |
|
return true; |
|
|
|
// Start at the first edge whose x value is <= -1.0f |
|
// if the occludee goes off the left side of the screen. |
|
float flNextTestX = pTestEdge->m_flX; |
|
if ( !testList.AtListStart( pTestEdge->m_pPrevActiveEdge ) ) |
|
{ |
|
// In this case, we should be on a span crossing from x <= -1.0f to x > 1.0f. |
|
// Do the first occlusion test at x = -1.0f. |
|
Assert( pTestEdge->m_flX > -1.0f ); |
|
pTestEdge = pTestEdge->m_pPrevActiveEdge; |
|
Assert( pTestEdge->m_flX <= -1.0f ); |
|
flNextTestX = -1.0f; |
|
} |
|
|
|
WingedEdge_t *pOccluderEdge = FirstActiveEdge(); |
|
pOccluderEdge = AdvanceActiveEdgeListToX( pOccluderEdge, flNextTestX ); |
|
|
|
Surface_t *pTestSurf = (pTestEdge->m_nEnterSurfID >= 0) ? &testList.m_Surfaces[pTestEdge->m_nEnterSurfID] : &m_BackSurface; |
|
|
|
// Use the leave surface because we know the occluder has been advanced *beyond* the test surf X. |
|
Surface_t *pOccluderSurf = (pOccluderEdge->m_nLeaveSurfID >= 0) ? &m_Surfaces[pOccluderEdge->m_nLeaveSurfID] : &m_BackSurface; |
|
|
|
float flCurrentX = flNextTestX; |
|
float flNextOccluderX = pOccluderEdge->m_flX; |
|
flNextTestX = pTestEdge->m_pNextActiveEdge->m_flX; |
|
|
|
while ( true ) |
|
{ |
|
// Is the occludee in front of the occluder? No dice! |
|
float flTestOOz = ComputeZValue( pTestSurf, flCurrentX, y ); |
|
float flOccluderOOz = ComputeZValue( pOccluderSurf, flCurrentX, y ); |
|
if ( flTestOOz < flOccluderOOz ) |
|
return false; |
|
|
|
// We're done if there's no more occludees |
|
if ( flNextTestX == FLT_MAX ) |
|
return true; |
|
|
|
// We're done if there's no more occluders |
|
if ( flNextOccluderX == FLT_MAX ) |
|
return false; |
|
|
|
if ( flNextTestX <= flNextOccluderX ) |
|
{ |
|
flCurrentX = flNextTestX; |
|
pTestEdge = pTestEdge->m_pNextActiveEdge; |
|
if ( pTestEdge->m_nEnterSurfID >= 0 ) |
|
{ |
|
pTestSurf = &testList.m_Surfaces[pTestEdge->m_nEnterSurfID]; |
|
} |
|
else |
|
{ |
|
pTestSurf = (pTestEdge->m_nLeaveSurfID >= 0) ? &testList.m_Surfaces[pTestEdge->m_nLeaveSurfID] : &m_BackSurface; |
|
} |
|
flNextTestX = pTestEdge->m_pNextActiveEdge->m_flX; |
|
} |
|
else |
|
{ |
|
flCurrentX = flNextOccluderX; |
|
pOccluderEdge = pOccluderEdge->m_pNextActiveEdge; |
|
pOccluderSurf = (pOccluderEdge->m_nLeaveSurfID >= 0) ? &m_Surfaces[pOccluderEdge->m_nLeaveSurfID] : &m_BackSurface; |
|
flNextOccluderX = pOccluderEdge->m_flX; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Does this edge list occlude another winged edge list? |
|
//----------------------------------------------------------------------------- |
|
bool CWingedEdgeList::IsOccludingEdgeList( CWingedEdgeList &testList ) |
|
{ |
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
testList.CheckConsistency(); |
|
CheckConsistency(); |
|
#endif |
|
|
|
// Did all the edges get culled for some reason? Then it's occluded |
|
if ( testList.EdgeCount() == 0 ) |
|
return true; |
|
|
|
testList.ResetActiveEdgeList(); |
|
ResetActiveEdgeList(); |
|
|
|
// What we're going to do is look for the first discontinuities we can find |
|
// in both edge lists. Then, at each discontinuity, we must check the |
|
// active edge lists against each other and see if the occluders always |
|
// block the occludees... |
|
float flCurrentY = testList.NextDiscontinuity(); |
|
|
|
// The edge list for the occluder must completely obscure the occludee... |
|
// If, then, the first occluder edge starts *below* the first occludee edge, it doesn't occlude. |
|
if ( flCurrentY < NextDiscontinuity() ) |
|
return false; |
|
|
|
// If we start outside the screen bounds, then it's occluded! |
|
if ( flCurrentY >= 1.0f ) |
|
return true; |
|
|
|
testList.AdvanceActiveEdgeList( flCurrentY ); |
|
AdvanceActiveEdgeList( flCurrentY ); |
|
|
|
while ( true ) |
|
{ |
|
if ( !IsOccludingActiveEdgeList( testList, flCurrentY ) ) |
|
return false; |
|
|
|
// If we got outside the screen bounds, then it's occluded! |
|
if ( flCurrentY >= 1.0f ) |
|
return true; |
|
|
|
float flTestY = testList.NextDiscontinuity(); |
|
float flOccluderY = NextDiscontinuity(); |
|
flCurrentY = min( flTestY, flOccluderY ); |
|
|
|
// NOTE: This check here is to help occlusion @ the top of the screen |
|
// We cut the occluders off at y = 1.0 + epsilon, which means there's |
|
// not necessarily a discontinuity at y == 1.0. We need to create a discontinuity |
|
// there so that the occluder edges are still being used. |
|
if ( flCurrentY > 1.0f ) |
|
{ |
|
flCurrentY = 1.0f; |
|
} |
|
|
|
// If the occludee list is empty, then it's occluded! |
|
if ( !testList.AdvanceActiveEdgeList( flCurrentY ) ) |
|
return true; |
|
|
|
// If the occluder list is empty, then the occludee is not occluded! |
|
if ( !AdvanceActiveEdgeList( flCurrentY ) ) |
|
return false; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Queues up stuff to visualize |
|
//----------------------------------------------------------------------------- |
|
void CWingedEdgeList::QueueVisualization( unsigned char *pColor ) |
|
{ |
|
#ifndef SWDS |
|
if ( !r_visocclusion.GetInt() ) |
|
return; |
|
|
|
int nFirst = g_EdgeVisualization.AddMultipleToTail( m_WingedEdges.Count() ); |
|
for ( int i = m_WingedEdges.Count(); --i >= 0; ) |
|
{ |
|
WingedEdge_t *pEdge = &m_WingedEdges[i]; |
|
EdgeVisualizationInfo_t &info = g_EdgeVisualization[nFirst + i]; |
|
info.m_vecPoint[0] = pEdge->m_vecPosition; |
|
info.m_vecPoint[1] = pEdge->m_vecPositionEnd; |
|
*(int*)(info.m_pColor) = *(int*)pColor; |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Renders the winged edge list for debugging |
|
//----------------------------------------------------------------------------- |
|
void CWingedEdgeList::Visualize( unsigned char *pColor ) |
|
{ |
|
#ifndef SWDS |
|
if ( !r_visocclusion.GetInt() ) |
|
return; |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->Bind( g_pMaterialWireframeVertexColorIgnoreZ ); |
|
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( ); |
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_LINES, m_WingedEdges.Count() ); |
|
|
|
int i; |
|
int nCount = m_WingedEdges.Count(); |
|
for ( i = nCount; --i >= 0; ) |
|
{ |
|
WingedEdge_t *pEdge = &m_WingedEdges[i]; |
|
meshBuilder.Position3fv( pEdge->m_vecPosition.Base() ); |
|
meshBuilder.Color4ubv( pColor ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3fv( pEdge->m_vecPositionEnd.Base() ); |
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
meshBuilder.Color4ub( 0, 0, 255, 255 ); |
|
#else |
|
meshBuilder.Color4ubv( pColor ); |
|
#endif |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PopMatrix(); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Edge list that's fast to iterate over, fast to insert into |
|
//----------------------------------------------------------------------------- |
|
class CEdgeList |
|
{ |
|
public: |
|
struct Edge_t |
|
{ |
|
Vector m_vecPosition; // of the upper point in y, measured in screen space |
|
Vector m_vecPositionEnd; // of the lower point in y, measured in screen space |
|
float m_flDxDy; // Change in x per unit in y. |
|
float m_flOODy; |
|
float m_flX; |
|
int m_nSurfID; // Unique index of the surface this is a part of |
|
|
|
// Active edge list |
|
Edge_t *m_pPrevActiveEdge; |
|
Edge_t *m_pNextActiveEdge; |
|
}; |
|
|
|
public: |
|
CEdgeList(); |
|
|
|
// Insertion |
|
void AddEdge( Vector **ppEdgeVertices, int nSurfID ); |
|
|
|
// Surface ID management |
|
int AddSurface( const cplane_t &plane ); |
|
void SetSurfaceArea( int nSurfID, float flArea ); |
|
|
|
// Removal |
|
void RemoveAll(); |
|
|
|
// Visualization |
|
void QueueVisualization( unsigned char *pColor ); |
|
void Visualize( unsigned char *pColor ); |
|
|
|
// Access |
|
int EdgeCount() const; |
|
int ActualEdgeCount() const; |
|
const Edge_t &EdgeFromSortIndex( int nSortIndex ) const; |
|
Edge_t &EdgeFromSortIndex( int nSortIndex ); |
|
|
|
// Is the test edge list occluded by this edge list |
|
bool IsOccludingEdgeList( CEdgeList &testList ); |
|
|
|
// Reduces the active occlusion edge list to the bare minimum set of edges |
|
void ReduceActiveList( CWingedEdgeList &newEdgeList ); |
|
|
|
// Removal of small occluders |
|
void CullSmallOccluders(); |
|
|
|
private: |
|
struct Surface_t |
|
{ |
|
cplane_t m_Plane; // measured in projection space |
|
float m_flOOz; |
|
Surface_t *m_pPrevSurface; |
|
Surface_t *m_pNextSurface; |
|
int m_nSurfID; |
|
float m_flArea; // Area in screen space |
|
}; |
|
|
|
struct ReduceInfo_t |
|
{ |
|
short m_hEdge; |
|
short m_nWingedEdge; |
|
const Edge_t *m_pEdge; |
|
}; |
|
|
|
enum |
|
{ |
|
MAX_EDGE_CROSSINGS = 64 |
|
}; |
|
|
|
typedef CUtlVector<Edge_t> EdgeList_t; |
|
|
|
private: |
|
// Gets an edge |
|
const Edge_t &Edge( int nIndex ) const; |
|
|
|
// Active edges... |
|
const Edge_t *FirstActiveEdge( ) const; |
|
Edge_t *FirstActiveEdge( ); |
|
const Edge_t *LastActiveEdge( ) const; |
|
Edge_t *LastActiveEdge( ); |
|
bool AtListEnd( const Edge_t* pEdge ) const; |
|
bool AtListStart( const Edge_t* pEdge ) const; |
|
void LinkActiveEdgeAfter( Edge_t *pPrevEdge, Edge_t *pInsertEdge ); |
|
void UnlinkActiveEdge( Edge_t *pEdge ); |
|
|
|
// Surface list |
|
Surface_t* TopSurface(); |
|
bool AtSurfListEnd( const Surface_t* pSurface ) const; |
|
void CleanupCurrentSurfaceList(); |
|
|
|
// Active edge list |
|
void ResetActiveEdgeList(); |
|
float NextDiscontinuity() const; |
|
|
|
// Clears the current scan line |
|
float ClearCurrentSurfaceList(); |
|
|
|
// Returns the z value of a surface given and x,y coordinate |
|
float ComputeZValue( const Surface_t *pSurface, float x, float y ) const; |
|
|
|
// Computes a point at a specified y value along an edge |
|
void ComputePointAlongEdge( const Edge_t *pEdge, int nSurfID, float y, Vector *pPoint ) const; |
|
|
|
// Inserts an edge into the active edge list, sorted by X |
|
void InsertActiveEdge( Edge_t *pPrevEdge, Edge_t *pInsertEdge ); |
|
|
|
// Used to insert an edge into the active edge list |
|
bool IsEdgeXGreater( const Edge_t *pEdge1, const Edge_t *pEdge2 ); |
|
|
|
// Reduces the active edge list into a subset of ones we truly care about |
|
void ReduceActiveEdgeList( CWingedEdgeList &newEdgeList, float flMinY, float flMaxY ); |
|
|
|
// Discovers the first edge crossing discontinuity |
|
float LocateEdgeCrossingDiscontinuity( float flNextY, float flPrevY, int &nCount, Edge_t **pInfo ); |
|
|
|
// Generates a list of surfaces on the current scan line |
|
void UpdateCurrentSurfaceZValues( float x, float y ); |
|
|
|
// Intoruces a single new edge |
|
void IntroduceSingleActiveEdge( const Edge_t *pEdge, float flCurrY ); |
|
|
|
// Returns true if pTestSurf is closer (lower z value) |
|
bool IsSurfaceBehind( Surface_t *pTestSurf, Surface_t *pSurf ); |
|
|
|
// Advances the X values of the active edge list, with no reordering |
|
void AdvanceActiveEdgeList( float flNextY ); |
|
|
|
void IntroduceNewActiveEdges( float y ); |
|
void ReorderActiveEdgeList( int nCount, Edge_t **ppInfo ); |
|
|
|
// Debugging spew |
|
void SpewActiveEdgeList( float y, bool bHex = false ); |
|
|
|
// Checks consistency of the edge list... |
|
void CheckConsistency(); |
|
|
|
class EdgeLess |
|
{ |
|
public: |
|
bool Less( const unsigned short& src1, const unsigned short& src2, void *pCtx ); |
|
}; |
|
|
|
static int __cdecl SurfCompare( const void *elem1, const void *elem2 ); |
|
|
|
private: |
|
// Used to sort surfaces by screen area |
|
static Surface_t *s_pSortSurfaces; |
|
|
|
// List of all edges |
|
EdgeList_t m_Edges; |
|
CUtlSortVector<unsigned short, EdgeLess > m_OrigSortIndices; |
|
CUtlVector<unsigned short> m_SortIndices; |
|
Edge_t m_StartTerminal; |
|
Edge_t m_EndTerminal; |
|
|
|
// Surfaces |
|
CUtlVector< Surface_t > m_Surfaces; |
|
CUtlVector< int > m_SurfaceSort; |
|
Surface_t m_StartSurfTerminal; |
|
Surface_t m_EndSurfTerminal; |
|
|
|
// Active edges |
|
int m_nCurrentEdgeIndex; |
|
float m_flNextDiscontinuity; |
|
|
|
// List of edges on the current Y scan-line |
|
Edge_t *m_pCurrentActiveEdge; |
|
|
|
// Last X on the current scan line |
|
float m_flLastX; |
|
|
|
// Reduce list |
|
ReduceInfo_t *m_pNewReduceInfo; |
|
ReduceInfo_t *m_pPrevReduceInfo; |
|
int m_nNewReduceCount; |
|
int m_nPrevReduceCount; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to sort the edge list |
|
//----------------------------------------------------------------------------- |
|
bool CEdgeList::EdgeLess::Less( const unsigned short& src1, const unsigned short& src2, void *pCtx ) |
|
{ |
|
EdgeList_t *pEdgeList = (EdgeList_t*)pCtx; |
|
|
|
const Edge_t &e1 = pEdgeList->Element(src1); |
|
const Edge_t &e2 = pEdgeList->Element(src2); |
|
|
|
if ( e1.m_vecPosition.y < e2.m_vecPosition.y ) |
|
return true; |
|
|
|
if ( e1.m_vecPosition.y > e2.m_vecPosition.y ) |
|
return false; |
|
|
|
if ( e1.m_vecPosition.x < e2.m_vecPosition.x ) |
|
return true; |
|
|
|
if ( e1.m_vecPosition.x > e2.m_vecPosition.x ) |
|
return false; |
|
|
|
// This makes it so that if two edges start on the same point, |
|
// the leftmost edge is always selected |
|
return ( e1.m_flDxDy <= e2.m_flDxDy ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CEdgeList::CEdgeList() : m_Edges( 0, 32 ), m_OrigSortIndices( 0, 32 ) |
|
{ |
|
m_OrigSortIndices.SetLessContext( &m_Edges ); |
|
|
|
m_StartTerminal.m_vecPosition.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); |
|
m_StartTerminal.m_vecPositionEnd.Init( -FLT_MAX, FLT_MAX, -FLT_MAX ); |
|
m_StartTerminal.m_nSurfID = -1; |
|
m_StartTerminal.m_pPrevActiveEdge = NULL; |
|
m_StartTerminal.m_pNextActiveEdge = NULL; |
|
m_StartTerminal.m_flDxDy = 0.0f; |
|
m_StartTerminal.m_flOODy = 0.0f; |
|
m_StartTerminal.m_flX = -FLT_MAX; |
|
|
|
m_EndTerminal.m_vecPosition.Init( FLT_MAX, -FLT_MAX, -FLT_MAX ); |
|
m_EndTerminal.m_vecPositionEnd.Init( FLT_MAX, FLT_MAX, -FLT_MAX ); |
|
m_EndTerminal.m_nSurfID = -1; |
|
m_EndTerminal.m_pPrevActiveEdge = NULL; |
|
m_EndTerminal.m_pNextActiveEdge = NULL; |
|
m_EndTerminal.m_flDxDy = 0.0f; |
|
m_EndTerminal.m_flOODy = 0.0f; |
|
m_EndTerminal.m_flX = FLT_MAX; |
|
|
|
m_StartSurfTerminal.m_flOOz = -FLT_MAX; |
|
m_StartSurfTerminal.m_Plane.normal.Init( 0, 0, 1 ); |
|
m_StartSurfTerminal.m_Plane.dist = -FLT_MAX; |
|
m_StartSurfTerminal.m_nSurfID = -1; |
|
m_StartSurfTerminal.m_pNextSurface = NULL; |
|
m_StartSurfTerminal.m_pPrevSurface = NULL; |
|
|
|
m_EndSurfTerminal.m_flOOz = FLT_MAX; |
|
m_EndSurfTerminal.m_Plane.normal.Init( 0, 0, 1 ); |
|
m_EndSurfTerminal.m_Plane.dist = FLT_MAX; |
|
m_EndSurfTerminal.m_nSurfID = -1; |
|
m_EndSurfTerminal.m_pNextSurface = NULL; |
|
m_EndSurfTerminal.m_pPrevSurface = NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// iteration |
|
//----------------------------------------------------------------------------- |
|
inline int CEdgeList::EdgeCount() const |
|
{ |
|
return m_Edges.Count(); |
|
} |
|
|
|
inline int CEdgeList::ActualEdgeCount() const |
|
{ |
|
return m_SortIndices.Count(); |
|
} |
|
|
|
inline const CEdgeList::Edge_t &CEdgeList::EdgeFromSortIndex( int nSortIndex ) const |
|
{ |
|
return m_Edges[ m_SortIndices[nSortIndex] ]; |
|
} |
|
|
|
inline CEdgeList::Edge_t &CEdgeList::EdgeFromSortIndex( int nSortIndex ) |
|
{ |
|
return m_Edges[ m_SortIndices[nSortIndex] ]; |
|
} |
|
|
|
inline const CEdgeList::Edge_t &CEdgeList::Edge( int nIndex ) const |
|
{ |
|
return m_Edges[ nIndex ]; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Active edges... |
|
//----------------------------------------------------------------------------- |
|
inline const CEdgeList::Edge_t *CEdgeList::FirstActiveEdge( ) const |
|
{ |
|
return m_StartTerminal.m_pNextActiveEdge; |
|
} |
|
|
|
inline CEdgeList::Edge_t *CEdgeList::FirstActiveEdge( ) |
|
{ |
|
return m_StartTerminal.m_pNextActiveEdge; |
|
} |
|
|
|
inline const CEdgeList::Edge_t *CEdgeList::LastActiveEdge( ) const |
|
{ |
|
return m_EndTerminal.m_pPrevActiveEdge; |
|
} |
|
|
|
inline CEdgeList::Edge_t *CEdgeList::LastActiveEdge( ) |
|
{ |
|
return m_EndTerminal.m_pPrevActiveEdge; |
|
} |
|
|
|
inline bool CEdgeList::AtListEnd( const Edge_t* pEdge ) const |
|
{ |
|
return pEdge == &m_EndTerminal; |
|
} |
|
|
|
inline bool CEdgeList::AtListStart( const Edge_t* pEdge ) const |
|
{ |
|
return pEdge == &m_StartTerminal; |
|
} |
|
|
|
inline void CEdgeList::LinkActiveEdgeAfter( Edge_t *pPrevEdge, Edge_t *pInsertEdge ) |
|
{ |
|
pInsertEdge->m_pNextActiveEdge = pPrevEdge->m_pNextActiveEdge; |
|
pInsertEdge->m_pPrevActiveEdge = pPrevEdge; |
|
pInsertEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pInsertEdge; |
|
pPrevEdge->m_pNextActiveEdge = pInsertEdge; |
|
} |
|
|
|
inline void CEdgeList::UnlinkActiveEdge( Edge_t *pEdge ) |
|
{ |
|
pEdge->m_pPrevActiveEdge->m_pNextActiveEdge = pEdge->m_pNextActiveEdge; |
|
pEdge->m_pNextActiveEdge->m_pPrevActiveEdge = pEdge->m_pPrevActiveEdge; |
|
|
|
#ifdef _DEBUG |
|
pEdge->m_pPrevActiveEdge = pEdge->m_pNextActiveEdge = NULL; |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Surface list |
|
//----------------------------------------------------------------------------- |
|
inline CEdgeList::Surface_t* CEdgeList::TopSurface() |
|
{ |
|
return m_StartSurfTerminal.m_pNextSurface; |
|
} |
|
|
|
inline bool CEdgeList::AtSurfListEnd( const Surface_t* pSurface ) const |
|
{ |
|
return pSurface == &m_EndSurfTerminal; |
|
} |
|
|
|
void CEdgeList::CleanupCurrentSurfaceList() |
|
{ |
|
Surface_t *pSurf = TopSurface(); |
|
while ( !AtSurfListEnd(pSurf) ) |
|
{ |
|
Surface_t *pNext = pSurf->m_pNextSurface; |
|
pSurf->m_pPrevSurface = pSurf->m_pNextSurface = NULL; |
|
pSurf = pNext; |
|
} |
|
} |
|
|
|
inline void CEdgeList::SetSurfaceArea( int nSurfID, float flArea ) |
|
{ |
|
m_Surfaces[nSurfID].m_flArea = flArea; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the z value of a surface given and x,y coordinate |
|
//----------------------------------------------------------------------------- |
|
inline float CEdgeList::ComputeZValue( const Surface_t *pSurface, float x, float y ) const |
|
{ |
|
const cplane_t &plane = pSurface->m_Plane; |
|
Assert( plane.normal.z == 1.0f ); |
|
return plane.dist - plane.normal.x * x - plane.normal.y * y; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes a point at a specified y value along an edge |
|
//----------------------------------------------------------------------------- |
|
inline void CEdgeList::ComputePointAlongEdge( const Edge_t *pEdge, int nSurfID, float y, Vector *pPoint ) const |
|
{ |
|
Assert( (y >= pEdge->m_vecPosition.y) && (y <= pEdge->m_vecPositionEnd.y) ); |
|
|
|
float t; |
|
t = (y - pEdge->m_vecPosition.y) * pEdge->m_flOODy; |
|
pPoint->x = pEdge->m_vecPosition.x + ( pEdge->m_vecPositionEnd.x - pEdge->m_vecPosition.x ) * t; |
|
pPoint->y = y; |
|
pPoint->z = ComputeZValue( &m_Surfaces[nSurfID], pPoint->x, y ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Surface ID management |
|
//----------------------------------------------------------------------------- |
|
int CEdgeList::AddSurface( const cplane_t &plane ) |
|
{ |
|
int nIndex = m_Surfaces.AddToTail(); |
|
|
|
Surface_t &surf = m_Surfaces[nIndex]; |
|
surf.m_flOOz = 0.0f; |
|
surf.m_Plane = plane; |
|
surf.m_pNextSurface = NULL; |
|
surf.m_pPrevSurface = NULL; |
|
surf.m_nSurfID = nIndex; |
|
|
|
m_SurfaceSort.AddToTail(nIndex); |
|
|
|
return nIndex; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Insertion |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::AddEdge( Vector **ppEdgeVertices, int nSurfID ) |
|
{ |
|
int nMinIndex = ( ppEdgeVertices[0]->y >= ppEdgeVertices[1]->y ); |
|
|
|
const Vector &vecStartVert = *(ppEdgeVertices[ nMinIndex ]); |
|
const Vector &vecEndVert = *(ppEdgeVertices[ 1 - nMinIndex ]); |
|
|
|
// This is true if we've clipped to the near clip plane |
|
Assert( (vecStartVert.z >= 0.0f) && (vecEndVert.z >= 0.0f) ); |
|
|
|
// Don't bother adding edges with dy == 0 |
|
float dy = vecEndVert.y - vecStartVert.y; |
|
if (dy == 0.0f) |
|
return; |
|
|
|
int i = m_Edges.AddToTail(); |
|
Edge_t &newEdge = m_Edges[i]; |
|
|
|
newEdge.m_flOODy = 1.0f / dy; |
|
newEdge.m_vecPosition = vecStartVert; |
|
newEdge.m_vecPositionEnd = vecEndVert; |
|
newEdge.m_nSurfID = nSurfID; |
|
newEdge.m_flDxDy = (vecEndVert.x - vecStartVert.x) * newEdge.m_flOODy; |
|
newEdge.m_pPrevActiveEdge = NULL; |
|
newEdge.m_pNextActiveEdge = NULL; |
|
|
|
// Insert it into the sorted list |
|
m_OrigSortIndices.Insert( i ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to sort the surfaces |
|
//----------------------------------------------------------------------------- |
|
CEdgeList::Surface_t *CEdgeList::s_pSortSurfaces = NULL; |
|
int __cdecl CEdgeList::SurfCompare( const void *elem1, const void *elem2 ) |
|
{ |
|
int nSurfID1 = *(int*)elem1; |
|
float flArea1 = s_pSortSurfaces[nSurfID1].m_flArea; |
|
|
|
int nSurfID2 = *(int*)elem2; |
|
float flArea2 = s_pSortSurfaces[nSurfID2].m_flArea; |
|
|
|
if (flArea1 > flArea2) |
|
return -1; |
|
if (flArea1 < flArea2) |
|
return 1; |
|
return 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Removal of small occluders |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::CullSmallOccluders() |
|
{ |
|
// Cull out all surfaces with too small of a screen area... |
|
// Sort the surfaces by screen area, in descending order |
|
int nSurfCount = m_Surfaces.Count(); |
|
s_pSortSurfaces = m_Surfaces.Base(); |
|
|
|
if( m_SurfaceSort.Base() ) |
|
qsort( m_SurfaceSort.Base(), nSurfCount, sizeof(int), SurfCompare ); |
|
|
|
// We're going to keep the greater of r_occludermin + All surfaces with a screen area >= r_occluderarea |
|
int nMinSurfaces = r_occludermincount.GetInt(); |
|
|
|
// The *2 here is because surf areas are 2x bigger than actual |
|
float flMinScreenArea = r_occluderminarea.GetFloat() * 0.02f; |
|
if ( flMinScreenArea == 0.0f ) |
|
{ |
|
flMinScreenArea = OcclusionSystem()->MinOccluderArea() * 0.02f; |
|
} |
|
|
|
bool *bUseSurface = (bool*)stackalloc( nSurfCount * sizeof(bool) ); |
|
memset( bUseSurface, 0, nSurfCount * sizeof(bool) ); |
|
|
|
int i; |
|
for ( i = 0; i < nSurfCount; ++i ) |
|
{ |
|
int nSurfID = m_SurfaceSort[i]; |
|
if (( m_Surfaces[ nSurfID ].m_flArea < flMinScreenArea ) && (i >= nMinSurfaces )) |
|
break; |
|
bUseSurface[nSurfID] = true; |
|
} |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
int nEdgeCount = m_OrigSortIndices.Count(); |
|
m_SortIndices.RemoveAll(); |
|
m_SortIndices.EnsureCapacity( nEdgeCount ); |
|
for( i = 0; i < nEdgeCount; ++i ) |
|
{ |
|
int nEdgeIndex = m_OrigSortIndices[i]; |
|
if ( bUseSurface[ m_Edges[ nEdgeIndex ].m_nSurfID ] ) |
|
{ |
|
m_SortIndices.AddToTail( nEdgeIndex ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Removal |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::RemoveAll() |
|
{ |
|
m_Edges.RemoveAll(); |
|
m_SortIndices.RemoveAll(); |
|
m_OrigSortIndices.RemoveAll(); |
|
m_Surfaces.RemoveAll(); |
|
m_SurfaceSort.RemoveAll(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Active edge list |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::ResetActiveEdgeList() |
|
{ |
|
// This shouldn't be called unless we're about to do active edge checking |
|
Assert( ActualEdgeCount() ); |
|
|
|
m_nCurrentEdgeIndex = 0; |
|
m_flNextDiscontinuity = EdgeFromSortIndex( 0 ).m_vecPosition.y; |
|
|
|
m_StartTerminal.m_pNextActiveEdge = &m_EndTerminal; |
|
m_EndTerminal.m_pPrevActiveEdge = &m_StartTerminal; |
|
Assert( m_StartTerminal.m_pPrevActiveEdge == NULL ); |
|
Assert( m_EndTerminal.m_pNextActiveEdge == NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the next time in Y the edge list will undergo a change |
|
//----------------------------------------------------------------------------- |
|
inline float CEdgeList::NextDiscontinuity() const |
|
{ |
|
return m_flNextDiscontinuity; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to insert an edge into the active edge list, sorted by X |
|
// If Xs match, sort by Dx/Dy |
|
//----------------------------------------------------------------------------- |
|
inline bool CEdgeList::IsEdgeXGreater( const Edge_t *pEdge1, const Edge_t *pEdge2 ) |
|
{ |
|
float flDelta = pEdge1->m_flX - pEdge2->m_flX; |
|
if ( flDelta > 0 ) |
|
return true; |
|
|
|
if ( flDelta < 0 ) |
|
return false; |
|
|
|
// NOTE: Using > instead of >= means coincident edges won't continually swap places |
|
return pEdge1->m_flDxDy > pEdge2->m_flDxDy; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Inserts an edge into the active edge list, sorted by X |
|
//----------------------------------------------------------------------------- |
|
inline void CEdgeList::InsertActiveEdge( Edge_t *pPrevEdge, Edge_t *pInsertEdge ) |
|
{ |
|
while( !AtListStart(pPrevEdge) && IsEdgeXGreater( pPrevEdge, pInsertEdge ) ) |
|
{ |
|
pPrevEdge = pPrevEdge->m_pPrevActiveEdge; |
|
} |
|
LinkActiveEdgeAfter( pPrevEdge, pInsertEdge ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Clears the current scan line |
|
//----------------------------------------------------------------------------- |
|
float CEdgeList::ClearCurrentSurfaceList() |
|
{ |
|
m_pCurrentActiveEdge = FirstActiveEdge(); |
|
m_flLastX = m_pCurrentActiveEdge->m_flX; |
|
m_StartSurfTerminal.m_pNextSurface = &m_EndSurfTerminal; |
|
m_EndSurfTerminal.m_pPrevSurface = &m_StartSurfTerminal; |
|
return m_flLastX; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates a list of surfaces on the current scan line |
|
//----------------------------------------------------------------------------- |
|
inline void CEdgeList::UpdateCurrentSurfaceZValues( float x, float y ) |
|
{ |
|
// Update the z values of all active surfaces |
|
for ( Surface_t *pSurf = TopSurface(); !AtSurfListEnd( pSurf ); pSurf = pSurf->m_pNextSurface ) |
|
{ |
|
// NOTE: As long as we assume no interpenetrating surfaces, |
|
// we don't need to re-sort by ooz here. |
|
pSurf->m_flOOz = ComputeZValue( pSurf, x, y ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true if pTestSurf is closer (lower z value) |
|
//----------------------------------------------------------------------------- |
|
inline bool CEdgeList::IsSurfaceBehind( Surface_t *pTestSurf, Surface_t *pSurf ) |
|
{ |
|
if ( pTestSurf->m_flOOz - pSurf->m_flOOz <= -1e-6 ) |
|
return true; |
|
if ( pTestSurf->m_flOOz - pSurf->m_flOOz >= 1e-6 ) |
|
return false; |
|
|
|
// If they're nearly equal, then the thing that's approaching the screen |
|
// more quickly as we ascend in y is closer |
|
return ( pTestSurf->m_Plane.normal.y >= pSurf->m_Plane.normal.y ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Introduces a single new edge |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::IntroduceSingleActiveEdge( const Edge_t *pEdge, float flCurrY ) |
|
{ |
|
Surface_t *pCurrentSurf = &m_Surfaces[ pEdge->m_nSurfID ]; |
|
if ( !pCurrentSurf->m_pNextSurface ) |
|
{ |
|
pCurrentSurf->m_flOOz = ComputeZValue( pCurrentSurf, pEdge->m_flX, flCurrY ); |
|
|
|
// Determine where to insert the surface into the surface list... |
|
// Insert it so that the surface list is sorted by OOz |
|
Surface_t *pNextSurface = TopSurface(); |
|
while( IsSurfaceBehind( pNextSurface, pCurrentSurf ) ) |
|
{ |
|
pNextSurface = pNextSurface->m_pNextSurface; |
|
} |
|
pCurrentSurf->m_pNextSurface = pNextSurface; |
|
pCurrentSurf->m_pPrevSurface = pNextSurface->m_pPrevSurface; |
|
pNextSurface->m_pPrevSurface = pCurrentSurf; |
|
pCurrentSurf->m_pPrevSurface->m_pNextSurface = pCurrentSurf; |
|
} |
|
else |
|
{ |
|
// This means this edge is associated with a surface |
|
// already in the current surface list |
|
// In this case, simply remove the surface from the surface list |
|
pCurrentSurf->m_pNextSurface->m_pPrevSurface = pCurrentSurf->m_pPrevSurface; |
|
pCurrentSurf->m_pPrevSurface->m_pNextSurface = pCurrentSurf->m_pNextSurface; |
|
pCurrentSurf->m_pPrevSurface = pCurrentSurf->m_pNextSurface = NULL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reduces the active occlusion edge list to the bare minimum set of edges |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::IntroduceNewActiveEdges( float y ) |
|
{ |
|
int nEdgeCount = ActualEdgeCount(); |
|
if ( m_nCurrentEdgeIndex == nEdgeCount ) |
|
return; |
|
|
|
Edge_t *pCurEdge = &EdgeFromSortIndex( m_nCurrentEdgeIndex ); |
|
|
|
// Add new edges, computing the x + z coordinates at the requested y value |
|
while ( pCurEdge->m_vecPosition.y <= y ) |
|
{ |
|
// This is necessary because of our initial skip up to y == -1.0f |
|
if (pCurEdge->m_vecPositionEnd.y > y) |
|
{ |
|
float flDy = y - pCurEdge->m_vecPosition.y; |
|
pCurEdge->m_flX = pCurEdge->m_vecPosition.x + flDy * pCurEdge->m_flDxDy; |
|
if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y; |
|
} |
|
|
|
// Now re-insert in the list, sorted by X |
|
InsertActiveEdge( LastActiveEdge(), pCurEdge ); |
|
} |
|
|
|
if ( ++m_nCurrentEdgeIndex == nEdgeCount ) |
|
return; |
|
|
|
pCurEdge = &EdgeFromSortIndex( m_nCurrentEdgeIndex ); |
|
} |
|
|
|
// The next edge in y will also present a discontinuity |
|
if ( pCurEdge->m_vecPosition.y < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = pCurEdge->m_vecPosition.y; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reduces the active edge list into a subset of ones we truly care about |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::ReduceActiveEdgeList( CWingedEdgeList &wingedEdgeList, float flMinY, float flMaxY ) |
|
{ |
|
// Surface lists should be empty |
|
int i; |
|
|
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
for ( i = m_Surfaces.Count(); --i >= 0; ) |
|
{ |
|
Assert( m_Surfaces[i].m_pNextSurface == NULL ); |
|
} |
|
#endif |
|
|
|
int nLeaveSurfID = -1; |
|
const Edge_t *pCurEdge = FirstActiveEdge(); |
|
const Edge_t *pNextEdge; |
|
|
|
// NOTE: This algorithm depends on the fact that the active edge |
|
// list is not only sorted by ascending X, but also because edges |
|
// that land on the same X value are sorted by ascending dy/dx |
|
float flPrevX = pCurEdge->m_flX; |
|
|
|
for ( ; !AtListEnd( pCurEdge ); pCurEdge = pNextEdge ) |
|
{ |
|
if ( pCurEdge->m_flX != flPrevX ) |
|
{ |
|
UpdateCurrentSurfaceZValues( pCurEdge->m_flX, flMinY ); |
|
} |
|
|
|
IntroduceSingleActiveEdge( pCurEdge, flMinY ); |
|
|
|
flPrevX = pCurEdge->m_flX; |
|
|
|
// If we have coincident edges, we have to introduce them at the same time... |
|
pNextEdge = pCurEdge->m_pNextActiveEdge; |
|
if ( (flPrevX == pNextEdge->m_flX) && (pCurEdge->m_flDxDy == pNextEdge->m_flDxDy) ) |
|
continue; |
|
|
|
// If there's more than one overlapping surface at this point, |
|
// we can eliminate some edges. |
|
int nEnterSurfID = TopSurface()->m_nSurfID; |
|
|
|
// No change in the top surface? No edges needed... |
|
if ( nLeaveSurfID == nEnterSurfID ) |
|
continue; |
|
|
|
Assert( ( nLeaveSurfID != -1 ) || ( nEnterSurfID != -1 ) ); |
|
|
|
int nEdgeSurfID = ( nEnterSurfID != -1 ) ? nEnterSurfID : nLeaveSurfID; |
|
|
|
// Seam up edges... |
|
for ( i = m_nPrevReduceCount; --i >= 0; ) |
|
{ |
|
CWingedEdgeList::WingedEdge_t &testEdge = wingedEdgeList.WingedEdge( m_pPrevReduceInfo[i].m_nWingedEdge ); |
|
if (( testEdge.m_nLeaveSurfID != nLeaveSurfID ) || ( testEdge.m_nEnterSurfID != nEnterSurfID )) |
|
continue; |
|
|
|
if ( ( testEdge.m_flDxDy != pCurEdge->m_flDxDy) || ( fabs( testEdge.m_vecPositionEnd.x - pCurEdge->m_flX ) >= 1e-3 ) ) |
|
continue; |
|
|
|
ComputePointAlongEdge( m_pPrevReduceInfo[i].m_pEdge, nEdgeSurfID, flMaxY, &testEdge.m_vecPositionEnd ); |
|
|
|
// Don't try to seam up edges that end on this line... |
|
if ( pCurEdge->m_vecPositionEnd.y > flMaxY ) |
|
{ |
|
ReduceInfo_t *pNewEdge = &m_pNewReduceInfo[ m_nNewReduceCount ]; |
|
++m_nNewReduceCount; |
|
pNewEdge->m_pEdge = m_pPrevReduceInfo[i].m_pEdge; |
|
pNewEdge->m_nWingedEdge = m_pPrevReduceInfo[i].m_nWingedEdge; |
|
} |
|
break; |
|
} |
|
|
|
// This edge didn't exist on the previous y discontinuity line |
|
// We'll need to make a new one |
|
if ( i < 0 ) |
|
{ |
|
i = wingedEdgeList.AddEdge(); |
|
CWingedEdgeList::WingedEdge_t &newWingedEdge = wingedEdgeList.WingedEdge(i); |
|
newWingedEdge.m_nLeaveSurfID = nLeaveSurfID; |
|
newWingedEdge.m_nEnterSurfID = nEnterSurfID; |
|
newWingedEdge.m_flDxDy = pCurEdge->m_flDxDy; |
|
ComputePointAlongEdge( pCurEdge, nEdgeSurfID, flMinY, &newWingedEdge.m_vecPosition ); |
|
ComputePointAlongEdge( pCurEdge, nEdgeSurfID, flMaxY, &newWingedEdge.m_vecPositionEnd ); |
|
|
|
// Enforce sort order... |
|
// Required because we're computing the x position here, which can introduce error. |
|
if ( i != 0 ) |
|
{ |
|
CWingedEdgeList::WingedEdge_t &prevWingedEdge = wingedEdgeList.WingedEdge(i - 1); |
|
if ( newWingedEdge.m_vecPosition.y == prevWingedEdge.m_vecPosition.y ) |
|
{ |
|
if ( newWingedEdge.m_vecPosition.x < prevWingedEdge.m_vecPosition.x ) |
|
{ |
|
newWingedEdge.m_vecPosition.x = prevWingedEdge.m_vecPosition.x; |
|
} |
|
} |
|
} |
|
|
|
// Don't try to seam up edges that end on this line... |
|
if ( pCurEdge->m_vecPositionEnd.y > flMaxY ) |
|
{ |
|
ReduceInfo_t *pNewEdge = &m_pNewReduceInfo[ m_nNewReduceCount ]; |
|
++m_nNewReduceCount; |
|
pNewEdge->m_pEdge = pCurEdge; |
|
pNewEdge->m_nWingedEdge = i; |
|
} |
|
|
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
wingedEdgeList.CheckConsistency(); |
|
#endif |
|
} |
|
|
|
nLeaveSurfID = nEnterSurfID; |
|
} |
|
|
|
Assert( nLeaveSurfID == -1 ); |
|
|
|
// Msg("\n"); |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Discovers the first edge crossing discontinuity |
|
//----------------------------------------------------------------------------- |
|
float CEdgeList::LocateEdgeCrossingDiscontinuity( float flNextY, float flPrevY, int &nCount, Edge_t **ppInfo ) |
|
{ |
|
nCount = 0; |
|
float flCurrX = -FLT_MAX; |
|
float flNextX = -FLT_MAX; |
|
float flCurrY = flNextY; |
|
|
|
Vector2D vecDelta, vecIntersection; |
|
|
|
Edge_t *pCurEdge; |
|
for ( pCurEdge = FirstActiveEdge(); !AtListEnd(pCurEdge); flCurrX = flNextX, pCurEdge = pCurEdge->m_pNextActiveEdge ) |
|
{ |
|
// Don't take into account edges that end on the current line |
|
Assert( pCurEdge->m_vecPositionEnd.y >= flCurrY ); |
|
|
|
flNextX = pCurEdge->m_vecPosition.x + (flCurrY - pCurEdge->m_vecPosition.y) * pCurEdge->m_flDxDy; |
|
|
|
// Look for an X-crossing... This check helps for nearly co-linear lines |
|
// NOTE: You might think this would crash since it could dereference a NULL |
|
// pointer the first time through the loop, but it never hits that check since the |
|
// first X test is guaranteed to pass |
|
Edge_t *pPrevEdge = pCurEdge->m_pPrevActiveEdge; |
|
if ( ( flNextX > flCurrX ) || ( pPrevEdge->m_flDxDy <= pCurEdge->m_flDxDy ) ) |
|
continue; |
|
|
|
// This test is necessary to not capture edges that meet at a point... |
|
if ( pPrevEdge->m_vecPositionEnd == pCurEdge->m_vecPositionEnd ) |
|
continue; |
|
|
|
Assert( pPrevEdge->m_flDxDy != pCurEdge->m_flDxDy ); |
|
|
|
// Found one! Let's find the intersection of these two |
|
// edges and up the Y discontinuity to that point. |
|
// We'll solve this by doing an intersection of point + plane in 2D... |
|
// For the line, we'll use the previous line where |
|
// P = Pop + D * t, Pop = prevEdge.m_vecPosition, D = [dx dy] = [(dx/dy) 1] |
|
// For the plane, we'll use the current line where |
|
// N * P = d |
|
// Normal is perpendicular to the line, therefore N = [-dy dx] = [-1 (dx/dy)] |
|
// d = DotProduct( N, edge.m_vecPosition ) = N dot Pon |
|
// So, the t that solve the equation is given by t = (d - N dot Pop) / (N dot D) |
|
// Or, t = (N dot Pon - N dot Pop) / (N dot D) |
|
// t = (N dot (Pon - Pop)) / (N dot D) |
|
|
|
float flDenominator = 1.0f / (-pPrevEdge->m_flDxDy + pCurEdge->m_flDxDy); |
|
Vector2DSubtract( pCurEdge->m_vecPosition.AsVector2D(), pPrevEdge->m_vecPosition.AsVector2D(), vecDelta ); |
|
float flNumerator = - vecDelta.x + pCurEdge->m_flDxDy * vecDelta.y; |
|
float t = flNumerator * flDenominator; |
|
float flYCrossing = pPrevEdge->m_vecPosition.y + t; |
|
|
|
// Precision errors... |
|
// NOTE: The optimizer unfortunately causes this test to not return == |
|
// if the bitpattern of flYCrossing and flNextY are the exact same, because it's |
|
// doing the test with the 80bit fp registers. flYCrossing is still sitting in the register |
|
// from the computation on the line above, but flNextY isn't. Therefore it returns not equal. |
|
// That's why I have to do the explicit bitpattern check. |
|
if ( ( flYCrossing >= flNextY ) || ( *(int*)&flYCrossing == *(int*)&flNextY ) ) |
|
continue; |
|
|
|
if ( flYCrossing < flPrevY ) |
|
{ |
|
flYCrossing = flPrevY; |
|
} |
|
|
|
// If we advanced in Y, then reset the edge crossings |
|
if ( flCurrY != flYCrossing ) |
|
{ |
|
flCurrY = flYCrossing; |
|
nCount = 0; |
|
} |
|
|
|
Assert( nCount < MAX_EDGE_CROSSINGS ); |
|
flNextX = pPrevEdge->m_vecPosition.x + t * pPrevEdge->m_flDxDy; |
|
ppInfo[nCount++] = pCurEdge; |
|
} |
|
return flCurrY; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Advances the X values of the active edge list, with no reordering |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::AdvanceActiveEdgeList( float flCurrY ) |
|
{ |
|
m_flNextDiscontinuity = FLT_MAX; |
|
|
|
// Advance all edges until the current Y; we don't need to re-order *any* edges. |
|
Edge_t *pCurEdge; |
|
Edge_t *pNextEdge; |
|
float flPrevX = -FLT_MAX; |
|
for ( pCurEdge = FirstActiveEdge(); !AtListEnd( pCurEdge ); pCurEdge = pNextEdge ) |
|
{ |
|
pNextEdge = pCurEdge->m_pNextActiveEdge; |
|
|
|
if ( pCurEdge->m_vecPositionEnd.y <= flCurrY ) |
|
{ |
|
UnlinkActiveEdge( pCurEdge ); |
|
continue; |
|
} |
|
|
|
pCurEdge->m_flX = pCurEdge->m_vecPosition.x + (flCurrY - pCurEdge->m_vecPosition.y) * pCurEdge->m_flDxDy; |
|
|
|
// Eliminate precision errors by guaranteeing sort ordering... |
|
if ( pCurEdge->m_flX < flPrevX ) |
|
{ |
|
pCurEdge->m_flX = flPrevX; |
|
} |
|
else |
|
{ |
|
flPrevX = pCurEdge->m_flX; |
|
} |
|
|
|
if ( pCurEdge->m_vecPositionEnd.y < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = pCurEdge->m_vecPositionEnd.y; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reorders the active edge list based on where edge crossings occur |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::ReorderActiveEdgeList( int nCount, Edge_t **ppCrossings ) |
|
{ |
|
int nCurCrossing = 0; |
|
while ( nCurCrossing < nCount ) |
|
{ |
|
// Re-order the list where the edge crossing occurred. |
|
// For all edges that passed through the exact same point, we need only |
|
// reverse the order of those edges. At the same time, slam the X value of each |
|
// crossing edge to reduce precision errors |
|
|
|
Edge_t *pCurCrossing = ppCrossings[nCurCrossing++]; |
|
Edge_t *pFirstCrossing = pCurCrossing->m_pPrevActiveEdge; |
|
|
|
// First, bring shared (or nearly shared) edges into the crossing list... |
|
while ( pFirstCrossing->m_pPrevActiveEdge->m_flX == pFirstCrossing->m_flX ) |
|
{ |
|
pFirstCrossing = pFirstCrossing->m_pPrevActiveEdge; |
|
} |
|
|
|
// Find the last crossing... |
|
Edge_t *pLastCrossing = pCurCrossing->m_pNextActiveEdge; |
|
Edge_t *pPrevCrossing = pCurCrossing; |
|
while ( true ) |
|
{ |
|
if ( (nCurCrossing < nCount) && (pLastCrossing == ppCrossings[nCurCrossing]) ) |
|
{ |
|
pPrevCrossing = pLastCrossing; |
|
pLastCrossing = pLastCrossing->m_pNextActiveEdge; |
|
++nCurCrossing; |
|
continue; |
|
} |
|
|
|
if ( pPrevCrossing->m_flX != pLastCrossing->m_flX ) |
|
break; |
|
|
|
pLastCrossing = pLastCrossing->m_pNextActiveEdge; |
|
} |
|
|
|
// This should always be true, since there's always an edge at FLT_MAX. |
|
Assert( pLastCrossing ); |
|
|
|
// Slam all x values to be the same to avoid precision errors... |
|
// This guarantees that this crossing at least will occur |
|
float flXCrossing = pFirstCrossing->m_flX; |
|
for ( Edge_t *pCrossing = pFirstCrossing->m_pNextActiveEdge; pCrossing != pLastCrossing; pCrossing = pCrossing->m_pNextActiveEdge ) |
|
{ |
|
pCrossing->m_flX = flXCrossing; |
|
} |
|
} |
|
|
|
// Now re-insert everything to take into account other edges which may well have |
|
// crossed on this line |
|
Edge_t *pEdge; |
|
Edge_t *pNextEdge; |
|
for( pEdge = FirstActiveEdge(); !AtListEnd(pEdge); pEdge = pNextEdge ) |
|
{ |
|
pNextEdge = pEdge->m_pNextActiveEdge; |
|
Edge_t *pPrevEdge = pEdge->m_pPrevActiveEdge; |
|
if ( pPrevEdge->m_flX == pEdge->m_flX ) |
|
{ |
|
UnlinkActiveEdge( pEdge ); |
|
InsertActiveEdge( pPrevEdge, pEdge ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reduces the active occlusion edge list to the bare minimum set of edges |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::SpewActiveEdgeList( float y, bool bHex) |
|
{ |
|
Edge_t *pEdge = FirstActiveEdge(); |
|
Msg( "%.3f : ", y ); |
|
while ( !AtListEnd( pEdge ) ) |
|
{ |
|
if (!bHex) |
|
{ |
|
Msg( "(%d %.3f [%d]) ", (int)(pEdge - m_Edges.Base()), pEdge->m_flX, pEdge->m_nSurfID ); |
|
} |
|
else |
|
{ |
|
Msg( "(%d %X [%d]) ", (int)(pEdge - m_Edges.Base()), *(int*)&pEdge->m_flX, pEdge->m_nSurfID ); |
|
} |
|
pEdge = pEdge->m_pNextActiveEdge; |
|
} |
|
Msg( "\n" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Checks consistency of the edge list... |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::CheckConsistency() |
|
{ |
|
Edge_t *pEdge = FirstActiveEdge(); |
|
while( !AtListEnd( pEdge ) ) |
|
{ |
|
Edge_t *pPrevEdge = pEdge->m_pPrevActiveEdge; |
|
Assert( pEdge->m_flX >= pPrevEdge->m_flX ); |
|
if ( pEdge->m_flX == pPrevEdge->m_flX ) |
|
{ |
|
// End point check necessary because of precision errors |
|
Assert( (pEdge->m_flDxDy >= pPrevEdge->m_flDxDy) || (pEdge->m_vecPositionEnd == pPrevEdge->m_vecPositionEnd) ); |
|
} |
|
|
|
pEdge = pEdge->m_pNextActiveEdge; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reduces the active occlusion edge list to the bare minimum set of edges |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::ReduceActiveList( CWingedEdgeList &newEdgeList ) |
|
{ |
|
int nEdgeCount = ActualEdgeCount(); |
|
if ( nEdgeCount == 0 ) |
|
return; |
|
|
|
// Copy the surfaces over |
|
int nCount = m_Surfaces.Count(); |
|
// newEdgeList.m_Surfaces.EnsureCapacity( nCount ); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
newEdgeList.AddSurface( m_Surfaces[i].m_Plane ); |
|
} |
|
|
|
Edge_t *pEdgeCrossings[MAX_EDGE_CROSSINGS]; |
|
ReduceInfo_t *pBuf[2]; |
|
pBuf[0] = (ReduceInfo_t*)stackalloc( nEdgeCount * sizeof(ReduceInfo_t) ); |
|
pBuf[1] = (ReduceInfo_t*)stackalloc( nEdgeCount * sizeof(ReduceInfo_t) ); |
|
m_nPrevReduceCount = m_nNewReduceCount = 0; |
|
int nIndex = 0; |
|
|
|
ResetActiveEdgeList(); |
|
ClearCurrentSurfaceList(); |
|
|
|
// We can skip everything up to y = -1.0f; since that's offscreen |
|
float flPrevY = NextDiscontinuity(); |
|
flPrevY = fpmax( -1.0f, flPrevY ); |
|
|
|
m_flNextDiscontinuity = FLT_MAX; |
|
IntroduceNewActiveEdges( flPrevY ); |
|
|
|
int nEdgeCrossingCount = 0; |
|
bool bDone = false; |
|
while( !bDone ) |
|
{ |
|
// Don't immediately progress to the next discontinuity if there are edge crossings. |
|
float flNextY = LocateEdgeCrossingDiscontinuity( NextDiscontinuity(), flPrevY, nEdgeCrossingCount, pEdgeCrossings ); |
|
|
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
if ( s_bSpew ) |
|
{ |
|
// Debugging spew |
|
SpewActiveEdgeList( flPrevY ); |
|
} |
|
#endif |
|
|
|
// Reduce the active edge list |
|
m_pNewReduceInfo = pBuf[1 - nIndex]; |
|
m_pPrevReduceInfo = pBuf[nIndex]; |
|
m_nPrevReduceCount = m_nNewReduceCount; |
|
m_nNewReduceCount = 0; |
|
|
|
// Add a small epsilon so we occlude things on the top edge at y = 1.0 |
|
if (flNextY >= 1.001f) |
|
{ |
|
flNextY = 1.001f; |
|
bDone = true; |
|
} |
|
|
|
ReduceActiveEdgeList( newEdgeList, flPrevY, flNextY ); |
|
flPrevY = flNextY; |
|
|
|
// Advance the active edge list, with no resorting necessary!! |
|
AdvanceActiveEdgeList( flNextY ); |
|
|
|
// If we had an edge crossing, re-order the edges. Otherwise introduce new active edges |
|
if ( !nEdgeCrossingCount ) |
|
{ |
|
IntroduceNewActiveEdges( flNextY ); |
|
|
|
// Keep advancing the active edge list until it's got no more discontinuities |
|
if ( NextDiscontinuity() == FLT_MAX ) |
|
return; |
|
} |
|
else |
|
{ |
|
ReorderActiveEdgeList( nEdgeCrossingCount, pEdgeCrossings ); |
|
|
|
// The next edge in y will also present a discontinuity |
|
if ( m_nCurrentEdgeIndex < nEdgeCount ) |
|
{ |
|
float flNextEdgeY = EdgeFromSortIndex( m_nCurrentEdgeIndex ).m_vecPosition.y; |
|
if ( flNextEdgeY < m_flNextDiscontinuity ) |
|
{ |
|
m_flNextDiscontinuity = flNextEdgeY; |
|
} |
|
} |
|
} |
|
|
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
CheckConsistency(); |
|
#endif |
|
|
|
nIndex = 1 - nIndex; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to debug the occlusion system |
|
//----------------------------------------------------------------------------- |
|
void CEdgeList::QueueVisualization( unsigned char *pColor ) |
|
{ |
|
#ifndef SWDS |
|
if ( !r_visocclusion.GetInt() ) |
|
return; |
|
|
|
int nFirst = g_EdgeVisualization.AddMultipleToTail( m_Edges.Count() ); |
|
for ( int i = m_Edges.Count(); --i >= 0; ) |
|
{ |
|
EdgeVisualizationInfo_t &info = g_EdgeVisualization[nFirst + i]; |
|
info.m_vecPoint[0] = m_Edges[i].m_vecPosition; |
|
info.m_vecPoint[1] = m_Edges[i].m_vecPositionEnd; |
|
*(int*)(info.m_pColor) = *(int*)pColor; |
|
} |
|
#endif |
|
} |
|
|
|
|
|
void CEdgeList::Visualize( unsigned char *pColor ) |
|
{ |
|
#ifndef SWDS |
|
if ( !r_visocclusion.GetInt() ) |
|
return; |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->Bind( g_pMaterialWireframeVertexColorIgnoreZ ); |
|
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( ); |
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_LINES, m_Edges.Count() ); |
|
|
|
int i; |
|
for ( i = m_Edges.Count(); --i >= 0; ) |
|
{ |
|
meshBuilder.Position3fv( m_Edges[i].m_vecPosition.Base() ); |
|
meshBuilder.Color4ubv( pColor ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3fv( m_Edges[i].m_vecPositionEnd.Base() ); |
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
meshBuilder.Color4ub( 0, 0, 255, 255 ); |
|
#else |
|
meshBuilder.Color4ubv( pColor ); |
|
#endif |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PopMatrix(); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Implementation of IOcclusionSystem |
|
//----------------------------------------------------------------------------- |
|
class COcclusionSystem : public IOcclusionSystem |
|
{ |
|
public: |
|
COcclusionSystem(); |
|
~COcclusionSystem(); |
|
|
|
// Inherited from IOcclusionSystem |
|
virtual void ActivateOccluder( int nOccluderIndex, bool bActive ); |
|
virtual void SetView( const Vector &vecCameraPos, float flFOV, const VMatrix &worldToCamera, const VMatrix &cameraToProjection, const VPlane &nearClipPlane ); |
|
virtual bool IsOccluded( const Vector &vecAbsMins, const Vector &vecAbsMaxs ); |
|
virtual void SetOcclusionParameters( float flMaxOccludeeArea, float flMinOccluderArea ); |
|
virtual float MinOccluderArea() const; |
|
virtual void DrawDebugOverlays(); |
|
|
|
private: |
|
struct AxisAlignedPlane_t |
|
{ |
|
int m_nAxis; |
|
float m_flSign; |
|
float m_flDist; |
|
}; |
|
|
|
// Recomputes the edge list for occluders |
|
void RecomputeOccluderEdgeList(); |
|
|
|
// Is the point inside the near plane? |
|
bool IsPointInsideNearPlane( const Vector &vecPos ) const; |
|
void IntersectWithNearPlane( const Vector &vecStart, const Vector &vecEnd, Vector &outPos ) const; |
|
|
|
// Clips a polygon to the near clip plane |
|
int ClipPolygonToNearPlane( Vector **ppVertices, int nVertexCount, Vector **ppOutVerts, bool *pClipped ) const; |
|
|
|
// Project world-space verts + add into the edge list |
|
void AddPolygonToEdgeList( CEdgeList &edgeList, Vector **ppPolygon, int nCount, int nSurfID, bool bClipped ); |
|
|
|
// Computes the plane equation of a polygon in screen space from a camera-space plane |
|
void ComputeScreenSpacePlane( const cplane_t &cameraSpacePlane, cplane_t *pScreenSpacePlane ); |
|
|
|
// Used to clip the screen space polygons to the screen |
|
void ResetClipTempVerts(); |
|
int ClipPolygonToAxisAlignedPlane( Vector **ppVertices, int nVertexCount, |
|
const AxisAlignedPlane_t &plane, Vector **ppOutVerts ) const; |
|
|
|
// Is the point within an axis-aligned plane? |
|
bool IsPointInsideAAPlane( const Vector &vecPos, const AxisAlignedPlane_t &plane ) const; |
|
void IntersectWithAAPlane( const Vector &vecStart, const Vector &vecEnd, const AxisAlignedPlane_t &plane, Vector &outPos ) const; |
|
|
|
// Stitches up clipped vertices |
|
void StitchClippedVertices( Vector *pVertices, int nCount ); |
|
|
|
private: |
|
// Per-frame information |
|
bool m_bEdgeListDirty; |
|
VMatrix m_WorldToProjection; |
|
VMatrix m_WorldToCamera; |
|
float m_flXProjScale; |
|
float m_flYProjScale; |
|
float m_flProjDistScale; |
|
float m_flProjDistOffset; |
|
Vector m_vecCameraPosition; // in world space |
|
cplane_t m_NearClipPlane; |
|
float m_flNearPlaneDist; |
|
float m_flFOVFactor; |
|
CEdgeList m_EdgeList; |
|
CWingedEdgeList m_WingedEdgeList; |
|
CUtlVector< Vector > m_ClippedVerts; |
|
|
|
float m_flMaxOccludeeArea; |
|
float m_flMinOccluderArea; |
|
|
|
// Stats |
|
int m_nTests; |
|
int m_nOccluded; |
|
}; |
|
|
|
static COcclusionSystem g_OcclusionSystem; |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Singleton accessor |
|
//----------------------------------------------------------------------------- |
|
IOcclusionSystem *OcclusionSystem() |
|
{ |
|
return &g_OcclusionSystem; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor, destructor |
|
//----------------------------------------------------------------------------- |
|
COcclusionSystem::COcclusionSystem() : m_ClippedVerts( 0, 64 ) |
|
{ |
|
m_bEdgeListDirty = false; |
|
m_nTests = 0; |
|
m_nOccluded = 0; |
|
m_flMinOccluderArea = DEFAULT_MIN_OCCLUDER_AREA; |
|
m_flMaxOccludeeArea = DEFAULT_MAX_OCCLUDEE_AREA; |
|
} |
|
|
|
COcclusionSystem::~COcclusionSystem() |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Occlusion parameters? |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::SetOcclusionParameters( float flMaxOccludeeArea, float flMinOccluderArea ) |
|
{ |
|
m_flMaxOccludeeArea = (flMaxOccludeeArea ? flMaxOccludeeArea : DEFAULT_MAX_OCCLUDEE_AREA) * 0.01f; |
|
m_flMinOccluderArea = (flMinOccluderArea ? flMinOccluderArea : DEFAULT_MIN_OCCLUDER_AREA); |
|
} |
|
|
|
float COcclusionSystem::MinOccluderArea() const |
|
{ |
|
return m_flMinOccluderArea; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is the point within the near plane? |
|
//----------------------------------------------------------------------------- |
|
inline bool COcclusionSystem::IsPointInsideNearPlane( const Vector &vecPos ) const |
|
{ |
|
return DotProduct( vecPos, m_NearClipPlane.normal ) >= m_NearClipPlane.dist; |
|
} |
|
|
|
inline void COcclusionSystem::IntersectWithNearPlane( const Vector &vecStart, const Vector &vecEnd, Vector &outPos ) const |
|
{ |
|
Vector vecDir; |
|
VectorSubtract( vecEnd, vecStart, vecDir ); |
|
float t = IntersectRayWithPlane( vecStart, vecDir, m_NearClipPlane.normal, m_NearClipPlane.dist ); |
|
VectorLerp( vecStart, vecEnd, t, outPos ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Clips a surface to the near clip plane |
|
// FIXME: This blows: a *third* S-H clipper in the engine! All because the |
|
// vertex formats are different owing to different goals of the 3 clippers |
|
//----------------------------------------------------------------------------- |
|
static Vector s_TempVertMemory[256]; |
|
|
|
int COcclusionSystem::ClipPolygonToNearPlane( Vector **ppVertices, int nVertexCount, Vector **ppOutVerts, bool *pClipped ) const |
|
{ |
|
*pClipped = false; |
|
|
|
if ( nVertexCount < 3 ) |
|
return 0; |
|
|
|
// Ye Olde Sutherland-Hodgman clipping algorithm |
|
int nOutVertCount = 0; |
|
int nNewVertCount = 0; |
|
|
|
Vector* pStart = ppVertices[ nVertexCount - 1 ]; |
|
bool bStartInside = IsPointInsideNearPlane( *pStart ); |
|
for ( int i = 0; i < nVertexCount; ++i ) |
|
{ |
|
Vector* pEnd = ppVertices[ i ]; |
|
bool bEndInside = IsPointInsideNearPlane( *pEnd ); |
|
if (bEndInside) |
|
{ |
|
if (!bStartInside) |
|
{ |
|
// Started outside, ended inside, need to clip the edge |
|
ppOutVerts[nOutVertCount] = &s_TempVertMemory[ nNewVertCount++ ]; |
|
IntersectWithNearPlane( *pStart, *pEnd, *ppOutVerts[nOutVertCount] ); |
|
++nOutVertCount; |
|
*pClipped = true; |
|
} |
|
ppOutVerts[nOutVertCount++] = pEnd; |
|
} |
|
else |
|
{ |
|
if (bStartInside) |
|
{ |
|
// Started inside, ended outside, need to clip the edge |
|
ppOutVerts[nOutVertCount] = &s_TempVertMemory[ nNewVertCount++ ]; |
|
IntersectWithNearPlane( *pStart, *pEnd, *ppOutVerts[nOutVertCount] ); |
|
++nOutVertCount; |
|
*pClipped = true; |
|
} |
|
} |
|
pStart = pEnd; |
|
bStartInside = bEndInside; |
|
} |
|
|
|
return nOutVertCount; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is the point within an axis-aligned plane? |
|
//----------------------------------------------------------------------------- |
|
inline bool COcclusionSystem::IsPointInsideAAPlane( const Vector &vecPos, const AxisAlignedPlane_t &plane ) const |
|
{ |
|
return vecPos[plane.m_nAxis] * plane.m_flSign >= plane.m_flDist; |
|
} |
|
|
|
inline void COcclusionSystem::IntersectWithAAPlane( const Vector &vecStart, const Vector &vecEnd, const AxisAlignedPlane_t &plane, Vector &outPos ) const |
|
{ |
|
float t = IntersectRayWithAAPlane( vecStart, vecEnd, plane.m_nAxis, plane.m_flSign, plane.m_flDist ); |
|
VectorLerp( vecStart, vecEnd, t, outPos ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Clips a surface to the edges of the screen (axis-aligned planes) |
|
//----------------------------------------------------------------------------- |
|
static int s_nTempVertCount = 0; |
|
|
|
void COcclusionSystem::ResetClipTempVerts() |
|
{ |
|
s_nTempVertCount = 0; |
|
} |
|
|
|
int COcclusionSystem::ClipPolygonToAxisAlignedPlane( Vector **ppVertices, int nVertexCount, |
|
const AxisAlignedPlane_t &plane, Vector **ppOutVerts ) const |
|
{ |
|
// Ye Olde Sutherland-Hodgman clipping algorithm |
|
int nOutVertCount = 0; |
|
|
|
Vector* pStart = ppVertices[ nVertexCount - 1 ]; |
|
bool bStartInside = IsPointInsideAAPlane( *pStart, plane ); |
|
for ( int i = 0; i < nVertexCount; ++i ) |
|
{ |
|
Vector* pEnd = ppVertices[ i ]; |
|
bool bEndInside = IsPointInsideAAPlane( *pEnd, plane ); |
|
if (bEndInside) |
|
{ |
|
if (!bStartInside) |
|
{ |
|
// Started outside, ended inside, need to clip the edge |
|
ppOutVerts[nOutVertCount] = &s_TempVertMemory[ s_nTempVertCount++ ]; |
|
IntersectWithAAPlane( *pStart, *pEnd, plane, *ppOutVerts[nOutVertCount] ); |
|
++nOutVertCount; |
|
} |
|
ppOutVerts[nOutVertCount++] = pEnd; |
|
} |
|
else |
|
{ |
|
if (bStartInside) |
|
{ |
|
// Started inside, ended outside, need to clip the edge |
|
ppOutVerts[nOutVertCount] = &s_TempVertMemory[ s_nTempVertCount++ ]; |
|
IntersectWithAAPlane( *pStart, *pEnd, plane, *ppOutVerts[nOutVertCount] ); |
|
++nOutVertCount; |
|
} |
|
} |
|
pStart = pEnd; |
|
bStartInside = bEndInside; |
|
} |
|
|
|
return nOutVertCount; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the plane equation of a polygon in screen space from a world-space plane |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::ComputeScreenSpacePlane( const cplane_t &cameraSpacePlane, cplane_t *pScreenSpacePlane ) |
|
{ |
|
// Here's how this is computed: |
|
// If the *camera* space plane is Ax+By+Cz = D, |
|
// and xs = -(xf) * x/z, ys = -(yf) y/z, zs = - (zc + zf * ooz) |
|
// Then x = -xs * z / xf, y = -ys * z / yf, ooz = -(zs + zc) / zf |
|
// So - A * xs * z / xf - B * ys * z / yf + C * z = D |
|
// - A xs / xf - B ys / yf + C = D * ooz |
|
// (A/D) xs/xf + (B/D) ys/yf + ooz = (C/D) |
|
// (A/D) xs/xf + (B/D) ys/yf - (zs + zc) / zf = (C/D) |
|
// -(A/D) xs/xf - (B/D) ys/yf + (zs + zc) / zf = -(C/D) |
|
// -zf * (A/D) xs/xf - zf * (B/D) ys/yf + zs = -zf * (C/D) - zc |
|
// Let A' = -zf/xf*(A/D), B' = -zf/yf*(B/D), D' = -zf * (C/D) - zc |
|
// A' xs + B' ys + zs = D' is the screen space plane equation |
|
|
|
float ooD = (cameraSpacePlane.dist != 0) ? (1.0f / cameraSpacePlane.dist) : 0.0f; |
|
pScreenSpacePlane->normal.x = cameraSpacePlane.normal.x * ooD * m_flXProjScale; |
|
pScreenSpacePlane->normal.y = cameraSpacePlane.normal.y * ooD * m_flYProjScale; |
|
pScreenSpacePlane->normal.z = 1; |
|
pScreenSpacePlane->dist = cameraSpacePlane.normal.z * ooD * m_flProjDistScale + m_flProjDistOffset; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Stitches up clipped vertices |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::StitchClippedVertices( Vector *pVertices, int nCount ) |
|
{ |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
// Only stitch ones that have been clipped by the near clip plane |
|
if ( fabs( pVertices[i].z ) > 1e-3 ) |
|
continue; |
|
|
|
int j; |
|
for ( j = m_ClippedVerts.Count(); --j >= 0; ) |
|
{ |
|
if ( VectorsAreEqual( pVertices[i], m_ClippedVerts[j], 1e-3 ) ) |
|
{ |
|
pVertices[i] = m_ClippedVerts[j]; |
|
break; |
|
} |
|
} |
|
|
|
if ( j < 0 ) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
// No match found... |
|
m_ClippedVerts.AddToTail( pVertices[i] ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Project world-space verts + add into the edge list |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::AddPolygonToEdgeList( CEdgeList &edgeList, Vector **ppPolygon, int nCount, int nSurfID, bool bClipped ) |
|
{ |
|
// Transform the verts into projection space |
|
// Transform into projection space (extra logic here is to simply guarantee that we project each vert exactly once) |
|
int nMaxClipVerts = (nCount * 4); |
|
int nClipCount, nClipCount1; |
|
Vector **ppClipVertex = (Vector**)stackalloc( nMaxClipVerts * sizeof(Vector*) ); |
|
Vector **ppClipVertex1 = (Vector**)stackalloc( nMaxClipVerts * sizeof(Vector*) ); |
|
Vector *pVecProjectedVertex = (Vector*)stackalloc( nCount * sizeof(Vector) ); |
|
|
|
int k; |
|
for ( k = 0; k < nCount; ++k ) |
|
{ |
|
Vector3DMultiplyPositionProjective( m_WorldToProjection, *(ppPolygon[k]), pVecProjectedVertex[k] ); |
|
|
|
// Clamp needed to avoid precision problems. |
|
// if ( pVecProjectedVertex[k].z < 0.0f ) |
|
// pVecProjectedVertex[k].z = 0.0f; |
|
pVecProjectedVertex[k].z *= (pVecProjectedVertex[k].z > 0.0f); |
|
ppClipVertex[k] = &pVecProjectedVertex[k]; |
|
} |
|
|
|
// Clip vertices to the screen in x,y... |
|
AxisAlignedPlane_t aaPlane; |
|
aaPlane.m_nAxis = 0; |
|
aaPlane.m_flDist = -1; |
|
aaPlane.m_flSign = -1; |
|
nClipCount = nCount; |
|
|
|
ResetClipTempVerts(); |
|
|
|
nClipCount1 = ClipPolygonToAxisAlignedPlane( ppClipVertex, nClipCount, aaPlane, ppClipVertex1 ); |
|
if ( nClipCount1 < 3 ) |
|
return; |
|
Assert( nClipCount1 < nMaxClipVerts ); |
|
|
|
aaPlane.m_flSign = 1; |
|
nClipCount = ClipPolygonToAxisAlignedPlane( ppClipVertex1, nClipCount1, aaPlane, ppClipVertex ); |
|
if ( nClipCount < 3 ) |
|
return; |
|
Assert( nClipCount < nMaxClipVerts ); |
|
|
|
aaPlane.m_nAxis = 1; |
|
nClipCount1 = ClipPolygonToAxisAlignedPlane( ppClipVertex, nClipCount, aaPlane, ppClipVertex1 ); |
|
if ( nClipCount1 < 3 ) |
|
return; |
|
Assert( nClipCount1 < nMaxClipVerts ); |
|
|
|
aaPlane.m_flSign = -1; |
|
nClipCount = ClipPolygonToAxisAlignedPlane( ppClipVertex1, nClipCount1, aaPlane, ppClipVertex ); |
|
if ( nClipCount < 3 ) |
|
return; |
|
Assert( nClipCount < nMaxClipVerts ); |
|
|
|
// Compute the screen area... |
|
float flScreenArea = 0.0f; |
|
int nLastClipVert = nClipCount - 1; |
|
for ( k = 1; k < nLastClipVert; ++k ) |
|
{ |
|
// Using area times two simply because it's faster... |
|
float flTriArea = TriArea2DTimesTwo( (*ppClipVertex[0]), (*ppClipVertex[k]), (*ppClipVertex[k+1]) ); |
|
Assert( flTriArea <= 1e-3 ); |
|
if ( flTriArea < 0 ) |
|
{ |
|
flScreenArea += -flTriArea; |
|
} |
|
} |
|
edgeList.SetSurfaceArea( nSurfID, flScreenArea ); |
|
|
|
// If there's a clipped vertex, attempt to seam up with other edges... |
|
if ( bClipped ) |
|
{ |
|
StitchClippedVertices( pVecProjectedVertex, nCount ); |
|
} |
|
|
|
// Add in the edges of the *unclipped* polygon: to avoid precision errors |
|
Vector *ppEdgeVertices[2]; |
|
int nLastVert = nCount - 1; |
|
ppEdgeVertices[ 1 ] = &pVecProjectedVertex[ nLastVert ]; |
|
for ( k = 0; k < nLastVert; ++k ) |
|
{ |
|
ppEdgeVertices[ k & 0x1 ] = &pVecProjectedVertex[ k ]; |
|
edgeList.AddEdge( ppEdgeVertices, nSurfID ); |
|
} |
|
ppEdgeVertices[ nLastVert & 0x1 ] = &pVecProjectedVertex[ nLastVert ]; |
|
edgeList.AddEdge( ppEdgeVertices, nSurfID ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Recomputes the occluder edge list |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::RecomputeOccluderEdgeList() |
|
{ |
|
if ( !m_bEdgeListDirty ) |
|
return; |
|
|
|
// Tracker 17772: If building cubemaps can end up calling into here w/o cl.pAreaBits setup yet, oh well. |
|
if ( !cl.m_bAreaBitsValid && CommandLine()->FindParm( "-buildcubemaps" ) ) |
|
return; |
|
|
|
m_bEdgeListDirty = false; |
|
m_EdgeList.RemoveAll(); |
|
m_WingedEdgeList.Clear(); |
|
m_ClippedVerts.RemoveAll(); |
|
|
|
mvertex_t *pVertices = host_state.worldbrush->vertexes; |
|
int *pIndices = host_state.worldbrush->occludervertindices; |
|
doccluderdata_t *pOccluders = host_state.worldbrush->occluders; |
|
|
|
int i, j, k; |
|
for ( i = host_state.worldbrush->numoccluders ; --i >= 0; ) |
|
{ |
|
if ( pOccluders[i].flags & OCCLUDER_FLAGS_INACTIVE ) |
|
continue; |
|
|
|
// Skip the occluder if it's in a disconnected area |
|
if ( cl.m_chAreaBits && |
|
(cl.m_chAreaBits[pOccluders[i].area >> 3] & (1 << ( pOccluders[i].area & 0x7 )) ) == 0 ) |
|
continue; |
|
|
|
int nSurfID = pOccluders[i].firstpoly; |
|
int nSurfCount = pOccluders[i].polycount; |
|
for ( j = 0; j < nSurfCount; ++j, ++nSurfID ) |
|
{ |
|
doccluderpolydata_t *pSurf = &host_state.worldbrush->occluderpolys[nSurfID]; |
|
|
|
int nFirstVertexIndex = pSurf->firstvertexindex; |
|
int nVertexCount = pSurf->vertexcount; |
|
|
|
// If the surface is backfacing, blow it off... |
|
const cplane_t &surfPlane = host_state.worldbrush->planes[ pSurf->planenum ]; |
|
if ( DotProduct( surfPlane.normal, m_vecCameraPosition ) <= surfPlane.dist ) |
|
continue; |
|
|
|
// Clip to the near plane (has to be done in world space) |
|
Vector **ppSurfVerts = (Vector**)stackalloc( ( nVertexCount ) * sizeof(Vector*) ); |
|
Vector **ppClipVerts = (Vector**)stackalloc( ( nVertexCount * 2 ) * sizeof(Vector*) ); |
|
for ( k = 0; k < nVertexCount; ++k ) |
|
{ |
|
int nVertIndex = pIndices[nFirstVertexIndex + k]; |
|
ppSurfVerts[k] = &( pVertices[nVertIndex].position ); |
|
} |
|
|
|
bool bClipped; |
|
int nClipCount = ClipPolygonToNearPlane( ppSurfVerts, nVertexCount, ppClipVerts, &bClipped ); |
|
Assert( nClipCount <= ( nVertexCount * 2 ) ); |
|
if ( nClipCount < 3 ) |
|
continue; |
|
|
|
cplane_t projectionSpacePlane; |
|
cplane_t cameraSpacePlane; |
|
MatrixTransformPlane( m_WorldToCamera, surfPlane, cameraSpacePlane ); |
|
ComputeScreenSpacePlane( cameraSpacePlane, &projectionSpacePlane ); |
|
int nEdgeSurfID = m_EdgeList.AddSurface( projectionSpacePlane ); |
|
|
|
// Transform into projection space (extra logic here is to simply guarantee that we project each vert exactly once) |
|
AddPolygonToEdgeList( m_EdgeList, ppClipVerts, nClipCount, nEdgeSurfID, bClipped ); |
|
} |
|
} |
|
|
|
m_EdgeList.CullSmallOccluders(); |
|
m_EdgeList.ReduceActiveList( m_WingedEdgeList ); |
|
// Msg("Edge count %d -> %d\n", m_EdgeList.EdgeCount(), m_WingedEdgeList.EdgeCount() ); |
|
|
|
// Draw the occluders |
|
unsigned char color[4] = { 255, 255, 255, 255 }; |
|
m_WingedEdgeList.QueueVisualization( color ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Occluder list management |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::ActivateOccluder( int nOccluderIndex, bool bActive ) |
|
{ |
|
if ( ( nOccluderIndex >= host_state.worldbrush->numoccluders ) || ( nOccluderIndex < 0 ) ) |
|
return; |
|
|
|
if ( bActive ) |
|
{ |
|
host_state.worldbrush->occluders[nOccluderIndex].flags &= ~OCCLUDER_FLAGS_INACTIVE; |
|
} |
|
else |
|
{ |
|
host_state.worldbrush->occluders[nOccluderIndex].flags |= OCCLUDER_FLAGS_INACTIVE; |
|
} |
|
|
|
m_bEdgeListDirty = true; |
|
} |
|
|
|
|
|
void COcclusionSystem::SetView( const Vector &vecCameraPos, float flFOV, const VMatrix &worldToCamera, |
|
const VMatrix &cameraToProjection, const VPlane &nearClipPlane ) |
|
{ |
|
m_vecCameraPosition = vecCameraPos; |
|
m_WorldToCamera = worldToCamera; |
|
|
|
// See ComputeScreenSpacePlane() for the use of these constants |
|
m_flXProjScale = -cameraToProjection[2][3] / cameraToProjection[0][0]; |
|
m_flYProjScale = -cameraToProjection[2][3] / cameraToProjection[1][1]; |
|
m_flProjDistScale = -cameraToProjection[2][3]; |
|
m_flProjDistOffset = -cameraToProjection[2][2]; |
|
MatrixMultiply( cameraToProjection, worldToCamera, m_WorldToProjection ); |
|
m_NearClipPlane.normal = nearClipPlane.m_Normal; |
|
m_NearClipPlane.dist = nearClipPlane.m_Dist; |
|
m_NearClipPlane.type = 3; |
|
m_bEdgeListDirty = true; |
|
m_flNearPlaneDist = -( DotProduct( vecCameraPos, m_NearClipPlane.normal ) - m_NearClipPlane.dist ); |
|
Assert( m_flNearPlaneDist > 0.0f ); |
|
m_flFOVFactor = m_flNearPlaneDist * tan( flFOV * 0.5f * M_PI / 180.0f ); |
|
m_flFOVFactor = m_flNearPlaneDist / m_flFOVFactor; |
|
m_flFOVFactor *= m_flFOVFactor; |
|
|
|
if ( r_occlusionspew.GetInt() ) |
|
{ |
|
if ( m_nTests ) |
|
{ |
|
float flPercent = 100.0f * ((float)m_nOccluded / (float)m_nTests); |
|
Msg("Occl %.2f (%d/%d)\n", flPercent, m_nOccluded, m_nTests ); |
|
m_nTests = 0; |
|
m_nOccluded = 0; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to build the quads to test for occlusion |
|
//----------------------------------------------------------------------------- |
|
static int s_pFaceIndices[6][4] = |
|
{ |
|
{ 0, 4, 6, 2 }, // -x |
|
{ 1, 3, 7, 5 }, // +x |
|
{ 0, 1, 5, 4 }, // -y |
|
{ 2, 6, 7, 3 }, // +y |
|
{ 0, 2, 3, 1 }, // -z |
|
{ 4, 5, 7, 6 }, // +z |
|
}; |
|
|
|
static int s_pSourceIndices[8] = |
|
{ |
|
-1, 0, 0, 1, 0, 1, 2, 3 |
|
}; |
|
|
|
static int s_pDeltaIndices[8] = |
|
{ |
|
-1, 0, 1, 1, 2, 2, 2, 2 |
|
}; |
|
|
|
static unsigned char s_VisualizationColor[2][4] = |
|
{ |
|
{ 255, 0, 0, 255 }, |
|
{ 0, 255, 0, 255 } |
|
}; |
|
|
|
struct EdgeInfo_t |
|
{ |
|
unsigned char m_nVert[2]; |
|
unsigned char m_nFace[2]; |
|
int m_nTestCount; |
|
int m_nMinVert; |
|
}; |
|
|
|
// NOTE: The face indices here have to very carefully ordered for the algorithm |
|
// to work. They must be ordered so that vert0 -> vert1 is clockwise |
|
// for the first face listed and vert1 -> vert0 is clockwise for the 2nd face listed |
|
static EdgeInfo_t s_pEdges[12] = |
|
{ |
|
{ { 0, 1 }, { 2, 4 }, 0, 0 }, // 0: Edge between -y + -z |
|
{ { 2, 0 }, { 0, 4 }, 0, 0 }, // 1: Edge between -x + -z |
|
{ { 1, 3 }, { 1, 4 }, 0, 0 }, // 2: Edge between +x + -z |
|
{ { 3, 2 }, { 3, 4 }, 0, 0 }, // 3: Edge between +y + -z |
|
{ { 0, 4 }, { 0, 2 }, 0, 0 }, // 4: Edge between -x + -y |
|
{ { 5, 1 }, { 1, 2 }, 0, 0 }, // 5: Edge between +x + -y |
|
{ { 6, 2 }, { 0, 3 }, 0, 0 }, // 6: Edge between -x + +y |
|
{ { 3, 7 }, { 1, 3 }, 0, 0 }, // 7: Edge between +x + +y |
|
{ { 5, 4 }, { 2, 5 }, 0, 0 }, // 8: Edge between -y + +z |
|
{ { 4, 6 }, { 0, 5 }, 0, 0 }, // 9: Edge between -x + +z |
|
{ { 7, 5 }, { 1, 5 }, 0, 0 }, // 10:Edge between +x + +z |
|
{ { 6, 7 }, { 3, 5 }, 0, 0 }, // 11:Edge between +y + +z |
|
}; |
|
|
|
static int s_pFaceEdges[6][4] = |
|
{ |
|
{ 4, 9, 6, 1 }, |
|
{ 2, 7, 10, 5 }, |
|
{ 0, 5, 8, 4 }, |
|
{ 6, 11, 7, 3 }, |
|
{ 1, 3, 2, 0 }, |
|
{ 8, 10, 11, 9 }, |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Occlusion checks |
|
//----------------------------------------------------------------------------- |
|
static CWingedEdgeList s_WingedTestEdgeList; |
|
|
|
class WingedEdgeLessFunc |
|
{ |
|
public: |
|
bool Less( const int& src1, const int& src2, void *pCtx ) |
|
{ |
|
Vector *pVertices = (Vector*)pCtx; |
|
|
|
EdgeInfo_t *pEdge1 = &s_pEdges[ src1 ]; |
|
EdgeInfo_t *pEdge2 = &s_pEdges[ src2 ]; |
|
|
|
Vector *pV1 = &pVertices[ pEdge1->m_nVert[ pEdge1->m_nMinVert ] ]; |
|
Vector *pV2 = &pVertices[ pEdge2->m_nVert[ pEdge2->m_nMinVert ] ]; |
|
|
|
if (pV1->y < pV2->y) |
|
return true; |
|
if (pV1->y > pV2->y) |
|
return false; |
|
if (pV1->x < pV2->x) |
|
return true; |
|
if (pV1->x > pV2->x) |
|
return false; |
|
|
|
// This is the same as the following line: |
|
// return (pEdge1->m_flDxDy <= pEdge2->m_flDxDy); |
|
Vector2D dEdge1, dEdge2; |
|
Vector2DSubtract( pVertices[ pEdge1->m_nVert[ 1 - pEdge1->m_nMinVert ] ].AsVector2D(), pV1->AsVector2D(), dEdge1 ); |
|
Vector2DSubtract( pVertices[ pEdge2->m_nVert[ 1 - pEdge2->m_nMinVert ] ].AsVector2D(), pV2->AsVector2D(), dEdge2 ); |
|
Assert( dEdge1.y >= 0.0f ); |
|
Assert( dEdge2.y >= 0.0f ); |
|
|
|
return dEdge1.x * dEdge2.y <= dEdge1.y * dEdge2.x; |
|
} |
|
}; |
|
|
|
bool COcclusionSystem::IsOccluded( const Vector &vecAbsMins, const Vector &vecAbsMaxs ) |
|
{ |
|
if ( r_occlusion.GetInt() == 0 ) |
|
return false; |
|
|
|
VPROF_BUDGET( "COcclusionSystem::IsOccluded", VPROF_BUDGETGROUP_OCCLUSION ); |
|
|
|
// @MULTICORE (toml 9/11/2006): need to eliminate this mutex |
|
static CThreadFastMutex mutex; |
|
AUTO_LOCK( mutex ); |
|
|
|
RecomputeOccluderEdgeList(); |
|
|
|
// No occluders? Then the edge list isn't occluded |
|
if ( m_WingedEdgeList.EdgeCount() == 0 ) |
|
return false; |
|
|
|
// Don't occlude things that have large screen area |
|
// Use a super cheap but inaccurate screen area computation |
|
Vector vecCenter; |
|
VectorAdd( vecAbsMaxs, vecAbsMins, vecCenter ); |
|
vecCenter *= 0.5f; |
|
|
|
vecCenter -= m_vecCameraPosition; |
|
float flDist = DotProduct( m_NearClipPlane.normal, vecCenter ); |
|
if (flDist <= 0.0f) |
|
return false; |
|
|
|
flDist += m_flNearPlaneDist; |
|
|
|
Vector vecSize; |
|
VectorSubtract( vecAbsMaxs, vecAbsMins, vecSize ); |
|
float flRadiusSq = DotProduct( vecSize, vecSize ) * 0.25f; |
|
|
|
float flScreenArea = m_flFOVFactor * flRadiusSq / (flDist * flDist); |
|
float flMaxSize = r_occludeemaxarea.GetFloat() * 0.01f; |
|
if ( flMaxSize == 0.0f ) |
|
{ |
|
flMaxSize = m_flMaxOccludeeArea; |
|
} |
|
if (flScreenArea >= flMaxSize) |
|
return false; |
|
|
|
// Clear out its state |
|
s_WingedTestEdgeList.Clear(); |
|
|
|
// NOTE: This assumes that frustum culling has already occurred on this object |
|
// If that were not the case, we'd need to add a little extra into this |
|
// (probably a single plane test, which tests if the box is wholly behind the camera ) |
|
|
|
// Convert the bbox into a max of 3 quads... |
|
const Vector *pCornerVert[2] = { &vecAbsMins, &vecAbsMaxs }; |
|
|
|
// Compute the 8 box verts, and transform them into projective space... |
|
// NOTE: We'd want to project them *after* the plane test if there were |
|
// no frustum culling. |
|
int i; |
|
Vector pVecProjectedVertex[8]; |
|
|
|
// NOTE: The code immediately below is an optimized version of this loop |
|
// The optimization takes advantage of the fact that the verts are all |
|
// axis aligned. |
|
// Vector vecBoxVertex; |
|
// for ( i = 0; i < 8; ++i ) |
|
// { |
|
// vecBoxVertex.x = pCornerVert[ (i & 0x1) ]->x; |
|
// vecBoxVertex.y = pCornerVert[ (i & 0x2) >> 1 ]->y; |
|
// vecBoxVertex.z = pCornerVert[ (i & 0x4) >> 2 ]->z; |
|
// Vector3DMultiplyPositionProjective( m_WorldToProjection, vecBoxVertex, pVecProjectedVertex[ i ] ); |
|
// if ( pVecProjectedVertex[ i ].z <= 0.0f ) |
|
// return false; |
|
// } |
|
|
|
Vector4D vecProjVert[8]; |
|
Vector4D vecDeltaProj[3]; |
|
Vector4D vecAbsMins4D( vecAbsMins.x, vecAbsMins.y, vecAbsMins.z, 1.0f ); |
|
Vector4DMultiply( m_WorldToProjection, vecAbsMins4D, vecProjVert[0] ); |
|
if ( vecProjVert[0].w <= 0.0f ) |
|
return false; |
|
float flOOW = 1.0f / vecProjVert[0].w; |
|
|
|
vecDeltaProj[0].Init( vecSize.x * m_WorldToProjection[0][0], vecSize.x * m_WorldToProjection[1][0], vecSize.x * m_WorldToProjection[2][0], vecSize.x * m_WorldToProjection[3][0] ); |
|
vecDeltaProj[1].Init( vecSize.y * m_WorldToProjection[0][1], vecSize.y * m_WorldToProjection[1][1], vecSize.y * m_WorldToProjection[2][1], vecSize.y * m_WorldToProjection[3][1] ); |
|
vecDeltaProj[2].Init( vecSize.z * m_WorldToProjection[0][2], vecSize.z * m_WorldToProjection[1][2], vecSize.z * m_WorldToProjection[2][2], vecSize.z * m_WorldToProjection[3][2] ); |
|
|
|
pVecProjectedVertex[0].Init( vecProjVert[0].x * flOOW, vecProjVert[0].y * flOOW, vecProjVert[0].z * flOOW ); |
|
if ( pVecProjectedVertex[0].z <= 0.0f ) |
|
return false; |
|
|
|
for ( i = 1; i < 8; ++i ) |
|
{ |
|
int nIndex = s_pSourceIndices[i]; |
|
int nDelta = s_pDeltaIndices[i]; |
|
Vector4DAdd( vecProjVert[nIndex], vecDeltaProj[nDelta], vecProjVert[i] ); |
|
if ( vecProjVert[ i ].w <= 0.0f ) |
|
return false; |
|
flOOW = 1.0f / vecProjVert[i].w; |
|
pVecProjectedVertex[ i ].Init( vecProjVert[i].x * flOOW, vecProjVert[i].y * flOOW, vecProjVert[i].z * flOOW ); |
|
if ( pVecProjectedVertex[ i ].z <= 0.0f ) |
|
return false; |
|
} |
|
|
|
// Precompute stuff needed by the loop over faces below |
|
float pSign[2] = { -1, 1 }; |
|
Vector vecDelta[2]; |
|
VectorSubtract( *pCornerVert[0], m_vecCameraPosition, vecDelta[0] ); |
|
VectorSubtract( m_vecCameraPosition, *pCornerVert[1], vecDelta[1] ); |
|
|
|
// Determine which faces + edges are visible... |
|
++m_nTests; |
|
int pSurfInd[6]; |
|
for ( i = 0; i < 6; ++i ) |
|
{ |
|
int nDim = ( i >> 1 ); |
|
int nInd = i & 0x1; |
|
|
|
// Try to backface cull each of the 6 box faces |
|
if ( vecDelta[nInd][nDim] <= 0.0f ) |
|
{ |
|
pSurfInd[i] = -1; |
|
continue; |
|
} |
|
|
|
cplane_t cameraSpacePlane, projectionSpacePlane; |
|
float flSign = pSign[nInd]; |
|
float flPlaneDist = (*pCornerVert[nInd])[ nDim ] * flSign; |
|
MatrixTransformAxisAlignedPlane( m_WorldToCamera, nDim, flSign, flPlaneDist, cameraSpacePlane ); |
|
ComputeScreenSpacePlane( cameraSpacePlane, &projectionSpacePlane ); |
|
int nSurfID = s_WingedTestEdgeList.AddSurface( projectionSpacePlane ); |
|
pSurfInd[i] = nSurfID; |
|
|
|
// Mark edges as being used... |
|
int *pFaceEdges = s_pFaceEdges[i]; |
|
s_pEdges[ pFaceEdges[0] ].m_nTestCount = m_nTests; |
|
s_pEdges[ pFaceEdges[1] ].m_nTestCount = m_nTests; |
|
s_pEdges[ pFaceEdges[2] ].m_nTestCount = m_nTests; |
|
s_pEdges[ pFaceEdges[3] ].m_nTestCount = m_nTests; |
|
} |
|
|
|
// Sort edges by minimum Y + dx/dy... |
|
int pEdgeSort[12]; |
|
CUtlSortVector< int, WingedEdgeLessFunc > edgeSort( pEdgeSort, 12 ); |
|
edgeSort.SetLessContext( pVecProjectedVertex ); |
|
for ( i = 0; i < 12; ++i ) |
|
{ |
|
// Skip non-visible edges |
|
EdgeInfo_t *pEdge = &s_pEdges[i]; |
|
if ( pEdge->m_nTestCount != m_nTests ) |
|
continue; |
|
|
|
pEdge->m_nMinVert = ( pVecProjectedVertex[ pEdge->m_nVert[0] ].y >= pVecProjectedVertex[ pEdge->m_nVert[1] ].y ); |
|
edgeSort.Insert( i ); |
|
} |
|
|
|
// Now add them into the winged edge list, in sorted order... |
|
int nEdgeCount = edgeSort.Count(); |
|
for ( i = 0; i < nEdgeCount; ++i ) |
|
{ |
|
EdgeInfo_t *pEdge = &s_pEdges[edgeSort[i]]; |
|
|
|
// The enter + leave ids depend entirely on which edge is further up |
|
// This works because the edges listed in s_pEdges show the edges as they |
|
// would be visited in *clockwise* order |
|
const Vector &startVert = pVecProjectedVertex[pEdge->m_nVert[pEdge->m_nMinVert]]; |
|
const Vector &endVert = pVecProjectedVertex[pEdge->m_nVert[1 - pEdge->m_nMinVert]]; |
|
int nLeaveSurfID = pSurfInd[ pEdge->m_nFace[pEdge->m_nMinVert] ]; |
|
int nEnterSurfID = pSurfInd[ pEdge->m_nFace[1 - pEdge->m_nMinVert] ]; |
|
|
|
s_WingedTestEdgeList.AddEdge( startVert, endVert, nLeaveSurfID, nEnterSurfID ); |
|
} |
|
|
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
s_WingedTestEdgeList.CheckConsistency(); |
|
#endif |
|
|
|
// Now let's see if this edge list is occluded or not.. |
|
bool bOccluded = m_WingedEdgeList.IsOccludingEdgeList( s_WingedTestEdgeList ); |
|
if (bOccluded) |
|
{ |
|
++m_nOccluded; |
|
} |
|
|
|
s_WingedTestEdgeList.QueueVisualization( s_VisualizationColor[bOccluded] ); |
|
|
|
return bOccluded; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to debug the occlusion system |
|
//----------------------------------------------------------------------------- |
|
void VisualizeQueuedEdges( ) |
|
{ |
|
#ifndef SWDS |
|
if ( !g_EdgeVisualization.Count() ) |
|
return; |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->Bind( g_pMaterialWireframeVertexColorIgnoreZ ); |
|
|
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( ); |
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_LINES, g_EdgeVisualization.Count() ); |
|
|
|
int i; |
|
for ( i = g_EdgeVisualization.Count(); --i >= 0; ) |
|
{ |
|
EdgeVisualizationInfo_t &info = g_EdgeVisualization[i]; |
|
|
|
meshBuilder.Position3fv( info.m_vecPoint[0].Base() ); |
|
meshBuilder.Color4ubv( info.m_pColor ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3fv( info.m_vecPoint[1].Base() ); |
|
#ifdef DEBUG_OCCLUSION_SYSTEM |
|
meshBuilder.Color4ub( 0, 0, 255, 255 ); |
|
#else |
|
meshBuilder.Color4ubv( info.m_pColor ); |
|
#endif |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PopMatrix(); |
|
|
|
g_EdgeVisualization.RemoveAll(); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Render debugging overlay |
|
//----------------------------------------------------------------------------- |
|
void COcclusionSystem::DrawDebugOverlays() |
|
{ |
|
// Draw the occludees |
|
VisualizeQueuedEdges(); |
|
}
|
|
|