//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "incremental.h" #include "lightmap.h" static bool g_bFileError = false; // -------------------------------------------------------------------------------- // // Static helpers. // -------------------------------------------------------------------------------- // static bool CompareLights( dworldlight_t *a, dworldlight_t *b ) { static float flEpsilon = 1e-7; bool a1 = VectorsAreEqual( a->origin, b->origin, flEpsilon ); bool a2 = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers bool a3 = VectorsAreEqual( a->normal, b->normal, flEpsilon ); bool a4 = fabs( a->constant_attn - b->constant_attn ) < flEpsilon; bool a5 = fabs( a->linear_attn - b->linear_attn ) < flEpsilon; bool a6 = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon; bool a7 = fabs( float( a->flags - b->flags ) ) < flEpsilon; bool a8 = fabs( a->stopdot - b->stopdot ) < flEpsilon; bool a9 = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon; bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon; bool a11 = fabs( a->radius - b->radius ) < flEpsilon; return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11; } long FileOpen( char const *pFilename, bool bRead ) { g_bFileError = false; return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" ); } void FileClose( long fp ) { if( fp ) g_pFileSystem->Close( (FILE*)fp ); } // Returns true if there was an error reading from the file. bool FileError() { return g_bFileError; } static inline void FileRead( long fp, void *pOut, int size ) { if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size ) { g_bFileError = true; memset( pOut, 0, size ); } } template static inline void FileRead( long fp, T &out ) { FileRead( fp, &out, sizeof(out) ); } static inline void FileWrite( long fp, void const *pData, int size ) { if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size ) { g_bFileError = true; } } template static inline void FileWrite( long fp, T out ) { FileWrite( fp, &out, sizeof(out) ); } IIncremental* GetIncremental() { static CIncremental inc; return &inc; } // -------------------------------------------------------------------------------- // // CIncremental. // -------------------------------------------------------------------------------- // CIncremental::CIncremental() { m_TotalMemory = 0; m_pIncrementalFilename = NULL; m_pBSPFilename = NULL; m_bSuccessfulRun = false; } CIncremental::~CIncremental() { } bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename ) { m_pBSPFilename = pBSPFilename; m_pIncrementalFilename = pIncrementalFilename; return true; } bool CIncremental::PrepareForLighting() { if( !m_pBSPFilename ) return false; // Clear the touched faces list. m_FacesTouched.SetSize( numfaces ); memset( m_FacesTouched.Base(), 0, numfaces ); // If we haven't done a complete successful run yet, then we either haven't // loaded the lights, or a run was aborted and our lights are half-done so we // should reload them. if( !m_bSuccessfulRun ) LoadIncrementalFile(); // unmatched = a list of the lights we have CUtlLinkedList unmatched; for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) ) unmatched.AddToTail( i ); // Match the light lists and get rid of lights that we already have all the data for. directlight_t *pNext; directlight_t **pPrev = &activelights; for( directlight_t *dl=activelights; dl != NULL; dl = pNext ) { pNext = dl->next; //float flClosest = 3000000000; //CIncLight *pClosest = 0; // Look for this light in our light list. int iNextUnmatched, iUnmatched; for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched ) { iNextUnmatched = unmatched.Next( iUnmatched ); CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; //float flTest = (pLight->m_Light.origin - dl->light.origin).Length(); //if( flTest < flClosest ) //{ // flClosest = flTest; // pClosest = pLight; //} if( CompareLights( &dl->light, &pLight->m_Light ) ) { unmatched.Remove( iUnmatched ); // Ok, we have this light's data already, yay! // Get rid of it from the active light list. *pPrev = dl->next; free( dl ); dl = 0; break; } } //bool bTest=false; //if(bTest) // CompareLights( &dl->light, &pClosest->m_Light ); if( iUnmatched == unmatched.InvalidIndex() ) pPrev = &dl->next; } // Remove any of our lights that were unmatched. for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) ) { CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; // First tag faces that it touched so they get recomposited. for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) { m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1; } delete pLight; m_Lights.Remove( unmatched[iUnmatched] ); } // Now add a light structure for each new light. AddLightsForActiveLights(); return true; } bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ) { int version; FileRead( fp, version ); if( version != INCREMENTALFILE_VERSION ) return false; int nFaces; FileRead( fp, nFaces ); pHeader->m_FaceLightmapSizes.SetSize( nFaces ); FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); return !FileError(); } bool CIncremental::WriteIncrementalHeader( long fp ) { int version = INCREMENTALFILE_VERSION; FileWrite( fp, version ); int nFaces = numfaces; FileWrite( fp, nFaces ); CIncrementalHeader hdr; hdr.m_FaceLightmapSizes.SetSize( nFaces ); for( int i=0; i < nFaces; i++ ) { hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0]; hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1]; } FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); return !FileError(); } bool CIncremental::IsIncrementalFileValid() { long fp = FileOpen( m_pIncrementalFilename, true ); if( !fp ) return false; bool bValid = false; CIncrementalHeader hdr; if( ReadIncrementalHeader( fp, &hdr ) ) { // If the number of faces is the same and their lightmap sizes are the same, // then this file is considered a legitimate incremental file. if( hdr.m_FaceLightmapSizes.Count() == numfaces ) { int i; for( i=0; i < numfaces; i++ ) { if( hdr.m_FaceLightmapSizes[i].m_Width != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] || hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] ) { break; } } // Were all faces valid? if( i == numfaces ) bValid = true; } } FileClose( fp ); return bValid && !FileError(); } void CIncremental::AddLightToFace( IncrementalLightID lightID, int iFace, int iSample, int lmSize, float dot, int iThread ) { // If we're not being used, don't do anything. if( !m_pIncrementalFilename ) return; CIncLight *pLight = m_Lights[lightID]; // Check for the 99.99% case in which the face already exists. CLightFace *pFace; if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) { pFace = pLight->m_pCachedFaces[iThread]; } else { bool bNew; EnterCriticalSection( &pLight->m_CS ); pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew ); LeaveCriticalSection( &pLight->m_CS ); pLight->m_pCachedFaces[iThread] = pFace; if( bNew ) m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] ); } // Add this into the light's data. pFace->m_LightValues[iSample].m_Dot = dot; } unsigned short DecodeCharOrShort( CUtlBuffer *pIn ) { unsigned short val = pIn->GetUnsignedChar(); if( val & 0x80 ) { val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar(); } return val; } void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val ) { if( (val & 0xFF80) == 0 ) { pBuf->PutUnsignedChar( (unsigned char)val ); } else { if( val > 32767 ) val = 32767; pBuf->PutUnsignedChar( (val >> 8) | 0x80 ); pBuf->PutUnsignedChar( val & 0xFF ); } } void DecompressLightData( CUtlBuffer *pIn, CUtlVector *pOut ) { int iOut = 0; while( pIn->TellGet() < pIn->TellPut() ) { unsigned char runLength = pIn->GetUnsignedChar(); unsigned short usVal = DecodeCharOrShort( pIn ); while( runLength > 0 ) { --runLength; pOut->Element(iOut).m_Dot = usVal; ++iOut; } } } #ifdef _WIN32 #pragma warning (disable:4701) #endif void CompressLightData( CLightValue const *pValues, int nValues, CUtlBuffer *pBuf ) { unsigned char runLength=0; unsigned short flLastValue; for( int i=0; i < nValues; i++ ) { unsigned short flCurValue = (unsigned short)pValues[i].m_Dot; if( i == 0 ) { flLastValue = flCurValue; runLength = 1; } else if( flCurValue == flLastValue && runLength < 255 ) { ++runLength; } else { pBuf->PutUnsignedChar( runLength ); EncodeCharOrShort( pBuf, flLastValue ); flLastValue = flCurValue; runLength = 1; } } // Write the end.. if( runLength ) { pBuf->PutUnsignedChar( runLength ); EncodeCharOrShort( pBuf, flLastValue ); } } #ifdef _WIN32 #pragma warning (default:4701) #endif void MultiplyValues( CUtlVector &values, float scale ) { for( int i=0; i < values.Count(); i++ ) values[i].m_Dot *= scale; } void CIncremental::FinishFace( IncrementalLightID lightID, int iFace, int iThread ) { CIncLight *pLight = m_Lights[lightID]; // Check for the 99.99% case in which the face already exists. CLightFace *pFace; if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) { pFace = pLight->m_pCachedFaces[iThread]; // Compress the data. MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity ); pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); CompressLightData( pFace->m_LightValues.Base(), pFace->m_LightValues.Count(), &pFace->m_CompressedData ); #if 0 // test decompression CUtlVector test; test.SetSize( 2048 ); pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); DecompressLightData( &pFace->m_CompressedData, &test ); #endif if( pFace->m_CompressedData.TellPut() == 0 ) { // No contribution.. delete this face from the light. EnterCriticalSection( &pLight->m_CS ); pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex ); delete pFace; LeaveCriticalSection( &pLight->m_CS ); } else { // Discard the uncompressed data. pFace->m_LightValues.Purge(); m_FacesTouched[ pFace->m_FaceIndex ] = 1; } } } bool CIncremental::Finalize() { // If we're not being used, don't do anything. if( !m_pIncrementalFilename || !m_pBSPFilename ) return false; CUtlVector faceLights; LinkLightsToFaces( faceLights ); Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)]; CUtlVector faceLightValues; faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) ); // Only update the faces we've touched. for( int facenum = 0; facenum < numfaces; facenum++ ) { if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() ) continue; int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1; int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1; int nLuxels = w * h; assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) ); // Clear the lighting for this face. memset( faceLight, 0, nLuxels * sizeof(Vector) ); // Composite all the light contributions. for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ ) { CLightFace *pFace = faceLights[facenum][iFace]; pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); DecompressLightData( &pFace->m_CompressedData, &faceLightValues ); for( int iSample=0; iSample < nLuxels; iSample++ ) { float flDot = faceLightValues[iSample].m_Dot; if( flDot ) { VectorMA( faceLight[iSample], flDot / pFace->m_pLight->m_flMaxIntensity, pFace->m_pLight->m_Light.intensity, faceLight[iSample] ); } } } // Convert to the floating-point representation in the BSP file. Vector *pSrc = faceLight; unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ]; for( int iSample=0; iSample < nLuxels; iSample++ ) { VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest ); pDest += 4; pSrc++; } } m_bSuccessfulRun = true; return true; } void CIncremental::GetFacesTouched( CUtlVector &touched ) { touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() ); } bool CIncremental::Serialize() { if( !SaveIncrementalFile() ) return false; WriteBSPFile( (char*)m_pBSPFilename ); return true; } void CIncremental::Term() { m_Lights.PurgeAndDeleteElements(); m_TotalMemory = 0; } void CIncremental::AddLightsForActiveLights() { // Create our lights. for( directlight_t *dl=activelights; dl != NULL; dl = dl->next ) { CIncLight *pLight = new CIncLight; dl->m_IncrementalID = m_Lights.AddToTail( pLight ); // Copy the light information. pLight->m_Light = dl->light; pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) ); } } bool CIncremental::LoadIncrementalFile() { Term(); if( !IsIncrementalFileValid() ) return false; long fp = FileOpen( m_pIncrementalFilename, true ); if( !fp ) return false; // Read the header. CIncrementalHeader hdr; if( !ReadIncrementalHeader( fp, &hdr ) ) { FileClose( fp ); return false; } // Read the lights. int nLights; FileRead( fp, nLights ); for( int iLight=0; iLight < nLights; iLight++ ) { CIncLight *pLight = new CIncLight; m_Lights.AddToTail( pLight ); FileRead( fp, pLight->m_Light ); pLight->m_flMaxIntensity = max( pLight->m_Light.intensity.x, max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) ); int nFaces; FileRead( fp, nFaces ); assert( nFaces < 70000 ); for( int iFace=0; iFace < nFaces; iFace++ ) { CLightFace *pFace = new CLightFace; pLight->m_LightFaces.AddToTail( pFace ); pFace->m_pLight = pLight; FileRead( fp, pFace->m_FaceIndex ); int dataSize; FileRead( fp, dataSize ); pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); while( dataSize ) { --dataSize; unsigned char ucData; FileRead( fp, ucData ); pFace->m_CompressedData.PutUnsignedChar( ucData ); } } } FileClose( fp ); return !FileError(); } bool CIncremental::SaveIncrementalFile() { long fp = FileOpen( m_pIncrementalFilename, false ); if( !fp ) return false; if( !WriteIncrementalHeader( fp ) ) { FileClose( fp ); return false; } // Write the lights. int nLights = m_Lights.Count(); FileWrite( fp, nLights ); for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) { CIncLight *pLight = m_Lights[iLight]; FileWrite( fp, pLight->m_Light ); int nFaces = pLight->m_LightFaces.Count(); FileWrite( fp, nFaces ); for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) { CLightFace *pFace = pLight->m_LightFaces[iFace]; FileWrite( fp, pFace->m_FaceIndex ); int dataSize = pFace->m_CompressedData.TellPut(); FileWrite( fp, dataSize ); pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); while( dataSize ) { --dataSize; FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() ); } } } FileClose( fp ); return !FileError(); } void CIncremental::LinkLightsToFaces( CUtlVector &faceLights ) { faceLights.SetSize( numfaces ); for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) { CIncLight *pLight = m_Lights[iLight]; for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) { CLightFace *pFace = pLight->m_LightFaces[iFace]; if( m_FacesTouched[pFace->m_FaceIndex] ) faceLights[ pFace->m_FaceIndex ].AddToTail( pFace ); } } } // ------------------------------------------------------------------ // // CIncLight // ------------------------------------------------------------------ // CIncLight::CIncLight() { memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) ); InitializeCriticalSection( &m_CS ); } CIncLight::~CIncLight() { m_LightFaces.PurgeAndDeleteElements(); DeleteCriticalSection( &m_CS ); } CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew ) { if( bNew ) *bNew = false; // Look for it. for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) ) { CLightFace *pFace = m_LightFaces[i]; if( pFace->m_FaceIndex == iFace ) { assert( pFace->m_LightValues.Count() == lmSize ); return pFace; } } // Ok, create one. CLightFace *pFace = new CLightFace; pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace ); pFace->m_pLight = this; pFace->m_FaceIndex = iFace; pFace->m_LightValues.SetSize( lmSize ); memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize ); if( bNew ) *bNew = true; return pFace; }