//========= 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 dispFaces; }; static CUtlVector 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 patches; }; static CUtlVector 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; jnumclusters; 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 ; knumleaffaces; 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) { }