From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/client/c_baseflex.cpp | 4142 ++++++++++++++++++------------------- 1 file changed, 2071 insertions(+), 2071 deletions(-) (limited to 'mp/src/game/client/c_baseflex.cpp') 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() + -- cgit v1.2.3