From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/utils/vbsp/cubemap.cpp | 1992 ++++++++++++++++++++--------------------- 1 file changed, 996 insertions(+), 996 deletions(-) (limited to 'mp/src/utils/vbsp/cubemap.cpp') diff --git a/mp/src/utils/vbsp/cubemap.cpp b/mp/src/utils/vbsp/cubemap.cpp index 2415115b..aeff12f1 100644 --- a/mp/src/utils/vbsp/cubemap.cpp +++ b/mp/src/utils/vbsp/cubemap.cpp @@ -1,996 +1,996 @@ -//========= 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 +#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