diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/client/c_baseflex.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/client/c_baseflex.cpp')
| -rw-r--r-- | mp/src/game/client/c_baseflex.cpp | 4142 |
1 files changed, 2071 insertions, 2071 deletions
diff --git a/mp/src/game/client/c_baseflex.cpp b/mp/src/game/client/c_baseflex.cpp index a39b5f35..0a5db8e5 100644 --- a/mp/src/game/client/c_baseflex.cpp +++ b/mp/src/game/client/c_baseflex.cpp @@ -1,2071 +1,2071 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//===========================================================================//
-#include "cbase.h"
-#include "filesystem.h"
-#include "sentence.h"
-#include "hud_closecaption.h"
-#include "engine/ivmodelinfo.h"
-#include "engine/ivdebugoverlay.h"
-#include "bone_setup.h"
-#include "soundinfo.h"
-#include "tools/bonelist.h"
-#include "KeyValues.h"
-#include "tier0/vprof.h"
-#include "toolframework/itoolframework.h"
-#include "choreoevent.h"
-#include "choreoscene.h"
-#include "choreoactor.h"
-#include "toolframework_client.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-bool UseHWMorphVCDs();
-
-ConVar g_CV_PhonemeDelay("phonemedelay", "0", 0, "Phoneme delay to account for sound system latency." );
-ConVar g_CV_PhonemeFilter("phonemefilter", "0.08", 0, "Time duration of box filter to pass over phonemes." );
-ConVar g_CV_FlexRules("flex_rules", "1", 0, "Allow flex animation rules to run." );
-ConVar g_CV_BlinkDuration("blink_duration", "0.2", 0, "How many seconds an eye blink will last." );
-ConVar g_CV_FlexSmooth("flex_smooth", "1", 0, "Applies smoothing/decay curve to flex animation controller changes." );
-
-#if defined( CBaseFlex )
-#undef CBaseFlex
-#endif
-
-IMPLEMENT_CLIENTCLASS_DT(C_BaseFlex, DT_BaseFlex, CBaseFlex)
- RecvPropArray3( RECVINFO_ARRAY(m_flexWeight), RecvPropFloat(RECVINFO(m_flexWeight[0]))),
- RecvPropInt(RECVINFO(m_blinktoggle)),
- RecvPropVector(RECVINFO(m_viewtarget)),
-
-#ifdef HL2_CLIENT_DLL
- RecvPropFloat( RECVINFO(m_vecViewOffset[0]) ),
- RecvPropFloat( RECVINFO(m_vecViewOffset[1]) ),
- RecvPropFloat( RECVINFO(m_vecViewOffset[2]) ),
-
- RecvPropVector(RECVINFO(m_vecLean)),
- RecvPropVector(RECVINFO(m_vecShift)),
-#endif
-
-END_RECV_TABLE()
-
-BEGIN_PREDICTION_DATA( C_BaseFlex )
-
-/*
- // DEFINE_FIELD( C_BaseFlex, m_viewtarget, FIELD_VECTOR ),
- // DEFINE_ARRAY( C_BaseFlex, m_flexWeight, FIELD_FLOAT, 64 ),
- // DEFINE_FIELD( C_BaseFlex, m_blinktoggle, FIELD_INTEGER ),
- // DEFINE_FIELD( C_BaseFlex, m_blinktime, FIELD_FLOAT ),
- // DEFINE_FIELD( C_BaseFlex, m_prevviewtarget, FIELD_VECTOR ),
- // DEFINE_ARRAY( C_BaseFlex, m_prevflexWeight, FIELD_FLOAT, 64 ),
- // DEFINE_FIELD( C_BaseFlex, m_prevblinktoggle, FIELD_INTEGER ),
- // DEFINE_FIELD( C_BaseFlex, m_iBlink, FIELD_INTEGER ),
- // DEFINE_FIELD( C_BaseFlex, m_iEyeUpdown, FIELD_INTEGER ),
- // DEFINE_FIELD( C_BaseFlex, m_iEyeRightleft, FIELD_INTEGER ),
- // DEFINE_FIELD( C_BaseFlex, m_FileList, CUtlVector < CFlexSceneFile * > ),
-*/
-
-END_PREDICTION_DATA()
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool GetHWMExpressionFileName( const char *pFilename, char *pHWMFilename )
-{
- // Are we even using hardware morph?
- if ( !UseHWMorphVCDs() )
- return false;
-
- // Do we have a valid filename?
- if ( !( pFilename && pFilename[0] ) )
- return false;
-
- // Check to see if we already have an player/hwm/* filename.
- if ( ( V_strstr( pFilename, "player/hwm" ) != NULL ) || ( V_strstr( pFilename, "player\\hwm" ) != NULL ) )
- {
- V_strcpy( pHWMFilename, pFilename );
- return true;
- }
-
- // Find the hardware morph scene name and pass that along as well.
- char szExpression[MAX_PATH];
- V_strcpy( szExpression, pFilename );
-
- char szExpressionHWM[MAX_PATH];
- szExpressionHWM[0] = '\0';
-
- char *pszToken = strtok( szExpression, "/\\" );
- while ( pszToken != NULL )
- {
- V_strcat( szExpressionHWM, pszToken, sizeof( szExpressionHWM ) );
- if ( !V_stricmp( pszToken, "player" ) )
- {
- V_strcat( szExpressionHWM, "\\hwm", sizeof( szExpressionHWM ) );
- }
-
- pszToken = strtok( NULL, "/\\" );
- if ( pszToken != NULL )
- {
- V_strcat( szExpressionHWM, "\\", sizeof( szExpressionHWM ) );
- }
- }
-
- V_strcpy( pHWMFilename, szExpressionHWM );
- return true;
-}
-
-C_BaseFlex::C_BaseFlex() :
- m_iv_viewtarget( "C_BaseFlex::m_iv_viewtarget" ),
- m_iv_flexWeight("C_BaseFlex:m_iv_flexWeight" ),
-#ifdef HL2_CLIENT_DLL
- m_iv_vecLean("C_BaseFlex:m_iv_vecLean" ),
- m_iv_vecShift("C_BaseFlex:m_iv_vecShift" ),
-#endif
- m_LocalToGlobal( 0, 0, FlexSettingLessFunc )
-{
-#ifdef _DEBUG
- ((Vector&)m_viewtarget).Init();
-#endif
-
- AddVar( &m_viewtarget, &m_iv_viewtarget, LATCH_ANIMATION_VAR | INTERPOLATE_LINEAR_ONLY );
- AddVar( m_flexWeight, &m_iv_flexWeight, LATCH_ANIMATION_VAR );
-
- // Fill in phoneme class lookup
- SetupMappings( "phonemes" );
-
- m_flFlexDelayedWeight = NULL;
- m_cFlexDelayedWeight = 0;
-
- /// Make sure size is correct
- Assert( PHONEME_CLASS_STRONG + 1 == NUM_PHONEME_CLASSES );
-
-#ifdef HL2_CLIENT_DLL
- // Get general lean vector
- AddVar( &m_vecLean, &m_iv_vecLean, LATCH_ANIMATION_VAR );
- AddVar( &m_vecShift, &m_iv_vecShift, LATCH_ANIMATION_VAR );
-#endif
-}
-
-C_BaseFlex::~C_BaseFlex()
-{
- delete[] m_flFlexDelayedWeight;
- m_SceneEvents.RemoveAll();
- m_LocalToGlobal.RemoveAll();
-}
-
-
-void C_BaseFlex::Spawn()
-{
- BaseClass::Spawn();
-
- InitPhonemeMappings();
-}
-
-// TF Player overrides all of these with class specific files
-void C_BaseFlex::InitPhonemeMappings()
-{
- SetupMappings( "phonemes" );
-}
-
-void C_BaseFlex::SetupMappings( char const *pchFileRoot )
-{
- // Fill in phoneme class lookup
- memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) );
-
- Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ];
- Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot );
- normal->required = true;
-
- Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ];
- Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot );
- Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ];
- Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: initialize fast lookups when model changes
-//-----------------------------------------------------------------------------
-
-CStudioHdr *C_BaseFlex::OnNewModel()
-{
- CStudioHdr *hdr = BaseClass::OnNewModel();
-
- // init to invalid setting
- m_iBlink = -1;
- m_iEyeUpdown = LocalFlexController_t(-1);
- m_iEyeRightleft = LocalFlexController_t(-1);
- m_bSearchedForEyeFlexes = false;
- m_iMouthAttachment = 0;
-
- delete[] m_flFlexDelayedWeight;
- m_flFlexDelayedWeight = NULL;
- m_cFlexDelayedWeight = 0;
-
- if (hdr)
- {
- if (hdr->numflexdesc())
- {
- m_cFlexDelayedWeight = hdr->numflexdesc();
- m_flFlexDelayedWeight = new float[ m_cFlexDelayedWeight ];
- memset( m_flFlexDelayedWeight, 0, sizeof( float ) * m_cFlexDelayedWeight );
- }
-
- m_iv_flexWeight.SetMaxCount( hdr->numflexcontrollers() );
-
- m_iMouthAttachment = LookupAttachment( "mouth" );
-
- LinkToGlobalFlexControllers( hdr );
- }
-
- return hdr;
-}
-
-
-void C_BaseFlex::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask )
-{
- BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask );
-
-#ifdef HL2_CLIENT_DLL
- // shift pelvis, rotate body
- if (hdr->GetNumIKChains() != 0 && (m_vecShift.x != 0.0 || m_vecShift.y != 0.0))
- {
- //CIKContext auto_ik;
- //auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask );
- //auto_ik.AddAllLocks( pos, q );
-
- matrix3x4_t rootxform;
- AngleMatrix( GetRenderAngles(), GetRenderOrigin(), rootxform );
-
- Vector localShift;
- VectorIRotate( m_vecShift, rootxform, localShift );
- Vector localLean;
- VectorIRotate( m_vecLean, rootxform, localLean );
-
- Vector p0 = pos[0];
- float length = VectorNormalize( p0 );
-
- // shift the root bone, but keep the height off the origin the same
- Vector shiftPos = pos[0] + localShift;
- VectorNormalize( shiftPos );
- Vector leanPos = pos[0] + localLean;
- VectorNormalize( leanPos );
- pos[0] = shiftPos * length;
-
- // rotate the root bone based on how much it was "leaned"
- Vector p1;
- CrossProduct( p0, leanPos, p1 );
- float sinAngle = VectorNormalize( p1 );
- float cosAngle = DotProduct( p0, leanPos );
- float angle = atan2( sinAngle, cosAngle ) * 180 / M_PI;
- Quaternion q1;
- angle = clamp( angle, -45, 45 );
- AxisAngleQuaternion( p1, angle, q1 );
- QuaternionMult( q1, q[0], q[0] );
- QuaternionNormalize( q[0] );
-
- // DevMsgRT( " (%.2f) %.2f %.2f %.2f\n", angle, p1.x, p1.y, p1.z );
- // auto_ik.SolveAllLocks( pos, q );
- }
-#endif
-}
-
-
-
-//-----------------------------------------------------------------------------
-// Purpose: place "voice" sounds on mouth
-//-----------------------------------------------------------------------------
-
-bool C_BaseFlex::GetSoundSpatialization( SpatializationInfo_t& info )
-{
- bool bret = BaseClass::GetSoundSpatialization( info );
- // Default things it's audible, put it at a better spot?
- if ( bret )
- {
- if ((info.info.nChannel == CHAN_VOICE || info.info.nChannel == CHAN_VOICE2) && m_iMouthAttachment > 0)
- {
- Vector origin;
- QAngle angles;
-
- C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false );
-
- if (GetAttachment( m_iMouthAttachment, origin, angles ))
- {
- if (info.pOrigin)
- {
- *info.pOrigin = origin;
- }
-
- if (info.pAngles)
- {
- *info.pAngles = angles;
- }
- }
- }
- }
-
- return bret;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: run the interpreted FAC's expressions, converting global flex_controller
-// values into FAC weights
-//-----------------------------------------------------------------------------
-void C_BaseFlex::RunFlexRules( CStudioHdr *hdr, float *dest )
-{
- if ( !g_CV_FlexRules.GetInt() )
- return;
-
- if ( !hdr )
- return;
-
-/*
- // 0 means run them all
- int nFlexRulesToRun = 0;
-
- const char *pszExpression = flex_expression.GetString();
- if ( pszExpression )
- {
- nFlexRulesToRun = atoi(pszExpression); // 0 will be returned if not a numeric string
- }
-//*/
-
- hdr->RunFlexRules( g_flexweight, dest );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CFlexSceneFileManager::Init()
-{
- // Trakcer 16692: Preload these at startup to avoid hitch first time we try to load them during actual gameplay
- FindSceneFile( NULL, "phonemes", true );
- FindSceneFile( NULL, "phonemes_weak", true );
- FindSceneFile(NULL, "phonemes_strong", true );
-
-#if defined( HL2_CLIENT_DLL )
- FindSceneFile( NULL, "random", true );
- FindSceneFile( NULL, "randomAlert", true );
-#endif
-
-#if defined( TF_CLIENT_DLL )
- // HACK TO ALL TF TO HAVE PER CLASS OVERRIDES
- char const *pTFClasses[] =
- {
- "scout",
- "sniper",
- "soldier",
- "demo",
- "medic",
- "heavy",
- "pyro",
- "spy",
- "engineer",
- };
-
- char fn[ MAX_PATH ];
- for ( int i = 0; i < ARRAYSIZE( pTFClasses ); ++i )
- {
- Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_weak", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_strong", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
-
- if ( !IsX360() )
- {
- Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_weak", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_strong", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- }
-
- Q_snprintf( fn, sizeof( fn ), "player/%s/emotion/emotion", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- if ( !IsX360() )
- {
- Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/emotion/emotion", pTFClasses[i] );
- FindSceneFile( NULL, fn, true );
- }
- }
-#endif
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Tracker 14992: We used to load 18K of .vfes for every C_BaseFlex who lipsynced, but now we only load those files once globally.
-// Note, we could wipe these between levels, but they don't ever load more than the weak/normal/strong phoneme classes that I can tell
-// so I'll just leave them loaded forever for now
-//-----------------------------------------------------------------------------
-void CFlexSceneFileManager::Shutdown()
-{
- DeleteSceneFiles();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets up translations
-// Input : *instance -
-// *pSettinghdr -
-// Output : void
-//-----------------------------------------------------------------------------
-void CFlexSceneFileManager::EnsureTranslations( IHasLocalToGlobalFlexSettings *instance, const flexsettinghdr_t *pSettinghdr )
-{
- // The only time instance is NULL is in Init() above, where we're just loading the .vfe files off of the hard disk.
- if ( instance )
- {
- instance->EnsureTranslations( pSettinghdr );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void *CFlexSceneFileManager::FindSceneFile( IHasLocalToGlobalFlexSettings *instance, const char *filename, bool allowBlockingIO )
-{
- char szFilename[MAX_PATH];
- Assert( V_strlen( filename ) < MAX_PATH );
- V_strcpy( szFilename, filename );
-
-#if defined( TF_CLIENT_DLL )
- char szHWMFilename[MAX_PATH];
- if ( GetHWMExpressionFileName( szFilename, szHWMFilename ) )
- {
- V_strcpy( szFilename, szHWMFilename );
- }
-#endif
-
- Q_FixSlashes( szFilename );
-
- // See if it's already loaded
- int i;
- for ( i = 0; i < m_FileList.Count(); i++ )
- {
- CFlexSceneFile *file = m_FileList[ i ];
- if ( file && !Q_stricmp( file->filename, szFilename ) )
- {
- // Make sure translations (local to global flex controller) are set up for this instance
- EnsureTranslations( instance, ( const flexsettinghdr_t * )file->buffer );
- return file->buffer;
- }
- }
-
- if ( !allowBlockingIO )
- {
- return NULL;
- }
-
- // Load file into memory
- void *buffer = NULL;
- int len = filesystem->ReadFileEx( VarArgs( "expressions/%s.vfe", szFilename ), "GAME", &buffer );
-
- if ( !len )
- return NULL;
-
- // Create scene entry
- CFlexSceneFile *pfile = new CFlexSceneFile;
- // Remember filename
- Q_strncpy( pfile->filename, szFilename, sizeof( pfile->filename ) );
- // Remember data pointer
- pfile->buffer = buffer;
- // Add to list
- m_FileList.AddToTail( pfile );
-
- // Swap the entire file
- if ( IsX360() )
- {
- CByteswap swap;
- swap.ActivateByteSwapping( true );
- byte *pData = (byte*)buffer;
- flexsettinghdr_t *pHdr = (flexsettinghdr_t*)pData;
- swap.SwapFieldsToTargetEndian( pHdr );
-
- // Flex Settings
- flexsetting_t *pFlexSetting = (flexsetting_t*)((byte*)pHdr + pHdr->flexsettingindex);
- for ( int i = 0; i < pHdr->numflexsettings; ++i, ++pFlexSetting )
- {
- swap.SwapFieldsToTargetEndian( pFlexSetting );
-
- flexweight_t *pWeight = (flexweight_t*)(((byte*)pFlexSetting) + pFlexSetting->settingindex );
- for ( int j = 0; j < pFlexSetting->numsettings; ++j, ++pWeight )
- {
- swap.SwapFieldsToTargetEndian( pWeight );
- }
- }
-
- // indexes
- pData = (byte*)pHdr + pHdr->indexindex;
- swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numindexes );
-
- // keymappings
- pData = (byte*)pHdr + pHdr->keymappingindex;
- swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys );
-
- // keyname indices
- pData = (byte*)pHdr + pHdr->keynameindex;
- swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys );
- }
-
- // Fill in translation table
- EnsureTranslations( instance, ( const flexsettinghdr_t * )pfile->buffer );
-
- // Return data
- return pfile->buffer;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFlexSceneFileManager::DeleteSceneFiles()
-{
- while ( m_FileList.Count() > 0 )
- {
- CFlexSceneFile *file = m_FileList[ 0 ];
- m_FileList.Remove( 0 );
- free( file->buffer );
- delete file;
- }
-}
-
-CFlexSceneFileManager g_FlexSceneFileManager;
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *filename -
-//-----------------------------------------------------------------------------
-void *C_BaseFlex::FindSceneFile( const char *filename )
-{
- return g_FlexSceneFileManager.FindSceneFile( this, filename, false );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: make sure the eyes are within 30 degrees of forward
-//-----------------------------------------------------------------------------
-Vector C_BaseFlex::SetViewTarget( CStudioHdr *pStudioHdr )
-{
- if ( !pStudioHdr )
- return Vector( 0, 0, 0);
-
- // aim the eyes
- Vector tmp = m_viewtarget;
-
- if ( !m_bSearchedForEyeFlexes )
- {
- m_bSearchedForEyeFlexes = true;
-
- m_iEyeUpdown = FindFlexController( "eyes_updown" );
- m_iEyeRightleft = FindFlexController( "eyes_rightleft" );
-
- if ( m_iEyeUpdown != -1 )
- {
- pStudioHdr->pFlexcontroller( m_iEyeUpdown )->localToGlobal = AddGlobalFlexController( "eyes_updown" );
- }
- if ( m_iEyeRightleft != -1 )
- {
- pStudioHdr->pFlexcontroller( m_iEyeRightleft )->localToGlobal = AddGlobalFlexController( "eyes_rightleft" );
- }
- }
-
- if (m_iEyeAttachment > 0)
- {
- matrix3x4_t attToWorld;
- if (!GetAttachment( m_iEyeAttachment, attToWorld ))
- {
- return Vector( 0, 0, 0);
- }
-
- Vector local;
- VectorITransform( tmp, attToWorld, local );
-
- // FIXME: clamp distance to something based on eyeball distance
- if (local.x < 6)
- {
- local.x = 6;
- }
- float flDist = local.Length();
- VectorNormalize( local );
-
- // calculate animated eye deflection
- Vector eyeDeflect;
- QAngle eyeAng( 0, 0, 0 );
- if ( m_iEyeUpdown != -1 )
- {
- mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeUpdown );
- eyeAng.x = g_flexweight[ pflex->localToGlobal ];
- }
-
- if ( m_iEyeRightleft != -1 )
- {
- mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeRightleft );
- eyeAng.y = g_flexweight[ pflex->localToGlobal ];
- }
-
- // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%5.3f %5.3f", eyeAng.x, eyeAng.y );
-
- AngleVectors( eyeAng, &eyeDeflect );
- eyeDeflect.x = 0;
-
- // reduce deflection the more the eye is off center
- // FIXME: this angles make no damn sense
- eyeDeflect = eyeDeflect * (local.x * local.x);
- local = local + eyeDeflect;
- VectorNormalize( local );
-
- // check to see if the eye is aiming outside the max eye deflection
- float flMaxEyeDeflection = pStudioHdr->MaxEyeDeflection();
- if ( local.x < flMaxEyeDeflection )
- {
- // if so, clamp it to 30 degrees offset
- // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 1, 0, "%5.3f %5.3f %5.3f", local.x, local.y, local.z );
- local.x = 0;
- float d = local.LengthSqr();
- if ( d > 0.0f )
- {
- d = sqrtf( ( 1.0f - flMaxEyeDeflection * flMaxEyeDeflection ) / ( local.y*local.y + local.z*local.z ) );
- local.x = flMaxEyeDeflection;
- local.y = local.y * d;
- local.z = local.z * d;
- }
- else
- {
- local.x = 1.0;
- }
- }
- local = local * flDist;
- VectorTransform( local, attToWorld, tmp );
- }
-
- modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp );
-
- /*
- debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%.2f %.2f %.2f : %.2f %.2f %.2f",
- m_viewtarget.x, m_viewtarget.y, m_viewtarget.z,
- m_prevviewtarget.x, m_prevviewtarget.y, m_prevviewtarget.z );
- */
-
- return tmp;
-}
-
-#define STRONG_CROSSFADE_START 0.60f
-#define WEAK_CROSSFADE_START 0.40f
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Here's the formula
-// 0.5 is neutral 100 % of the default setting
-// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END
-// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START
-// so we don't get huge numbers
-// Input : *classes -
-// emphasis_intensity -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity )
-{
- // See which blends are available for the current phoneme
- bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid;
- bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid;
-
- // Better have phonemes in general
- Assert( classes[ PHONEME_CLASS_NORMAL ].valid );
-
- if ( emphasis_intensity > STRONG_CROSSFADE_START )
- {
- if ( has_strong )
- {
- // Blend in some of strong
- float dist_remaining = 1.0f - emphasis_intensity;
- float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START );
-
- classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START;
- classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac;
- }
- else
- {
- emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START );
- classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
- }
- }
- else if ( emphasis_intensity < WEAK_CROSSFADE_START )
- {
- if ( has_weak )
- {
- // Blend in some weak
- float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity;
- float frac = dist_remaining / ( WEAK_CROSSFADE_START );
-
- classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START;
- classes[ PHONEME_CLASS_WEAK ].amount = frac;
- }
- else
- {
- emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START );
- classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
- }
- }
- else
- {
- // Assume 0.5 (neutral) becomes a scaling of 1.0f
- classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *classes -
-// phoneme -
-// scale -
-// newexpression -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression )
-{
- int type;
-
- // Setup weights for any emphasis blends
- bool skip = SetupEmphasisBlend( classes, phoneme );
- // Uh-oh, missing or unknown phoneme???
- if ( skip )
- {
- return;
- }
-
- // Compute blend weights
- ComputeBlendedSetting( classes, emphasis_intensity );
-
- for ( type = 0; type < NUM_PHONEME_CLASSES; type++ )
- {
- Emphasized_Phoneme *info = &classes[ type ];
- if ( !info->valid || info->amount == 0.0f )
- continue;
-
- const flexsettinghdr_t *actual_flexsetting_header = info->base;
- const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme );
- if (!pSetting)
- {
- continue;
- }
-
- flexweight_t *pWeights = NULL;
-
- int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights );
- if ( pWeights )
- {
- for ( int i = 0; i < truecount; i++)
- {
- // Translate to global controller number
- int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key );
- // Add scaled weighting in
- g_flexweight[j] += info->amount * scale * pWeights->weight;
- // Go to next setting
- pWeights++;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: A lot of the one time setup and also resets amount to 0.0f default
-// for strong/weak/normal tracks
-// Returning true == skip this phoneme
-// Input : *classes -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme )
-{
- int i;
-
- bool skip = false;
-
- for ( i = 0; i < NUM_PHONEME_CLASSES; i++ )
- {
- Emphasized_Phoneme *info = &classes[ i ];
-
- // Assume it's bogus
- info->valid = false;
- info->amount = 0.0f;
-
- // One time setup
- if ( !info->basechecked )
- {
- info->basechecked = true;
- info->base = (flexsettinghdr_t *)FindSceneFile( info->classname );
- }
- info->exp = NULL;
- if ( info->base )
- {
- Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') );
- info->exp = info->base->pIndexedSetting( phoneme );
- }
-
- if ( info->required && ( !info->base || !info->exp ) )
- {
- skip = true;
- break;
- }
-
- if ( info->exp )
- {
- info->valid = true;
- }
- }
-
- return skip;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *classes -
-// *sentence -
-// t -
-// dt -
-// juststarted -
-//-----------------------------------------------------------------------------
-ConVar g_CV_PhonemeSnap("phonemesnap", "2", 0, "Lod at level at which visemes stops always considering two phonemes, regardless of duration." );
-void C_BaseFlex::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted )
-{
- CStudioHdr *hdr = GetModelPtr();
- if ( !hdr )
- {
- return;
- }
-
- int pcount = sentence->GetRuntimePhonemeCount();
- for ( int k = 0; k < pcount; k++ )
- {
- const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k );
-
- if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime())
- {
- bool bCrossfade = true;
- if ((hdr->flags() & STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE) == 0)
- {
- if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD0)
- {
- bCrossfade = (g_CV_PhonemeSnap.GetInt() > 0);
- }
- else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD1)
- {
- bCrossfade = (g_CV_PhonemeSnap.GetInt() > 1);
- }
- else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD2)
- {
- bCrossfade = (g_CV_PhonemeSnap.GetInt() > 2);
- }
- else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD3)
- {
- bCrossfade = (g_CV_PhonemeSnap.GetInt() > 3);
- }
- else
- {
- bCrossfade = false;
- }
- }
-
- if (bCrossfade)
- {
- if (k < pcount-1)
- {
- const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 );
- // if I have a neighbor
- if ( next )
- {
- // and they're touching
- if (next->GetStartTime() == phoneme->GetEndTime() )
- {
- // no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme
- dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
- }
- else
- {
- // dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme
- dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
- }
- }
- else
- {
- // last phoneme in list, increase the blend length to the length of the current phoneme
- dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() );
- }
- }
- }
- }
-
- float t1 = ( phoneme->GetStartTime() - t) / dt;
- float t2 = ( phoneme->GetEndTime() - t) / dt;
-
- if (t1 < 1.0 && t2 > 0)
- {
- float scale;
-
- // clamp
- if (t2 > 1)
- t2 = 1;
- if (t1 < 0)
- t1 = 0;
-
- // FIXME: simple box filter. Should use something fancier
- scale = (t2 - t1);
-
- AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *classes -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::ProcessVisemes( Emphasized_Phoneme *classes )
-{
- // Any sounds being played?
- if ( !MouthInfo().IsActive() )
- return;
-
- // Multiple phoneme tracks can overlap, look across all such tracks.
- for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ )
- {
- CVoiceData *vd = MouthInfo().GetVoiceSource( source );
- if ( !vd || vd->ShouldIgnorePhonemes() )
- continue;
-
- CSentence *sentence = engine->GetSentence( vd->GetSource() );
- if ( !sentence )
- continue;
-
- float sentence_length = engine->GetSentenceLength( vd->GetSource() );
- float timesincestart = vd->GetElapsedTime();
-
- // This sound should be done...why hasn't it been removed yet???
- if ( timesincestart >= ( sentence_length + 2.0f ) )
- continue;
-
- // Adjust actual time
- float t = timesincestart - g_CV_PhonemeDelay.GetFloat();
-
- // Get box filter duration
- float dt = g_CV_PhonemeFilter.GetFloat();
-
- // Streaming sounds get an additional delay...
- /*
- // Tracker 20534: Probably not needed any more with the async sound stuff that
- // we now have (we don't have a disk i/o hitch on startup which might have been
- // messing up the startup timing a bit )
- bool streaming = engine->IsStreaming( vd->m_pAudioSource );
- if ( streaming )
- {
- t -= g_CV_PhonemeDelayStreaming.GetFloat();
- }
- */
-
- // Assume sound has been playing for a while...
- bool juststarted = false;
-
- // Get intensity setting for this time (from spline)
- float emphasis_intensity = sentence->GetIntensity( t, sentence_length );
-
- // Blend and add visemes together
- AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: fill keyvalues message with flex state
-// Input :
-//-----------------------------------------------------------------------------
-void C_BaseFlex::GetToolRecordingState( KeyValues *msg )
-{
- if ( !ToolsEnabled() )
- return;
-
- VPROF_BUDGET( "C_BaseFlex::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS );
-
- BaseClass::GetToolRecordingState( msg );
-
- CStudioHdr *hdr = GetModelPtr();
- if ( !hdr )
- return;
-
- memset( g_flexweight, 0, sizeof( g_flexweight ) );
-
- if ( hdr->numflexcontrollers() == 0 )
- return;
-
- LocalFlexController_t i;
-
- ProcessSceneEvents( true );
-
- // FIXME: shouldn't this happen at runtime?
- // initialize the models local to global flex controller mappings
- if (hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1)
- {
- for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
- {
- int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() );
- hdr->pFlexcontroller( i )->localToGlobal = j;
- }
- }
-
- // blend weights from server
- for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
- {
- mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );
-
- g_flexweight[pflex->localToGlobal] = m_flexWeight[i];
- // rescale
- g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
- }
-
- ProcessSceneEvents( false );
-
- // check for blinking
- if (m_blinktoggle != m_prevblinktoggle)
- {
- m_prevblinktoggle = m_blinktoggle;
- m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat();
- }
-
- if (m_iBlink == -1)
- m_iBlink = AddGlobalFlexController( "blink" );
- g_flexweight[m_iBlink] = 0;
-
- // FIXME: this needs a better algorithm
- // blink the eyes
- float t = (m_blinktime - gpGlobals->curtime) * M_PI * 0.5 * (1.0/g_CV_BlinkDuration.GetFloat());
- if (t > 0)
- {
- // do eyeblink falloff curve
- t = cos(t);
- if (t > 0)
- {
- g_flexweight[m_iBlink] = sqrtf( t ) * 2;
- if (g_flexweight[m_iBlink] > 1)
- g_flexweight[m_iBlink] = 2.0 - g_flexweight[m_iBlink];
- }
- }
-
- // Drive the mouth from .wav file playback...
- ProcessVisemes( m_PhonemeClasses );
-
- // Necessary???
- SetViewTarget( hdr );
-
- Vector viewtarget = m_viewtarget; // Use the unfiltered value
-
- // HACK HACK: Unmap eyes right/left amounts
- if (m_iEyeUpdown != -1 && m_iEyeRightleft != -1)
- {
- mstudioflexcontroller_t *flexupdown = hdr->pFlexcontroller( m_iEyeUpdown );
- mstudioflexcontroller_t *flexrightleft = hdr->pFlexcontroller( m_iEyeRightleft );
-
- if ( flexupdown->localToGlobal != -1 && flexrightleft->localToGlobal != -1 )
- {
- float updown = g_flexweight[ flexupdown->localToGlobal ];
- float rightleft = g_flexweight[ flexrightleft->localToGlobal ];
-
- if ( flexupdown->min != flexupdown->max )
- {
- updown = RemapVal( updown, flexupdown->min, flexupdown->max, 0.0f, 1.0f );
- }
- if ( flexrightleft->min != flexrightleft->max )
- {
- rightleft = RemapVal( rightleft, flexrightleft->min, flexrightleft->max, 0.0f, 1.0f );
- }
-
- g_flexweight[ flexupdown->localToGlobal ] = updown;
- g_flexweight[ flexrightleft->localToGlobal ] = rightleft;
- }
- }
-
- // Convert back to normalized weights
- for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
- {
- mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );
-
- // rescale
- if ( pflex->max != pflex->min )
- {
- g_flexweight[pflex->localToGlobal] = ( g_flexweight[pflex->localToGlobal] - pflex->min ) / ( pflex->max - pflex->min );
- }
- }
-
- static BaseFlexRecordingState_t state;
- state.m_nFlexCount = MAXSTUDIOFLEXCTRL;
- state.m_pDestWeight = g_flexweight;
- state.m_vecViewTarget = viewtarget;
- msg->SetPtr( "baseflex", &state );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void C_BaseFlex::OnThreadedDrawSetup()
-{
- if (m_iEyeAttachment < 0)
- return;
-
- CStudioHdr *hdr = GetModelPtr();
- if ( !hdr )
- {
- return;
- }
- CalcAttachments();
-}
-
-
-//-----------------------------------------------------------------------------
-// Should we use delayed flex weights?
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::UsesFlexDelayedWeights()
-{
- return ( m_flFlexDelayedWeight && g_CV_FlexSmooth.GetBool() );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void C_BaseFlex::LinkToGlobalFlexControllers( CStudioHdr *hdr )
-{
- if ( hdr && hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1 )
- {
- for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
- {
- int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() );
- hdr->pFlexcontroller( i )->localToGlobal = j;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Rendering callback to allow the client to set up all the model specific flex weights
-//-----------------------------------------------------------------------------
-void C_BaseFlex::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
-{
- // hack in an initialization
- LinkToGlobalFlexControllers( GetModelPtr() );
- m_iBlink = AddGlobalFlexController( "UH" );
-
- if ( SetupGlobalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ) )
- {
- SetupLocalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: process the entities networked state, vcd playback, wav file visemes, and blinks into a global shared flex controller array
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::SetupGlobalWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
-{
- CStudioHdr *hdr = GetModelPtr();
- if ( !hdr )
- return false;
-
- memset( g_flexweight, 0, sizeof(g_flexweight) );
-
- // FIXME: this should assert then, it's too complex a class for the model
- if ( hdr->numflexcontrollers() == 0 )
- {
- int nSizeInBytes = nFlexWeightCount * sizeof( float );
- memset( pFlexWeights, 0, nSizeInBytes );
- if ( pFlexDelayedWeights )
- {
- memset( pFlexDelayedWeights, 0, nSizeInBytes );
- }
- return false;
- }
-
- LocalFlexController_t i;
-
- ProcessSceneEvents( true );
-
- Assert( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 );
-
- // get the networked flexweights and convert them from 0..1 to real dynamic range
- for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++)
- {
- mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );
-
- g_flexweight[pflex->localToGlobal] = m_flexWeight[i];
- // rescale
- g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
- }
-
- ProcessSceneEvents( false );
-
- // check for blinking
- if (m_blinktoggle != m_prevblinktoggle)
- {
- m_prevblinktoggle = m_blinktoggle;
- m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat();
- }
-
- if (m_iBlink == -1)
- {
- m_iBlink = AddGlobalFlexController( "blink" );
- }
-
- // FIXME: this needs a better algorithm
- // blink the eyes
- float flBlinkDuration = g_CV_BlinkDuration.GetFloat();
- float flOOBlinkDuration = ( flBlinkDuration > 0 ) ? 1.0f / flBlinkDuration : 0.0f;
- float t = ( m_blinktime - gpGlobals->curtime ) * M_PI * 0.5 * flOOBlinkDuration;
- if (t > 0)
- {
- // do eyeblink falloff curve
- t = cos(t);
- if (t > 0.0f && t < 1.0f)
- {
- t = sqrtf( t ) * 2.0f;
- if (t > 1.0f)
- t = 2.0f - t;
- t = clamp( t, 0.0f, 1.0f );
- // add it to whatever the blink track is doing
- g_flexweight[m_iBlink] = clamp( g_flexweight[m_iBlink] + t, 0.0f, 1.0f );
- }
- }
-
- // Drive the mouth from .wav file playback...
- ProcessVisemes( m_PhonemeClasses );
-
- return true;
-}
-
-void C_BaseFlex::RunFlexDelay( int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights, float &flFlexDelayTime )
-{
- // process the delayed version of the flexweights
- if ( flFlexDelayTime > 0.0f && flFlexDelayTime < gpGlobals->curtime )
- {
- float d = clamp( gpGlobals->curtime - flFlexDelayTime, 0.0, gpGlobals->frametime );
- d = ExponentialDecay( 0.8, 0.033, d );
-
- for ( int i = 0; i < nFlexWeightCount; i++)
- {
- pFlexDelayedWeights[i] = pFlexDelayedWeights[i] * d + pFlexWeights[i] * (1.0f - d);
- }
- }
- flFlexDelayTime = gpGlobals->curtime;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: convert the global flex controllers into model specific flex weights
-//-----------------------------------------------------------------------------
-void C_BaseFlex::SetupLocalWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
-{
- CStudioHdr *hdr = GetModelPtr();
- if ( !hdr )
- return;
-
- // BUGBUG: We have a bug with SetCustomModel that causes a disagreement between the studio header here and the one used in l_studio.cpp CModelRender::DrawModelExecute
- // So when we hit that case, let's not do any work because otherwise we'd crash since the array sizes (m_flFlexDelayedWeight vs pFlexWeights) don't match.
- // Note that this check is duplicated in CEconEntity::SetupWeights.
- AssertMsg( nFlexWeightCount == m_cFlexDelayedWeight, "Disagreement between the number of flex weights. Do the studio headers match?" );
- if ( nFlexWeightCount != m_cFlexDelayedWeight )
- {
- return;
- }
-
- // convert the flex controllers into actual flex values
- RunFlexRules( hdr, pFlexWeights );
-
- // aim the eyes
- SetViewTarget( hdr );
-
- AssertOnce( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 );
-
- if ( pFlexDelayedWeights )
- {
- RunFlexDelay( nFlexWeightCount, pFlexWeights, m_flFlexDelayedWeight, m_flFlexDelayTime );
- memcpy( pFlexDelayedWeights, m_flFlexDelayedWeight, sizeof( float ) * nFlexWeightCount );
- }
-
- /*
- LocalFlexController_t i;
-
- for (i = 0; i < hdr->numflexdesc; i++)
- {
- debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), i-hdr->numflexcontrollers, 0, "%2d:%s : %3.2f", i, hdr->pFlexdesc( i )->pszFACS(), pFlexWeights[i] );
- }
- */
-
- /*
- for (i = 0; i < g_numflexcontrollers; i++)
- {
- int j = hdr->pFlexcontroller( i )->link;
- debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), -i, 0, "%s %3.2f", g_flexcontroller[i], g_flexweight[j] );
- }
- */
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Unified set of flex controller entries that all systems can talk to
-//-----------------------------------------------------------------------------
-
-int C_BaseFlex::g_numflexcontrollers;
-char * C_BaseFlex::g_flexcontroller[MAXSTUDIOFLEXCTRL*4];
-float C_BaseFlex::g_flexweight[MAXSTUDIOFLEXDESC];
-
-int C_BaseFlex::AddGlobalFlexController( const char *szName )
-{
- int i;
- for (i = 0; i < g_numflexcontrollers; i++)
- {
- if (Q_stricmp( g_flexcontroller[i], szName ) == 0)
- {
- return i;
- }
- }
-
- if ( g_numflexcontrollers < MAXSTUDIOFLEXCTRL * 4 )
- {
- g_flexcontroller[g_numflexcontrollers++] = strdup( szName );
- }
- else
- {
- // FIXME: missing runtime error condition
- }
- return i;
-}
-
-char const *C_BaseFlex::GetGlobalFlexControllerName( int idx )
-{
- if ( idx < 0 || idx >= g_numflexcontrollers )
- {
- return "";
- }
-
- return g_flexcontroller[ idx ];
-}
-
-const flexsetting_t *C_BaseFlex::FindNamedSetting( const flexsettinghdr_t *pSettinghdr, const char *expr )
-{
- int i;
- const flexsetting_t *pSetting = NULL;
-
- for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
- {
- pSetting = pSettinghdr->pSetting( i );
- if ( !pSetting )
- continue;
-
- const char *name = pSetting->pszName();
-
- if ( !stricmp( name, expr ) )
- break;
- }
-
- if ( i>=pSettinghdr->numflexsettings )
- {
- return NULL;
- }
-
- return pSetting;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void C_BaseFlex::StartChoreoScene( CChoreoScene *scene )
-{
- if ( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() )
- {
- return;
- }
-
- m_ActiveChoreoScenes.AddToTail( scene );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void C_BaseFlex::RemoveChoreoScene( CChoreoScene *scene )
-{
- // Assert( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() );
-
- m_ActiveChoreoScenes.FindAndRemove( scene );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Remove all active SceneEvents
-//-----------------------------------------------------------------------------
-void C_BaseFlex::ClearSceneEvents( CChoreoScene *scene, bool canceled )
-{
- if ( !scene )
- {
- m_SceneEvents.RemoveAll();
- return;
- }
-
- for ( int i = m_SceneEvents.Count() - 1; i >= 0; i-- )
- {
- CSceneEventInfo *info = &m_SceneEvents[ i ];
-
- Assert( info );
- Assert( info->m_pScene );
- Assert( info->m_pEvent );
-
- if ( info->m_pScene != scene )
- continue;
-
- if ( !ClearSceneEvent( info, false, canceled ))
- {
- // unknown expression to clear!!
- Assert( 0 );
- }
-
- // Free this slot
- info->m_pEvent = NULL;
- info->m_pScene = NULL;
- info->m_bStarted = false;
-
- m_SceneEvents.Remove( i );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Stop specifics of expression
-//-----------------------------------------------------------------------------
-
-bool C_BaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled )
-{
- Assert( info );
- Assert( info->m_pScene );
- Assert( info->m_pEvent );
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Add string indexed scene/expression/duration to list of active SceneEvents
-// Input : scenefile -
-// expression -
-// duration -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, bool bClientSide )
-{
- if ( !scene || !event )
- {
- Msg( "C_BaseFlex::AddSceneEvent: scene or event was NULL!!!\n" );
- return;
- }
-
- CChoreoActor *actor = event->GetActor();
- if ( !actor )
- {
- Msg( "C_BaseFlex::AddSceneEvent: event->GetActor() was NULL!!!\n" );
- return;
- }
-
-
- CSceneEventInfo info;
-
- memset( (void *)&info, 0, sizeof( info ) );
-
- info.m_pEvent = event;
- info.m_pScene = scene;
- info.m_hTarget = pTarget;
- info.m_bStarted = false;
- info.m_bClientSide = bClientSide;
-
- if (StartSceneEvent( &info, scene, event, actor, pTarget ))
- {
- m_SceneEvents.AddToTail( info );
- }
- else
- {
- scene->SceneMsg( "C_BaseFlex::AddSceneEvent: event failed\n" );
- // Assert( 0 ); // expression failed to start
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
-{
- switch ( event->GetType() )
- {
- default:
- break;
-
- case CChoreoEvent::FLEXANIMATION:
- info->InitWeight( this );
- return true;
-
- case CChoreoEvent::EXPRESSION:
- return true;
-
- case CChoreoEvent::SEQUENCE:
- if ( info->m_bClientSide )
- {
- return RequestStartSequenceSceneEvent( info, scene, event, actor, pTarget );
- }
- break;
-
- case CChoreoEvent::SPEAK:
- if ( info->m_bClientSide )
- {
- return true;
- }
- break;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::RequestStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
-{
- info->m_nSequence = LookupSequence( event->GetParameters() );
-
- // make sure sequence exists
- if ( info->m_nSequence < 0 )
- return false;
-
- info->m_pActor = actor;
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Remove expression
-// Input : scenefile -
-// expression -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill )
-{
- Assert( event );
-
- for ( int i = 0 ; i < m_SceneEvents.Count(); i++ )
- {
- CSceneEventInfo *info = &m_SceneEvents[ i ];
-
- Assert( info );
- Assert( info->m_pEvent );
-
- if ( info->m_pScene != scene )
- continue;
-
- if ( info->m_pEvent != event)
- continue;
-
- if (ClearSceneEvent( info, fastKill, false ))
- {
- // Free this slot
- info->m_pEvent = NULL;
- info->m_pScene = NULL;
- info->m_bStarted = false;
-
- m_SceneEvents.Remove( i );
- return;
- }
- }
-
- // many events refuse to start due to bogus parameters
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Checks to see if the event should be considered "completed"
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::CheckSceneEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
-{
- for ( int i = 0 ; i < m_SceneEvents.Count(); i++ )
- {
- CSceneEventInfo *info = &m_SceneEvents[ i ];
-
- Assert( info );
- Assert( info->m_pEvent );
-
- if ( info->m_pScene != scene )
- continue;
-
- if ( info->m_pEvent != event)
- continue;
-
- return CheckSceneEventCompletion( info, currenttime, scene, event );
- }
- return true;
-}
-
-
-
-bool C_BaseFlex::CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event )
-{
- return true;
-}
-
-void C_BaseFlex::SetFlexWeight( LocalFlexController_t index, float value )
-{
- if (index >= 0 && index < GetNumFlexControllers())
- {
- CStudioHdr *pstudiohdr = GetModelPtr( );
- if (! pstudiohdr)
- return;
-
- mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index );
-
- if (pflexcontroller->max != pflexcontroller->min)
- {
- value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min);
- value = clamp( value, 0.0f, 1.0f );
- }
-
- m_flexWeight[ index ] = value;
- }
-}
-
-float C_BaseFlex::GetFlexWeight( LocalFlexController_t index )
-{
- if (index >= 0 && index < GetNumFlexControllers())
- {
- CStudioHdr *pstudiohdr = GetModelPtr( );
- if (! pstudiohdr)
- return 0;
-
- mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index );
-
- if (pflexcontroller->max != pflexcontroller->min)
- {
- return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min;
- }
-
- return m_flexWeight[index];
- }
- return 0.0;
-}
-
-LocalFlexController_t C_BaseFlex::FindFlexController( const char *szName )
-{
- for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
- {
- if (stricmp( GetFlexControllerName( i ), szName ) == 0)
- {
- return i;
- }
- }
-
- // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) );
- return LocalFlexController_t(-1);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Default implementation
-//-----------------------------------------------------------------------------
-void C_BaseFlex::ProcessSceneEvents( bool bFlexEvents )
-{
- CStudioHdr *hdr = GetModelPtr();
- if ( !hdr )
- {
- return;
- }
-
- // slowly decay to netural expression
-
- if ( bFlexEvents )
- {
- for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
- {
- SetFlexWeight( i, GetFlexWeight( i ) * 0.95 );
- }
- }
-
- // Iterate SceneEvents and look for active slots
- for ( int i = 0; i < m_SceneEvents.Count(); i++ )
- {
- CSceneEventInfo *info = &m_SceneEvents[ i ];
- Assert( info );
-
- // FIXME: Need a safe handle to m_pEvent in case of memory deletion?
- CChoreoEvent *event = info->m_pEvent;
- Assert( event );
-
- CChoreoScene *scene = info->m_pScene;
- Assert( scene );
-
- if ( ProcessSceneEvent( bFlexEvents, info, scene, event ) )
- {
- info->m_bStarted = true;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Various methods to process facial SceneEvents:
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::ProcessFlexAnimationSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
-{
- Assert( event->HasEndTime() );
- if ( event->HasEndTime() )
- {
- AddFlexAnimation( info );
- }
- return true;
-}
-
-bool C_BaseFlex::ProcessFlexSettingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
-{
- // Flexanimations have to have an end time!!!
- if ( !event->HasEndTime() )
- return true;
-
- VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" );
-
- // Look up the actual strings
- const char *scenefile = event->GetParameters();
- const char *name = event->GetParameters2();
-
- // Have to find both strings
- if ( scenefile && name )
- {
- // Find the scene file
- const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
- if ( pExpHdr )
- {
- float scenetime = scene->GetTime();
-
- float scale = event->GetIntensity( scenetime );
-
- // Add the named expression
- AddFlexSetting( name, scale, pExpHdr, !info->m_bStarted );
- }
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Each CBaseFlex maintains a UtlRBTree of mappings, one for each loaded flex scene file it uses. This is used to
-// sort the entries in the RBTree
-// Input : lhs -
-// rhs -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool FlexSettingLessFunc( const FS_LocalToGlobal_t& lhs, const FS_LocalToGlobal_t& rhs )
-{
- return lhs.m_Key < rhs.m_Key;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but
-// we just do this in memory with an array of integers (could be shorts, I suppose)
-// Input : *pSettinghdr -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::EnsureTranslations( const flexsettinghdr_t *pSettinghdr )
-{
- Assert( pSettinghdr );
-
- FS_LocalToGlobal_t entry( pSettinghdr );
-
- unsigned short idx = m_LocalToGlobal.Find( entry );
- if ( idx != m_LocalToGlobal.InvalidIndex() )
- return;
-
- entry.SetCount( pSettinghdr->numkeys );
-
- for ( int i = 0; i < pSettinghdr->numkeys; ++i )
- {
- entry.m_Mapping[ i ] = AddGlobalFlexController( pSettinghdr->pLocalName( i ) );
- }
-
- m_LocalToGlobal.Insert( entry );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Look up instance specific mapping
-// Input : *pSettinghdr -
-// key -
-// Output : int
-//-----------------------------------------------------------------------------
-int C_BaseFlex::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key )
-{
- FS_LocalToGlobal_t entry( pSettinghdr );
-
- int idx = m_LocalToGlobal.Find( entry );
- if ( idx == m_LocalToGlobal.InvalidIndex() )
- {
- // This should never happen!!!
- Assert( 0 );
- Warning( "Unable to find mapping for flexcontroller %i, settings %p on %i/%s\n", key, pSettinghdr, entindex(), GetClassname() );
- EnsureTranslations( pSettinghdr );
- idx = m_LocalToGlobal.Find( entry );
- if ( idx == m_LocalToGlobal.InvalidIndex() )
- {
- Error( "CBaseFlex::FlexControllerLocalToGlobal failed!\n" );
- }
- }
-
- FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ];
- // Validate lookup
- Assert( result.m_nCount != 0 && key < result.m_nCount );
- int index = result.m_Mapping[ key ];
- return index;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *expr -
-// scale -
-// *pSettinghdr -
-// newexpression -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::AddFlexSetting( const char *expr, float scale,
- const flexsettinghdr_t *pSettinghdr, bool newexpression )
-{
- int i;
- const flexsetting_t *pSetting = NULL;
-
- // Find the named setting in the base
- for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
- {
- pSetting = pSettinghdr->pSetting( i );
- if ( !pSetting )
- continue;
-
- const char *name = pSetting->pszName();
-
- if ( !V_stricmp( name, expr ) )
- break;
- }
-
- if ( i>=pSettinghdr->numflexsettings )
- {
- return;
- }
-
- flexweight_t *pWeights = NULL;
- int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights );
- if ( !pWeights )
- return;
-
- for (i = 0; i < truecount; i++, pWeights++)
- {
- // Translate to local flex controller
- // this is translating from the settings's local index to the models local index
- int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key );
-
- // blend scaled weighting in to total (post networking g_flexweight!!!!)
- float s = clamp( scale * pWeights->influence, 0.0f, 1.0f );
- g_flexweight[index] = g_flexweight[index] * (1.0f - s) + pWeights->weight * s;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::ProcessSceneEvent( bool bFlexEvents, CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
-{
- switch ( event->GetType() )
- {
- default:
- break;
- case CChoreoEvent::FLEXANIMATION:
- if ( bFlexEvents )
- {
- return ProcessFlexAnimationSceneEvent( info, scene, event );
- }
- return true;
-
- case CChoreoEvent::EXPRESSION:
- if ( !bFlexEvents )
- {
- return ProcessFlexSettingSceneEvent( info, scene, event );
- }
- return true;
-
- case CChoreoEvent::SEQUENCE:
- if ( info->m_bClientSide )
- {
- if ( !bFlexEvents )
- {
- return ProcessSequenceSceneEvent( info, scene, event );
- }
- return true;
- }
- break;
-
- case CChoreoEvent::SPEAK:
- if ( info->m_bClientSide )
- {
- return true;
- }
- break;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *actor -
-// *parameters -
-//-----------------------------------------------------------------------------
-bool C_BaseFlex::ProcessSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event )
-{
- if ( !info || !event || !scene )
- return false;
-
- SetSequence( info->m_nSequence );
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *event -
-//-----------------------------------------------------------------------------
-void C_BaseFlex::AddFlexAnimation( CSceneEventInfo *info )
-{
- if ( !info )
- return;
-
- CChoreoEvent *event = info->m_pEvent;
- if ( !event )
- return;
-
- CChoreoScene *scene = info->m_pScene;
- if ( !scene )
- return;
-
- if ( !event->GetTrackLookupSet() )
- {
- // Create lookup data
- for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
- {
- CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
- if ( !track )
- continue;
-
- if ( track->IsComboType() )
- {
- char name[ 512 ];
- Q_strncpy( name, "right_" ,sizeof(name));
- Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
-
- track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 );
-
- Q_strncpy( name, "left_" ,sizeof(name));
- Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
-
- track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 );
- }
- else
- {
- track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 );
- }
- }
-
- event->SetTrackLookupSet( true );
- }
-
- if ( !scene_clientflex.GetBool() )
- return;
-
- float scenetime = scene->GetTime();
-
- float weight = event->GetIntensity( scenetime );
-
- // decay if this is a background scene and there's other flex animations playing
- weight = weight * info->UpdateWeight( this );
-
- // Compute intensity for each track in animation and apply
- // Iterate animation tracks
- for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
- {
- CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
- if ( !track )
- continue;
-
- // Disabled
- if ( !track->IsTrackActive() )
- continue;
-
- // Map track flex controller to global name
- if ( track->IsComboType() )
- {
- for ( int side = 0; side < 2; side++ )
- {
- LocalFlexController_t controller = track->GetRawFlexControllerIndex( side );
-
- // Get spline intensity for controller
- float flIntensity = track->GetIntensity( scenetime, side );
- if ( controller >= LocalFlexController_t(0) )
- {
- float orig = GetFlexWeight( controller );
- float value = orig * (1 - weight) + flIntensity * weight;
- SetFlexWeight( controller, value );
- }
- }
- }
- else
- {
- LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 );
-
- // Get spline intensity for controller
- float flIntensity = track->GetIntensity( scenetime, 0 );
- if ( controller >= LocalFlexController_t(0) )
- {
- float orig = GetFlexWeight( controller );
- float value = orig * (1 - weight) + flIntensity * weight;
- SetFlexWeight( controller, value );
- }
- }
- }
-
- info->m_bStarted = true;
-}
-
-void CSceneEventInfo::InitWeight( C_BaseFlex *pActor )
-{
- m_flWeight = 1.0;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: update weight for background events. Only call once per think
-//-----------------------------------------------------------------------------
-
-float CSceneEventInfo::UpdateWeight( C_BaseFlex *pActor )
-{
- m_flWeight = MIN( m_flWeight + 0.1, 1.0 );
- return m_flWeight;
-}
-
-BEGIN_BYTESWAP_DATADESC( flexsettinghdr_t )
- DEFINE_FIELD( id, FIELD_INTEGER ),
- DEFINE_FIELD( version, FIELD_INTEGER ),
- DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ),
- DEFINE_FIELD( length, FIELD_INTEGER ),
- DEFINE_FIELD( numflexsettings, FIELD_INTEGER ),
- DEFINE_FIELD( flexsettingindex, FIELD_INTEGER ),
- DEFINE_FIELD( nameindex, FIELD_INTEGER ),
- DEFINE_FIELD( numindexes, FIELD_INTEGER ),
- DEFINE_FIELD( indexindex, FIELD_INTEGER ),
- DEFINE_FIELD( numkeys, FIELD_INTEGER ),
- DEFINE_FIELD( keynameindex, FIELD_INTEGER ),
- DEFINE_FIELD( keymappingindex, FIELD_INTEGER ),
-END_BYTESWAP_DATADESC()
-
-BEGIN_BYTESWAP_DATADESC( flexsetting_t )
- DEFINE_FIELD( nameindex, FIELD_INTEGER ),
- DEFINE_FIELD( obsolete1, FIELD_INTEGER ),
- DEFINE_FIELD( numsettings, FIELD_INTEGER ),
- DEFINE_FIELD( index, FIELD_INTEGER ),
- DEFINE_FIELD( obsolete2, FIELD_INTEGER ),
- DEFINE_FIELD( settingindex, FIELD_INTEGER ),
-END_BYTESWAP_DATADESC()
-
-BEGIN_BYTESWAP_DATADESC( flexweight_t )
- DEFINE_FIELD( key, FIELD_INTEGER ),
- DEFINE_FIELD( weight, FIELD_FLOAT ),
- DEFINE_FIELD( influence, FIELD_FLOAT ),
-END_BYTESWAP_DATADESC()
-
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "filesystem.h" +#include "sentence.h" +#include "hud_closecaption.h" +#include "engine/ivmodelinfo.h" +#include "engine/ivdebugoverlay.h" +#include "bone_setup.h" +#include "soundinfo.h" +#include "tools/bonelist.h" +#include "KeyValues.h" +#include "tier0/vprof.h" +#include "toolframework/itoolframework.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "toolframework_client.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool UseHWMorphVCDs(); + +ConVar g_CV_PhonemeDelay("phonemedelay", "0", 0, "Phoneme delay to account for sound system latency." ); +ConVar g_CV_PhonemeFilter("phonemefilter", "0.08", 0, "Time duration of box filter to pass over phonemes." ); +ConVar g_CV_FlexRules("flex_rules", "1", 0, "Allow flex animation rules to run." ); +ConVar g_CV_BlinkDuration("blink_duration", "0.2", 0, "How many seconds an eye blink will last." ); +ConVar g_CV_FlexSmooth("flex_smooth", "1", 0, "Applies smoothing/decay curve to flex animation controller changes." ); + +#if defined( CBaseFlex ) +#undef CBaseFlex +#endif + +IMPLEMENT_CLIENTCLASS_DT(C_BaseFlex, DT_BaseFlex, CBaseFlex) + RecvPropArray3( RECVINFO_ARRAY(m_flexWeight), RecvPropFloat(RECVINFO(m_flexWeight[0]))), + RecvPropInt(RECVINFO(m_blinktoggle)), + RecvPropVector(RECVINFO(m_viewtarget)), + +#ifdef HL2_CLIENT_DLL + RecvPropFloat( RECVINFO(m_vecViewOffset[0]) ), + RecvPropFloat( RECVINFO(m_vecViewOffset[1]) ), + RecvPropFloat( RECVINFO(m_vecViewOffset[2]) ), + + RecvPropVector(RECVINFO(m_vecLean)), + RecvPropVector(RECVINFO(m_vecShift)), +#endif + +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_BaseFlex ) + +/* + // DEFINE_FIELD( C_BaseFlex, m_viewtarget, FIELD_VECTOR ), + // DEFINE_ARRAY( C_BaseFlex, m_flexWeight, FIELD_FLOAT, 64 ), + // DEFINE_FIELD( C_BaseFlex, m_blinktoggle, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_blinktime, FIELD_FLOAT ), + // DEFINE_FIELD( C_BaseFlex, m_prevviewtarget, FIELD_VECTOR ), + // DEFINE_ARRAY( C_BaseFlex, m_prevflexWeight, FIELD_FLOAT, 64 ), + // DEFINE_FIELD( C_BaseFlex, m_prevblinktoggle, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_iBlink, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_iEyeUpdown, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_iEyeRightleft, FIELD_INTEGER ), + // DEFINE_FIELD( C_BaseFlex, m_FileList, CUtlVector < CFlexSceneFile * > ), +*/ + +END_PREDICTION_DATA() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool GetHWMExpressionFileName( const char *pFilename, char *pHWMFilename ) +{ + // Are we even using hardware morph? + if ( !UseHWMorphVCDs() ) + return false; + + // Do we have a valid filename? + if ( !( pFilename && pFilename[0] ) ) + return false; + + // Check to see if we already have an player/hwm/* filename. + if ( ( V_strstr( pFilename, "player/hwm" ) != NULL ) || ( V_strstr( pFilename, "player\\hwm" ) != NULL ) ) + { + V_strcpy( pHWMFilename, pFilename ); + return true; + } + + // Find the hardware morph scene name and pass that along as well. + char szExpression[MAX_PATH]; + V_strcpy( szExpression, pFilename ); + + char szExpressionHWM[MAX_PATH]; + szExpressionHWM[0] = '\0'; + + char *pszToken = strtok( szExpression, "/\\" ); + while ( pszToken != NULL ) + { + V_strcat( szExpressionHWM, pszToken, sizeof( szExpressionHWM ) ); + if ( !V_stricmp( pszToken, "player" ) ) + { + V_strcat( szExpressionHWM, "\\hwm", sizeof( szExpressionHWM ) ); + } + + pszToken = strtok( NULL, "/\\" ); + if ( pszToken != NULL ) + { + V_strcat( szExpressionHWM, "\\", sizeof( szExpressionHWM ) ); + } + } + + V_strcpy( pHWMFilename, szExpressionHWM ); + return true; +} + +C_BaseFlex::C_BaseFlex() : + m_iv_viewtarget( "C_BaseFlex::m_iv_viewtarget" ), + m_iv_flexWeight("C_BaseFlex:m_iv_flexWeight" ), +#ifdef HL2_CLIENT_DLL + m_iv_vecLean("C_BaseFlex:m_iv_vecLean" ), + m_iv_vecShift("C_BaseFlex:m_iv_vecShift" ), +#endif + m_LocalToGlobal( 0, 0, FlexSettingLessFunc ) +{ +#ifdef _DEBUG + ((Vector&)m_viewtarget).Init(); +#endif + + AddVar( &m_viewtarget, &m_iv_viewtarget, LATCH_ANIMATION_VAR | INTERPOLATE_LINEAR_ONLY ); + AddVar( m_flexWeight, &m_iv_flexWeight, LATCH_ANIMATION_VAR ); + + // Fill in phoneme class lookup + SetupMappings( "phonemes" ); + + m_flFlexDelayedWeight = NULL; + m_cFlexDelayedWeight = 0; + + /// Make sure size is correct + Assert( PHONEME_CLASS_STRONG + 1 == NUM_PHONEME_CLASSES ); + +#ifdef HL2_CLIENT_DLL + // Get general lean vector + AddVar( &m_vecLean, &m_iv_vecLean, LATCH_ANIMATION_VAR ); + AddVar( &m_vecShift, &m_iv_vecShift, LATCH_ANIMATION_VAR ); +#endif +} + +C_BaseFlex::~C_BaseFlex() +{ + delete[] m_flFlexDelayedWeight; + m_SceneEvents.RemoveAll(); + m_LocalToGlobal.RemoveAll(); +} + + +void C_BaseFlex::Spawn() +{ + BaseClass::Spawn(); + + InitPhonemeMappings(); +} + +// TF Player overrides all of these with class specific files +void C_BaseFlex::InitPhonemeMappings() +{ + SetupMappings( "phonemes" ); +} + +void C_BaseFlex::SetupMappings( char const *pchFileRoot ) +{ + // Fill in phoneme class lookup + memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) ); + + Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ]; + Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot ); + normal->required = true; + + Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ]; + Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot ); + Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ]; + Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot ); +} + +//----------------------------------------------------------------------------- +// Purpose: initialize fast lookups when model changes +//----------------------------------------------------------------------------- + +CStudioHdr *C_BaseFlex::OnNewModel() +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + + // init to invalid setting + m_iBlink = -1; + m_iEyeUpdown = LocalFlexController_t(-1); + m_iEyeRightleft = LocalFlexController_t(-1); + m_bSearchedForEyeFlexes = false; + m_iMouthAttachment = 0; + + delete[] m_flFlexDelayedWeight; + m_flFlexDelayedWeight = NULL; + m_cFlexDelayedWeight = 0; + + if (hdr) + { + if (hdr->numflexdesc()) + { + m_cFlexDelayedWeight = hdr->numflexdesc(); + m_flFlexDelayedWeight = new float[ m_cFlexDelayedWeight ]; + memset( m_flFlexDelayedWeight, 0, sizeof( float ) * m_cFlexDelayedWeight ); + } + + m_iv_flexWeight.SetMaxCount( hdr->numflexcontrollers() ); + + m_iMouthAttachment = LookupAttachment( "mouth" ); + + LinkToGlobalFlexControllers( hdr ); + } + + return hdr; +} + + +void C_BaseFlex::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask ); + +#ifdef HL2_CLIENT_DLL + // shift pelvis, rotate body + if (hdr->GetNumIKChains() != 0 && (m_vecShift.x != 0.0 || m_vecShift.y != 0.0)) + { + //CIKContext auto_ik; + //auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask ); + //auto_ik.AddAllLocks( pos, q ); + + matrix3x4_t rootxform; + AngleMatrix( GetRenderAngles(), GetRenderOrigin(), rootxform ); + + Vector localShift; + VectorIRotate( m_vecShift, rootxform, localShift ); + Vector localLean; + VectorIRotate( m_vecLean, rootxform, localLean ); + + Vector p0 = pos[0]; + float length = VectorNormalize( p0 ); + + // shift the root bone, but keep the height off the origin the same + Vector shiftPos = pos[0] + localShift; + VectorNormalize( shiftPos ); + Vector leanPos = pos[0] + localLean; + VectorNormalize( leanPos ); + pos[0] = shiftPos * length; + + // rotate the root bone based on how much it was "leaned" + Vector p1; + CrossProduct( p0, leanPos, p1 ); + float sinAngle = VectorNormalize( p1 ); + float cosAngle = DotProduct( p0, leanPos ); + float angle = atan2( sinAngle, cosAngle ) * 180 / M_PI; + Quaternion q1; + angle = clamp( angle, -45, 45 ); + AxisAngleQuaternion( p1, angle, q1 ); + QuaternionMult( q1, q[0], q[0] ); + QuaternionNormalize( q[0] ); + + // DevMsgRT( " (%.2f) %.2f %.2f %.2f\n", angle, p1.x, p1.y, p1.z ); + // auto_ik.SolveAllLocks( pos, q ); + } +#endif +} + + + +//----------------------------------------------------------------------------- +// Purpose: place "voice" sounds on mouth +//----------------------------------------------------------------------------- + +bool C_BaseFlex::GetSoundSpatialization( SpatializationInfo_t& info ) +{ + bool bret = BaseClass::GetSoundSpatialization( info ); + // Default things it's audible, put it at a better spot? + if ( bret ) + { + if ((info.info.nChannel == CHAN_VOICE || info.info.nChannel == CHAN_VOICE2) && m_iMouthAttachment > 0) + { + Vector origin; + QAngle angles; + + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + + if (GetAttachment( m_iMouthAttachment, origin, angles )) + { + if (info.pOrigin) + { + *info.pOrigin = origin; + } + + if (info.pAngles) + { + *info.pAngles = angles; + } + } + } + } + + return bret; +} + + +//----------------------------------------------------------------------------- +// Purpose: run the interpreted FAC's expressions, converting global flex_controller +// values into FAC weights +//----------------------------------------------------------------------------- +void C_BaseFlex::RunFlexRules( CStudioHdr *hdr, float *dest ) +{ + if ( !g_CV_FlexRules.GetInt() ) + return; + + if ( !hdr ) + return; + +/* + // 0 means run them all + int nFlexRulesToRun = 0; + + const char *pszExpression = flex_expression.GetString(); + if ( pszExpression ) + { + nFlexRulesToRun = atoi(pszExpression); // 0 will be returned if not a numeric string + } +//*/ + + hdr->RunFlexRules( g_flexweight, dest ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFlexSceneFileManager::Init() +{ + // Trakcer 16692: Preload these at startup to avoid hitch first time we try to load them during actual gameplay + FindSceneFile( NULL, "phonemes", true ); + FindSceneFile( NULL, "phonemes_weak", true ); + FindSceneFile(NULL, "phonemes_strong", true ); + +#if defined( HL2_CLIENT_DLL ) + FindSceneFile( NULL, "random", true ); + FindSceneFile( NULL, "randomAlert", true ); +#endif + +#if defined( TF_CLIENT_DLL ) + // HACK TO ALL TF TO HAVE PER CLASS OVERRIDES + char const *pTFClasses[] = + { + "scout", + "sniper", + "soldier", + "demo", + "medic", + "heavy", + "pyro", + "spy", + "engineer", + }; + + char fn[ MAX_PATH ]; + for ( int i = 0; i < ARRAYSIZE( pTFClasses ); ++i ) + { + Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_weak", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/%s/phonemes/phonemes_strong", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + + if ( !IsX360() ) + { + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_weak", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/phonemes/phonemes_strong", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + } + + Q_snprintf( fn, sizeof( fn ), "player/%s/emotion/emotion", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + if ( !IsX360() ) + { + Q_snprintf( fn, sizeof( fn ), "player/hwm/%s/emotion/emotion", pTFClasses[i] ); + FindSceneFile( NULL, fn, true ); + } + } +#endif + + return true; +} + +//----------------------------------------------------------------------------- +// Tracker 14992: We used to load 18K of .vfes for every C_BaseFlex who lipsynced, but now we only load those files once globally. +// Note, we could wipe these between levels, but they don't ever load more than the weak/normal/strong phoneme classes that I can tell +// so I'll just leave them loaded forever for now +//----------------------------------------------------------------------------- +void CFlexSceneFileManager::Shutdown() +{ + DeleteSceneFiles(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets up translations +// Input : *instance - +// *pSettinghdr - +// Output : void +//----------------------------------------------------------------------------- +void CFlexSceneFileManager::EnsureTranslations( IHasLocalToGlobalFlexSettings *instance, const flexsettinghdr_t *pSettinghdr ) +{ + // The only time instance is NULL is in Init() above, where we're just loading the .vfe files off of the hard disk. + if ( instance ) + { + instance->EnsureTranslations( pSettinghdr ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void *CFlexSceneFileManager::FindSceneFile( IHasLocalToGlobalFlexSettings *instance, const char *filename, bool allowBlockingIO ) +{ + char szFilename[MAX_PATH]; + Assert( V_strlen( filename ) < MAX_PATH ); + V_strcpy( szFilename, filename ); + +#if defined( TF_CLIENT_DLL ) + char szHWMFilename[MAX_PATH]; + if ( GetHWMExpressionFileName( szFilename, szHWMFilename ) ) + { + V_strcpy( szFilename, szHWMFilename ); + } +#endif + + Q_FixSlashes( szFilename ); + + // See if it's already loaded + int i; + for ( i = 0; i < m_FileList.Count(); i++ ) + { + CFlexSceneFile *file = m_FileList[ i ]; + if ( file && !Q_stricmp( file->filename, szFilename ) ) + { + // Make sure translations (local to global flex controller) are set up for this instance + EnsureTranslations( instance, ( const flexsettinghdr_t * )file->buffer ); + return file->buffer; + } + } + + if ( !allowBlockingIO ) + { + return NULL; + } + + // Load file into memory + void *buffer = NULL; + int len = filesystem->ReadFileEx( VarArgs( "expressions/%s.vfe", szFilename ), "GAME", &buffer ); + + if ( !len ) + return NULL; + + // Create scene entry + CFlexSceneFile *pfile = new CFlexSceneFile; + // Remember filename + Q_strncpy( pfile->filename, szFilename, sizeof( pfile->filename ) ); + // Remember data pointer + pfile->buffer = buffer; + // Add to list + m_FileList.AddToTail( pfile ); + + // Swap the entire file + if ( IsX360() ) + { + CByteswap swap; + swap.ActivateByteSwapping( true ); + byte *pData = (byte*)buffer; + flexsettinghdr_t *pHdr = (flexsettinghdr_t*)pData; + swap.SwapFieldsToTargetEndian( pHdr ); + + // Flex Settings + flexsetting_t *pFlexSetting = (flexsetting_t*)((byte*)pHdr + pHdr->flexsettingindex); + for ( int i = 0; i < pHdr->numflexsettings; ++i, ++pFlexSetting ) + { + swap.SwapFieldsToTargetEndian( pFlexSetting ); + + flexweight_t *pWeight = (flexweight_t*)(((byte*)pFlexSetting) + pFlexSetting->settingindex ); + for ( int j = 0; j < pFlexSetting->numsettings; ++j, ++pWeight ) + { + swap.SwapFieldsToTargetEndian( pWeight ); + } + } + + // indexes + pData = (byte*)pHdr + pHdr->indexindex; + swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numindexes ); + + // keymappings + pData = (byte*)pHdr + pHdr->keymappingindex; + swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys ); + + // keyname indices + pData = (byte*)pHdr + pHdr->keynameindex; + swap.SwapBufferToTargetEndian( (int*)pData, (int*)pData, pHdr->numkeys ); + } + + // Fill in translation table + EnsureTranslations( instance, ( const flexsettinghdr_t * )pfile->buffer ); + + // Return data + return pfile->buffer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlexSceneFileManager::DeleteSceneFiles() +{ + while ( m_FileList.Count() > 0 ) + { + CFlexSceneFile *file = m_FileList[ 0 ]; + m_FileList.Remove( 0 ); + free( file->buffer ); + delete file; + } +} + +CFlexSceneFileManager g_FlexSceneFileManager; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void *C_BaseFlex::FindSceneFile( const char *filename ) +{ + return g_FlexSceneFileManager.FindSceneFile( this, filename, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: make sure the eyes are within 30 degrees of forward +//----------------------------------------------------------------------------- +Vector C_BaseFlex::SetViewTarget( CStudioHdr *pStudioHdr ) +{ + if ( !pStudioHdr ) + return Vector( 0, 0, 0); + + // aim the eyes + Vector tmp = m_viewtarget; + + if ( !m_bSearchedForEyeFlexes ) + { + m_bSearchedForEyeFlexes = true; + + m_iEyeUpdown = FindFlexController( "eyes_updown" ); + m_iEyeRightleft = FindFlexController( "eyes_rightleft" ); + + if ( m_iEyeUpdown != -1 ) + { + pStudioHdr->pFlexcontroller( m_iEyeUpdown )->localToGlobal = AddGlobalFlexController( "eyes_updown" ); + } + if ( m_iEyeRightleft != -1 ) + { + pStudioHdr->pFlexcontroller( m_iEyeRightleft )->localToGlobal = AddGlobalFlexController( "eyes_rightleft" ); + } + } + + if (m_iEyeAttachment > 0) + { + matrix3x4_t attToWorld; + if (!GetAttachment( m_iEyeAttachment, attToWorld )) + { + return Vector( 0, 0, 0); + } + + Vector local; + VectorITransform( tmp, attToWorld, local ); + + // FIXME: clamp distance to something based on eyeball distance + if (local.x < 6) + { + local.x = 6; + } + float flDist = local.Length(); + VectorNormalize( local ); + + // calculate animated eye deflection + Vector eyeDeflect; + QAngle eyeAng( 0, 0, 0 ); + if ( m_iEyeUpdown != -1 ) + { + mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeUpdown ); + eyeAng.x = g_flexweight[ pflex->localToGlobal ]; + } + + if ( m_iEyeRightleft != -1 ) + { + mstudioflexcontroller_t *pflex = pStudioHdr->pFlexcontroller( m_iEyeRightleft ); + eyeAng.y = g_flexweight[ pflex->localToGlobal ]; + } + + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%5.3f %5.3f", eyeAng.x, eyeAng.y ); + + AngleVectors( eyeAng, &eyeDeflect ); + eyeDeflect.x = 0; + + // reduce deflection the more the eye is off center + // FIXME: this angles make no damn sense + eyeDeflect = eyeDeflect * (local.x * local.x); + local = local + eyeDeflect; + VectorNormalize( local ); + + // check to see if the eye is aiming outside the max eye deflection + float flMaxEyeDeflection = pStudioHdr->MaxEyeDeflection(); + if ( local.x < flMaxEyeDeflection ) + { + // if so, clamp it to 30 degrees offset + // debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 1, 0, "%5.3f %5.3f %5.3f", local.x, local.y, local.z ); + local.x = 0; + float d = local.LengthSqr(); + if ( d > 0.0f ) + { + d = sqrtf( ( 1.0f - flMaxEyeDeflection * flMaxEyeDeflection ) / ( local.y*local.y + local.z*local.z ) ); + local.x = flMaxEyeDeflection; + local.y = local.y * d; + local.z = local.z * d; + } + else + { + local.x = 1.0; + } + } + local = local * flDist; + VectorTransform( local, attToWorld, tmp ); + } + + modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp ); + + /* + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%.2f %.2f %.2f : %.2f %.2f %.2f", + m_viewtarget.x, m_viewtarget.y, m_viewtarget.z, + m_prevviewtarget.x, m_prevviewtarget.y, m_prevviewtarget.z ); + */ + + return tmp; +} + +#define STRONG_CROSSFADE_START 0.60f +#define WEAK_CROSSFADE_START 0.40f + +//----------------------------------------------------------------------------- +// Purpose: +// Here's the formula +// 0.5 is neutral 100 % of the default setting +// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END +// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START +// so we don't get huge numbers +// Input : *classes - +// emphasis_intensity - +//----------------------------------------------------------------------------- +void C_BaseFlex::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ) +{ + // See which blends are available for the current phoneme + bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid; + bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid; + + // Better have phonemes in general + Assert( classes[ PHONEME_CLASS_NORMAL ].valid ); + + if ( emphasis_intensity > STRONG_CROSSFADE_START ) + { + if ( has_strong ) + { + // Blend in some of strong + float dist_remaining = 1.0f - emphasis_intensity; + float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START; + classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac; + } + else + { + emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else if ( emphasis_intensity < WEAK_CROSSFADE_START ) + { + if ( has_weak ) + { + // Blend in some weak + float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity; + float frac = dist_remaining / ( WEAK_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START; + classes[ PHONEME_CLASS_WEAK ].amount = frac; + } + else + { + emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else + { + // Assume 0.5 (neutral) becomes a scaling of 1.0f + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +// phoneme - +// scale - +// newexpression - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression ) +{ + int type; + + // Setup weights for any emphasis blends + bool skip = SetupEmphasisBlend( classes, phoneme ); + // Uh-oh, missing or unknown phoneme??? + if ( skip ) + { + return; + } + + // Compute blend weights + ComputeBlendedSetting( classes, emphasis_intensity ); + + for ( type = 0; type < NUM_PHONEME_CLASSES; type++ ) + { + Emphasized_Phoneme *info = &classes[ type ]; + if ( !info->valid || info->amount == 0.0f ) + continue; + + const flexsettinghdr_t *actual_flexsetting_header = info->base; + const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme ); + if (!pSetting) + { + continue; + } + + flexweight_t *pWeights = NULL; + + int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights ); + if ( pWeights ) + { + for ( int i = 0; i < truecount; i++) + { + // Translate to global controller number + int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key ); + // Add scaled weighting in + g_flexweight[j] += info->amount * scale * pWeights->weight; + // Go to next setting + pWeights++; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A lot of the one time setup and also resets amount to 0.0f default +// for strong/weak/normal tracks +// Returning true == skip this phoneme +// Input : *classes - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool C_BaseFlex::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme ) +{ + int i; + + bool skip = false; + + for ( i = 0; i < NUM_PHONEME_CLASSES; i++ ) + { + Emphasized_Phoneme *info = &classes[ i ]; + + // Assume it's bogus + info->valid = false; + info->amount = 0.0f; + + // One time setup + if ( !info->basechecked ) + { + info->basechecked = true; + info->base = (flexsettinghdr_t *)FindSceneFile( info->classname ); + } + info->exp = NULL; + if ( info->base ) + { + Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') ); + info->exp = info->base->pIndexedSetting( phoneme ); + } + + if ( info->required && ( !info->base || !info->exp ) ) + { + skip = true; + break; + } + + if ( info->exp ) + { + info->valid = true; + } + } + + return skip; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +// *sentence - +// t - +// dt - +// juststarted - +//----------------------------------------------------------------------------- +ConVar g_CV_PhonemeSnap("phonemesnap", "2", 0, "Lod at level at which visemes stops always considering two phonemes, regardless of duration." ); +void C_BaseFlex::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + + int pcount = sentence->GetRuntimePhonemeCount(); + for ( int k = 0; k < pcount; k++ ) + { + const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k ); + + if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime()) + { + bool bCrossfade = true; + if ((hdr->flags() & STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE) == 0) + { + if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD0) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 0); + } + else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD1) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 1); + } + else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD2) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 2); + } + else if (m_iAccumulatedBoneMask & BONE_USED_BY_VERTEX_LOD3) + { + bCrossfade = (g_CV_PhonemeSnap.GetInt() > 3); + } + else + { + bCrossfade = false; + } + } + + if (bCrossfade) + { + if (k < pcount-1) + { + const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 ); + // if I have a neighbor + if ( next ) + { + // and they're touching + if (next->GetStartTime() == phoneme->GetEndTime() ) + { + // no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme + dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + else + { + // dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme + dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + } + else + { + // last phoneme in list, increase the blend length to the length of the current phoneme + dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() ); + } + } + } + } + + float t1 = ( phoneme->GetStartTime() - t) / dt; + float t2 = ( phoneme->GetEndTime() - t) / dt; + + if (t1 < 1.0 && t2 > 0) + { + float scale; + + // clamp + if (t2 > 1) + t2 = 1; + if (t1 < 0) + t1 = 0; + + // FIXME: simple box filter. Should use something fancier + scale = (t2 - t1); + + AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classes - +//----------------------------------------------------------------------------- +void C_BaseFlex::ProcessVisemes( Emphasized_Phoneme *classes ) +{ + // Any sounds being played? + if ( !MouthInfo().IsActive() ) + return; + + // Multiple phoneme tracks can overlap, look across all such tracks. + for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ ) + { + CVoiceData *vd = MouthInfo().GetVoiceSource( source ); + if ( !vd || vd->ShouldIgnorePhonemes() ) + continue; + + CSentence *sentence = engine->GetSentence( vd->GetSource() ); + if ( !sentence ) + continue; + + float sentence_length = engine->GetSentenceLength( vd->GetSource() ); + float timesincestart = vd->GetElapsedTime(); + + // This sound should be done...why hasn't it been removed yet??? + if ( timesincestart >= ( sentence_length + 2.0f ) ) + continue; + + // Adjust actual time + float t = timesincestart - g_CV_PhonemeDelay.GetFloat(); + + // Get box filter duration + float dt = g_CV_PhonemeFilter.GetFloat(); + + // Streaming sounds get an additional delay... + /* + // Tracker 20534: Probably not needed any more with the async sound stuff that + // we now have (we don't have a disk i/o hitch on startup which might have been + // messing up the startup timing a bit ) + bool streaming = engine->IsStreaming( vd->m_pAudioSource ); + if ( streaming ) + { + t -= g_CV_PhonemeDelayStreaming.GetFloat(); + } + */ + + // Assume sound has been playing for a while... + bool juststarted = false; + + // Get intensity setting for this time (from spline) + float emphasis_intensity = sentence->GetIntensity( t, sentence_length ); + + // Blend and add visemes together + AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: fill keyvalues message with flex state +// Input : +//----------------------------------------------------------------------------- +void C_BaseFlex::GetToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + VPROF_BUDGET( "C_BaseFlex::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); + + BaseClass::GetToolRecordingState( msg ); + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return; + + memset( g_flexweight, 0, sizeof( g_flexweight ) ); + + if ( hdr->numflexcontrollers() == 0 ) + return; + + LocalFlexController_t i; + + ProcessSceneEvents( true ); + + // FIXME: shouldn't this happen at runtime? + // initialize the models local to global flex controller mappings + if (hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1) + { + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() ); + hdr->pFlexcontroller( i )->localToGlobal = j; + } + } + + // blend weights from server + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i ); + + g_flexweight[pflex->localToGlobal] = m_flexWeight[i]; + // rescale + g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min; + } + + ProcessSceneEvents( false ); + + // check for blinking + if (m_blinktoggle != m_prevblinktoggle) + { + m_prevblinktoggle = m_blinktoggle; + m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat(); + } + + if (m_iBlink == -1) + m_iBlink = AddGlobalFlexController( "blink" ); + g_flexweight[m_iBlink] = 0; + + // FIXME: this needs a better algorithm + // blink the eyes + float t = (m_blinktime - gpGlobals->curtime) * M_PI * 0.5 * (1.0/g_CV_BlinkDuration.GetFloat()); + if (t > 0) + { + // do eyeblink falloff curve + t = cos(t); + if (t > 0) + { + g_flexweight[m_iBlink] = sqrtf( t ) * 2; + if (g_flexweight[m_iBlink] > 1) + g_flexweight[m_iBlink] = 2.0 - g_flexweight[m_iBlink]; + } + } + + // Drive the mouth from .wav file playback... + ProcessVisemes( m_PhonemeClasses ); + + // Necessary??? + SetViewTarget( hdr ); + + Vector viewtarget = m_viewtarget; // Use the unfiltered value + + // HACK HACK: Unmap eyes right/left amounts + if (m_iEyeUpdown != -1 && m_iEyeRightleft != -1) + { + mstudioflexcontroller_t *flexupdown = hdr->pFlexcontroller( m_iEyeUpdown ); + mstudioflexcontroller_t *flexrightleft = hdr->pFlexcontroller( m_iEyeRightleft ); + + if ( flexupdown->localToGlobal != -1 && flexrightleft->localToGlobal != -1 ) + { + float updown = g_flexweight[ flexupdown->localToGlobal ]; + float rightleft = g_flexweight[ flexrightleft->localToGlobal ]; + + if ( flexupdown->min != flexupdown->max ) + { + updown = RemapVal( updown, flexupdown->min, flexupdown->max, 0.0f, 1.0f ); + } + if ( flexrightleft->min != flexrightleft->max ) + { + rightleft = RemapVal( rightleft, flexrightleft->min, flexrightleft->max, 0.0f, 1.0f ); + } + + g_flexweight[ flexupdown->localToGlobal ] = updown; + g_flexweight[ flexrightleft->localToGlobal ] = rightleft; + } + } + + // Convert back to normalized weights + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i ); + + // rescale + if ( pflex->max != pflex->min ) + { + g_flexweight[pflex->localToGlobal] = ( g_flexweight[pflex->localToGlobal] - pflex->min ) / ( pflex->max - pflex->min ); + } + } + + static BaseFlexRecordingState_t state; + state.m_nFlexCount = MAXSTUDIOFLEXCTRL; + state.m_pDestWeight = g_flexweight; + state.m_vecViewTarget = viewtarget; + msg->SetPtr( "baseflex", &state ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::OnThreadedDrawSetup() +{ + if (m_iEyeAttachment < 0) + return; + + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + CalcAttachments(); +} + + +//----------------------------------------------------------------------------- +// Should we use delayed flex weights? +//----------------------------------------------------------------------------- +bool C_BaseFlex::UsesFlexDelayedWeights() +{ + return ( m_flFlexDelayedWeight && g_CV_FlexSmooth.GetBool() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::LinkToGlobalFlexControllers( CStudioHdr *hdr ) +{ + if ( hdr && hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1 ) + { + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() ); + hdr->pFlexcontroller( i )->localToGlobal = j; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Rendering callback to allow the client to set up all the model specific flex weights +//----------------------------------------------------------------------------- +void C_BaseFlex::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ + // hack in an initialization + LinkToGlobalFlexControllers( GetModelPtr() ); + m_iBlink = AddGlobalFlexController( "UH" ); + + if ( SetupGlobalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ) ) + { + SetupLocalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: process the entities networked state, vcd playback, wav file visemes, and blinks into a global shared flex controller array +//----------------------------------------------------------------------------- +bool C_BaseFlex::SetupGlobalWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return false; + + memset( g_flexweight, 0, sizeof(g_flexweight) ); + + // FIXME: this should assert then, it's too complex a class for the model + if ( hdr->numflexcontrollers() == 0 ) + { + int nSizeInBytes = nFlexWeightCount * sizeof( float ); + memset( pFlexWeights, 0, nSizeInBytes ); + if ( pFlexDelayedWeights ) + { + memset( pFlexDelayedWeights, 0, nSizeInBytes ); + } + return false; + } + + LocalFlexController_t i; + + ProcessSceneEvents( true ); + + Assert( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 ); + + // get the networked flexweights and convert them from 0..1 to real dynamic range + for (i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i ); + + g_flexweight[pflex->localToGlobal] = m_flexWeight[i]; + // rescale + g_flexweight[pflex->localToGlobal] = g_flexweight[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min; + } + + ProcessSceneEvents( false ); + + // check for blinking + if (m_blinktoggle != m_prevblinktoggle) + { + m_prevblinktoggle = m_blinktoggle; + m_blinktime = gpGlobals->curtime + g_CV_BlinkDuration.GetFloat(); + } + + if (m_iBlink == -1) + { + m_iBlink = AddGlobalFlexController( "blink" ); + } + + // FIXME: this needs a better algorithm + // blink the eyes + float flBlinkDuration = g_CV_BlinkDuration.GetFloat(); + float flOOBlinkDuration = ( flBlinkDuration > 0 ) ? 1.0f / flBlinkDuration : 0.0f; + float t = ( m_blinktime - gpGlobals->curtime ) * M_PI * 0.5 * flOOBlinkDuration; + if (t > 0) + { + // do eyeblink falloff curve + t = cos(t); + if (t > 0.0f && t < 1.0f) + { + t = sqrtf( t ) * 2.0f; + if (t > 1.0f) + t = 2.0f - t; + t = clamp( t, 0.0f, 1.0f ); + // add it to whatever the blink track is doing + g_flexweight[m_iBlink] = clamp( g_flexweight[m_iBlink] + t, 0.0f, 1.0f ); + } + } + + // Drive the mouth from .wav file playback... + ProcessVisemes( m_PhonemeClasses ); + + return true; +} + +void C_BaseFlex::RunFlexDelay( int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights, float &flFlexDelayTime ) +{ + // process the delayed version of the flexweights + if ( flFlexDelayTime > 0.0f && flFlexDelayTime < gpGlobals->curtime ) + { + float d = clamp( gpGlobals->curtime - flFlexDelayTime, 0.0, gpGlobals->frametime ); + d = ExponentialDecay( 0.8, 0.033, d ); + + for ( int i = 0; i < nFlexWeightCount; i++) + { + pFlexDelayedWeights[i] = pFlexDelayedWeights[i] * d + pFlexWeights[i] * (1.0f - d); + } + } + flFlexDelayTime = gpGlobals->curtime; +} + + +//----------------------------------------------------------------------------- +// Purpose: convert the global flex controllers into model specific flex weights +//----------------------------------------------------------------------------- +void C_BaseFlex::SetupLocalWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + return; + + // BUGBUG: We have a bug with SetCustomModel that causes a disagreement between the studio header here and the one used in l_studio.cpp CModelRender::DrawModelExecute + // So when we hit that case, let's not do any work because otherwise we'd crash since the array sizes (m_flFlexDelayedWeight vs pFlexWeights) don't match. + // Note that this check is duplicated in CEconEntity::SetupWeights. + AssertMsg( nFlexWeightCount == m_cFlexDelayedWeight, "Disagreement between the number of flex weights. Do the studio headers match?" ); + if ( nFlexWeightCount != m_cFlexDelayedWeight ) + { + return; + } + + // convert the flex controllers into actual flex values + RunFlexRules( hdr, pFlexWeights ); + + // aim the eyes + SetViewTarget( hdr ); + + AssertOnce( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 ); + + if ( pFlexDelayedWeights ) + { + RunFlexDelay( nFlexWeightCount, pFlexWeights, m_flFlexDelayedWeight, m_flFlexDelayTime ); + memcpy( pFlexDelayedWeights, m_flFlexDelayedWeight, sizeof( float ) * nFlexWeightCount ); + } + + /* + LocalFlexController_t i; + + for (i = 0; i < hdr->numflexdesc; i++) + { + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), i-hdr->numflexcontrollers, 0, "%2d:%s : %3.2f", i, hdr->pFlexdesc( i )->pszFACS(), pFlexWeights[i] ); + } + */ + + /* + for (i = 0; i < g_numflexcontrollers; i++) + { + int j = hdr->pFlexcontroller( i )->link; + debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), -i, 0, "%s %3.2f", g_flexcontroller[i], g_flexweight[j] ); + } + */ +} + + +//----------------------------------------------------------------------------- +// Purpose: Unified set of flex controller entries that all systems can talk to +//----------------------------------------------------------------------------- + +int C_BaseFlex::g_numflexcontrollers; +char * C_BaseFlex::g_flexcontroller[MAXSTUDIOFLEXCTRL*4]; +float C_BaseFlex::g_flexweight[MAXSTUDIOFLEXDESC]; + +int C_BaseFlex::AddGlobalFlexController( const char *szName ) +{ + int i; + for (i = 0; i < g_numflexcontrollers; i++) + { + if (Q_stricmp( g_flexcontroller[i], szName ) == 0) + { + return i; + } + } + + if ( g_numflexcontrollers < MAXSTUDIOFLEXCTRL * 4 ) + { + g_flexcontroller[g_numflexcontrollers++] = strdup( szName ); + } + else + { + // FIXME: missing runtime error condition + } + return i; +} + +char const *C_BaseFlex::GetGlobalFlexControllerName( int idx ) +{ + if ( idx < 0 || idx >= g_numflexcontrollers ) + { + return ""; + } + + return g_flexcontroller[ idx ]; +} + +const flexsetting_t *C_BaseFlex::FindNamedSetting( const flexsettinghdr_t *pSettinghdr, const char *expr ) +{ + int i; + const flexsetting_t *pSetting = NULL; + + for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) + { + pSetting = pSettinghdr->pSetting( i ); + if ( !pSetting ) + continue; + + const char *name = pSetting->pszName(); + + if ( !stricmp( name, expr ) ) + break; + } + + if ( i>=pSettinghdr->numflexsettings ) + { + return NULL; + } + + return pSetting; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::StartChoreoScene( CChoreoScene *scene ) +{ + if ( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() ) + { + return; + } + + m_ActiveChoreoScenes.AddToTail( scene ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseFlex::RemoveChoreoScene( CChoreoScene *scene ) +{ + // Assert( m_ActiveChoreoScenes.Find( scene ) != m_ActiveChoreoScenes.InvalidIndex() ); + + m_ActiveChoreoScenes.FindAndRemove( scene ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all active SceneEvents +//----------------------------------------------------------------------------- +void C_BaseFlex::ClearSceneEvents( CChoreoScene *scene, bool canceled ) +{ + if ( !scene ) + { + m_SceneEvents.RemoveAll(); + return; + } + + for ( int i = m_SceneEvents.Count() - 1; i >= 0; i-- ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + + Assert( info ); + Assert( info->m_pScene ); + Assert( info->m_pEvent ); + + if ( info->m_pScene != scene ) + continue; + + if ( !ClearSceneEvent( info, false, canceled )) + { + // unknown expression to clear!! + Assert( 0 ); + } + + // Free this slot + info->m_pEvent = NULL; + info->m_pScene = NULL; + info->m_bStarted = false; + + m_SceneEvents.Remove( i ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Stop specifics of expression +//----------------------------------------------------------------------------- + +bool C_BaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ) +{ + Assert( info ); + Assert( info->m_pScene ); + Assert( info->m_pEvent ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add string indexed scene/expression/duration to list of active SceneEvents +// Input : scenefile - +// expression - +// duration - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, bool bClientSide ) +{ + if ( !scene || !event ) + { + Msg( "C_BaseFlex::AddSceneEvent: scene or event was NULL!!!\n" ); + return; + } + + CChoreoActor *actor = event->GetActor(); + if ( !actor ) + { + Msg( "C_BaseFlex::AddSceneEvent: event->GetActor() was NULL!!!\n" ); + return; + } + + + CSceneEventInfo info; + + memset( (void *)&info, 0, sizeof( info ) ); + + info.m_pEvent = event; + info.m_pScene = scene; + info.m_hTarget = pTarget; + info.m_bStarted = false; + info.m_bClientSide = bClientSide; + + if (StartSceneEvent( &info, scene, event, actor, pTarget )) + { + m_SceneEvents.AddToTail( info ); + } + else + { + scene->SceneMsg( "C_BaseFlex::AddSceneEvent: event failed\n" ); + // Assert( 0 ); // expression failed to start + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) +{ + switch ( event->GetType() ) + { + default: + break; + + case CChoreoEvent::FLEXANIMATION: + info->InitWeight( this ); + return true; + + case CChoreoEvent::EXPRESSION: + return true; + + case CChoreoEvent::SEQUENCE: + if ( info->m_bClientSide ) + { + return RequestStartSequenceSceneEvent( info, scene, event, actor, pTarget ); + } + break; + + case CChoreoEvent::SPEAK: + if ( info->m_bClientSide ) + { + return true; + } + break; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseFlex::RequestStartSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) +{ + info->m_nSequence = LookupSequence( event->GetParameters() ); + + // make sure sequence exists + if ( info->m_nSequence < 0 ) + return false; + + info->m_pActor = actor; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove expression +// Input : scenefile - +// expression - +//----------------------------------------------------------------------------- +void C_BaseFlex::RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill ) +{ + Assert( event ); + + for ( int i = 0 ; i < m_SceneEvents.Count(); i++ ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + + Assert( info ); + Assert( info->m_pEvent ); + + if ( info->m_pScene != scene ) + continue; + + if ( info->m_pEvent != event) + continue; + + if (ClearSceneEvent( info, fastKill, false )) + { + // Free this slot + info->m_pEvent = NULL; + info->m_pScene = NULL; + info->m_bStarted = false; + + m_SceneEvents.Remove( i ); + return; + } + } + + // many events refuse to start due to bogus parameters +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if the event should be considered "completed" +//----------------------------------------------------------------------------- +bool C_BaseFlex::CheckSceneEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + for ( int i = 0 ; i < m_SceneEvents.Count(); i++ ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + + Assert( info ); + Assert( info->m_pEvent ); + + if ( info->m_pScene != scene ) + continue; + + if ( info->m_pEvent != event) + continue; + + return CheckSceneEventCompletion( info, currenttime, scene, event ); + } + return true; +} + + + +bool C_BaseFlex::CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + return true; +} + +void C_BaseFlex::SetFlexWeight( LocalFlexController_t index, float value ) +{ + if (index >= 0 && index < GetNumFlexControllers()) + { + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return; + + mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index ); + + if (pflexcontroller->max != pflexcontroller->min) + { + value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min); + value = clamp( value, 0.0f, 1.0f ); + } + + m_flexWeight[ index ] = value; + } +} + +float C_BaseFlex::GetFlexWeight( LocalFlexController_t index ) +{ + if (index >= 0 && index < GetNumFlexControllers()) + { + CStudioHdr *pstudiohdr = GetModelPtr( ); + if (! pstudiohdr) + return 0; + + mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( index ); + + if (pflexcontroller->max != pflexcontroller->min) + { + return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min; + } + + return m_flexWeight[index]; + } + return 0.0; +} + +LocalFlexController_t C_BaseFlex::FindFlexController( const char *szName ) +{ + for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) + { + if (stricmp( GetFlexControllerName( i ), szName ) == 0) + { + return i; + } + } + + // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) ); + return LocalFlexController_t(-1); +} + +//----------------------------------------------------------------------------- +// Purpose: Default implementation +//----------------------------------------------------------------------------- +void C_BaseFlex::ProcessSceneEvents( bool bFlexEvents ) +{ + CStudioHdr *hdr = GetModelPtr(); + if ( !hdr ) + { + return; + } + + // slowly decay to netural expression + + if ( bFlexEvents ) + { + for ( LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) + { + SetFlexWeight( i, GetFlexWeight( i ) * 0.95 ); + } + } + + // Iterate SceneEvents and look for active slots + for ( int i = 0; i < m_SceneEvents.Count(); i++ ) + { + CSceneEventInfo *info = &m_SceneEvents[ i ]; + Assert( info ); + + // FIXME: Need a safe handle to m_pEvent in case of memory deletion? + CChoreoEvent *event = info->m_pEvent; + Assert( event ); + + CChoreoScene *scene = info->m_pScene; + Assert( scene ); + + if ( ProcessSceneEvent( bFlexEvents, info, scene, event ) ) + { + info->m_bStarted = true; + } + } +} + +//----------------------------------------------------------------------------- +// Various methods to process facial SceneEvents: +//----------------------------------------------------------------------------- +bool C_BaseFlex::ProcessFlexAnimationSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->HasEndTime() ); + if ( event->HasEndTime() ) + { + AddFlexAnimation( info ); + } + return true; +} + +bool C_BaseFlex::ProcessFlexSettingSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + // Flexanimations have to have an end time!!! + if ( !event->HasEndTime() ) + return true; + + VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" ); + + // Look up the actual strings + const char *scenefile = event->GetParameters(); + const char *name = event->GetParameters2(); + + // Have to find both strings + if ( scenefile && name ) + { + // Find the scene file + const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true ); + if ( pExpHdr ) + { + float scenetime = scene->GetTime(); + + float scale = event->GetIntensity( scenetime ); + + // Add the named expression + AddFlexSetting( name, scale, pExpHdr, !info->m_bStarted ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Each CBaseFlex maintains a UtlRBTree of mappings, one for each loaded flex scene file it uses. This is used to +// sort the entries in the RBTree +// Input : lhs - +// rhs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FlexSettingLessFunc( const FS_LocalToGlobal_t& lhs, const FS_LocalToGlobal_t& rhs ) +{ + return lhs.m_Key < rhs.m_Key; +} + +//----------------------------------------------------------------------------- +// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but +// we just do this in memory with an array of integers (could be shorts, I suppose) +// Input : *pSettinghdr - +//----------------------------------------------------------------------------- +void C_BaseFlex::EnsureTranslations( const flexsettinghdr_t *pSettinghdr ) +{ + Assert( pSettinghdr ); + + FS_LocalToGlobal_t entry( pSettinghdr ); + + unsigned short idx = m_LocalToGlobal.Find( entry ); + if ( idx != m_LocalToGlobal.InvalidIndex() ) + return; + + entry.SetCount( pSettinghdr->numkeys ); + + for ( int i = 0; i < pSettinghdr->numkeys; ++i ) + { + entry.m_Mapping[ i ] = AddGlobalFlexController( pSettinghdr->pLocalName( i ) ); + } + + m_LocalToGlobal.Insert( entry ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look up instance specific mapping +// Input : *pSettinghdr - +// key - +// Output : int +//----------------------------------------------------------------------------- +int C_BaseFlex::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key ) +{ + FS_LocalToGlobal_t entry( pSettinghdr ); + + int idx = m_LocalToGlobal.Find( entry ); + if ( idx == m_LocalToGlobal.InvalidIndex() ) + { + // This should never happen!!! + Assert( 0 ); + Warning( "Unable to find mapping for flexcontroller %i, settings %p on %i/%s\n", key, pSettinghdr, entindex(), GetClassname() ); + EnsureTranslations( pSettinghdr ); + idx = m_LocalToGlobal.Find( entry ); + if ( idx == m_LocalToGlobal.InvalidIndex() ) + { + Error( "CBaseFlex::FlexControllerLocalToGlobal failed!\n" ); + } + } + + FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ]; + // Validate lookup + Assert( result.m_nCount != 0 && key < result.m_nCount ); + int index = result.m_Mapping[ key ]; + return index; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *expr - +// scale - +// *pSettinghdr - +// newexpression - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddFlexSetting( const char *expr, float scale, + const flexsettinghdr_t *pSettinghdr, bool newexpression ) +{ + int i; + const flexsetting_t *pSetting = NULL; + + // Find the named setting in the base + for ( i = 0; i < pSettinghdr->numflexsettings; i++ ) + { + pSetting = pSettinghdr->pSetting( i ); + if ( !pSetting ) + continue; + + const char *name = pSetting->pszName(); + + if ( !V_stricmp( name, expr ) ) + break; + } + + if ( i>=pSettinghdr->numflexsettings ) + { + return; + } + + flexweight_t *pWeights = NULL; + int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights ); + if ( !pWeights ) + return; + + for (i = 0; i < truecount; i++, pWeights++) + { + // Translate to local flex controller + // this is translating from the settings's local index to the models local index + int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key ); + + // blend scaled weighting in to total (post networking g_flexweight!!!!) + float s = clamp( scale * pWeights->influence, 0.0f, 1.0f ); + g_flexweight[index] = g_flexweight[index] * (1.0f - s) + pWeights->weight * s; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseFlex::ProcessSceneEvent( bool bFlexEvents, CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + if ( bFlexEvents ) + { + return ProcessFlexAnimationSceneEvent( info, scene, event ); + } + return true; + + case CChoreoEvent::EXPRESSION: + if ( !bFlexEvents ) + { + return ProcessFlexSettingSceneEvent( info, scene, event ); + } + return true; + + case CChoreoEvent::SEQUENCE: + if ( info->m_bClientSide ) + { + if ( !bFlexEvents ) + { + return ProcessSequenceSceneEvent( info, scene, event ); + } + return true; + } + break; + + case CChoreoEvent::SPEAK: + if ( info->m_bClientSide ) + { + return true; + } + break; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *parameters - +//----------------------------------------------------------------------------- +bool C_BaseFlex::ProcessSequenceSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !info || !event || !scene ) + return false; + + SetSequence( info->m_nSequence ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void C_BaseFlex::AddFlexAnimation( CSceneEventInfo *info ) +{ + if ( !info ) + return; + + CChoreoEvent *event = info->m_pEvent; + if ( !event ) + return; + + CChoreoScene *scene = info->m_pScene; + if ( !scene ) + return; + + if ( !event->GetTrackLookupSet() ) + { + // Create lookup data + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + if ( track->IsComboType() ) + { + char name[ 512 ]; + Q_strncpy( name, "right_" ,sizeof(name)); + Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); + + track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 ); + + Q_strncpy( name, "left_" ,sizeof(name)); + Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS ); + + track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 ); + } + else + { + track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 ); + } + } + + event->SetTrackLookupSet( true ); + } + + if ( !scene_clientflex.GetBool() ) + return; + + float scenetime = scene->GetTime(); + + float weight = event->GetIntensity( scenetime ); + + // decay if this is a background scene and there's other flex animations playing + weight = weight * info->UpdateWeight( this ); + + // Compute intensity for each track in animation and apply + // Iterate animation tracks + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + if ( track->IsComboType() ) + { + for ( int side = 0; side < 2; side++ ) + { + LocalFlexController_t controller = track->GetRawFlexControllerIndex( side ); + + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, side ); + if ( controller >= LocalFlexController_t(0) ) + { + float orig = GetFlexWeight( controller ); + float value = orig * (1 - weight) + flIntensity * weight; + SetFlexWeight( controller, value ); + } + } + } + else + { + LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 ); + + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, 0 ); + if ( controller >= LocalFlexController_t(0) ) + { + float orig = GetFlexWeight( controller ); + float value = orig * (1 - weight) + flIntensity * weight; + SetFlexWeight( controller, value ); + } + } + } + + info->m_bStarted = true; +} + +void CSceneEventInfo::InitWeight( C_BaseFlex *pActor ) +{ + m_flWeight = 1.0; +} + +//----------------------------------------------------------------------------- +// Purpose: update weight for background events. Only call once per think +//----------------------------------------------------------------------------- + +float CSceneEventInfo::UpdateWeight( C_BaseFlex *pActor ) +{ + m_flWeight = MIN( m_flWeight + 0.1, 1.0 ); + return m_flWeight; +} + +BEGIN_BYTESWAP_DATADESC( flexsettinghdr_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_ARRAY( name, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( length, FIELD_INTEGER ), + DEFINE_FIELD( numflexsettings, FIELD_INTEGER ), + DEFINE_FIELD( flexsettingindex, FIELD_INTEGER ), + DEFINE_FIELD( nameindex, FIELD_INTEGER ), + DEFINE_FIELD( numindexes, FIELD_INTEGER ), + DEFINE_FIELD( indexindex, FIELD_INTEGER ), + DEFINE_FIELD( numkeys, FIELD_INTEGER ), + DEFINE_FIELD( keynameindex, FIELD_INTEGER ), + DEFINE_FIELD( keymappingindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( flexsetting_t ) + DEFINE_FIELD( nameindex, FIELD_INTEGER ), + DEFINE_FIELD( obsolete1, FIELD_INTEGER ), + DEFINE_FIELD( numsettings, FIELD_INTEGER ), + DEFINE_FIELD( index, FIELD_INTEGER ), + DEFINE_FIELD( obsolete2, FIELD_INTEGER ), + DEFINE_FIELD( settingindex, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( flexweight_t ) + DEFINE_FIELD( key, FIELD_INTEGER ), + DEFINE_FIELD( weight, FIELD_FLOAT ), + DEFINE_FIELD( influence, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + |