aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/client/c_baseflex.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/client/c_baseflex.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/client/c_baseflex.cpp')
-rw-r--r--mp/src/game/client/c_baseflex.cpp2071
1 files changed, 2071 insertions, 0 deletions
diff --git a/mp/src/game/client/c_baseflex.cpp b/mp/src/game/client/c_baseflex.cpp
new file mode 100644
index 00000000..a39b5f35
--- /dev/null
+++ b/mp/src/game/client/c_baseflex.cpp
@@ -0,0 +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()
+