//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "hlfaceposer.h" #include "expressions.h" #include #include "ControlPanel.h" #include "StudioModel.h" #include "expclass.h" #include "mxExpressionTab.h" #include "mxExpressionTray.h" #include "filesystem.h" #include "faceposer_models.h" #include "utldict.h" #include "scriplib.h" #include "checksum_crc.h" bool Sys_Error(const char *pMsg, ...); extern char g_appTitle[]; static CUtlVector< CUtlSymbol > g_GlobalFlexControllers; static CUtlDict< int, int > g_GlobalFlexControllerLookup; void ChecksumFlexControllers( bool bSpew, char const *name, CRC32_t &crc, const float *settings, const float *weights ) { CRC32_Init( &crc ); // Walk them alphabetically so that load order doesn't matter for ( int i = g_GlobalFlexControllerLookup.First() ; i != g_GlobalFlexControllerLookup.InvalidIndex(); i = g_GlobalFlexControllerLookup.Next( i ) ) { int controllerIndex = g_GlobalFlexControllerLookup[ i ]; char const *pszName = g_GlobalFlexControllerLookup.GetElementName( i ); // Only count active controllers in checksum float s = settings[ controllerIndex ]; float w = weights[ controllerIndex ]; if ( s == 0.0f && w == 0.0f ) { continue; } CRC32_ProcessBuffer( &crc, (void *)pszName, Q_strlen( pszName ) ); CRC32_ProcessBuffer( &crc, (void *)&s, sizeof( s ) ); CRC32_ProcessBuffer( &crc, (void *)&w, sizeof( w ) ); if ( bSpew ) { Msg( "[%d] %s == %f %f\n", controllerIndex, pszName, s, w ); } } CRC32_Final( &crc ); if ( bSpew ) { char hex[ 17 ]; Q_binarytohex( (const byte *)&crc, sizeof( crc ), hex, sizeof( hex ) ); Msg( "%s checksum = %sf\n", name, hex ); } } //----------------------------------------------------------------------------- // Purpose: // Input : index - // Output : char const //----------------------------------------------------------------------------- char const *GetGlobalFlexControllerName( int index ) { return g_GlobalFlexControllers[ index ].String(); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int GetGlobalFlexControllerCount( void ) { return g_GlobalFlexControllers.Count(); } //----------------------------------------------------------------------------- // Purpose: Accumulates throughout runtime session, oh well // Input : *szName - // Output : int //----------------------------------------------------------------------------- int AddGlobalFlexController( StudioModel *model, const char *szName ) { int idx = g_GlobalFlexControllerLookup.Find( szName ); if ( idx != g_GlobalFlexControllerLookup.InvalidIndex() ) { return g_GlobalFlexControllerLookup[ idx ]; } CUtlSymbol sym; sym = szName; idx = g_GlobalFlexControllers.AddToTail( sym ); g_GlobalFlexControllerLookup.Insert( szName, idx ); // Con_Printf( "Added global flex controller %i %s from %s\n", idx, szName, model->GetStudioHdr()->name ); return idx; } //----------------------------------------------------------------------------- // Purpose: // Input : *model - //----------------------------------------------------------------------------- void SetupModelFlexcontrollerLinks( StudioModel *model ) { if ( !model ) return; CStudioHdr *hdr = model->GetStudioHdr(); if ( !hdr ) return; if ( hdr->numflexcontrollers() <= 0 ) return; // Already set up!!! if ( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 ) return; for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) { int j = AddGlobalFlexController( model, hdr->pFlexcontroller( i )->pszName() ); hdr->pFlexcontroller( i )->localToGlobal = j; model->SetFlexController( i, 0.0f ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CExpressionManager : public IExpressionManager { public: CExpressionManager( void ); ~CExpressionManager( void ); void Reset( void ); void ActivateExpressionClass( CExpClass *cl ); // File I/O void LoadClass( const char *filename ); void CreateNewClass( const char *filename ); bool CloseClass( CExpClass *cl ); CExpClass *AddCExpClass( const char *classname, const char *filename ); int GetNumClasses( void ); CExpression *GetCopyBuffer( void ); bool CanClose( void ); CExpClass *GetActiveClass( void ); CExpClass *GetClass( int num ); CExpClass *FindClass( const char *classname, bool bMatchBaseNameOnly ); private: // Methods const char *GetClassnameFromFilename( const char *filename ); // UI void PopulateClassCB( CExpClass *cl ); void RemoveCExpClass( CExpClass *cl ); private: // Data CExpClass *m_pActiveClass; CUtlVector < CExpClass * > m_Classes; CExpression m_CopyBuffer; }; // Expose interface static CExpressionManager g_ExpressionManager; IExpressionManager *expressions = &g_ExpressionManager; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CExpressionManager::CExpressionManager( void ) { m_pActiveClass = NULL; Reset(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CExpressionManager::~CExpressionManager( void ) { Reset(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CExpressionManager::Reset( void ) { while ( m_Classes.Size() > 0 ) { CExpClass *p = m_Classes[ 0 ]; m_Classes.Remove( 0 ); delete p; } m_pActiveClass = NULL; memset( &m_CopyBuffer, 0, sizeof( m_CopyBuffer ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CExpClass *CExpressionManager::GetActiveClass( void ) { return m_pActiveClass; } //----------------------------------------------------------------------------- // Purpose: // Input : num - // Output : CExpClass //----------------------------------------------------------------------------- CExpClass *CExpressionManager::GetClass( int num ) { return m_Classes[ num ]; } //----------------------------------------------------------------------------- // Purpose: // Input : *classname - // *filename - // Output : CExpClass * //----------------------------------------------------------------------------- CExpClass * CExpressionManager::AddCExpClass( const char *classname, const char *filename ) { Assert( !FindClass( classname, false ) ); CExpClass *pclass = new CExpClass( classname ); if ( !pclass ) return NULL; m_Classes.AddToTail( pclass ); pclass->SetFileName( filename ); return pclass; } //----------------------------------------------------------------------------- // Purpose: // Input : *cl - //----------------------------------------------------------------------------- void CExpressionManager::RemoveCExpClass( CExpClass *cl ) { for ( int i = 0; i < m_Classes.Size(); i++ ) { CExpClass *p = m_Classes[ i ]; if ( p == cl ) { m_Classes.Remove( i ); delete p; break; } } if ( m_Classes.Size() >= 1 ) { ActivateExpressionClass( m_Classes[ 0 ] ); } else { ActivateExpressionClass( NULL ); } } //----------------------------------------------------------------------------- // Purpose: // Input : *cl - //----------------------------------------------------------------------------- void CExpressionManager::ActivateExpressionClass( CExpClass *cl ) { m_pActiveClass = cl; int select = 0; for ( int i = 0; i < GetNumClasses(); i++ ) { CExpClass *c = GetClass( i ); if ( cl == c ) { select = i; break; } } g_pExpressionClass->select( select ); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CExpressionManager::GetNumClasses( void ) { return m_Classes.Size(); } //----------------------------------------------------------------------------- // Purpose: // Input : *classname - // Output : CExpClass //----------------------------------------------------------------------------- CExpClass *CExpressionManager::FindClass( const char *classname, bool bMatchBaseNameOnly ) { char search[ 256 ]; if ( bMatchBaseNameOnly ) { Q_FileBase( classname, search, sizeof( search ) ); } else { Q_strncpy( search, classname, sizeof( search ) ); } Q_FixSlashes( search ); Q_strlower( search ); for ( int i = 0; i < m_Classes.Size(); i++ ) { CExpClass *cl = m_Classes[ i ]; if ( !Q_stricmp( search, bMatchBaseNameOnly ? cl->GetBaseName() : cl->GetName() ) ) { return cl; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : *filename - // Output : const char //----------------------------------------------------------------------------- const char *CExpressionManager::GetClassnameFromFilename( const char *filename ) { char cleanname[ 256 ]; static char classname[ 256 ]; classname[ 0 ] = 0; Assert( filename && filename[ 0 ] ); // Strip the .txt Q_StripExtension( filename, cleanname, sizeof( cleanname ) ); char *p = Q_stristr( cleanname, "expressions" ); if ( p ) { Q_strncpy( classname, p + Q_strlen( "expressions" ) + 1, sizeof( classname ) ); } else { Assert( 0 ); Q_strncpy( classname, cleanname, sizeof( classname ) ); } Q_FixSlashes( classname ); Q_strlower( classname ); return classname; }; //----------------------------------------------------------------------------- // Purpose: // Output : CExpression //----------------------------------------------------------------------------- CExpression *CExpressionManager::GetCopyBuffer( void ) { return &m_CopyBuffer; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CExpressionManager::CanClose( void ) { for ( int i = 0; i < m_Classes.Size(); i++ ) { CExpClass *pclass = m_Classes[ i ]; if ( pclass->GetDirty() ) { return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *filename - //----------------------------------------------------------------------------- void CExpressionManager::LoadClass( const char *inpath ) { MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); if ( inpath[ 0 ] == '/' || inpath[ 0 ] == '\\' ) ++inpath; char filename[ 512 ]; Q_strncpy( filename, inpath, sizeof( filename ) ); CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); if ( !hdr ) { Con_ErrorPrintf( "Can't load expressions from %s, must load a .mdl file first!\n", filename ); return; } Con_Printf( "Loading expressions from %s\n", filename ); const char *classname = GetClassnameFromFilename( filename ); // Already loaded, don't do anything if ( FindClass( classname, false ) ) return; // Import actual data LoadScriptFile( filename, SCRIPT_USE_RELATIVE_PATH ); CExpClass *active = AddCExpClass( classname, filename ); if ( !active ) return; ActivateExpressionClass( active ); int numflexmaps = 0; int flexmap[128]; // maps file local controls into global controls LocalFlexController_t localflexmap[128]; // maps file local controls into local controls bool bHasWeighting = false; bool bNormalized = false; EnableStickySnapshotMode( ); while (1) { GetToken (true); if (endofscript) break; if (stricmp( token, "$keys" ) == 0) { numflexmaps = 0; while (TokenAvailable()) { flexmap[numflexmaps] = -1; localflexmap[numflexmaps] = LocalFlexController_t(-1); GetToken( false ); bool bFound = false; for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) { if (stricmp( hdr->pFlexcontroller(i)->pszName(), token ) == 0) { localflexmap[numflexmaps] = i; flexmap[numflexmaps] = AddGlobalFlexController( models->GetActiveStudioModel(), hdr->pFlexcontroller(i)->pszName() ); bFound = true; break; } } if ( !bFound ) { flexmap[ numflexmaps ] = AddGlobalFlexController( models->GetActiveStudioModel(), token ); } numflexmaps++; } } else if ( !stricmp( token, "$hasweighting" ) ) { bHasWeighting = true; } else if ( !stricmp( token, "$normalized" ) ) { bNormalized = true; } else { float setting[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; float weight[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; char name[ 256 ]; char desc[ 256 ]; int index; memset( setting, 0, sizeof( setting ) ); memset( weight, 0, sizeof( weight ) ); strcpy( name, token ); // phoneme index GetToken( false ); if (token[1] == 'x') { sscanf( &token[2], "%x", &index ); } else { index = (int)token[0]; } // key values for (int i = 0; i < numflexmaps; i++) { if (flexmap[i] > -1) { GetToken( false ); setting[flexmap[i]] = atof( token ); if (bHasWeighting) { GetToken( false ); weight[flexmap[i]] = atof( token ); } else { weight[flexmap[i]] = 1.0; } if ( bNormalized && localflexmap[ i ] > -1 ) { mstudioflexcontroller_t *pFlex = hdr->pFlexcontroller( localflexmap[i] ); if ( pFlex->min != pFlex->max ) { setting[flexmap[i]] = Lerp( setting[flexmap[i]], pFlex->min, pFlex->max ); } } } else { GetToken( false ); if (bHasWeighting) { GetToken( false ); } } } // description GetToken( false ); strcpy( desc, token ); CExpression *exp = active->AddExpression( name, desc, setting, weight, false, false ); if ( active->IsPhonemeClass() && exp ) { if ( exp->index != index ) { Con_Printf( "CExpressionManager::LoadClass (%s): phoneme index for %s in .txt file is wrong (expecting %i got %i), ignoring...\n", classname, name, exp->index, index ); } } } } active->CheckBitmapConsistency(); DisableStickySnapshotMode( ); PopulateClassCB( active ); active->DeselectExpression(); Assert( !active->GetDirty() ); active->SetDirty( false ); } //----------------------------------------------------------------------------- // Purpose: // Input : *filename - //----------------------------------------------------------------------------- void CExpressionManager::CreateNewClass( const char *filename ) { CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); if ( !hdr ) { Con_ErrorPrintf( "Can't create new expression file %s, must load a .mdl file first!\n", filename ); return; } // Tell the use that the filename was loaded, expressions are empty for now const char *classname = GetClassnameFromFilename( filename ); // Already loaded, don't do anything if ( FindClass( classname, false ) ) return; Con_Printf( "Creating %s\n", filename ); CExpClass *active = AddCExpClass( classname, filename ); if ( !active ) return; ActivateExpressionClass( active ); // Select the newly created class PopulateClassCB( active ); // Select first expression active->SelectExpression( 0 ); // Nothing has changed so far active->SetDirty( false ); } //----------------------------------------------------------------------------- // Purpose: // Input : *cl - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CExpressionManager::CloseClass( CExpClass *cl ) { if ( !cl ) return true; if ( cl->GetDirty() ) { int retval = mxMessageBox( NULL, va( "Save changes to class '%s'?", cl->GetName() ), g_appTitle, MX_MB_YESNOCANCEL ); if ( retval == 2 ) { return false; } if ( retval == 0 ) { Con_Printf( "Saving changes to %s : %s\n", cl->GetName(), cl->GetFileName() ); cl->Save(); } } // The memory can be freed here, so be more careful char temp[ 256 ]; V_strcpy_safe( temp, cl->GetName() ); RemoveCExpClass( cl ); Con_Printf( "Closed expression class %s\n", temp ); CExpClass *active = GetActiveClass(); if ( !active ) { PopulateClassCB( NULL ); g_pExpressionTrayTool->redraw(); return true; } // Select the first remaining class PopulateClassCB( active ); // Select first expression active->DeselectExpression(); return true; } //----------------------------------------------------------------------------- // Purpose: // Input : classnum - //----------------------------------------------------------------------------- void CExpressionManager::PopulateClassCB( CExpClass *current ) { g_pExpressionClass->removeAll(); int select = 0; for ( int i = 0; i < GetNumClasses(); i++ ) { CExpClass *cl = GetClass( i ); if ( !cl ) continue; g_pExpressionClass->add( cl->GetName() ); if ( cl == current ) { select = i; } } g_pExpressionClass->select( select ); }