PVS
A Potentially Visible Set (or Potential Visibility Set) is a collection of visleaves which might be visible from a given location. It is not a precise measure of visibility, but thanks to being pre-compiled into a map, it is a very fast one. It forms the basis for many run-time tests, including networking filtering and rendering control.
Console Commands
r_novis
<boolean>- Disables the PVS system, causing the whole world to be rendered at once.
r_lockpvs
<boolean>- Prevents the PVS system from updating. Allows direct inspection "behind the scenes".
mat_wireframe
<choices>- While not strictly PVS-related, wireframe mode does allow you to see leaves popping in and out of view.
sv_strict_notarget
<boolean>- If set, notarget will cause entities to never think they are in the PVS.
test_randomizeinpvs
<void>- [Todo]
Programming
Clusters
int engine->GetClusterForOrigin( Vector origin )
- Gets the cluster for the given origin.
- int engine->GetClusterCount()
- The number of clusters in the map. Tip:
ceil(engine->GetClusterCount() / 8.0f)
returns the appropriate length for a PVS buffer in the current map. - int engine->GetAllClusterBounds( bbox_t *pBBoxList, int maxBBox )
- Gets a list of all clusters' bounds. Returns total number of clusters.
bbox_t
is a struct containing a min and max vector.
PVS
A PVS is stored in a byte buffer in which each bit represents the visibility of a cluster.
int engine->GetPVSForCluster( int cluster, int outputpvslength, byte* outputpvs )
- Places the PVS for the given cluster in
outputpvs
. The return value is the number of bytes that were written; values in the rest of the buffer are undefined. bool engine->CheckOriginInPVS( Vector origin, byte *checkpvs, int checkpvssize )
- Determines whether the given location is within the given PVS.
bool engine->CheckBoxInPVS( Vector mins, Vector maxs, byte *checkpvs, int checkpvssize )
- Determines whether the given box is within the given PVS at any point.
void engine->ResetPVS( byte *pvs, int pvssize )
- Resets
pvssize
bytes to 0, starting atpvs
. void engine->AddOriginToPVS( Vector origin )
- Deprecated?[confirm] Adds the PVS of the given origin to the "current accumulated pvs" with an "8 unit fudge factor".
Example
This function bypasses the problem of a dynamic light entity being processed needlessly when its bounding box pokes through a wall. The code is quite expensive, but not as expensive as a dynamic light!
int CMyEnt::ShouldTransmit(const CCheckTransmitInfo *pInfo)
{
Vector vecSurroundMins, vecSurroundMaxs;
CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
// Are our surrounding bounds in PVS?
if ( !engine->CheckBoxInPVS(vecSurroundMins, vecSurroundMaxs, pInfo->m_PVS, pInfo->m_nPVSSize) )
return FL_EDICT_DONTSEND;
// Get our own PVS (cache the buffer if you use this code)
int pvsSize = ceil(engine->GetClusterCount() / 8.0f); // bits to bytes
byte* pvs = new byte[pvsSize];
int cluster = engine->GetClusterForOrigin(GetAbsOrigin());
engine->GetPVSForCluster(cluster, pvsSize, pvs);
// Find the intersection of our PVS and the player's
byte* pvs_shared = new byte[pvsSize];
bool found_overlap = false;
for (int i=0; i < pvsSize; i++)
{
pvs_shared[i] = pvs[i] & pInfo->PVS[i]; // bitwise AND
if (!found_overlap && pvs_shared[i] != 0)
found_overlap = true;
}
bool in_pvs = false;
if (found_overlap)
in_pvs = engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs_shared, pvsSize );
// Free the memory we just allocated
delete[] pvs;
delete[] pvs_shared;
// transmit if we are in the shared PVS
return in_pvs ? FL_EDICT_ALWAYS : FL_EDICT_DONTSEND;
}
Areas
Areas are completely separate collections of leaves, either set in stone by brushwork or created dynamically by closed areaportals (the latter of which PVS alone cannot account for).
int engine->GetArea( Vector origin )
- Determine which area the origin is within.
void engine->GetAreaBits( int area, byte *bits, int buflen )
- Get a bit buffer of which areas the given area can flow into.
int engine->CheckAreasConnected( int area1, int area2 )
- Check whether area1 flows into area2 and vice versa. Result depends on areaportal state. Note:Areaportals will close of their own accord depending on the position of the player. Area testing is therefore not always a good idea!
bool engine->GetAreaPortalPlane( Vector vViewOrigin, int portalKey, VPlane *pPlane )
- Given a view origin (which tells us the area to start looking in) and a portal key, fill in the plane that leads out of this area (it points into whatever area it leads to).
void engine->SetAreaPortalState( int portalNumber, int isOpen )
void engine->SetAreaPortalStates( int *portalNumbers, int *isOpen, int nPortals )
- Open or close one or many areaportals.
See also
- Visibility optimization for in-depth discussion of how leaves and the PVS work.
- Half-Life 2 Map Editing Optimization Guide: Visleafs [sic]
- VBSP, which generates leaves
- VVIS, which determines visibility between leaves
- PAS, the Potentially Audible Set
- Wikipedia:Potentially visible set