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.
520 lines
12 KiB
520 lines
12 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "vrad.h"
|
|
#include "vmpi.h"
|
|
#ifdef MPI
|
|
#include "messbuf.h"
|
|
static MessageBuffer mb;
|
|
#endif
|
|
|
|
#define HALFBIT
|
|
|
|
extern char source[MAX_PATH];
|
|
extern char vismatfile[_MAX_PATH];
|
|
extern char incrementfile[_MAX_PATH];
|
|
extern qboolean incremental;
|
|
|
|
/*
|
|
===================================================================
|
|
|
|
VISIBILITY MATRIX
|
|
|
|
Determine which patches can see each other
|
|
Use the PVS to accelerate if available
|
|
===================================================================
|
|
*/
|
|
|
|
#define TEST_EPSILON 0.1
|
|
#define PLANE_TEST_EPSILON 0.01 // patch must be this much in front of the plane to be considered "in front"
|
|
#define PATCH_FACE_OFFSET 0.1 // push patch origins off from the face by this amount to avoid self collisions
|
|
|
|
#define STREAM_SIZE 512
|
|
|
|
class CTransferMaker
|
|
{
|
|
public:
|
|
|
|
CTransferMaker( transfer_t *all_transfers );
|
|
~CTransferMaker();
|
|
|
|
FORCEINLINE void TestMakeTransfer( Vector start, Vector stop, int ndxShooter, int ndxReciever )
|
|
{
|
|
g_RtEnv.AddToRayStream( m_RayStream, start, stop, &m_pResults[m_nTests] );
|
|
m_pShooterPatches[m_nTests] = ndxShooter;
|
|
m_pRecieverPatches[m_nTests] = ndxReciever;
|
|
++m_nTests;
|
|
}
|
|
|
|
void Finish();
|
|
|
|
private:
|
|
|
|
int m_nTests;
|
|
RayTracingSingleResult *m_pResults;
|
|
int *m_pShooterPatches;
|
|
int *m_pRecieverPatches;
|
|
RayStream m_RayStream;
|
|
transfer_t *m_AllTransfers;
|
|
};
|
|
|
|
CTransferMaker::CTransferMaker( transfer_t *all_transfers ) :
|
|
m_AllTransfers( all_transfers ), m_nTests( 0 )
|
|
{
|
|
m_pResults = (RayTracingSingleResult *)calloc( 1, MAX_PATCHES * sizeof ( RayTracingSingleResult ) );
|
|
m_pShooterPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) );
|
|
m_pRecieverPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) );
|
|
}
|
|
|
|
CTransferMaker::~CTransferMaker()
|
|
{
|
|
free ( m_pResults );
|
|
free ( m_pShooterPatches );
|
|
free (m_pRecieverPatches );
|
|
}
|
|
|
|
void CTransferMaker::Finish()
|
|
{
|
|
g_RtEnv.FinishRayStream( m_RayStream );
|
|
for ( int i = 0; i < m_nTests; ++i )
|
|
{
|
|
if ( m_pResults[i].HitID == -1 || m_pResults[i].HitDistance >= m_pResults[i].ray_length )
|
|
{
|
|
MakeTransfer( m_pShooterPatches[i], m_pRecieverPatches[i], m_AllTransfers );
|
|
}
|
|
}
|
|
m_nTests = 0;
|
|
}
|
|
|
|
|
|
dleaf_t* PointInLeaf (int iNode, Vector const& point)
|
|
{
|
|
if ( iNode < 0 )
|
|
return &dleafs[ (-1-iNode) ];
|
|
|
|
dnode_t *node = &dnodes[iNode];
|
|
dplane_t *plane = &dplanes[ node->planenum ];
|
|
|
|
float dist = DotProduct (point, plane->normal) - plane->dist;
|
|
if ( dist > TEST_EPSILON )
|
|
{
|
|
return PointInLeaf( node->children[0], point );
|
|
}
|
|
else if ( dist < -TEST_EPSILON )
|
|
{
|
|
return PointInLeaf( node->children[1], point );
|
|
}
|
|
else
|
|
{
|
|
dleaf_t *pTest = PointInLeaf( node->children[0], point );
|
|
if ( pTest->cluster != -1 )
|
|
return pTest;
|
|
|
|
return PointInLeaf( node->children[1], point );
|
|
}
|
|
}
|
|
|
|
|
|
int ClusterFromPoint( Vector const& point )
|
|
{
|
|
dleaf_t *leaf = PointInLeaf( 0, point );
|
|
|
|
return leaf->cluster;
|
|
}
|
|
|
|
void PvsForOrigin (Vector& org, byte *pvs)
|
|
{
|
|
int visofs;
|
|
int cluster;
|
|
|
|
if (!visdatasize)
|
|
{
|
|
memset (pvs, 255, (dvis->numclusters+7)/8 );
|
|
return;
|
|
}
|
|
|
|
cluster = ClusterFromPoint( org );
|
|
if ( cluster < 0 )
|
|
{
|
|
visofs = -1;
|
|
}
|
|
else
|
|
{
|
|
visofs = dvis->bitofs[ cluster ][DVIS_PVS];
|
|
}
|
|
|
|
if (visofs == -1)
|
|
Error ("visofs == -1");
|
|
|
|
DecompressVis (&dvisdata[visofs], pvs);
|
|
}
|
|
|
|
|
|
void TestPatchToPatch( int ndxPatch1, int ndxPatch2, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread )
|
|
{
|
|
Vector tmp;
|
|
|
|
//
|
|
// get patches
|
|
//
|
|
if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() )
|
|
return;
|
|
|
|
CPatch *patch = &g_Patches.Element( ndxPatch1 );
|
|
CPatch *patch2 = &g_Patches.Element( ndxPatch2 );
|
|
|
|
if (patch2->child1 != g_Patches.InvalidIndex() )
|
|
{
|
|
// check to see if we should use a child node instead
|
|
|
|
VectorSubtract( patch->origin, patch2->origin, tmp );
|
|
// SQRT( 1/4 )
|
|
// FIXME: should be based on form-factor (ie. include visible angle, etc)
|
|
if ( DotProduct(tmp, tmp) * 0.0625 < patch2->area )
|
|
{
|
|
TestPatchToPatch( ndxPatch1, patch2->child1, head, transfers, transferMaker, iThread );
|
|
TestPatchToPatch( ndxPatch1, patch2->child2, head, transfers, transferMaker, iThread );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check vis between patch and patch2
|
|
// if bit has not already been set
|
|
// && v2 is not behind light plane
|
|
// && v2 is visible from v1
|
|
if ( DotProduct( patch2->origin, patch->normal ) > patch->planeDist + PLANE_TEST_EPSILON )
|
|
{
|
|
// push out origins from face so that don't intersect their owners
|
|
Vector p1, p2;
|
|
VectorAdd( patch->origin, patch->normal, p1 );
|
|
VectorAdd( patch2->origin, patch2->normal, p2 );
|
|
transferMaker.TestMakeTransfer( p1, p2, ndxPatch1, ndxPatch2 );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
TestPatchToFace
|
|
|
|
Sets vis bits for all patches in the face
|
|
==============
|
|
*/
|
|
void TestPatchToFace (unsigned patchnum, int facenum, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread )
|
|
{
|
|
if( faceParents.Element( facenum ) == g_Patches.InvalidIndex() || patchnum == g_Patches.InvalidIndex() )
|
|
return;
|
|
|
|
CPatch *patch = &g_Patches.Element( patchnum );
|
|
CPatch *patch2 = &g_Patches.Element( faceParents.Element( facenum ) );
|
|
|
|
// if emitter is behind that face plane, skip all patches
|
|
|
|
CPatch *pNextPatch;
|
|
|
|
if ( patch2 && DotProduct(patch->origin, patch2->normal) > patch2->planeDist + PLANE_TEST_EPSILON )
|
|
{
|
|
// we need to do a real test
|
|
for( ; patch2; patch2 = pNextPatch )
|
|
{
|
|
// next patch
|
|
pNextPatch = NULL;
|
|
if( patch2->ndxNextParent != g_Patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &g_Patches.Element( patch2->ndxNextParent );
|
|
}
|
|
|
|
/*
|
|
// skip patches too far away
|
|
VectorSubtract( patch->origin, patch2->origin, tmp );
|
|
if (DotProduct( tmp, tmp ) > 512 * 512)
|
|
continue;
|
|
*/
|
|
|
|
int ndxPatch2 = patch2 - g_Patches.Base();
|
|
TestPatchToPatch( patchnum, ndxPatch2, head, transfers, transferMaker, iThread );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct ClusterDispList_t
|
|
{
|
|
CUtlVector<int> dispFaces;
|
|
};
|
|
|
|
static CUtlVector<ClusterDispList_t> g_ClusterDispFaces;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Helps us find all displacements associated with a particular cluster
|
|
//-----------------------------------------------------------------------------
|
|
void AddDispsToClusterTable( void )
|
|
{
|
|
g_ClusterDispFaces.SetCount( g_ClusterLeaves.Count() );
|
|
|
|
//
|
|
// add displacement faces to the cluster table
|
|
//
|
|
for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ )
|
|
{
|
|
// search for displacement faces
|
|
if( g_pFaces[ndxFace].dispinfo == -1 )
|
|
continue;
|
|
|
|
//
|
|
// get the clusters associated with the face
|
|
//
|
|
if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() )
|
|
{
|
|
CPatch *pNextPatch = NULL;
|
|
for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch )
|
|
{
|
|
// next patch
|
|
pNextPatch = NULL;
|
|
if( pPatch->ndxNext != g_Patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &g_Patches.Element( pPatch->ndxNext );
|
|
}
|
|
|
|
if( pPatch->clusterNumber != g_Patches.InvalidIndex() )
|
|
{
|
|
int ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.Find( ndxFace );
|
|
if( ndxDisp == -1 )
|
|
{
|
|
ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.AddToTail();
|
|
g_ClusterDispFaces[pPatch->clusterNumber].dispFaces[ndxDisp] = ndxFace;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct ClusterPatchList_t
|
|
{
|
|
CUtlVector<int> patches;
|
|
};
|
|
|
|
static CUtlVector<ClusterPatchList_t> g_ClusterStaticPropPatches;
|
|
|
|
void AddStaticPropPatchesToClusterTable()
|
|
{
|
|
g_ClusterStaticPropPatches.SetCount( g_ClusterLeaves.Count() );
|
|
|
|
for ( int i = 0; i < g_Patches.Count(); i++ )
|
|
{
|
|
const CPatch &patch = g_Patches[ i ];
|
|
if ( patch.faceNumber >= 0 || patch.clusterNumber < 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
g_ClusterStaticPropPatches[ patch.clusterNumber ].patches.AddToTail( i );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
BuildVisRow
|
|
|
|
Calc vis bits from a single patch
|
|
==============
|
|
*/
|
|
void BuildVisRow (int patchnum, byte *pvs, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread )
|
|
{
|
|
int j, k, l, leafIndex;
|
|
CPatch *patch;
|
|
dleaf_t *leaf;
|
|
byte face_tested[MAX_MAP_FACES];
|
|
byte disp_tested[MAX_MAP_FACES];
|
|
|
|
patch = &g_Patches.Element( patchnum );
|
|
|
|
memset( face_tested, 0, numfaces ) ;
|
|
memset( disp_tested, 0, numfaces );
|
|
|
|
for (j=0; j<dvis->numclusters; j++)
|
|
{
|
|
if ( ! ( pvs[(j)>>3] & (1<<((j)&7)) ) )
|
|
{
|
|
continue; // not in pvs
|
|
}
|
|
|
|
for ( leafIndex = 0; leafIndex < g_ClusterLeaves[j].leafCount; leafIndex++ )
|
|
{
|
|
leaf = dleafs + g_ClusterLeaves[j].leafs[leafIndex];
|
|
|
|
for (k=0 ; k<leaf->numleaffaces; k++)
|
|
{
|
|
l = dleaffaces[leaf->firstleafface + k];
|
|
// faces can be marksurfed by multiple leaves, but
|
|
// don't bother testing again
|
|
if (face_tested[l])
|
|
{
|
|
continue;
|
|
}
|
|
face_tested[l] = 1;
|
|
|
|
// don't check patches on the same face
|
|
if (patch->faceNumber == l)
|
|
continue;
|
|
TestPatchToFace (patchnum, l, head, transfers, transferMaker, iThread );
|
|
}
|
|
}
|
|
|
|
int dispCount = g_ClusterDispFaces[j].dispFaces.Count();
|
|
for( int ndxDisp = 0; ndxDisp < dispCount; ndxDisp++ )
|
|
{
|
|
int ndxFace = g_ClusterDispFaces[j].dispFaces[ndxDisp];
|
|
if( disp_tested[ndxFace] )
|
|
continue;
|
|
|
|
disp_tested[ndxFace] = 1;
|
|
|
|
// don't check patches on the same face
|
|
if( patch->faceNumber == ndxFace )
|
|
continue;
|
|
|
|
TestPatchToFace( patchnum, ndxFace, head, transfers, transferMaker, iThread );
|
|
}
|
|
|
|
if ( g_bStaticPropBounce )
|
|
{
|
|
// Test static prop patches
|
|
int staticPropPatchCount = g_ClusterStaticPropPatches[ j ].patches.Count();
|
|
for ( int i = 0; i < staticPropPatchCount; i++ )
|
|
{
|
|
int nPatchIdx = g_ClusterStaticPropPatches[ j ].patches[ i ];
|
|
if ( nPatchIdx != patchnum )
|
|
{
|
|
TestPatchToPatch( patchnum, nPatchIdx, head, transfers, transferMaker, iThread );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Msg("%d) Transfers: %5d\n", patchnum, patch->numtransfers);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===========
|
|
BuildVisLeafs
|
|
|
|
This is run by multiple threads
|
|
===========
|
|
*/
|
|
|
|
transfer_t* BuildVisLeafs_Start()
|
|
{
|
|
return (transfer_t *)calloc( 1, MAX_PATCHES * sizeof( transfer_t ) );
|
|
}
|
|
|
|
|
|
// If PatchCB is non-null, it is called after each row is generated (used by MPI).
|
|
void BuildVisLeafs_Cluster(
|
|
int threadnum,
|
|
transfer_t *transfers,
|
|
int iCluster,
|
|
void (*PatchCB)(int iThread, int patchnum, CPatch *patch)
|
|
)
|
|
{
|
|
byte pvs[(MAX_MAP_CLUSTERS+7)/8];
|
|
CPatch *patch;
|
|
int head;
|
|
unsigned patchnum;
|
|
|
|
DecompressVis( &dvisdata[ dvis->bitofs[ iCluster ][DVIS_PVS] ], pvs);
|
|
head = 0;
|
|
|
|
CTransferMaker transferMaker( transfers );
|
|
|
|
// light every patch in the cluster
|
|
if( clusterChildren.Element( iCluster ) != clusterChildren.InvalidIndex() )
|
|
{
|
|
CPatch *pNextPatch;
|
|
for( patch = &g_Patches.Element( clusterChildren.Element( iCluster ) ); patch; patch = pNextPatch )
|
|
{
|
|
//
|
|
// next patch
|
|
//
|
|
pNextPatch = NULL;
|
|
if( patch->ndxNextClusterChild != g_Patches.InvalidIndex() )
|
|
{
|
|
pNextPatch = &g_Patches.Element( patch->ndxNextClusterChild );
|
|
}
|
|
|
|
patchnum = patch - g_Patches.Base();
|
|
|
|
// build to all other world clusters
|
|
BuildVisRow (patchnum, pvs, head, transfers, transferMaker, threadnum );
|
|
transferMaker.Finish();
|
|
|
|
// do the transfers
|
|
MakeScales( patchnum, transfers );
|
|
|
|
// Let MPI aggregate the data if it's being used.
|
|
if ( PatchCB )
|
|
PatchCB( threadnum, patchnum, patch );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BuildVisLeafs_End( transfer_t *transfers )
|
|
{
|
|
free( transfers );
|
|
}
|
|
|
|
|
|
void BuildVisLeafs( int threadnum, void *pUserData )
|
|
{
|
|
transfer_t *transfers = BuildVisLeafs_Start();
|
|
|
|
while ( 1 )
|
|
{
|
|
//
|
|
// build a minimal BSP tree that only
|
|
// covers areas relevent to the PVS
|
|
//
|
|
// JAY: Now this returns a cluster index
|
|
int iCluster = GetThreadWork();
|
|
if ( iCluster == -1 )
|
|
break;
|
|
|
|
BuildVisLeafs_Cluster( threadnum, transfers, iCluster, NULL );
|
|
}
|
|
|
|
BuildVisLeafs_End( transfers );
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
BuildVisMatrix
|
|
==============
|
|
*/
|
|
void BuildVisMatrix (void)
|
|
{
|
|
if ( g_bUseMPI )
|
|
{
|
|
RunMPIBuildVisLeafs();
|
|
}
|
|
else
|
|
{
|
|
RunThreadsOn (dvis->numclusters, true, BuildVisLeafs);
|
|
}
|
|
}
|
|
|
|
void FreeVisMatrix (void)
|
|
{
|
|
|
|
}
|