Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

735 lines
19 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "hlfaceposer.h"
#include <mxtk/mx.h>
#include "expressions.h"
#include "expclass.h"
#include "hlfaceposer.h"
#include "StudioModel.h"
#include "filesystem.h"
#include "FlexPanel.h"
#include "ControlPanel.h"
#include "mxExpressionTray.h"
#include "UtlBuffer.h"
#include "filesystem.h"
#include "ExpressionTool.h"
#include "faceposer_models.h"
#include "mdlviewer.h"
#include "phonemeconverter.h"
#include "ProgressDialog.h"
#include "tier1/fmtstr.h"
#include "tier1/utlstring.h"
#include "tier1/utlvector.h"
#undef ALIGN4
#undef ALIGN16
#define ALIGN4( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3)
#define ALIGN16( a ) a = (byte *)((int)((byte *)a + 15) & ~ 15)
char const *GetGlobalFlexControllerName( int index );
int GetGlobalFlexControllerCount( void );
//-----------------------------------------------------------------------------
// Purpose:
// Input : *classname -
//-----------------------------------------------------------------------------
CExpClass::CExpClass( const char *classname )
{
Q_strncpy( m_szClassName, classname, sizeof( m_szClassName ) );
Q_FileBase( m_szClassName, m_szBaseName, sizeof( m_szBaseName ) );
m_szFileName[ 0 ] = 0;
m_bDirty = false;
m_nSelectedExpression = -1;
m_bIsPhonemeClass = Q_strstr( classname, "phonemes" ) ? true : false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CExpClass::~CExpClass( void )
{
m_Expressions.Purge();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *exp -
//-----------------------------------------------------------------------------
int CExpClass::FindExpressionIndex( CExpression *exp )
{
for ( int i = 0 ; i < GetNumExpressions(); i++ )
{
CExpression *e = GetExpression( i );
if ( e == exp )
return i;
}
return -1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CExpClass::Save( void )
{
CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr();
if ( !hdr )
{
return;
}
const char *filename = GetFileName();
if ( !filename || !filename[ 0 ] )
return;
Con_Printf( "Saving changes to %s to file %s\n", GetName(), GetFileName() );
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
int i, j;
int numflexmaps = 0;
int flexmap[128]; // maps file local controlls into global controls
CExpression *expr = NULL;
// find all used controllers
int fc = GetGlobalFlexControllerCount();
for ( j = 0; j < fc; ++j )
{
for (i = 0; i < GetNumExpressions(); i++)
{
expr = GetExpression( i );
Assert( expr );
float *settings = expr->GetSettings();
float *weights = expr->GetWeights();
if ( settings[j] != 0 ||
weights[j] != 0 )
{
flexmap[ numflexmaps++ ] = j;
break;
}
}
}
buf.Printf( "$keys" );
for (j = 0; j < numflexmaps; j++)
{
buf.Printf( " %s", GetGlobalFlexControllerName( flexmap[j] ) );
}
buf.Printf( "\n" );
buf.Printf( "$hasweighting\n" );
for (i = 0; i < GetNumExpressions(); i++)
{
expr = GetExpression( i );
buf.Printf( "\"%s\" ", expr->name );
// isalpha returns non zero for ents > 256
if (expr->index <= 'z')
{
buf.Printf( "\"%c\" ", expr->index );
}
else
{
buf.Printf( "\"0x%04x\" ", expr->index );
}
float *settings = expr->GetSettings();
float *weights = expr->GetWeights();
Assert( settings );
Assert( weights );
for (j = 0; j < numflexmaps; j++)
{
buf.Printf( "%.3f %.3f ", settings[flexmap[j]], weights[flexmap[j]] );
}
if ( Q_strstr( expr->name, "Right Side Smile" ) )
{
Con_Printf( "wrote %s with checksum %s\n",
expr->name, expr->GetBitmapCheckSum() );
}
buf.Printf( "\"%s\"\n", expr->description );
}
char relative[ 512 ];
filesystem->FullPathToRelativePath( filename, relative, sizeof( relative ) );
MakeFileWriteable( relative );
FileHandle_t fh = filesystem->Open( relative, "wt" );
if ( !fh )
{
Con_ErrorPrintf( "Unable to write to %s (read-only?)\n", relative );
return;
}
else
{
filesystem->Write( buf.Base(), buf.TellPut(), fh );
filesystem->Close(fh);
}
SetDirty( false );
for (i = 0; i < GetNumExpressions(); i++)
{
expr = GetExpression( i );
if ( expr )
{
expr->ResetUndo();
expr->SetDirty( false );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CExpClass::Export( void )
{
char vfefilename[ 512 ];
Q_StripExtension( GetFileName(), vfefilename, sizeof( vfefilename ) );
Q_DefaultExtension( vfefilename, ".vfe", sizeof( vfefilename ) );
Con_Printf( "Exporting %s to %s\n", GetName(), vfefilename );
int i, j;
int numflexmaps = 0;
int flexmap[128]; // maps file local controlls into global controls
CExpression *expr = NULL;
// find all used controllers
int fc_count = GetGlobalFlexControllerCount();
for (j = 0; j < fc_count; j++)
{
int k = j;
for (i = 0; i < GetNumExpressions(); i++)
{
expr = GetExpression( i );
Assert( expr );
float *settings = expr->GetSettings();
float *weights = expr->GetWeights();
Assert( settings );
Assert( weights );
if ( settings[k] != 0 || weights[k] != 0 )
{
flexmap[numflexmaps++] = k;
break;
}
}
}
byte *pData = (byte *)calloc( 1024 * 1024, 1 );
byte *pDataStart = pData;
flexsettinghdr_t *fhdr = (flexsettinghdr_t *)pData;
fhdr->id = ('V' << 16) + ('F' << 8) + ('E');
fhdr->version = 0;
V_strncpy( fhdr->name, vfefilename, sizeof( fhdr->name ) );
// allocate room for header
pData += sizeof( flexsettinghdr_t );
ALIGN4( pData );
// store flex settings
flexsetting_t *pSetting = (flexsetting_t *)pData;
fhdr->numflexsettings = GetNumExpressions();
fhdr->flexsettingindex = pData - pDataStart;
pData += sizeof( flexsetting_t ) * fhdr->numflexsettings;
ALIGN4( pData );
for (i = 0; i < fhdr->numflexsettings; i++)
{
expr = GetExpression( i );
Assert( expr );
pSetting[i].index = expr->index;
pSetting[i].settingindex = pData - (byte *)(&pSetting[i]);
flexweight_t *pFlexWeights = (flexweight_t *)pData;
float *settings = expr->GetSettings();
float *weights = expr->GetWeights();
Assert( settings );
Assert( weights );
for (j = 0; j < numflexmaps; j++)
{
if (settings[flexmap[j]] != 0 || weights[flexmap[j]] != 0)
{
pSetting[i].numsettings++;
pFlexWeights->key = j;
pFlexWeights->weight = settings[flexmap[j]];
pFlexWeights->influence = weights[flexmap[j]];
pFlexWeights++;
}
pData = (byte *)pFlexWeights;
ALIGN4( pData );
}
}
// store indexed table
int numindexes = 1;
for (i = 0; i < fhdr->numflexsettings; i++)
{
if (pSetting[i].index >= numindexes)
numindexes = pSetting[i].index + 1;
}
int *pIndex = (int *)pData;
fhdr->numindexes = numindexes;
fhdr->indexindex = pData - pDataStart;
pData += sizeof( int ) * numindexes;
ALIGN4( pData );
for (i = 0; i < numindexes; i++)
{
pIndex[i] = -1;
}
for (i = 0; i < fhdr->numflexsettings; i++)
{
pIndex[pSetting[i].index] = i;
}
// store flex setting names
for (i = 0; i < fhdr->numflexsettings; i++)
{
expr = GetExpression( i );
pSetting[i].nameindex = pData - (byte *)(&pSetting[i]);
strcpy( (char *)pData, expr->name );
pData += strlen( expr->name ) + 1;
}
ALIGN4( pData );
// store key names
char **pKeynames = (char **)pData;
fhdr->numkeys = numflexmaps;
fhdr->keynameindex = pData - pDataStart;
pData += sizeof( char *) * numflexmaps;
for (i = 0; i < numflexmaps; i++)
{
pKeynames[i] = (char *)(pData - pDataStart);
strcpy( (char *)pData, GetGlobalFlexControllerName( flexmap[i] ) );
pData += strlen( GetGlobalFlexControllerName( flexmap[i] ) ) + 1;
}
ALIGN4( pData );
// allocate room for remapping
int *keymapping = (int *)pData;
fhdr->keymappingindex = pData - pDataStart;
pData += sizeof( int ) * numflexmaps;
for (i = 0; i < numflexmaps; i++)
{
keymapping[i] = -1;
}
ALIGN4( pData );
fhdr->length = pData - pDataStart;
char relative[ 512 ];
filesystem->FullPathToRelativePath( vfefilename, relative, sizeof( relative ) );
MakeFileWriteable( relative );
FileHandle_t fh = filesystem->Open( relative, "wb" );
if ( !fh )
{
Con_ErrorPrintf( "Unable to write to %s (read-only?)\n", relative );
return;
}
else
{
filesystem->Write( pDataStart, fhdr->length, fh );
filesystem->Close(fh);
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : const char
//-----------------------------------------------------------------------------
const char *CExpClass::GetBaseName( void ) const
{
return m_szBaseName;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : const char
//-----------------------------------------------------------------------------
const char *CExpClass::GetName( void ) const
{
return m_szClassName;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : const char
//-----------------------------------------------------------------------------
const char *CExpClass::GetFileName( void ) const
{
return m_szFileName;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *filename -
//-----------------------------------------------------------------------------
void CExpClass::SetFileName( const char *filename )
{
strcpy( m_szFileName, filename );
}
bool IsUsingPerPlayerExpressions();
void CExpClass::ReloadBitmaps( void )
{
bool bUsingPerPlayerOverrides = IsUsingPerPlayerExpressions();
int c = models->Count();
for ( int model = 0; model < MAX_FP_MODELS; model++ )
{
// Only reload bitmaps for current model index
if ( bUsingPerPlayerOverrides && model != models->GetActiveModelIndex() )
continue;
models->ForceActiveModelIndex( model );
for ( int i = 0 ; i < GetNumExpressions(); i++ )
{
CExpression *e = GetExpression( i );
if ( !e )
continue;
if ( e->m_Bitmap[ model ].valid )
{
DeleteObject( e->m_Bitmap[ model ].image );
e->m_Bitmap[ model ].valid = false;
}
if ( model >= c )
continue;
if ( !LoadBitmapFromFile( e->GetBitmapFilename( model ), e->m_Bitmap[ model ] ) )
{
e->CreateNewBitmap( model );
}
}
}
models->UnForceActiveModelIndex();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
// *description -
// *flexsettings -
// selectnewitem -
// Output : CExpression
//-----------------------------------------------------------------------------
CExpression *CExpClass::AddExpression( const char *name, const char *description, float *flexsettings, float *flexweights, bool selectnewitem, bool bDirtyClass )
{
CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr();
if ( !hdr )
return NULL;
CExpression *exp = FindExpression( name );
if ( exp )
{
Con_ErrorPrintf( "Can't create, an expression with the name '%s' already exists.\n", name );
return NULL;
}
// Add to end of list
int idx = m_Expressions.AddToTail();
exp = &m_Expressions[ idx ];
float *settings = exp->GetSettings();
float *weights = exp->GetWeights();
Assert( settings );
Assert( weights );
exp->SetExpressionClass( GetName() );
strcpy( exp->name, name );
strcpy( exp->description, description );
memcpy( settings, flexsettings, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) );
memcpy( weights, flexweights, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) );
exp->index = '_';
if ( IsPhonemeClass() )
{
exp->index = TextToPhoneme( name );
}
exp->m_Bitmap[ models->GetActiveModelIndex() ].valid = false;
if ( !LoadBitmapFromFile( exp->GetBitmapFilename( models->GetActiveModelIndex() ), exp->m_Bitmap[ models->GetActiveModelIndex() ] ) )
{
exp->CreateNewBitmap( models->GetActiveModelIndex() );
}
if ( selectnewitem )
{
SelectExpression( idx );
}
if ( bDirtyClass )
{
SetDirty( true );
}
return exp;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
// Output : CExpression
//-----------------------------------------------------------------------------
CExpression *CExpClass::FindExpression( const char *name )
{
for ( int i = 0 ; i < m_Expressions.Size(); i++ )
{
CExpression *exp = &m_Expressions[ i ];
if ( !stricmp( exp->name, name ) )
{
return exp;
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
//-----------------------------------------------------------------------------
void CExpClass::DeleteExpression( const char *name )
{
for ( int i = 0 ; i < m_Expressions.Size(); i++ )
{
CExpression *exp = &m_Expressions[ i ];
if ( !stricmp( exp->name, name ) )
{
SetDirty( true );
m_Expressions.Remove( i );
return;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CExpClass::GetNumExpressions( void )
{
return m_Expressions.Size();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : num -
// Output : CExpression
//-----------------------------------------------------------------------------
CExpression *CExpClass::GetExpression( int num )
{
if ( num < 0 || num >= m_Expressions.Size() )
{
return NULL;
}
CExpression *exp = &m_Expressions[ num ];
return exp;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CExpClass::GetDirty( void )
{
return m_bDirty;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : dirty -
//-----------------------------------------------------------------------------
void CExpClass::SetDirty( bool dirty )
{
m_bDirty = dirty;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CExpClass::GetIndex( void )
{
for ( int i = 0; i < expressions->GetNumClasses(); i++ )
{
CExpClass *cl = expressions->GetClass( i );
if ( cl == this )
return i;
}
return -1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : num -
//-----------------------------------------------------------------------------
void CExpClass::SelectExpression( int num, bool deselect )
{
m_nSelectedExpression = num;
g_pFlexPanel->setExpression( num );
g_pExpressionTrayTool->Select( num, deselect );
g_pExpressionTrayTool->redraw();
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CExpClass::GetSelectedExpression( void )
{
return m_nSelectedExpression;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CExpClass::DeselectExpression( void )
{
m_nSelectedExpression = -1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : exp1 -
// exp2 -
//-----------------------------------------------------------------------------
void CExpClass::SwapExpressionOrder( int exp1, int exp2 )
{
CExpression temp1 = m_Expressions[ exp1 ];
CExpression temp2 = m_Expressions[ exp2 ];
m_Expressions.Remove( exp1 );
m_Expressions.InsertBefore( exp1, temp2 );
m_Expressions.Remove( exp2 );
m_Expressions.InsertBefore( exp2, temp1 );
}
void CExpClass::BuildValidChecksums( CUtlRBTree< CRC32_t > &tree )
{
for ( int i = 0; i < m_Expressions.Size(); i++ )
{
CExpression *exp = &m_Expressions[ i ];
if ( !exp )
continue;
CRC32_t crc = exp->GetBitmapCRC();
tree.Insert( crc );
}
}
//-----------------------------------------------------------------------------
// Purpose: After a class is loaded, check the class directory and delete any bmp files that aren't
// still referenced
//-----------------------------------------------------------------------------
void CExpClass::CheckBitmapConsistency( void )
{
char path[ 512 ];
Q_snprintf( path, sizeof( path ), "expressions/%s/%s/*.bmp", models->GetActiveModelName(), GetBaseName() );
Q_FixSlashes( path );
Q_strlower( path );
g_pProgressDialog->Start( CFmtStr( "%s / %s - Reconcile Expression Thumbnails", models->GetActiveModelName(), GetBaseName() ), "", true );
CUtlVector< CUtlString > workList;
FileFindHandle_t hFindFile;
char const *fn = filesystem->FindFirstEx( path, "MOD", &hFindFile );
if ( fn )
{
while ( fn )
{
// Don't do anything with directories
if ( !filesystem->FindIsDirectory( hFindFile ) )
{
CUtlString s = fn;
workList.AddToTail( s );
}
fn = filesystem->FindNext( hFindFile );
}
filesystem->FindClose( hFindFile );
}
CUtlRBTree< CRC32_t > tree( 0, 0, DefLessFunc( CRC32_t ) );
BuildValidChecksums( tree );
for ( int i = 0 ; i < workList.Count(); ++i )
{
char testname[ 256 ];
Q_StripExtension( workList[ i ].String(), testname, sizeof( testname ) );
g_pProgressDialog->UpdateText( "%s", testname );
g_pProgressDialog->Update( (float)i / (float)workList.Count() );
CRC32_t check;
Q_hextobinary( testname, Q_strlen( testname ), (byte *)&check, sizeof( check ) );
if ( tree.Find( check ) == tree.InvalidIndex() )
{
char kill[ 512 ];
Q_snprintf( kill, sizeof( kill ), "expressions/%s/%s/%s", models->GetActiveModelName(), GetBaseName(), fn );
Q_FixSlashes( kill );
Q_strlower( kill );
// Delete it
Con_ErrorPrintf( "Removing unused bitmap file '%s'\n", kill );
filesystem->RemoveFile( kill, "MOD" );
}
if ( g_pProgressDialog->IsCancelled() )
{
Msg( "Cancelled\n" );
break;
}
}
g_pProgressDialog->Finish();
}
//-----------------------------------------------------------------------------
// Purpose: Does this class have expression indices based on phoneme lookups
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CExpClass::IsPhonemeClass( void ) const
{
return m_bIsPhonemeClass;
}