//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "vbsp.h" #include "bsplib.h" #include "tier1/UtlBuffer.h" #include "tier1/utlvector.h" #include "bitmap/imageformat.h" #include #include "tier1/strtools.h" #include "tier1/utlsymbol.h" #include "vtf/vtf.h" #include "materialpatch.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" /* Meager documentation for how the cubemaps are assigned. While loading the map, it calls: *** Cubemap_SaveBrushSides Builds a list of what cubemaps manually were assigned to what faces in s_EnvCubemapToBrushSides. Immediately after loading the map, it calls: *** Cubemap_FixupBrushSidesMaterials Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each side referenced by an env_cubemap manually. Then it calls Cubemap_AttachDefaultCubemapToSpecularSides: *** Cubemap_InitCubemapSideData: Setup s_aCubemapSideData.bHasEnvMapInMaterial and bManuallyPickedByAnEnvCubemap for each side. bHasEnvMapInMaterial is set if the side's material has $envmap. bManuallyPickedByAnEnvCubemap is true if the side was in s_EnvCubemapToBrushSides. Then, for each bHasEnvMapInMaterial and !bManuallyPickedByAnEnvCubemap (ie: every specular surface that wasn't referenced by some env_cubemap), it does Cubemap_CreateTexInfo. */ struct PatchInfo_t { char *m_pMapName; int m_pOrigin[3]; }; struct CubemapInfo_t { int m_nTableId; bool m_bSpecular; }; static bool CubemapLessFunc( const CubemapInfo_t &lhs, const CubemapInfo_t &rhs ) { return ( lhs.m_nTableId < rhs.m_nTableId ); } typedef CUtlVector IntVector_t; static CUtlVector s_EnvCubemapToBrushSides; static CUtlVector s_DefaultCubemapNames; static char g_IsCubemapTexData[MAX_MAP_TEXDATA]; struct CubemapSideData_t { bool bHasEnvMapInMaterial; bool bManuallyPickedByAnEnvCubemap; }; static CubemapSideData_t s_aCubemapSideData[MAX_MAP_BRUSHSIDES]; inline bool SideHasCubemapAndWasntManuallyReferenced( int iSide ) { return s_aCubemapSideData[iSide].bHasEnvMapInMaterial && !s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap; } void Cubemap_InsertSample( const Vector& origin, int size ) { dcubemapsample_t *pSample = &g_CubemapSamples[g_nCubemapSamples]; pSample->origin[0] = ( int )origin[0]; pSample->origin[1] = ( int )origin[1]; pSample->origin[2] = ( int )origin[2]; pSample->size = size; g_nCubemapSamples++; } static const char *FindSkyboxMaterialName( void ) { for( int i = 0; i < g_MainMap->num_entities; i++ ) { char* pEntity = ValueForKey(&g_MainMap->entities[i], "classname"); if (!strcmp(pEntity, "worldspawn")) { return ValueForKey( &g_MainMap->entities[i], "skyname" ); } } return NULL; } static void BackSlashToForwardSlash( char *pname ) { while ( *pname ) { if ( *pname == '\\' ) *pname = '/'; pname++; } } static void ForwardSlashToBackSlash( char *pname ) { while ( *pname ) { if ( *pname == '/' ) *pname = '\\'; pname++; } } //----------------------------------------------------------------------------- // Finds materials that are used by a particular material //----------------------------------------------------------------------------- #define MAX_MATERIAL_NAME 512 // This is the list of materialvars which are used in our codebase to look up dependent materials static const char *s_pDependentMaterialVar[] = { "$bottommaterial", // Used by water materials "$crackmaterial", // Used by shattered glass materials "$fallbackmaterial", // Used by all materials "", // Always must be last }; static const char *FindDependentMaterial( const char *pMaterialName, const char **ppMaterialVar = NULL ) { // FIXME: This is a terrible way of doing this! It creates a dependency // between vbsp and *all* code which reads dependent materials from materialvars // At the time of writing this function, that means the engine + studiorender. // We need a better way of figuring out how to do this, but for now I'm trying to do // the fastest solution possible since it's close to ship static char pDependentMaterialName[MAX_MATERIAL_NAME]; for( int i = 0; s_pDependentMaterialVar[i][0]; ++i ) { if ( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName, MAX_MATERIAL_NAME - 1 ) ) continue; if ( !Q_stricmp( pDependentMaterialName, pMaterialName ) ) { Warning( "Material %s is depending on itself through materialvar %s! Ignoring...\n", pMaterialName, s_pDependentMaterialVar[i] ); continue; } // Return the material var that caused the dependency if ( ppMaterialVar ) { *ppMaterialVar = s_pDependentMaterialVar[i]; } #ifdef _DEBUG // FIXME: Note that this code breaks if a material has more than 1 dependent material ++i; static char pDependentMaterialName2[MAX_MATERIAL_NAME]; while( s_pDependentMaterialVar[i][0] ) { Assert( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName2, MAX_MATERIAL_NAME - 1 ) ); ++i; } #endif return pDependentMaterialName; } return NULL; } //----------------------------------------------------------------------------- // Loads VTF files //----------------------------------------------------------------------------- static bool LoadSrcVTFFiles( IVTFTexture *pSrcVTFTextures[6], const char *pSkyboxMaterialBaseName, int *pUnionTextureFlags, bool bHDR ) { const char *facingName[6] = { "rt", "lf", "bk", "ft", "up", "dn" }; int i; for( i = 0; i < 6; i++ ) { char srcMaterialName[1024]; sprintf( srcMaterialName, "%s%s", pSkyboxMaterialBaseName, facingName[i] ); IMaterial *pSkyboxMaterial = g_pMaterialSystem->FindMaterial( srcMaterialName, "skybox" ); //IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( bHDR ? "$hdrbasetexture" : "$basetexture", NULL ); //, bHDR ? false : true ); IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( "$basetexture", NULL ); // Since we're setting it to black anyway, just use $basetexture for HDR const char *vtfName = pSkyTextureVar->GetStringValue(); char srcVTFFileName[MAX_PATH]; Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); CUtlBuffer buf; if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) { // Try looking for a compressed HDR texture if ( bHDR ) { /* // FIXME: We need a way to uncompress this format! bool bHDRCompressed = true; pSkyTextureVar = pSkyboxMaterial->FindVar( "$hdrcompressedTexture", NULL ); vtfName = pSkyTextureVar->GetStringValue(); Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) */ { return false; } } else { return false; } } pSrcVTFTextures[i] = CreateVTFTexture(); if (!pSrcVTFTextures[i]->Unserialize(buf)) { Warning("*** Error unserializing skybox texture: %s\n", pSkyboxMaterialBaseName ); return false; } *pUnionTextureFlags |= pSrcVTFTextures[i]->Flags(); int flagsNoAlpha = pSrcVTFTextures[i]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); int flagsFirstNoAlpha = pSrcVTFTextures[0]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); // NOTE: texture[0] is a side texture that could be 1/2 height, so allow this and also allow 4x4 faces if ( ( ( pSrcVTFTextures[i]->Width() != pSrcVTFTextures[0]->Width() ) && ( pSrcVTFTextures[i]->Width() != 4 ) ) || ( ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height() ) && ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height()*2 ) && ( pSrcVTFTextures[i]->Height() != 4 ) ) || ( flagsNoAlpha != flagsFirstNoAlpha ) ) { Warning("*** Error: Skybox vtf files for %s weren't compiled with the same size texture and/or same flags!\n", pSkyboxMaterialBaseName ); return false; } if ( bHDR ) { pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGB323232F, false ); pSrcVTFTextures[i]->GenerateMipmaps(); pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616F, false ); } } return true; } void VTFNameToHDRVTFName( const char *pSrcName, char *pDest, int maxLen, bool bHDR ) { Q_strncpy( pDest, pSrcName, maxLen ); if( !bHDR ) { return; } char *pDot = Q_stristr( pDest, ".vtf" ); if( !pDot ) { return; } Q_strncpy( pDot, ".hdr.vtf", maxLen - ( pDot - pDest ) ); } #define DEFAULT_CUBEMAP_SIZE 32 void CreateDefaultCubemaps( bool bHDR ) { memset( g_IsCubemapTexData, 0, sizeof(g_IsCubemapTexData) ); // NOTE: This implementation depends on the fact that all VTF files contain // all mipmap levels const char *pSkyboxBaseName = FindSkyboxMaterialName(); if( !pSkyboxBaseName ) { if( s_DefaultCubemapNames.Count() ) { Warning( "This map uses env_cubemap, and you don't have a skybox, so no default env_cubemaps will be generated.\n" ); } return; } char skyboxMaterialName[MAX_PATH]; Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName ); IVTFTexture *pSrcVTFTextures[6]; int unionTextureFlags = 0; if( !LoadSrcVTFFiles( pSrcVTFTextures, skyboxMaterialName, &unionTextureFlags, bHDR ) ) { Warning( "Can't load skybox file %s to build the default cubemap!\n", skyboxMaterialName ); return; } Msg( "Creating default %scubemaps for env_cubemap using skybox materials:\n %s*.vmt\n" " ! Run buildcubemaps in the engine to get the correct cube maps.\n", bHDR ? "HDR " : "LDR ", skyboxMaterialName ); // Figure out the mip differences between the two textures int iMipLevelOffset = 0; int tmp = pSrcVTFTextures[0]->Width(); while( tmp > DEFAULT_CUBEMAP_SIZE ) { iMipLevelOffset++; tmp >>= 1; } // Create the destination cubemap IVTFTexture *pDstCubemap = CreateVTFTexture(); pDstCubemap->Init( DEFAULT_CUBEMAP_SIZE, DEFAULT_CUBEMAP_SIZE, 1, pSrcVTFTextures[0]->Format(), unionTextureFlags | TEXTUREFLAGS_ENVMAP, pSrcVTFTextures[0]->FrameCount() ); // First iterate over all frames for (int iFrame = 0; iFrame < pDstCubemap->FrameCount(); ++iFrame) { // Next iterate over all normal cube faces (we know there's 6 cause it's an envmap) for (int iFace = 0; iFace < 6; ++iFace ) { // Finally, iterate over all mip levels in the *destination* for (int iMip = 0; iMip < pDstCubemap->MipCount(); ++iMip ) { // Copy the bits from the source images into the cube faces unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, iMip + iMipLevelOffset ); unsigned char *pDstBits = pDstCubemap->ImageData( iFrame, iFace, iMip ); int iSize = pDstCubemap->ComputeMipSize( iMip ); int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( iMip + iMipLevelOffset ); // !!! FIXME: Set this to black until HDR cubemaps are built properly! memset( pDstBits, 0, iSize ); continue; if ( ( pSrcVTFTextures[iFace]->Width() == 4 ) && ( pSrcVTFTextures[iFace]->Height() == 4 ) ) // If texture is 4x4 square { // Force mip level 2 to get the 1x1 face unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, 2 ); int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( 2 ); // Replicate 1x1 mip level across entire face //memset( pDstBits, 0, iSize ); for ( int i = 0; i < ( iSize / iSrcMipSize ); i++ ) { memcpy( pDstBits + ( i * iSrcMipSize ), pSrcBits, iSrcMipSize ); } } else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height() ) // If texture is square { if ( iSrcMipSize != iSize ) { Warning( "%s - ERROR! Cannot copy square face for default cubemap! iSrcMipSize(%d) != iSize(%d)\n", skyboxMaterialName, iSrcMipSize, iSize ); memset( pDstBits, 0, iSize ); } else { // Just copy the mip level memcpy( pDstBits, pSrcBits, iSize ); } } else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height()*2 ) // If texture is rectangle 2x wide { int iMipWidth, iMipHeight, iMipDepth; pDstCubemap->ComputeMipLevelDimensions( iMip, &iMipWidth, &iMipHeight, &iMipDepth ); if ( ( iMipHeight > 1 ) && ( iSrcMipSize*2 != iSize ) ) { Warning( "%s - ERROR building default cube map! %d*2 != %d\n", skyboxMaterialName, iSrcMipSize, iSize ); memset( pDstBits, 0, iSize ); } else { // Copy row at a time and repeat last row memcpy( pDstBits, pSrcBits, iSize/2 ); //memcpy( pDstBits + iSize/2, pSrcBits, iSize/2 ); int nSrcRowSize = pSrcVTFTextures[iFace]->RowSizeInBytes( iMip + iMipLevelOffset ); int nDstRowSize = pDstCubemap->RowSizeInBytes( iMip ); if ( nSrcRowSize != nDstRowSize ) { Warning( "%s - ERROR building default cube map! nSrcRowSize(%d) != nDstRowSize(%d)!\n", skyboxMaterialName, nSrcRowSize, nDstRowSize ); memset( pDstBits, 0, iSize ); } else { for ( int i = 0; i < ( iSize/2 / nSrcRowSize ); i++ ) { memcpy( pDstBits + iSize/2 + i*nSrcRowSize, pSrcBits + iSrcMipSize - nSrcRowSize, nSrcRowSize ); } } } } else { // ERROR! This code only supports square and rectangluar 2x wide Warning( "%s - Couldn't create default cubemap because texture res is %dx%d\n", skyboxMaterialName, pSrcVTFTextures[iFace]->Width(), pSrcVTFTextures[iFace]->Height() ); memset( pDstBits, 0, iSize ); return; } } } } ImageFormat originalFormat = pDstCubemap->Format(); if( !bHDR ) { // Convert the cube to format that we can apply tools to it... pDstCubemap->ConvertImageFormat( IMAGE_FORMAT_DEFAULT, false ); } // Fixup the cubemap facing pDstCubemap->FixCubemapFaceOrientation(); // Now that the bits are in place, compute the spheremaps... pDstCubemap->GenerateSpheremap(); if( !bHDR ) { // Convert the cubemap to the final format pDstCubemap->ConvertImageFormat( originalFormat, false ); } // Write the puppy out! char dstVTFFileName[1024]; if( bHDR ) { sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.hdr.vtf", mapbase ); } else { sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.vtf", mapbase ); } CUtlBuffer outputBuf; if (!pDstCubemap->Serialize( outputBuf )) { Warning( "Error serializing default cubemap %s\n", dstVTFFileName ); return; } IZip *pak = GetPakFile(); // spit out the default one. AddBufferToPak( pak, dstVTFFileName, outputBuf.Base(), outputBuf.TellPut(), false ); // spit out all of the ones that are attached to world geometry. int i; for( i = 0; i < s_DefaultCubemapNames.Count(); i++ ) { char vtfName[MAX_PATH]; VTFNameToHDRVTFName( s_DefaultCubemapNames[i], vtfName, MAX_PATH, bHDR ); if( FileExistsInPak( pak, vtfName ) ) { continue; } AddBufferToPak( pak, vtfName, outputBuf.Base(),outputBuf.TellPut(), false ); } // Clean up the textures for( i = 0; i < 6; i++ ) { DestroyVTFTexture( pSrcVTFTextures[i] ); } DestroyVTFTexture( pDstCubemap ); } void Cubemap_CreateDefaultCubemaps( void ) { CreateDefaultCubemaps( false ); CreateDefaultCubemaps( true ); } // Builds a list of what cubemaps manually were assigned to what faces // in s_EnvCubemapToBrushSides. void Cubemap_SaveBrushSides( const char *pSideListStr ) { IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[s_EnvCubemapToBrushSides.AddToTail()]; char *pTmp = ( char * )_alloca( strlen( pSideListStr ) + 1 ); strcpy( pTmp, pSideListStr ); const char *pScan = strtok( pTmp, " " ); if( !pScan ) { return; } do { int brushSideID; if( sscanf( pScan, "%d", &brushSideID ) == 1 ) { brushSidesVector.AddToTail( brushSideID ); } } while( ( pScan = strtok( NULL, " " ) ) ); } //----------------------------------------------------------------------------- // Generate patched material name //----------------------------------------------------------------------------- static void GeneratePatchedName( const char *pMaterialName, const PatchInfo_t &info, bool bMaterialName, char *pBuffer, int nMaxLen ) { const char *pSeparator = bMaterialName ? "_" : ""; int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s%s%d_%d_%d", info.m_pMapName, pMaterialName, pSeparator, info.m_pOrigin[0], info.m_pOrigin[1], info.m_pOrigin[2] ); if ( bMaterialName ) { Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) { Error( "Generated env_cubemap patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); } } BackSlashToForwardSlash( pBuffer ); Q_strlower( pBuffer ); } //----------------------------------------------------------------------------- // Patches the $envmap for a material and all its dependents, returns true if any patching happened //----------------------------------------------------------------------------- static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture ) { // Do *NOT* patch the material if there is an $envmap specified and it's not 'env_cubemap' // FIXME: It's theoretically ok to patch the material if $envmap is not specified, // because we're using the 'replace' block, which will only add the env_cubemap if // $envmap is specified in the source material. But it will fail if someone adds // a specific non-env_cubemap $envmap to the source material at a later point. Bleah // See if we have an $envmap to patch bool bShouldPatchEnvCubemap = DoesMaterialHaveKeyValuePair( pMaterialName, "$envmap", "env_cubemap" ); // See if we have a dependent material to patch bool bDependentMaterialPatched = false; const char *pDependentMaterialVar = NULL; const char *pDependentMaterial = FindDependentMaterial( pMaterialName, &pDependentMaterialVar ); if ( pDependentMaterial ) { bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture ); } // If we have neither to patch, we're done if ( !bShouldPatchEnvCubemap && !bDependentMaterialPatched ) return false; // Otherwise we have to make a patched version of ourselves char pPatchedMaterialName[1024]; GeneratePatchedName( pMaterialName, info, true, pPatchedMaterialName, 1024 ); MaterialPatchInfo_t pPatchInfo[2]; int nPatchCount = 0; if ( bShouldPatchEnvCubemap ) { pPatchInfo[nPatchCount].m_pKey = "$envmap"; pPatchInfo[nPatchCount].m_pRequiredOriginalValue = "env_cubemap"; pPatchInfo[nPatchCount].m_pValue = pCubemapTexture; ++nPatchCount; } char pDependentPatchedMaterialName[1024]; if ( bDependentMaterialPatched ) { // FIXME: Annoying! I either have to pass back the patched dependent material name // or reconstruct it. Both are sucky. GeneratePatchedName( pDependentMaterial, info, true, pDependentPatchedMaterialName, 1024 ); pPatchInfo[nPatchCount].m_pKey = pDependentMaterialVar; pPatchInfo[nPatchCount].m_pValue = pDependentPatchedMaterialName; ++nPatchCount; } CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); return true; } //----------------------------------------------------------------------------- // Finds a texinfo that has a particular //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Create a VMT to override the specified texinfo which references the cubemap entity at the specified origin. // Returns the index of the new (or preexisting) texinfo referencing that VMT. // // Also adds the new cubemap VTF filename to s_DefaultCubemapNames so it can copy the // default (skybox) cubemap into this file so the cubemap doesn't have the pink checkerboard at // runtime before they run buildcubemaps. //----------------------------------------------------------------------------- static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) { // Don't make cubemap tex infos for nodes if ( originalTexInfo == TEXINFO_NODE ) return originalTexInfo; texinfo_t *pTexInfo = &texinfo[originalTexInfo]; dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); if ( g_IsCubemapTexData[pTexInfo->texdata] ) { Warning("Multiple references for cubemap on texture %s!!!\n", pMaterialName ); return originalTexInfo; } // Get out of here if the originalTexInfo is already a generated material for this position. char pStringToSearchFor[512]; Q_snprintf( pStringToSearchFor, 512, "_%d_%d_%d", origin[0], origin[1], origin[2] ); if ( Q_stristr( pMaterialName, pStringToSearchFor ) ) return originalTexInfo; // Package up information needed to generate patch names PatchInfo_t info; info.m_pMapName = mapbase; info.m_pOrigin[0] = origin[0]; info.m_pOrigin[1] = origin[1]; info.m_pOrigin[2] = origin[2]; // Generate the name of the patched material char pGeneratedTexDataName[1024]; GeneratePatchedName( pMaterialName, info, true, pGeneratedTexDataName, 1024 ); // Make sure the texdata doesn't already exist. int nTexDataID = FindTexData( pGeneratedTexDataName ); bool bHasTexData = (nTexDataID != -1); if( !bHasTexData ) { // Generate the new "$envmap" texture name. char pTextureName[1024]; GeneratePatchedName( "c", info, false, pTextureName, 1024 ); // Hook the texture into the material and all dependent materials // but if no hooking was necessary, exit out if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) return originalTexInfo; // Store off the name of the cubemap that we need to create since we successfully patched char pFileName[1024]; int nLen = Q_snprintf( pFileName, 1024, "materials/%s.vtf", pTextureName ); int id = s_DefaultCubemapNames.AddToTail(); s_DefaultCubemapNames[id] = new char[ nLen + 1 ]; strcpy( s_DefaultCubemapNames[id], pFileName ); // Make a new texdata nTexDataID = AddCloneTexData( pTexData, pGeneratedTexDataName ); g_IsCubemapTexData[nTexDataID] = true; } Assert( nTexDataID != -1 ); texinfo_t newTexInfo; newTexInfo = *pTexInfo; newTexInfo.texdata = nTexDataID; int nTexInfoID = -1; // See if we need to make a new texinfo bool bHasTexInfo = false; if( bHasTexData ) { nTexInfoID = FindTexInfo( newTexInfo ); bHasTexInfo = (nTexInfoID != -1); } // Make a new texinfo if we need to. if( !bHasTexInfo ) { nTexInfoID = texinfo.AddToTail( newTexInfo ); } Assert( nTexInfoID != -1 ); return nTexInfoID; } static int SideIDToIndex( int brushSideID ) { int i; for( i = 0; i < g_MainMap->nummapbrushsides; i++ ) { if( g_MainMap->brushsides[i].id == brushSideID ) { return i; } } return -1; } //----------------------------------------------------------------------------- // Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each // side referenced by an env_cubemap manually. //----------------------------------------------------------------------------- void Cubemap_FixupBrushSidesMaterials( void ) { Msg( "fixing up env_cubemap materials on brush sides...\n" ); Assert( s_EnvCubemapToBrushSides.Count() == g_nCubemapSamples ); int cubemapID; for( cubemapID = 0; cubemapID < g_nCubemapSamples; cubemapID++ ) { IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[cubemapID]; int i; for( i = 0; i < brushSidesVector.Count(); i++ ) { int brushSideID = brushSidesVector[i]; int sideIndex = SideIDToIndex( brushSideID ); if( sideIndex < 0 ) { Warning("env_cubemap pointing at deleted brushside near (%d, %d, %d)\n", g_CubemapSamples[cubemapID].origin[0], g_CubemapSamples[cubemapID].origin[1], g_CubemapSamples[cubemapID].origin[2] ); continue; } side_t *pSide = &g_MainMap->brushsides[sideIndex]; #ifdef DEBUG if ( pSide->pMapDisp ) { Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); } #endif pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); if ( pSide->pMapDisp ) { pSide->pMapDisp->face.texinfo = pSide->texinfo; } } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void Cubemap_ResetCubemapSideData( void ) { for ( int iSide = 0; iSide < MAX_MAP_BRUSHSIDES; ++iSide ) { s_aCubemapSideData[iSide].bHasEnvMapInMaterial = false; s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap = false; } } //----------------------------------------------------------------------------- // Returns true if the material or any of its dependents use an $envmap //----------------------------------------------------------------------------- bool DoesMaterialOrDependentsUseEnvmap( const char *pPatchedMaterialName ) { const char *pOriginalMaterialName = GetOriginalMaterialNameForPatchedMaterial( pPatchedMaterialName ); if( DoesMaterialHaveKey( pOriginalMaterialName, "$envmap" ) ) return true; const char *pDependentMaterial = FindDependentMaterial( pOriginalMaterialName ); if ( !pDependentMaterial ) return false; return DoesMaterialOrDependentsUseEnvmap( pDependentMaterial ); } //----------------------------------------------------------------------------- // Builds a list of all texdatas which need fixing up //----------------------------------------------------------------------------- void Cubemap_InitCubemapSideData( void ) { // This tree is used to prevent re-parsing material vars multiple times CUtlRBTree lookup( 0, g_MainMap->nummapbrushsides, CubemapLessFunc ); // Fill in specular data. for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) { side_t *pSide = &g_MainMap->brushsides[iSide]; if ( !pSide ) continue; if ( pSide->texinfo == TEXINFO_NODE ) continue; texinfo_t *pTex = &texinfo[pSide->texinfo]; if ( !pTex ) continue; dtexdata_t *pTexData = GetTexData( pTex->texdata ); if ( !pTexData ) continue; CubemapInfo_t info; info.m_nTableId = pTexData->nameStringTableID; // Have we encountered this materal? If so, then copy the data we cached off before int i = lookup.Find( info ); if ( i != lookup.InvalidIndex() ) { s_aCubemapSideData[iSide].bHasEnvMapInMaterial = lookup[i].m_bSpecular; continue; } // First time we've seen this material. Figure out if it uses env_cubemap const char *pPatchedMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); info.m_bSpecular = DoesMaterialOrDependentsUseEnvmap( pPatchedMaterialName ); s_aCubemapSideData[ iSide ].bHasEnvMapInMaterial = info.m_bSpecular; lookup.Insert( info ); } // Fill in cube map data. for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) { IntVector_t &sideList = s_EnvCubemapToBrushSides[iCubemap]; int nSideCount = sideList.Count(); for ( int iSide = 0; iSide < nSideCount; ++iSide ) { int nSideID = sideList[iSide]; int nIndex = SideIDToIndex( nSideID ); if ( nIndex < 0 ) continue; s_aCubemapSideData[nIndex].bManuallyPickedByAnEnvCubemap = true; } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int Cubemap_FindClosestCubemap( const Vector &entityOrigin, side_t *pSide ) { if ( !pSide ) return -1; // Return a valid (if random) cubemap if there's no winding if ( !pSide->winding ) return 0; // Calculate the center point. Vector vecCenter; vecCenter.Init(); for ( int iPoint = 0; iPoint < pSide->winding->numpoints; ++iPoint ) { VectorAdd( vecCenter, pSide->winding->p[iPoint], vecCenter ); } VectorScale( vecCenter, 1.0f / pSide->winding->numpoints, vecCenter ); vecCenter += entityOrigin; plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; // Find the closest cubemap. int iMinCubemap = -1; float flMinDist = FLT_MAX; // Look for cubemaps in front of the surface first. for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) { dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; Vector vecSampleOrigin( static_cast( pSample->origin[0] ), static_cast( pSample->origin[1] ), static_cast( pSample->origin[2] ) ); Vector vecDelta; VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); float flDist = vecDelta.NormalizeInPlace(); float flDot = DotProduct( vecDelta, pPlane->normal ); if ( ( flDot >= 0.0f ) && ( flDist < flMinDist ) ) { flMinDist = flDist; iMinCubemap = iCubemap; } } // Didn't find anything in front search for closest. if( iMinCubemap == -1 ) { for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) { dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; Vector vecSampleOrigin( static_cast( pSample->origin[0] ), static_cast( pSample->origin[1] ), static_cast( pSample->origin[2] ) ); Vector vecDelta; VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); float flDist = vecDelta.Length(); if ( flDist < flMinDist ) { flMinDist = flDist; iMinCubemap = iCubemap; } } } return iMinCubemap; } //----------------------------------------------------------------------------- // For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo. //----------------------------------------------------------------------------- void Cubemap_AttachDefaultCubemapToSpecularSides( void ) { Cubemap_ResetCubemapSideData(); Cubemap_InitCubemapSideData(); // build a mapping from side to entity id so that we can get the entity origin CUtlVector sideToEntityIndex; sideToEntityIndex.SetCount(g_MainMap->nummapbrushsides); int i; for ( i = 0; i < g_MainMap->nummapbrushsides; i++ ) { sideToEntityIndex[i] = -1; } for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) { int entityIndex = g_MainMap->mapbrushes[i].entitynum; for ( int j = 0; j < g_MainMap->mapbrushes[i].numsides; j++ ) { side_t *side = &g_MainMap->mapbrushes[i].original_sides[j]; int sideIndex = side - g_MainMap->brushsides; sideToEntityIndex[sideIndex] = entityIndex; } } for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) { side_t *pSide = &g_MainMap->brushsides[iSide]; if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) ) continue; int currentEntity = sideToEntityIndex[iSide]; int iCubemap = Cubemap_FindClosestCubemap( g_MainMap->entities[currentEntity].origin, pSide ); if ( iCubemap == -1 ) continue; #ifdef DEBUG if ( pSide->pMapDisp ) { Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); } #endif pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); if ( pSide->pMapDisp ) { pSide->pMapDisp->face.texinfo = pSide->texinfo; } } } // Populate with cubemaps that were skipped void Cubemap_AddUnreferencedCubemaps() { char pTextureName[1024]; char pFileName[1024]; PatchInfo_t info; dcubemapsample_t *pSample; int i,j; for ( i=0; iorigin[0]; info.m_pOrigin[1] = pSample->origin[1]; info.m_pOrigin[2] = pSample->origin[2]; GeneratePatchedName( "c", info, false, pTextureName, 1024 ); // find or add for ( j=0; j