mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2024-12-23 01:59:43 +08:00
1731 lines
48 KiB
C++
1731 lines
48 KiB
C++
//===== Copyright © 1996-2005, 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"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
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]) ),
|
|
#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()
|
|
|
|
C_BaseFlex::C_BaseFlex() : m_iv_viewtarget( "C_BaseFlex::m_iv_viewtarget" ), m_iv_flexWeight("C_BaseFlex:m_iv_flexWeight" ),
|
|
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
|
|
memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) );
|
|
|
|
Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ];
|
|
Q_strncpy( weak->classname, "phonemes_weak", sizeof( weak->classname ) );
|
|
weak->required = false;
|
|
Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ];
|
|
Q_strncpy( normal->classname, "phonemes", sizeof( normal->classname ) );
|
|
normal->required = true;
|
|
Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ];
|
|
Q_strncpy( strong->classname, "phonemes_strong", sizeof( strong->classname ) );
|
|
strong->required = false;
|
|
|
|
m_flFlexDelayedWeight = NULL;
|
|
|
|
/// Make sure size is correct
|
|
Assert( PHONEME_CLASS_STRONG + 1 == NUM_PHONEME_CLASSES );
|
|
}
|
|
|
|
C_BaseFlex::~C_BaseFlex()
|
|
{
|
|
delete[] m_flFlexDelayedWeight;
|
|
m_SceneEvents.RemoveAll();
|
|
m_LocalToGlobal.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: initialize fast lookups when model changes
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CStudioHdr *C_BaseFlex::OnNewModel()
|
|
{
|
|
CStudioHdr *hdr = BaseClass::OnNewModel();
|
|
|
|
// init to invalid setting
|
|
m_iBlink = -1;
|
|
m_iEyeUpdown = -1;
|
|
m_iEyeRightleft = -1;
|
|
m_iMouthAttachment = 0;
|
|
|
|
delete[] m_flFlexDelayedWeight;
|
|
m_flFlexDelayedWeight = NULL;
|
|
|
|
if (hdr)
|
|
{
|
|
if (hdr->numflexdesc())
|
|
{
|
|
m_flFlexDelayedWeight = new float [hdr->numflexdesc()];
|
|
|
|
for (int i = 0; i < hdr->numflexdesc(); i++)
|
|
{
|
|
m_flFlexDelayedWeight[i] = 0.0;
|
|
}
|
|
}
|
|
|
|
m_iv_flexWeight.SetMaxCount( hdr->numflexcontrollers() );
|
|
|
|
m_iMouthAttachment = LookupAttachment( "mouth" );
|
|
}
|
|
|
|
return hdr;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 && m_iMouthAttachment > 0)
|
|
{
|
|
Vector origin;
|
|
QAngle angles;
|
|
|
|
C_BaseAnimating::PushAllowBoneAccess( true, false );
|
|
|
|
if (GetAttachment( m_iMouthAttachment, origin, angles ))
|
|
{
|
|
if (info.pOrigin)
|
|
{
|
|
*info.pOrigin = origin;
|
|
}
|
|
|
|
if (info.pAngles)
|
|
{
|
|
*info.pAngles = angles;
|
|
}
|
|
}
|
|
|
|
C_BaseAnimating::PopBoneAccess();
|
|
}
|
|
}
|
|
|
|
return bret;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: run the interpreted FAC's expressions, converting 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 );
|
|
}
|
|
|
|
class CFlexSceneFileManager : CAutoGameSystem
|
|
{
|
|
public:
|
|
|
|
CFlexSceneFileManager() : CAutoGameSystem( "CFlexSceneFileManager" )
|
|
{
|
|
}
|
|
|
|
virtual bool 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
|
|
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
|
|
virtual void Shutdown()
|
|
{
|
|
DeleteSceneFiles();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets up translations
|
|
// Input : *instance -
|
|
// *pSettinghdr -
|
|
// Output : void
|
|
//-----------------------------------------------------------------------------
|
|
void EnsureTranslations( C_BaseFlex *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 );
|
|
}
|
|
}
|
|
|
|
void *FindSceneFile( C_BaseFlex *instance, const char *filename, bool allowBlockingIO )
|
|
{
|
|
// See if it's already loaded
|
|
int i;
|
|
for ( i = 0; i < m_FileList.Count(); i++ )
|
|
{
|
|
CFlexSceneFile *file = m_FileList[ i ];
|
|
if ( file && !stricmp( file->filename, filename ) )
|
|
{
|
|
// 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", filename ), "GAME", &buffer );
|
|
|
|
if ( !len )
|
|
return NULL;
|
|
|
|
// Create scene entry
|
|
CFlexSceneFile *pfile = new CFlexSceneFile;
|
|
// Remember filename
|
|
Q_strncpy( pfile->filename, filename, sizeof( pfile->filename ) );
|
|
// Remember data pointer
|
|
pfile->buffer = buffer;
|
|
// Add to list
|
|
m_FileList.AddToTail( pfile );
|
|
|
|
// Fill in translation table
|
|
EnsureTranslations( instance, ( const flexsettinghdr_t * )pfile->buffer );
|
|
|
|
// Return data
|
|
return pfile->buffer;
|
|
}
|
|
|
|
private:
|
|
|
|
void DeleteSceneFiles()
|
|
{
|
|
while ( m_FileList.Count() > 0 )
|
|
{
|
|
CFlexSceneFile *file = m_FileList[ 0 ];
|
|
m_FileList.Remove( 0 );
|
|
delete[] file->buffer;
|
|
delete file;
|
|
}
|
|
}
|
|
|
|
CUtlVector< CFlexSceneFile * > m_FileList;
|
|
};
|
|
|
|
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_iEyeUpdown == -1)
|
|
m_iEyeUpdown = AddGlobalFlexController( "eyes_updown" );
|
|
|
|
if (m_iEyeRightleft == -1)
|
|
m_iEyeRightleft = 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)
|
|
{
|
|
eyeAng.x = g_flexweight[ m_iEyeUpdown ];
|
|
}
|
|
|
|
if ( m_iEyeRightleft != -1)
|
|
{
|
|
eyeAng.y = g_flexweight[ m_iEyeRightleft ];
|
|
}
|
|
|
|
// 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 a 30 degree cone
|
|
if (local.x < 0.866) // cos(30)
|
|
{
|
|
// 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.0)
|
|
{
|
|
d = sqrtf( (1.0 - 0.866 * 0.866) / (local.y*local.y + local.z*local.z) );
|
|
local.x = 0.866;
|
|
local.y = local.y * d;
|
|
local.z = local.z * d;
|
|
}
|
|
else
|
|
{
|
|
local.x = 1.0;
|
|
}
|
|
}
|
|
local = local * flDist;
|
|
VectorTransform( local, attToWorld, tmp );
|
|
}
|
|
|
|
modelrender->SetViewTarget( 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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
static void NewMarkovIndex( flexsetting_t *pSetting )
|
|
{
|
|
if ( pSetting->type != FS_MARKOV )
|
|
return;
|
|
|
|
int weighttotal = 0;
|
|
int member = 0;
|
|
for (int i = 0; i < pSetting->numsettings; i++)
|
|
{
|
|
flexmarkovgroup_t *group = pSetting->pMarkovGroup( i );
|
|
if ( !group )
|
|
continue;
|
|
|
|
weighttotal += group->weight;
|
|
if ( !weighttotal || random->RandomInt(0,weighttotal-1) < group->weight )
|
|
{
|
|
member = i;
|
|
}
|
|
}
|
|
|
|
pSetting->currentindex = member;
|
|
}
|
|
|
|
#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 overrides 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;
|
|
|
|
// Assume that we're not using overrieds
|
|
const flexsettinghdr_t *actual_flexsetting_header = info->base;
|
|
|
|
const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme );
|
|
if (!pSetting)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( newexpression )
|
|
{
|
|
if ( pSetting->type == FS_MARKOV )
|
|
{
|
|
NewMarkovIndex( ( flexsetting_t * )pSetting );
|
|
}
|
|
}
|
|
|
|
// Determine its index
|
|
int i = pSetting - actual_flexsetting_header->pSetting( 0 );
|
|
Assert( i >= 0 );
|
|
Assert( i < actual_flexsetting_header->numflexsettings );
|
|
|
|
// Resolve markov chain for the returned setting, probably not an issue for visemes
|
|
pSetting = actual_flexsetting_header->pTranslatedSetting( i );
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
// Check for overrides
|
|
if ( info->override )
|
|
{
|
|
// Get name from setting
|
|
const char *resolvedName = pSetting->pszName();
|
|
if ( resolvedName )
|
|
{
|
|
// See if resolvedName exists in the override file
|
|
const flexsetting_t *override = FindNamedSetting( info->override, resolvedName );
|
|
if ( override )
|
|
{
|
|
// If so, point at the override file instead
|
|
actual_flexsetting_header = info->override;
|
|
pSetting = override;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
flexweight_t *pWeights = NULL;
|
|
|
|
int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights );
|
|
if ( pWeights )
|
|
{
|
|
for (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 );
|
|
}
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
info->override = NULL;
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
// NOTE: We never actually used any overrides in HL2/Aftermath, so doing this disk check could lead to hitches due to calling filesystem->Open on each
|
|
// possibility. If we ever need to use these overrides, I would suggest adding a flag on the server to the keyvalues for NPCs specifying "use overrides",
|
|
// networking the flag down, and then only checking for overrides if the flag is set on the client. ALternateley, we could crawl the expressions folders
|
|
// with findfirst/next and find all override files and create a database, we'd do that at startup if we did it. However, that would add a bit of time to startup
|
|
// due to recursively crawling the directories (though we could just enumerate dirs off of the expressions dir...).
|
|
// ywb 2/8/06
|
|
#if 0
|
|
#if !defined( NO_ENTITY_PREDICTION )
|
|
// Find overrides, if any exist
|
|
// Also a one-time setup
|
|
if ( !info->overridechecked )
|
|
{
|
|
char overridefile[ 512 ];
|
|
char shortname[ 128 ];
|
|
char modelname[ 128 ];
|
|
|
|
Q_strncpy( modelname, modelinfo->GetModelName( GetModel() ), sizeof( modelname ) );
|
|
|
|
// Fix up the name
|
|
Q_FileBase( modelname, shortname, sizeof( shortname ) );
|
|
|
|
Q_snprintf( overridefile, sizeof( overridefile ), "%s/%s", shortname, info->classname );
|
|
|
|
info->overridechecked = true;
|
|
info->override = ( flexsettinghdr_t * )FindSceneFile( overridefile );
|
|
}
|
|
#else
|
|
info->overridechecked = true;
|
|
info->override = 0;
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
return skip;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *classes -
|
|
// *sentence -
|
|
// t -
|
|
// dt -
|
|
// juststarted -
|
|
//-----------------------------------------------------------------------------
|
|
ConVar g_CV_PhonemeSnap("phonemesnap", "1", 0, "Don't force visemes to always consider 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 ((!g_CV_PhonemeSnap.GetBool() || (hdr->flags() & STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE)) && t > phoneme->GetStartTime() && t < phoneme->GetEndTime())
|
|
{
|
|
if (k < pcount-1)
|
|
{
|
|
const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 );
|
|
if ( next )
|
|
{
|
|
dt = MAX( dt, MIN( next->GetEndTime() - t, 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 )
|
|
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;
|
|
/*
|
|
// FIXME: Do we really want to support markov chains for the phonemes?
|
|
// If so, we'll need to uncomment out these lines.
|
|
if ( timesincestart < 0.001 )
|
|
{
|
|
juststarted = true;
|
|
}
|
|
*/
|
|
|
|
// 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;
|
|
|
|
int i, j;
|
|
|
|
ProcessSceneEvents( true );
|
|
|
|
// FIXME: shouldn't this happen at runtime?
|
|
// initialize the models local to global flex controller mappings
|
|
if (hdr->pFlexcontroller( 0 )->link == -1)
|
|
{
|
|
for (i = 0; i < hdr->numflexcontrollers(); i++)
|
|
{
|
|
j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() );
|
|
hdr->pFlexcontroller( i )->link = j;
|
|
}
|
|
}
|
|
|
|
// blend weights from server
|
|
for (i = 0; i < hdr->numflexcontrollers(); i++)
|
|
{
|
|
mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );
|
|
|
|
g_flexweight[pflex->link] = m_flexWeight[i];
|
|
// rescale
|
|
g_flexweight[pflex->link] = g_flexweight[pflex->link] * (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 );
|
|
|
|
Vector viewtarget = SetViewTarget( hdr );
|
|
|
|
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::SetupWeights( )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
return;
|
|
|
|
int i, j;
|
|
|
|
ProcessSceneEvents( true );
|
|
|
|
// FIXME: shouldn't this happen at runtime?
|
|
// initialize the models local to global flex controller mappings
|
|
if (hdr->pFlexcontroller( 0 )->link == -1)
|
|
{
|
|
for (i = 0; i < hdr->numflexcontrollers(); i++)
|
|
{
|
|
j = AddGlobalFlexController( hdr->pFlexcontroller( i )->pszName() );
|
|
hdr->pFlexcontroller( i )->link = j;
|
|
}
|
|
}
|
|
|
|
// get the networked flexweights and convert them from 0..1 to real dynamic range
|
|
for (i = 0; i < hdr->numflexcontrollers(); i++)
|
|
{
|
|
mstudioflexcontroller_t *pflex = hdr->pFlexcontroller( i );
|
|
|
|
g_flexweight[pflex->link] = m_flexWeight[i];
|
|
// rescale
|
|
g_flexweight[pflex->link] = g_flexweight[pflex->link] * (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 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.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.0, 1.0 );
|
|
}
|
|
}
|
|
|
|
// Drive the mouth from .wav file playback...
|
|
ProcessVisemes( m_PhonemeClasses );
|
|
|
|
// convert the flex controllers into actual flex values
|
|
float destweight[MAXSTUDIOFLEXDESC];
|
|
RunFlexRules( hdr, destweight );
|
|
|
|
// aim the eyes
|
|
SetViewTarget( hdr );
|
|
|
|
if (m_flFlexDelayedWeight && g_CV_FlexSmooth.GetBool())
|
|
{
|
|
// process the delayed version of the flexweights
|
|
float d = 1.0;
|
|
if (gpGlobals->frametime != 0)
|
|
{
|
|
d = ExponentialDecay( 0.8, 0.033, gpGlobals->frametime );
|
|
}
|
|
for ( i = 0; i < hdr->numflexdesc(); i++)
|
|
{
|
|
m_flFlexDelayedWeight[i] = m_flFlexDelayedWeight[i] * d + destweight[i] * (1 - d);
|
|
}
|
|
// debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), i-hdr->numflexcontrollers, 0, "%.3f", d );
|
|
|
|
// send the flex values to the renderer
|
|
modelrender->SetFlexWeights( hdr->numflexdesc(), destweight, m_flFlexDelayedWeight );
|
|
}
|
|
else
|
|
{
|
|
// send the flex values to the renderer
|
|
modelrender->SetFlexWeights( hdr->numflexdesc(), destweight );
|
|
}
|
|
|
|
/*
|
|
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(), destweight[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] );
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
int C_BaseFlex::g_numflexcontrollers;
|
|
char * C_BaseFlex::g_flexcontroller[MAXSTUDIOFLEXCTRL*4];
|
|
float C_BaseFlex::g_flexweight[MAXSTUDIOFLEXDESC];
|
|
|
|
int C_BaseFlex::AddGlobalFlexController( 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 )
|
|
{
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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( int 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.0, 1.0 );
|
|
}
|
|
|
|
m_flexWeight[ index ] = value;
|
|
}
|
|
}
|
|
|
|
float C_BaseFlex::GetFlexWeight( int 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;
|
|
}
|
|
|
|
int C_BaseFlex::FindFlexController( const char *szName )
|
|
{
|
|
for (int i = 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 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Default implementation
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseFlex::ProcessSceneEvents( bool bFlexEvents )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// slowly decay to netural expression
|
|
int i;
|
|
if ( bFlexEvents )
|
|
{
|
|
for ( i = 0; i < GetNumFlexControllers(); i++)
|
|
{
|
|
SetFlexWeight( i, GetFlexWeight( i ) * 0.95 );
|
|
}
|
|
}
|
|
|
|
// Iterate SceneEvents and look for active slots
|
|
for ( 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;
|
|
}
|
|
|
|
#define AllowSceneOverrides() 0
|
|
|
|
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 )
|
|
{
|
|
const flexsettinghdr_t *pOverrideHdr = NULL;
|
|
|
|
// Find overrides, if any exist
|
|
CStudioHdr *hdr;
|
|
|
|
if ( AllowSceneOverrides() && ( hdr = GetModelPtr() ) != NULL )
|
|
{
|
|
char overridefile[ 512 ];
|
|
char shortname[ 128 ];
|
|
char modelname[ 128 ];
|
|
|
|
//Q_strncpy( modelname, modelinfo->GetModelName( model ) ,sizeof(modelname));
|
|
Q_strncpy( modelname, hdr->pszName() ,sizeof(modelname));
|
|
|
|
// Fix up the name
|
|
Q_FileBase( modelname, shortname, sizeof( shortname ) );
|
|
|
|
Q_snprintf( overridefile,sizeof(overridefile), "%s/%s", shortname, scenefile );
|
|
|
|
pOverrideHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, overridefile, true );
|
|
}
|
|
|
|
float scenetime = scene->GetTime();
|
|
|
|
float scale = event->GetIntensity( event, scenetime );
|
|
|
|
// Add the named expression
|
|
AddFlexSetting( name, scale, pExpHdr, pOverrideHdr, !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 C_BaseFlex::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 -
|
|
// *pOverrideHdr -
|
|
// newexpression -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseFlex::AddFlexSetting( const char *expr, float scale,
|
|
const flexsettinghdr_t *pSettinghdr, const flexsettinghdr_t *pOverrideHdr, 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 ( !stricmp( name, expr ) )
|
|
break;
|
|
}
|
|
|
|
if ( i>=pSettinghdr->numflexsettings )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update markov chain if needed
|
|
if ( newexpression )
|
|
{
|
|
if ( pSetting->type == FS_MARKOV )
|
|
{
|
|
NewMarkovIndex( (flexsetting_t *)pSetting );
|
|
}
|
|
}
|
|
|
|
// Resolve markov chain for the returned setting
|
|
pSetting = pSettinghdr->pTranslatedSetting( i );
|
|
|
|
// Check for overrides
|
|
if ( AllowSceneOverrides() && pOverrideHdr )
|
|
{
|
|
// Get name from setting
|
|
const char *resolvedName = pSetting->pszName();
|
|
if ( resolvedName )
|
|
{
|
|
// See if resolvedName exists in the override file
|
|
const flexsetting_t *override = FindNamedSetting( pOverrideHdr, resolvedName );
|
|
if ( override )
|
|
{
|
|
// If so, point at the override file instead
|
|
pSettinghdr = pOverrideHdr;
|
|
pSetting = override;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
|
|
// Add scaled weighting in to total (post networking g_flexweight!!!!)
|
|
float value = g_flexweight[index] + scale * pWeights->weight;
|
|
g_flexweight[index] = value;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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( 0, FindFlexController( name ), 0 );
|
|
|
|
Q_strncpy( name, "left_" ,sizeof(name));
|
|
Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
|
|
|
|
track->SetFlexControllerIndex( 0, FindFlexController( name ), 1 );
|
|
}
|
|
else
|
|
{
|
|
track->SetFlexControllerIndex( 0, FindFlexController( (char *)track->GetFlexControllerName() ) );
|
|
}
|
|
}
|
|
|
|
event->SetTrackLookupSet( true );
|
|
}
|
|
|
|
if ( !scene_clientflex.GetBool() )
|
|
return;
|
|
|
|
float scenetime = scene->GetTime();
|
|
|
|
float weight = event->GetIntensity( event, 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++ )
|
|
{
|
|
int controller = track->GetFlexControllerIndex( side );
|
|
|
|
// Get spline intensity for controller
|
|
float flIntensity = track->GetIntensity( scenetime, side );
|
|
if ( controller >= 0 )
|
|
{
|
|
float orig = GetFlexWeight( controller );
|
|
float value = orig * (1 - weight) + flIntensity * weight;
|
|
SetFlexWeight( controller, value );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int controller = track->GetFlexControllerIndex( 0 );
|
|
|
|
// Get spline intensity for controller
|
|
float flIntensity = track->GetIntensity( scenetime, 0 );
|
|
if ( controller >= 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;
|
|
}
|