diff options
Diffstat (limited to 'hammer/textureconverter.cpp')
| -rw-r--r-- | hammer/textureconverter.cpp | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/hammer/textureconverter.cpp b/hammer/textureconverter.cpp new file mode 100644 index 0000000..205393e --- /dev/null +++ b/hammer/textureconverter.cpp @@ -0,0 +1,639 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdafx.h> +#include "MapWorld.h" +#include "MessageWnd.h" +#include "IEditorTexture.h" +#include "GlobalFunctions.h" +#include "TextureSystem.h" +#include "TextureConverter.h" +#include "filesystem.h" +#include "Hammer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +CProgressDlg * CTextureConverter::m_pProgDlg; +int CTextureConverter::m_nSolidCount; +int CTextureConverter::m_nFaceCount; +int CTextureConverter::m_nDecalCount; +int CTextureConverter::m_nCurrentSolid; +int CTextureConverter::m_nCurrentDecal; +int CTextureConverter::m_nSuccesses; +int CTextureConverter::m_nErrors; +int CTextureConverter::m_nSkipped; +int CTextureConverter::m_nWarnings; + + +//----------------------------------------------------------------------------- +// Purpose: Reset counters. +// Input : +// Output : Counters all reset to 0. +//----------------------------------------------------------------------------- +void CTextureConverter::Initialize( void ) +{ + m_nSolidCount = 0; + m_nCurrentSolid = 0; + m_nFaceCount = 0; + m_nDecalCount = 0; + m_nCurrentDecal = 0; + + m_nSuccesses = 0; + m_nErrors = 0; + m_nSkipped = 0; + m_nWarnings = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Recurse through the contents of the map, passing solid objects on +// to be converted. +// Input : pWorld - pointer to the map to have textures converted. +// Output : All solid faces and decals in the world have WAD3 textures +// converted to VMT. +//----------------------------------------------------------------------------- +void CTextureConverter::ConvertWorldTextures( CMapWorld * pWorld ) +{ + Initialize(); + + // Bring the message window to the front, to display conversion info + g_pwndMessage->Activate(); + + Msg( mwStatus, "Converting textures from WAD to VMT format..." ); + + // Set up a progress meter dialogue + m_pProgDlg = new CProgressDlg; + m_pProgDlg->Create(); + m_pProgDlg->SetStep( 1 ); + m_pProgDlg->SetWindowText( "Preparing to convert textures..." ); + + // Run the converter + ConvertSolids( pWorld ); + ConvertDecals( pWorld ); + DisplayStatistics(); + + // Destroy the progress meter + if ( m_pProgDlg ) + { + m_pProgDlg->DestroyWindow(); + delete m_pProgDlg; + m_pProgDlg = NULL; + } + + AfxMessageBox( "Conversion complete. Check the Hammer \"Messages\" window for complete details." ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Recurse through the contents of the map, passing solid objects on +// to be converted. +// Input : pWorld - pointer to the map to have textures converted. +// Output : All solid faces in the world have WAD3 textures converted to VMT. +//----------------------------------------------------------------------------- +void CTextureConverter::ConvertSolids( CMapWorld * pWorld ) +{ + // Count total map solids so we know how many we have to do (for progress meter). + pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CountMapSolids ), 0, MAPCLASS_TYPE( CMapSolid ) ); + + m_pProgDlg->SetRange( 0, m_nSolidCount ); + m_pProgDlg->SetStep( 2 ); + m_pProgDlg->SetWindowText( "Converting solids..." ); + + // Cycle through the solids again and convert as necessary. + pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CheckSolidTextures ), 0, MAPCLASS_TYPE( CMapSolid ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Enumeration function, increment the solids counter. +// Input : +// Output : Always return true to continue enumerating. +//----------------------------------------------------------------------------- +bool CTextureConverter::CountMapSolids( CMapSolid *, DWORD ) +{ + m_nSolidCount++; + + return true; // return true to continue enumerating +} + + +//----------------------------------------------------------------------------- +// Purpose: Enumeration function, check all the faces of a solid for texture conversion +// Input : pSolid - map solid to be checked. +// Output : Always return true to continue enumerating. +//----------------------------------------------------------------------------- +bool CTextureConverter::CheckSolidTextures( CMapSolid * pSolid, DWORD ) +{ + int nFaceCount; + + m_nCurrentSolid++; + + if ( m_nCurrentSolid % 100 == 0 ) + m_pProgDlg->SetPos( m_nCurrentSolid ); + + // check each face of the solid + nFaceCount = pSolid->GetFaceCount(); + while( nFaceCount-- ) + { + CheckFaceTexture( pSolid->GetFace( nFaceCount ) ); + } + + return true; // return true to continue enumerating +} + + +//----------------------------------------------------------------------------- +// Purpose: Check the texture of a face to determine if conversion is necessary. +// Input : pFace - a map face. +// Output : +//----------------------------------------------------------------------------- +void CTextureConverter::CheckFaceTexture( CMapFace * pFace ) +{ + m_nFaceCount++; + + // Criteria for needing conversion is a) being a dummy texture AND b) having no slashes + // in the texture name. + + if ( !pFace->GetTexture()->IsDummy() ) + { + m_nSkipped++; + return; + } + + if ( strchr( pFace->GetTexture()->GetName(), '/') != NULL ) + { + m_nSkipped++; + return; + } + + ConvertFaceTexture( pFace ); +} + + +bool TextureEndsIn( const char *pTextureName, const char *pEnd ) +{ + const char *pLast = strrchr( pTextureName, '\\' ); + if ( strrchr( pTextureName, '/' ) > pLast ) + pLast = strrchr( pTextureName, '/' ); + + if ( pLast ) + return stricmp( pLast+1, pEnd ) == 0; + else + return stricmp( pTextureName, pEnd ) == 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Determine if any materials match the old texture of a face and replace +// appropriately. +// Input : pFace - a map face known to need conversion. +// Output : +//----------------------------------------------------------------------------- +void CTextureConverter::ConvertFaceTexture( CMapFace * pFace ) +{ + EditorTextureList_t tlMatches; + IEditorTexture *pNewTexture; + + const char *pTextureName = pFace->GetTexture()->GetName(); + + // Check for SKY and SKIP brushes. + char *replacements[][2] = + { + { "sky", "tools/toolsskybox" }, + { "skip", "tools/toolsskip" }, + { "aaatrigger", "tools/toolstrigger" }, + { "hint", "tools/toolshint" }, + { "clip", "tools/toolsclip" }, + { "null", "tools/toolsnodraw" } + }; + for ( int i=0; i < sizeof( replacements ) / sizeof( replacements[0] ); i++ ) + { + if ( TextureEndsIn( pTextureName, replacements[i][0] ) ) + { + pNewTexture = g_Textures.FindActiveTexture( replacements[i][1] ); + if ( pNewTexture ) + { + ReplaceFaceTexture( pFace, pNewTexture ); + return; + } + } + } + + GetNewTextureMatches( pTextureName, tlMatches ); + + switch( tlMatches.Count() ) + { + case 0: + MsgConvertFace( pFace, "ERROR: No matching material. Cannot convert." ); + m_nErrors++; + + break; + case 1: + pNewTexture = tlMatches.Element(0); + ReplaceFaceTexture( pFace, pNewTexture ); + + break; + default: + // Multiple matches. For now, just use the first. + pNewTexture = tlMatches.Element(0); + ReplaceFaceTexture( pFace, pNewTexture ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Recurse through the contents of the map, passing decal objects on +// to be converted. +// Input : pWorld - pointer to the map to have textures converted. +// Output : All decals in the world have WAD3 textures converted to VMT. +//----------------------------------------------------------------------------- +void CTextureConverter::ConvertDecals( CMapWorld * pWorld ) +{ + // Count total map decals so we know how many we have to do (for progress meter). + pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CountMapDecals ), 0, MAPCLASS_TYPE( CMapEntity ) ); + + m_pProgDlg->SetRange( 0, m_nDecalCount ); + m_pProgDlg->SetStep( 3 ); + m_pProgDlg->SetWindowText( "Converting decals..." ); + + // Cycle through the solids again and convert as necessary. + pWorld->EnumChildren( ENUMMAPCHILDRENPROC( CheckDecalTextures ), 0, MAPCLASS_TYPE( CMapEntity ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Enumeration function, increment the decals counter if entity is a decal. +// Input : +// Output : Always return true to continue enumerating. +//----------------------------------------------------------------------------- +bool CTextureConverter::CountMapDecals( CMapEntity * pEnt, DWORD ) +{ + if ( !strcmp( pEnt->GetClassName(), "infodecal" ) ) + m_nDecalCount++; + + return true; // return true to continue enumerating +} + + +//----------------------------------------------------------------------------- +// Purpose: Enumeration function, check a decal's texture to determine if +// conversion is necessary. +// Input : pEnt - map decal to be checked. +// Output : Always return true to continue enumerating. +//----------------------------------------------------------------------------- +bool CTextureConverter::CheckDecalTextures( CMapEntity * pEnt, DWORD ) +{ + if ( strcmp( pEnt->GetClassName(), "infodecal" ) ) + return true; // not a decal, return true to continue enumerating + + m_nCurrentDecal++; + + m_pProgDlg->SetPos( m_nCurrentDecal ); + + if ( strchr( pEnt->GetKeyValue( "texture" ), '/') != NULL ) + { + m_nSkipped++; + } + else + { + ConvertDecalTexture( pEnt ); + } + + return true; // return true to continue enumerating +} + + +//----------------------------------------------------------------------------- +// Purpose: Determine if any materials match the old texture of a decal and replace +// appropriately. +// Input : pEnt - a map decal known to need conversion. +// Output : +//----------------------------------------------------------------------------- +void CTextureConverter::ConvertDecalTexture( CMapEntity * pEnt ) +{ + EditorTextureList_t tlMatches; + IEditorTexture *pNewTexture; + + GetNewTextureMatches( pEnt->GetKeyValue( "texture" ), tlMatches ); + + switch( tlMatches.Count() ) + { + case 0: + MsgConvertDecal( pEnt, "ERROR: No matching material. Cannot convert." ); + m_nErrors++; + + break; + case 1: + pNewTexture = tlMatches.Element(0); + ReplaceDecalTexture( pEnt, pNewTexture ); + + break; + default: + // Multiple matches. For now, just use the first. + pNewTexture = tlMatches.Element(0); + + MsgConvertDecal( pEnt, "WARNING: Multiple matches found. Using first match (%s).", pNewTexture->GetName() ); + m_nWarnings++; + + ReplaceDecalTexture( pEnt, pNewTexture ); + + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Look for material matches for an old texture and add them to a list. +// Input : pszOldName - old texture name. +// pMatchList - empty texture list. +// Output : pMatchList - texture list is filled in with matching material textures. +//----------------------------------------------------------------------------- +void CTextureConverter::GetNewTextureMatches( const char * pszOldName, EditorTextureList_t &tlMatchList ) +{ + IEditorTexture * pTexture; + int nIndex; + + nIndex = 0; + pTexture = g_Textures.EnumActiveTextures( &nIndex, tfVMT ); + + // loop through all VMT textures + while ( pTexture != NULL ) + { + if ( TextureNameMatchesMaterialName( pszOldName, pTexture->GetName() ) ) // check for a match + { + tlMatchList.AddToTail( pTexture ); + } + + pTexture = g_Textures.EnumActiveTextures( &nIndex, tfVMT ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Compare an old texture name to a new material name. +// Input : pszTextureName - Old texture name. +// pszMaterialName - New material name. +// Output : Return true if the old texture name is the same (case insensitive) +// as the last token (delimiter '/') of the new material name, otherwise +// return false. +//----------------------------------------------------------------------------- +bool CTextureConverter::TextureNameMatchesMaterialName( const char * pszTextureName, const char * pszMaterialName ) +{ + const char * pszPartialMaterialName; // sublocation of the material name + + pszPartialMaterialName = strrchr( pszMaterialName, '/' ); // Find the last '/' + + if ( pszPartialMaterialName != NULL) + { + pszPartialMaterialName++; // Point to the character after the '/' + } + else + { + pszPartialMaterialName = pszMaterialName; // No slashes found, just point to the name + } + + // No '/' found in the VMT name, or the name ended in a '/'. This shouldn't happen. + if ( ( pszPartialMaterialName == NULL ) || strlen( pszPartialMaterialName ) == 0 ) + return false; + + if ( stricmp( pszTextureName, pszPartialMaterialName ) == 0 ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Change the texture on a face, re-scaling if possible. +// Input : pFace - a map face +// pNewTexture - texture to place on the map face. +// Output : pFace has a new texture pointer set. +//----------------------------------------------------------------------------- +void CTextureConverter::ReplaceFaceTexture( CMapFace * pFace, IEditorTexture * pNewTexture ) +{ + if ( !pNewTexture->Load() ) // make sure new texture is loaded + { + MsgConvertFace( pFace, "WARNING: Couldn't load new material. Texture converted but not re-scaled." ); + m_nWarnings++; + } + + RescaleFaceTexture( pFace, pNewTexture ); + + pFace->SetTexture( pNewTexture ); + + m_nSuccesses++; +} + + +//----------------------------------------------------------------------------- +// Purpose: Change the texture on a decal +// Input : pEntity - a map decal +// pNewTexture - texture to place on the map face. +// Output : pEnt has a new texture set +//----------------------------------------------------------------------------- +void CTextureConverter::ReplaceDecalTexture( CMapEntity * pEnt, IEditorTexture * pNewTexture ) +{ + pEnt->SetKeyValue( "texture", pNewTexture->GetName() ); + m_nSuccesses++; +} + + +//----------------------------------------------------------------------------- +// Purpose: Find a WAD3 texture based on a name search. +// Input : pszName - name of the texture to search for. +// Output : return a texture if found, otherwise NULL. +//----------------------------------------------------------------------------- +IEditorTexture * CTextureConverter::FindWAD3Texture( const char * pszName ) +{ + IEditorTexture * pTexture; + int nIndex; + + nIndex = 0; + pTexture = g_Textures.EnumActiveTextures( &nIndex, tfWAD3 ); + + // loop through all the WAD3 textures + while ( pTexture != NULL ) + { + if ( !strcmp( pTexture->GetName(), pszName ) ) //check for exact match + return pTexture; + + pTexture = g_Textures.EnumActiveTextures( &nIndex, tfWAD3 ); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Change the scale and shift values of a texture, based on old texture +// image dimensions compared to new texture image dimensions. +// Input : pFace - map face that the texture to be scaled is on. +// pOldTexture - the old texture. +// pNewTexture - the new texture. +// Output : pFace->scale and pface->texture are modified if either the height or +// width (or both) of the texture has changed. +//----------------------------------------------------------------------------- +void CTextureConverter::RescaleFaceTexture( CMapFace * pFace, IEditorTexture * pNewTexture ) +{ + int nNewWidth; + int nNewHeight; + + int nOldWidth = -1; + int nOldHeight = -1; + + // First look for the .resizeinfo in the mod dir (hl2\dod), then the game dir (hl2\hl2). + char resizeInfoFilename[512]; + Q_snprintf( resizeInfoFilename, sizeof( resizeInfoFilename ), "materials\\%s.resizeinfo", pNewTexture->GetName() ); + FileHandle_t fp = g_pFileSystem->Open( resizeInfoFilename, "rt" ); + if ( !fp ) + { + return; + } + + char line[512]; + int nScanned = 0; + if ( g_pFullFileSystem->ReadLine( line, sizeof( line ), fp ) ) + { + nScanned = sscanf( line, "%d %d", &nOldWidth, &nOldHeight ); + } + g_pFileSystem->Close( fp ); + if ( nScanned != 2 || nOldWidth < 0 || nOldHeight < 0 || nOldWidth > 5000 || nOldHeight > 5000 ) + return; + + nNewWidth = pNewTexture->GetWidth(); + nNewHeight = pNewTexture->GetHeight(); + + // Divide by 0 checks + if ( ( nOldWidth == 0 ) || ( nOldHeight == 0 ) ) + { + MsgConvertFace( pFace, + "WARNING: Invalid old texture dimensions (%dx%d). Texture converted but not re-scaled.", + nOldWidth, + nOldHeight + ); + m_nWarnings++; + return; + } + + // Divide by 0 checks + if ( ( nNewWidth == 0 ) || ( nNewHeight == 0 ) ) + { + MsgConvertFace( pFace, + "WARNING: Invalid new material dimensions (%dx%d). Texture converted but not re-scaled.", + nNewWidth, + nNewHeight + ); + m_nWarnings++; + return; + } + + + + if ( nOldWidth != nNewWidth ) + { + // Adjust the width scale by an old to new ratio + pFace->texture.scale[ 0 ] = pFace->texture.scale[ 0 ] * nOldWidth / nNewWidth; + + // Adjust the height shift by a new to old ratio + pFace->texture.UAxis[ 3 ] = pFace->texture.UAxis[ 3 ] * nNewWidth / nOldWidth; + } + + if ( nOldHeight != nNewHeight ) + { + // Adjust the height scale by an old to new ratio + pFace->texture.scale[ 1 ] = pFace->texture.scale[ 1 ] * nOldHeight / nNewHeight; + + // Adjust the height shift by a new to old ratio + pFace->texture.VAxis[ 3 ] = pFace->texture.VAxis[ 3 ] * nNewHeight / nOldHeight; + } + + pFace->CalcTextureCoords(); // recompute internals base on the new scaling and shifting. +} + + +//----------------------------------------------------------------------------- +// Purpose: Send a message to WC's message window about the specified face. +// Input : pFace - The map face the message relates to +// format - The message format string, *printf style +// ... - The remaining arguments of the *printf style message +// Output : A status message is sent to the WC message window. +//----------------------------------------------------------------------------- +void CTextureConverter::MsgConvertFace( CMapFace * pFace, const char * format, ... ) +{ + va_list ptr; + char message[ 1024 ]; + Vector vecFaceCenter; + + pFace->GetCenter( vecFaceCenter ); + + va_start( ptr, format ); + _vsnprintf( message, 1024, format, ptr ); + va_end( ptr ); + + Msg( mwStatus, + "[face] %s at (%d,%d,%d): %s", + pFace->GetTexture()->GetName(), + (int)vecFaceCenter[ 0 ], + (int)vecFaceCenter[ 1 ], + (int)vecFaceCenter[ 2 ], + message + ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Send a message to WC's message window about the specified decal. +// Input : pEnt - The map decal the message relates to +// format - The message format string, *printf style +// ... - The remaining arguments of the *printf style message +// Output : A status message is sent to the WC message window. +//----------------------------------------------------------------------------- +void CTextureConverter::MsgConvertDecal( CMapEntity * pEnt, const char * format, ... ) +{ + va_list ptr; + char message[ 1024 ]; + Vector vecOrigin; + + pEnt->GetOrigin( vecOrigin ); + + va_start( ptr, format ); + _vsnprintf( message, 1024, format, ptr ); + va_end( ptr ); + + Msg( mwStatus, + "[decal] %s at (%d,%d,%d): %s", + pEnt->GetKeyValue("texture"), + (int) vecOrigin.x, + (int) vecOrigin.y, + (int) vecOrigin.z, + message + ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Display information about the full conversion process. +// Input : +// Output : Values of the counters are logged. +//----------------------------------------------------------------------------- +void CTextureConverter::DisplayStatistics( void ) +{ + Msg( mwStatus, "==================" ); + Msg( mwStatus, "Conversion summary:" ); + Msg( mwStatus, "==================" ); + Msg( mwStatus, "Total solids: %10d", m_nSolidCount ); + Msg( mwStatus, "Total faces: %10d", m_nFaceCount ); + Msg( mwStatus, "Total decals: %10d", m_nDecalCount ); + Msg( mwStatus, "Total conversions: %10d", m_nFaceCount + m_nDecalCount ); + Msg( mwStatus, "Successful conversions: %10d", m_nSuccesses ); + Msg( mwStatus, "Skipped conversions %10d", m_nSkipped ); + Msg( mwStatus, "Conversion errors: %10d", m_nErrors ); + Msg( mwStatus, "Conversion warnings: %10d", m_nWarnings ); +} |