//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "importkeyvaluebase.h" #include "dmserializers.h" #include "datamodel/idatamodel.h" #include "datamodel/dmelement.h" #include "tier1/KeyValues.h" #include "tier1/utlbuffer.h" #include "datamodel/dmattribute.h" #include "filesystem.h" #include "tier2/tier2.h" //----------------------------------------------------------------------------- // Serialization class for Key Values //----------------------------------------------------------------------------- class CImportVMT : public CImportKeyValueBase { public: virtual const char *GetName() const { return "vmt"; } virtual const char *GetDescription() const { return "Valve Material File"; } virtual int GetCurrentVersion() const { return 0; } // doesn't store a version bool Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ); CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues ); private: // Unserialize fallbacks bool UnserializeFallbacks( CDmElement *pRoot, KeyValues *pFallbackKeyValues ); // Unserialize proxies bool UnserializeProxies( CDmElement *pRoot, KeyValues *pKeyValues ); // Creates a shader parameter from a key value bool UnserializeShaderParam( CDmElement *pRoot, KeyValues* pKeyValue ); // Creates a matrix material var bool CreateMatrixMaterialVarFromKeyValue( CDmElement *pRoot, const char *pParamName, const char *pString ); // Creates a vector shader parameter bool CreateVectorMaterialVarFromKeyValue( CDmElement *pRoot, const char *pParamName, const char *pString ); // Writes out a single shader parameter bool SerializeShaderParameter( CUtlBuffer &buf, CDmAttribute *pAttribute ); // Writes out all shader parameters bool SerializeShaderParameters( CUtlBuffer &buf, CDmElement *pRoot ); // Writes out all shader fallbacks bool SerializeFallbacks( CUtlBuffer &buf, CDmElement *pRoot ); // Writes out all material proxies bool SerializeProxies( CUtlBuffer &buf, CDmElement *pRoot ); // Handle patch files void ExpandPatchFile( KeyValues *pKeyValues ); }; //----------------------------------------------------------------------------- // Singleton instance //----------------------------------------------------------------------------- static CImportVMT s_ImportVMT; void InstallVMTImporter( IDataModel *pFactory ) { pFactory->AddSerializer( &s_ImportVMT ); } //----------------------------------------------------------------------------- // Writes out a single shader parameter //----------------------------------------------------------------------------- bool CImportVMT::SerializeShaderParameter( CUtlBuffer &buf, CDmAttribute *pAttribute ) { // We have a shader parameter at this point. switch ( pAttribute->GetType() ) { case AT_INT: buf.Printf( "\"%s\" \"%d\"\n", pAttribute->GetName(), pAttribute->GetValue( ) ); break; case AT_BOOL: buf.Printf( "\"%s\" \"%d\"\n", pAttribute->GetName(), pAttribute->GetValue( ) ); break; case AT_FLOAT: buf.Printf( "\"%s\" \"%f\"\n", pAttribute->GetName(), pAttribute->GetValue( ) ); break; case AT_STRING: buf.Printf( "\"%s\" \"%s\"\n", pAttribute->GetName(), pAttribute->GetValue( ).Get() ); break; case AT_VECTOR2: { const Vector2D &vec = pAttribute->GetValue( ); buf.Printf( "\"%s\" \"[ %f %f ]\"\n", pAttribute->GetName(), vec.x, vec.y ); } break; case AT_VECTOR3: { const Vector &vec = pAttribute->GetValue( ); buf.Printf( "\"%s\" \"[ %f %f %f ]\"\n", pAttribute->GetName(), vec.x, vec.y, vec.z ); } break; case AT_VECTOR4: { const Vector4D &vec = pAttribute->GetValue( ); buf.Printf( "\"%s\" \"[ %f %f %f %f ]\"\n", pAttribute->GetName(), vec.x, vec.y, vec.z, vec.w ); } break; case AT_COLOR: { // NOTE: VMTs only support 3 component color (no alpha) const Color &color = pAttribute->GetValue( ); buf.Printf( "\"%s\" \"{ %d %d %d }\"\n", pAttribute->GetName(), color.r(), color.g(), color.b() ); } break; case AT_VMATRIX: { const VMatrix &mat = pAttribute->GetValue( ); buf.Printf( "\"%s\" \"[ %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]\"\n", pAttribute->GetName(), mat[0][0], mat[0][1], mat[0][2], mat[0][3], mat[1][0], mat[1][1], mat[1][2], mat[1][3], mat[2][0], mat[2][1], mat[2][2], mat[2][3], mat[3][0], mat[3][1], mat[3][2], mat[3][3] ); } break; default: Warning( "Attempted to serialize an unsupported shader parameter type %s (%s)\n", pAttribute->GetName(), g_pDataModel->GetAttributeNameForType( pAttribute->GetType() ) ); return false; } return true; } //----------------------------------------------------------------------------- // Writes out all shader parameters //----------------------------------------------------------------------------- bool CImportVMT::SerializeShaderParameters( CUtlBuffer &buf, CDmElement *pRoot ) { for ( CDmAttribute *pAttribute = pRoot->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) { // Skip the standard attributes if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) ) continue; // Skip the shader name const char *pName = pAttribute->GetName(); if ( !Q_stricmp( pAttribute->GetName(), "shader" ) ) continue; // Names that don't start with a $ or a % are not shader parameters if ( pName[0] != '$' && pName[0] != '%' ) continue; // Skip element array children; we'll handle them separately. if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) continue; // Write out the shader parameter if ( !SerializeShaderParameter( buf, pAttribute ) ) return false; } return true; } //----------------------------------------------------------------------------- // Writes out all shader fallbacks //----------------------------------------------------------------------------- bool CImportVMT::SerializeFallbacks( CUtlBuffer &buf, CDmElement *pRoot ) { if ( !pRoot->HasAttribute( "fallbacks" ) ) return true; CDmAttribute *pFallbacks = pRoot->GetAttribute( "fallbacks" ); if ( pFallbacks->GetType() != AT_ELEMENT_ARRAY ) return false; CDmrElementArray<> array( pFallbacks ); int nCount = array.Count(); for ( int i = 0; i < nCount; ++i ) { CDmElement *pFallback = array[i]; Assert( pFallback ); PrintStringAttribute( pFallback, buf, "shader", false, true ); buf.Printf( "{\n" ); buf.PushTab(); if ( !SerializeShaderParameters( buf, pFallback ) ) return false; buf.PopTab(); buf.Printf( "}\n" ); } return true; } //----------------------------------------------------------------------------- // Writes out all material proxies //----------------------------------------------------------------------------- bool CImportVMT::SerializeProxies( CUtlBuffer &buf, CDmElement *pRoot ) { if ( !pRoot->HasAttribute( "proxies" ) ) return true; CDmAttribute *pProxies = pRoot->GetAttribute( "proxies" ); if ( pProxies->GetType() != AT_ELEMENT_ARRAY ) return false; CDmrElementArray<> array( pProxies ); int nCount = array.Count(); if ( nCount == 0 ) return true; buf.Printf( "\"Proxies\"\n" ); buf.Printf( "{\n" ); buf.PushTab(); for ( int i = 0; i < nCount; ++i ) { CDmElement *pProxy = array[i]; Assert( pProxy ); PrintStringAttribute( pProxy, buf, "proxyType", false, true ); buf.Printf( "{\n" ); buf.PushTab(); if ( !SerializeShaderParameters( buf, pProxy ) ) return false; buf.PopTab(); buf.Printf( "}\n" ); } buf.PopTab(); buf.Printf( "}\n" ); return true; } //----------------------------------------------------------------------------- // Writes out a new vmt file //----------------------------------------------------------------------------- bool CImportVMT::Serialize( CUtlBuffer &buf, CDmElement *pRoot ) { PrintStringAttribute( pRoot, buf, "shader", false, true ); buf.Printf( "{\n" ); buf.PushTab(); if ( !SerializeShaderParameters( buf, pRoot ) ) return false; if ( !SerializeFallbacks( buf, pRoot ) ) return false; if ( !SerializeProxies( buf, pRoot ) ) return false; buf.PopTab(); buf.Printf( "}\n" ); return true; } //----------------------------------------------------------------------------- // Parser utilities //----------------------------------------------------------------------------- static inline bool IsWhitespace( char c ) { return c == ' ' || c == '\t'; } static inline bool IsEndline( char c ) { return c == '\n' || c == '\0'; } static inline bool IsVector( char const* v ) { while (IsWhitespace(*v)) { ++v; if (IsEndline(*v)) return false; } return *v == '[' || *v == '{'; } //----------------------------------------------------------------------------- // Creates a vector material var //----------------------------------------------------------------------------- int ParseVectorFromKeyValueString( const char *pParamName, const char* pScan, const char *pMaterialName, float vecVal[4] ) { bool divideBy255 = false; // skip whitespace while( IsWhitespace(*pScan) ) { ++pScan; } if( *pScan == '{' ) { divideBy255 = true; } else { Assert( *pScan == '[' ); } // skip the '[' ++pScan; int i; for( i = 0; i < 4; i++ ) { // skip whitespace while( IsWhitespace(*pScan) ) { ++pScan; } if( IsEndline(*pScan) || *pScan == ']' || *pScan == '}' ) { if (*pScan != ']' && *pScan != '}') { Warning( "Warning in .VMT file (%s): no ']' or '}' found in vector key \"%s\".\n" "Did you forget to surround the vector with \"s?\n", pMaterialName, pParamName ); } // allow for vec2's, etc. vecVal[i] = 0.0f; break; } char* pEnd; vecVal[i] = strtod( pScan, &pEnd ); if (pScan == pEnd) { Warning( "Error in .VMT file: error parsing vector element \"%s\" in \"%s\"\n", pParamName, pMaterialName ); return 0; } pScan = pEnd; } if( divideBy255 ) { vecVal[0] *= ( 1.0f / 255.0f ); vecVal[1] *= ( 1.0f / 255.0f ); vecVal[2] *= ( 1.0f / 255.0f ); vecVal[3] *= ( 1.0f / 255.0f ); } return i; } //----------------------------------------------------------------------------- // Sets shader parameter attributes //----------------------------------------------------------------------------- template< class T > inline bool SetShaderParamAttribute( CDmElement *pElement, const char *pAttributeName, const T &value ) { if ( !pElement ) return false; if ( !pElement->SetValue( pAttributeName, value ) ) return false; CDmAttribute *pAttribute = pElement->GetAttribute( pAttributeName ); pAttribute->AddFlag( FATTRIB_USERDEFINED ); return true; } inline bool SetShaderParamAttribute( CDmElement *pElement, const char *pAttributeName, const char *value ) { if ( !pElement ) return false; if ( !pElement->SetValue( pAttributeName, value ) ) return false; CDmAttribute *pAttribute = pElement->GetAttribute( pAttributeName ); pAttribute->AddFlag( FATTRIB_USERDEFINED ); return true; } //----------------------------------------------------------------------------- // Creates a vector shader parameter //----------------------------------------------------------------------------- bool CImportVMT::CreateVectorMaterialVarFromKeyValue( CDmElement *pElement, const char *pParamName, const char *pString ) { Vector4D vecVal; int nDim = ParseVectorFromKeyValueString( pParamName, pString, FileName(), vecVal.Base() ); if ( nDim == 0 ) return false; // Create the variable! switch ( nDim ) { case 1: return SetShaderParamAttribute( pElement, pParamName, vecVal[0] ); case 2: return SetShaderParamAttribute( pElement, pParamName, vecVal.AsVector2D() ); case 3: return SetShaderParamAttribute( pElement, pParamName, vecVal.AsVector3D() ); case 4: return SetShaderParamAttribute( pElement, pParamName, vecVal ); } return false; } //----------------------------------------------------------------------------- // Creates a matrix shader parameter //----------------------------------------------------------------------------- bool CImportVMT::CreateMatrixMaterialVarFromKeyValue( CDmElement *pElement, const char *pParamName, const char *pScan ) { // Matrices can be specified one of two ways: // [ # # # # # # # # # # # # # # # # ] // or // center # # scale # # rotate # translate # # VMatrix mat; int count = sscanf( pScan, " [ %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]", &mat.m[0][0], &mat.m[0][1], &mat.m[0][2], &mat.m[0][3], &mat.m[1][0], &mat.m[1][1], &mat.m[1][2], &mat.m[1][3], &mat.m[2][0], &mat.m[2][1], &mat.m[2][2], &mat.m[2][3], &mat.m[3][0], &mat.m[3][1], &mat.m[3][2], &mat.m[3][3] ); if (count == 16) { return SetShaderParamAttribute( pElement, pParamName, mat ); } Vector2D scale, center; float angle; Vector2D translation; count = sscanf( pScan, " center %f %f scale %f %f rotate %f translate %f %f", ¢er.x, ¢er.y, &scale.x, &scale.y, &angle, &translation.x, &translation.y ); if (count != 7) return false; VMatrix temp; MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f ); MatrixBuildScale( temp, scale.x, scale.y, 1.0f ); MatrixMultiply( temp, mat, mat ); MatrixBuildRotateZ( temp, angle ); MatrixMultiply( temp, mat, mat ); MatrixBuildTranslation( temp, center.x + translation.x, center.y + translation.y, 0.0f ); MatrixMultiply( temp, mat, mat ); // Create the variable! return SetShaderParamAttribute( pElement, pParamName, mat ); } //----------------------------------------------------------------------------- // Creates a shader parameter from a key value //----------------------------------------------------------------------------- bool CImportVMT::UnserializeShaderParam( CDmElement *pRoot, KeyValues* pKeyValues ) { char pParamName[512]; Q_strncpy( pParamName, pKeyValues->GetName(), sizeof(pParamName) ); Q_strlower( pParamName ); switch( pKeyValues->GetDataType() ) { case KeyValues::TYPE_INT: return SetShaderParamAttribute( pRoot, pParamName, pKeyValues->GetInt() ); case KeyValues::TYPE_FLOAT: return SetShaderParamAttribute( pRoot, pParamName, pKeyValues->GetFloat() ); case KeyValues::TYPE_STRING: { char const* pString = pKeyValues->GetString(); // Only valid if it's a texture attribute if ( !pString || !pString[0] ) return SetShaderParamAttribute( pRoot, pParamName, pString ); // Look for matrices if ( CreateMatrixMaterialVarFromKeyValue( pRoot, pParamName, pString ) ) return true; // Look for vectors if ( !IsVector( pString ) ) return SetShaderParamAttribute( pRoot, pParamName, pString ); // Parse the string as a vector... return CreateVectorMaterialVarFromKeyValue( pRoot, pParamName, pString ); } } return false; } //----------------------------------------------------------------------------- // Unserialize proxies //----------------------------------------------------------------------------- bool CImportVMT::UnserializeProxies( CDmElement *pElement, KeyValues *pKeyValues ) { // Create a child element array to contain all material proxies CDmAttribute *pProxies = pElement->AddAttribute( "proxies", AT_ELEMENT_ARRAY ); if ( !pProxies ) return false; CDmrElementArray<> array( pProxies ); // Proxies are a list of sub-keys, the name is the proxy name, subkeys are values for ( KeyValues *pProxy = pKeyValues->GetFirstTrueSubKey(); pProxy != NULL; pProxy = pProxy->GetNextTrueSubKey() ) { CDmElement *pProxyElement = CreateDmElement( "DmElement", pProxy->GetName(), NULL ); array.AddToTail( pProxyElement ); pProxyElement->SetValue( "proxyType", pKeyValues->GetName() ); pProxyElement->SetValue( "editorType", "vmtProxy" ); // Normal keys are proxy parameters for ( KeyValues *pProxyParam = pProxy->GetFirstValue(); pProxyParam != NULL; pProxyParam = pProxyParam->GetNextValue() ) { switch( pProxyParam->GetDataType() ) { case KeyValues::TYPE_INT: pProxyElement->SetValue( pProxyParam->GetName(), pProxyParam->GetInt() ); return true; case KeyValues::TYPE_FLOAT: pProxyElement->SetValue( pProxyParam->GetName(), pProxyParam->GetFloat() ); return true; case KeyValues::TYPE_STRING: pProxyElement->SetValue( pProxyParam->GetName(), pProxyParam->GetString() ); return true; default: Warning( "Unhandled proxy keyvalues type (proxy %s var %s)\n", pProxy->GetName(), pProxyParam->GetName() ); return false; } } } return true; } //----------------------------------------------------------------------------- // Unserialize fallbacks //----------------------------------------------------------------------------- bool CImportVMT::UnserializeFallbacks( CDmElement *pElement, KeyValues *pFallbackKeyValues ) { // Create a child element array to contain all material proxies CDmAttribute *pFallbacks = pElement->AddAttribute( "fallbacks", AT_ELEMENT_ARRAY ); if ( !pFallbacks ) return false; CDmrElementArray<> array( pFallbacks ); CDmElement *pFallback = CreateDmElement( "DmElement", pFallbackKeyValues->GetName(), NULL ); array.AddToTail( pFallback ); pFallback->SetValue( "editorType", "vmtFallback" ); // Normal keys are shader parameters for ( KeyValues *pShaderParam = pFallbackKeyValues->GetFirstValue(); pShaderParam != NULL; pShaderParam = pShaderParam->GetNextValue() ) { if ( !UnserializeShaderParam( pFallback, pShaderParam ) ) { Warning( "Error importing vmt shader parameter %s\n", pShaderParam->GetName() ); return NULL; } } return true; } //----------------------------------------------------------------------------- // VMT parser //----------------------------------------------------------------------------- void InsertKeyValues( KeyValues& dst, KeyValues& src, bool bCheckForExistence ) { KeyValues *pSrcVar = src.GetFirstSubKey(); while( pSrcVar ) { if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) { switch( pSrcVar->GetDataType() ) { case KeyValues::TYPE_STRING: dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); break; case KeyValues::TYPE_INT: dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); break; case KeyValues::TYPE_FLOAT: dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); break; case KeyValues::TYPE_PTR: dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); break; } } pSrcVar = pSrcVar->GetNextKey(); } if( bCheckForExistence ) { for( KeyValues *pScan = dst.GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) { KeyValues *pTmp = src.FindKey( pScan->GetName() ); if( !pTmp ) continue; // make sure that this is a subkey. if( pTmp->GetDataType() != KeyValues::TYPE_NONE ) continue; InsertKeyValues( *pScan, *pTmp, bCheckForExistence ); } } } //----------------------------------------------------------------------------- // Handle patch files //----------------------------------------------------------------------------- void CImportVMT::ExpandPatchFile( KeyValues *pKeyValues ) { int count = 0; while( count < 10 && stricmp( pKeyValues->GetName(), "patch" ) == 0 ) { // WriteKeyValuesToFile( "patch.txt", keyValues ); const char *pIncludeFileName = pKeyValues->GetString( "include" ); if( pIncludeFileName ) { KeyValues * includeKeyValues = new KeyValues( "vmt" ); bool success = includeKeyValues->LoadFromFile( g_pFullFileSystem, pIncludeFileName, IsX360() ? "GAME" : NULL ); if( success ) { KeyValues *pInsertSection = pKeyValues->FindKey( "insert" ); if( pInsertSection ) { InsertKeyValues( *includeKeyValues, *pInsertSection, false ); } KeyValues *pReplaceSection = pKeyValues->FindKey( "replace" ); if( pReplaceSection ) { InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); } *pKeyValues = *includeKeyValues; includeKeyValues->deleteThis(); // Could add other commands here, like "delete", "rename", etc. } else { includeKeyValues->deleteThis(); return; } } else { return; } count++; } if( count >= 10 ) { Warning( "Infinite recursion in patch file?\n" ); } } //----------------------------------------------------------------------------- // Main entry point for the unserialization //----------------------------------------------------------------------------- CDmElement* CImportVMT::UnserializeFromKeyValues( KeyValues *pKeyValues ) { ExpandPatchFile( pKeyValues ); // Create the main element CDmElement *pRoot = CreateDmElement( "DmElement", "VMT", NULL ); if ( !pRoot ) return NULL; // Each material needs to have an editortype associated with it so it displays nicely in editors pRoot->SetValue( "editorType", "vmt" ); // Each material needs a proxy list and a fallback list if ( !pRoot->AddAttribute( "proxies", AT_ELEMENT_ARRAY ) ) return NULL; if ( !pRoot->AddAttribute( "fallbacks", AT_ELEMENT_ARRAY ) ) return NULL; // The keyvalues name is the shader name pRoot->SetValue( "shader", pKeyValues->GetName() ); // Normal keys are shader parameters for ( KeyValues *pShaderParam = pKeyValues->GetFirstValue(); pShaderParam != NULL; pShaderParam = pShaderParam->GetNextValue() ) { if ( !UnserializeShaderParam( pRoot, pShaderParam ) ) { Warning( "Error importing vmt shader parameter %s\n", pShaderParam->GetName() ); return NULL; } } // Subkeys are either proxies or fallbacks for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextTrueSubKey() ) { if ( !Q_stricmp( pSubKey->GetName(), "Proxies" ) ) { UnserializeProxies( pRoot, pSubKey ); } else { UnserializeFallbacks( pRoot, pSubKey ); } } // Resolve all element references recursively RecursivelyResolveElement( pRoot ); return pRoot; }