You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
951 lines
36 KiB
951 lines
36 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// A class used to build flex animation controls for an animation set |
|
// |
|
//============================================================================= |
|
|
|
#include "sfmobjects/flexcontrolbuilder.h" |
|
#include "studio.h" |
|
#include "movieobjects/dmeanimationset.h" |
|
#include "movieobjects/dmeclip.h" |
|
#include "movieobjects/dmetrackgroup.h" |
|
#include "movieobjects/dmetrack.h" |
|
#include "movieobjects/dmegamemodel.h" |
|
#include "movieobjects/dmechannel.h" |
|
#include "movieobjects/dmebalancetostereocalculatoroperator.h" |
|
#include "tier1/utlsymbol.h" |
|
|
|
|
|
// Names of attributes in controls we attach channels to |
|
#define CONTROL_CHANNEL_ATTRIBUTE_COUNT 4 |
|
static const char *s_pChannelControls[CONTROL_CHANNEL_ATTRIBUTE_COUNT] = |
|
{ |
|
"channel", "valuechannel", "balancechannel", "multilevelchannel" |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Flex controller |
|
//----------------------------------------------------------------------------- |
|
class CDefaultGlobalFlexController : public IGlobalFlexController |
|
{ |
|
public: |
|
CDefaultGlobalFlexController() : m_SymbolTable( 0, 32, true ) {} |
|
|
|
virtual int FindGlobalFlexController( const char *name ) |
|
{ |
|
return m_SymbolTable.AddString( name ); |
|
} |
|
|
|
virtual const char *GetGlobalFlexControllerName( int idx ) |
|
{ |
|
return m_SymbolTable.String( (CUtlSymbol)idx ); |
|
} |
|
|
|
private: |
|
CUtlSymbolTable m_SymbolTable; |
|
}; |
|
|
|
static CDefaultGlobalFlexController s_GlobalFlexController; |
|
extern IGlobalFlexController *g_pGlobalFlexController; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This builds a list of the desired flex controllers we need to have controls for |
|
// by the time we're all done with this enormous process. |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::BuildDesiredFlexControlList( CDmeGameModel *pGameModel ) |
|
{ |
|
CStudioHdr cHdr( pGameModel->GetStudioHdr() ); |
|
LocalFlexController_t nCount = cHdr.numflexcontrollers(); |
|
|
|
m_FlexControllerInfo.EnsureCapacity( nCount ); |
|
for ( LocalFlexController_t i = LocalFlexController_t(0); i < nCount; ++i ) |
|
{ |
|
int j = m_FlexControllerInfo.AddToTail(); |
|
|
|
FlexControllerInfo_t& info = m_FlexControllerInfo[j]; |
|
mstudioflexcontroller_t *pFlex = cHdr.pFlexcontroller( i ); |
|
Q_strncpy( info.m_pFlexControlName, pFlex->pszName(), sizeof( info.m_pFlexControlName ) ); |
|
info.m_nGlobalIndex = g_pGlobalFlexController->FindGlobalFlexController( pFlex->pszName() ); |
|
info.m_flDefaultValue = 0.0f; |
|
if ( pFlex->max != pFlex->min ) |
|
{ |
|
// FIXME: Is this the correct default value? |
|
info.m_flDefaultValue = ( 0.0f - pFlex->min ) / ( pFlex->max - pFlex->min ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This builds a list of the desired input controls we need to have controls for |
|
// by the time we're all done with this enormous process. |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::BuildDesiredControlList( CDmeGameModel *pGameModel ) |
|
{ |
|
int nCount = m_FlexControllerInfo.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
int j = m_ControlInfo.AddToTail(); |
|
ControlInfo_t &controlInfo = m_ControlInfo[j]; |
|
memset( &controlInfo, 0, sizeof(ControlInfo_t) ); |
|
|
|
FlexControllerInfo_t& info = m_FlexControllerInfo[i]; |
|
const char *pFlexName = info.m_pFlexControlName; |
|
|
|
// Deal with stereo/mono controls |
|
if ( !Q_strnicmp( "right_", pFlexName, 6 ) && ( i < nCount - 1 ) ) |
|
{ |
|
FlexControllerInfo_t& leftInfo = m_FlexControllerInfo[i+1]; |
|
Assert( !Q_strnicmp( "left_", leftInfo.m_pFlexControlName, 5 ) ); |
|
|
|
controlInfo.m_bIsStereo = true; |
|
controlInfo.m_pControllerIndex[ OUTPUT_RIGHT ] = i; |
|
controlInfo.m_pControllerIndex[ OUTPUT_LEFT ] = i+1; |
|
Q_strncpy( controlInfo.m_pControlName, pFlexName + 6, sizeof(controlInfo.m_pControlName) ); |
|
|
|
// Convert default values into value/balance |
|
LeftRightToValueBalance( &controlInfo.m_pDefaultValue[ CONTROL_VALUE ], |
|
&controlInfo.m_pDefaultValue[ CONTROL_BALANCE ], |
|
leftInfo.m_flDefaultValue, info.m_flDefaultValue ); |
|
|
|
// Skip the 'left_' flex control |
|
++i; |
|
} |
|
else |
|
{ |
|
controlInfo.m_bIsStereo = false; |
|
controlInfo.m_pControllerIndex[ OUTPUT_MONO ] = i; |
|
controlInfo.m_pControllerIndex[ OUTPUT_LEFT ] = -1; |
|
Q_strncpy( controlInfo.m_pControlName, pFlexName, sizeof(controlInfo.m_pControlName) ); |
|
controlInfo.m_pDefaultValue[ CONTROL_VALUE ] = info.m_flDefaultValue; |
|
controlInfo.m_pDefaultValue[ CONTROL_BALANCE ] = 0.5f; |
|
} |
|
|
|
// Deal with multi controls |
|
controlInfo.m_bIsMulti = ( i+1 < nCount ) && !Q_strnicmp( "multi_", m_FlexControllerInfo[ i+1 ].m_pFlexControlName, 6 ); |
|
if ( controlInfo.m_bIsMulti ) |
|
{ |
|
FlexControllerInfo_t& multiInfo = m_FlexControllerInfo[i+1]; |
|
|
|
controlInfo.m_pControllerIndex[ OUTPUT_MULTILEVEL ] = i+1; |
|
controlInfo.m_pDefaultValue[ CONTROL_MULTILEVEL ] = multiInfo.m_flDefaultValue; |
|
|
|
// Skip the 'multi_' flex control |
|
++i; |
|
} |
|
else |
|
{ |
|
controlInfo.m_pControllerIndex[ OUTPUT_MULTILEVEL ] = -1; |
|
controlInfo.m_pDefaultValue[ CONTROL_MULTILEVEL ] = 0.5f; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a desired flex controller index in the m_FlexControllerInfo array |
|
//----------------------------------------------------------------------------- |
|
int CFlexControlBuilder::FindDesiredFlexController( const char *pFlexControllerName ) const |
|
{ |
|
int nCount = m_FlexControllerInfo.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( !Q_stricmp( pFlexControllerName, m_FlexControllerInfo[i].m_pFlexControlName ) ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Removes a channel from the channels clip referring to it. |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::RemoveChannelFromClips( CDmeChannel *pChannel ) |
|
{ |
|
// First, try to grab the channels referring to this op |
|
CUtlVector< CDmeChannelsClip* > channelsClips; |
|
FindAncestorsReferencingElement( pChannel, channelsClips ); |
|
int nChannelsClips = channelsClips.Count(); |
|
for ( int i = 0; i < nChannelsClips; ++i ) |
|
{ |
|
channelsClips[ i ]->RemoveChannel( pChannel ); |
|
} |
|
|
|
// Next, remove the channel from values controls it may be attached to |
|
for ( int i = 0; i < CONTROL_CHANNEL_ATTRIBUTE_COUNT; ++i ) |
|
{ |
|
UtlSymId_t symChannelControl = g_pDataModel->GetSymbol( s_pChannelControls[i] ); |
|
CDmElement *pControl = FindReferringElement< CDmElement >( pChannel, symChannelControl ); |
|
if ( pControl ) |
|
{ |
|
pControl->RemoveAttribute( s_pChannelControls[i] ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Removes a stereo operator from the animation set referring to it |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::RemoveStereoOpFromSet( CDmeBalanceToStereoCalculatorOperator *pSteroOp ) |
|
{ |
|
// First, try to grab the channel referring to this op |
|
const static UtlSymId_t symOperators = g_pDataModel->GetSymbol( "operators" ); |
|
CDmeAnimationSet *pAnimationSet = FindReferringElement< CDmeAnimationSet >( pSteroOp, symOperators ); |
|
if ( pAnimationSet ) |
|
{ |
|
pAnimationSet->RemoveOperator( pSteroOp ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Blows away the various elements trying to control a flex controller op |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::CleanupExistingFlexController( CDmeGameModel *pGameModel, CDmeGlobalFlexControllerOperator *pOp ) |
|
{ |
|
CDmeBalanceToStereoCalculatorOperator *pStereoOp; |
|
|
|
// First, try to grab the channel referring to this op |
|
const static UtlSymId_t symToElement = g_pDataModel->GetSymbol( "toElement" ); |
|
CDmeChannel *pChannel = FindReferringElement< CDmeChannel >( pOp, symToElement ); |
|
if ( !pChannel ) |
|
goto destroyOp; |
|
|
|
// Sometimes a stereo op will be read from by this channel |
|
pStereoOp = CastElement< CDmeBalanceToStereoCalculatorOperator >( pChannel->GetFromElement() ); |
|
RemoveChannelFromClips( pChannel ); |
|
DestroyElement( pChannel ); |
|
if ( !pStereoOp ) |
|
goto destroyOp; |
|
|
|
RemoveStereoOpFromSet( pStereoOp ); |
|
|
|
// If we have a stereo op, then blow away all channels targetting that stereo op |
|
DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( pStereoOp->GetHandle() ); |
|
DmAttributeReferenceIterator_t next; |
|
for ( ; i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; i = next ) |
|
{ |
|
next = g_pDataModel->NextAttributeReferencingElement( i ); |
|
|
|
CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); |
|
pChannel = CastElement<CDmeChannel>( pAttribute->GetOwner() ); |
|
if ( pChannel && pAttribute->GetNameSymbol() == symToElement ) |
|
{ |
|
RemoveChannelFromClips( pChannel ); |
|
DestroyElement( pChannel ); |
|
} |
|
} |
|
|
|
DestroyElement( pStereoOp ); |
|
|
|
destroyOp: |
|
pGameModel->RemoveGlobalFlexController( pOp ); |
|
DestroyElement( pOp ); |
|
} |
|
|
|
bool RemoveChannelIfUnused( CDmeChannel *pChannel, CDmeChannelsClip *pChannelsClip ) |
|
{ |
|
if ( !pChannel ) |
|
return false; |
|
if ( pChannel->GetToElement() != NULL ) |
|
return false; |
|
|
|
pChannelsClip->RemoveChannel( pChannel ); |
|
DestroyElement( pChannel ); |
|
return true; |
|
} |
|
|
|
// finds controls whose channels don't point to anything anymore, and deletes both the channels and the control |
|
void CFlexControlBuilder::RemoveUnusedControlsAndChannels( CDmeAnimationSet *pAnimationSet, CDmeChannelsClip *pChannelsClip ) |
|
{ |
|
CDmrElementArray<> controls = pAnimationSet->GetControls(); |
|
int nControls = controls.Count(); |
|
for ( int i = nControls - 1; i >= 0 ; --i ) |
|
{ |
|
CDmElement *pControl = controls[ i ]; |
|
if ( pControl ) |
|
{ |
|
bool bRemoved = RemoveChannelIfUnused( pControl->GetValueElement< CDmeChannel >( "channel" ), pChannelsClip ); |
|
bRemoved = bRemoved || RemoveChannelIfUnused( pControl->GetValueElement< CDmeChannel >( "valuechannel" ), pChannelsClip ); |
|
bRemoved = bRemoved || RemoveChannelIfUnused( pControl->GetValueElement< CDmeChannel >( "balancechannel" ), pChannelsClip ); |
|
bRemoved = bRemoved || RemoveChannelIfUnused( pControl->GetValueElement< CDmeChannel >( "multilevelchannel" ), pChannelsClip ); |
|
|
|
if ( !bRemoved ) |
|
continue; |
|
|
|
DestroyElement( pControl ); |
|
} |
|
|
|
controls.Remove( i ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// This removes existing controls on the animationset that aren't in the desired state |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::RemoveUnusedExistingFlexControllers( CDmeGameModel *pGameModel ) |
|
{ |
|
// These are the current flex controllers |
|
// NOTE: Name of these controllers should match the names of the flex controllers |
|
int nCount = pGameModel->NumGlobalFlexControllers(); |
|
for ( int i = nCount; --i >= 0; ) |
|
{ |
|
CDmeGlobalFlexControllerOperator *pOp = pGameModel->GetGlobalFlexController( i ); |
|
Assert( pOp ); |
|
if ( pOp && FindDesiredFlexController( pOp->GetName() ) < 0 ) |
|
{ |
|
Msg( "removing flex controller %s\n", pOp->GetName() ); |
|
|
|
CleanupExistingFlexController( pGameModel, pOp ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns an existing mono log |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::GetExistingMonoLog( ExistingLogInfo_t *pExistingLog, |
|
CDmeFilmClip *pClip, CDmeGlobalFlexControllerOperator *pMonoOp ) |
|
{ |
|
pExistingLog->m_pLog = NULL; |
|
|
|
const static UtlSymId_t symToElement = g_pDataModel->GetSymbol( "toElement" ); |
|
CDmeChannel *pMonoChannel = FindReferringElement< CDmeChannel >( pMonoOp, symToElement ); |
|
if ( !pMonoChannel ) |
|
return; |
|
|
|
// First, try to grab the channel referring to this op |
|
CDmeFloatLog *pLog = CastElement< CDmeFloatLog >( pMonoChannel->GetLog() ); |
|
if ( !pLog ) |
|
return; |
|
|
|
if ( ComputeChannelTimeTransform( &pExistingLog->m_GlobalOffset, &pExistingLog->m_flGlobalScale, pClip, pMonoChannel ) ) |
|
{ |
|
pExistingLog->m_pLog = pLog; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a channels clip containing a particular channel |
|
//----------------------------------------------------------------------------- |
|
CDmeChannelsClip* CFlexControlBuilder::FindChannelsClipContainingChannel( CDmeFilmClip *pClip, CDmeChannel *pSearch ) |
|
{ |
|
int gc = pClip->GetTrackGroupCount(); |
|
for ( int i = 0; i < gc; ++i ) |
|
{ |
|
CDmeTrackGroup *pTrackGroup = pClip->GetTrackGroup( i ); |
|
DMETRACKGROUP_FOREACH_CLIP_TYPE_START( CDmeChannelsClip, pTrackGroup, pTrack, pChannelsClip ) |
|
|
|
int nChannels = pChannelsClip->m_Channels.Count(); |
|
for ( int j = 0; j < nChannels; ++j ) |
|
{ |
|
CDmeChannel *pChannel = pChannelsClip->m_Channels[ j ]; |
|
if ( pChannel == pSearch ) |
|
return pChannelsClip; |
|
} |
|
|
|
DMETRACKGROUP_FOREACH_CLIP_TYPE_END() |
|
} |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes a global offset and scale to convert from log time to global time |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::ComputeChannelTimeTransform( DmeTime_t *pOffset, double *pScale, CDmeChannelsClip *pChannelsClip ) |
|
{ |
|
// Determine the global time of the start + end of the log |
|
DmeClipStack_t srcStack; |
|
pChannelsClip->BuildClipStack( &srcStack, m_pMovie, NULL ); |
|
|
|
*pOffset = CDmeClip::FromChildMediaTime( srcStack, DMETIME_ZERO, false ); |
|
DmeTime_t duration = CDmeClip::FromChildMediaTime( srcStack, DmeTime_t( 10000 ), false ); |
|
duration -= *pOffset; |
|
*pScale = duration.GetSeconds(); |
|
} |
|
|
|
bool CFlexControlBuilder::ComputeChannelTimeTransform( DmeTime_t *pOffset, double *pScale, CDmeFilmClip* pClip, CDmeChannel* pChannel ) |
|
{ |
|
CDmeChannelsClip *pChannelsClip = FindChannelsClipContainingChannel( pClip, pChannel ); |
|
if ( !pChannelsClip ) |
|
return false; |
|
|
|
ComputeChannelTimeTransform( pOffset, pScale, pChannelsClip ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns an existing value/balance log |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::GetExistingVBLog( ExistingLogInfo_t *pLogs, CDmeFilmClip *pClip, CDmeBalanceToStereoCalculatorOperator *pStereoOp ) |
|
{ |
|
// Stereo operators always have value/balance logs attached |
|
DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( pStereoOp->GetHandle() ); |
|
for ( ; i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; i = g_pDataModel->NextAttributeReferencingElement( i ) ) |
|
{ |
|
CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); |
|
CDmeChannel *pChannel = CastElement< CDmeChannel >( pAttribute->GetOwner() ); |
|
const static UtlSymId_t symToElement = g_pDataModel->GetSymbol( "toElement" ); |
|
if ( !pChannel || pAttribute->GetNameSymbol() != symToElement ) |
|
continue; |
|
|
|
const char *pToAttributeName = pChannel->GetToAttribute()->GetName(); |
|
int nLogIndex = -1; |
|
if ( !Q_stricmp( pToAttributeName, "value" ) ) |
|
{ |
|
nLogIndex = CONTROL_VALUE; |
|
} |
|
else if ( !Q_stricmp( pToAttributeName, "balance" ) ) |
|
{ |
|
nLogIndex = CONTROL_BALANCE; |
|
} |
|
else |
|
{ |
|
continue; |
|
} |
|
|
|
CDmeFloatLog *pLog = CastElement< CDmeFloatLog >( pChannel->GetLog() ); |
|
if ( !pLog ) |
|
continue; |
|
|
|
// Compute a scale and offset transforming log time into global time |
|
if ( !ComputeChannelTimeTransform( &pLogs[nLogIndex].m_GlobalOffset, &pLogs[nLogIndex].m_flGlobalScale, pClip, pChannel ) ) |
|
continue; |
|
|
|
// Detach the |
|
pLogs[nLogIndex].m_pLog = pLog; |
|
pChannel->SetLog( NULL ); // Detach |
|
} |
|
} |
|
|
|
|
|
static void AddKeyToLogs( CDmeTypedLog< float > *valueLog, CDmeTypedLog< float > *balanceLog, const DmeTime_t& keyTime, float lval, float rval ) |
|
{ |
|
// Convert left right into value, balance |
|
float value, balance; |
|
LeftRightToValueBalance( &value, &balance, lval, rval ); |
|
|
|
// Msg( "%.5f setting l/r %f %f to value %f balance %f\n", |
|
// keyTime.GetSeconds(), lval, rval, value, balance ); |
|
|
|
valueLog->SetKey( keyTime, value ); |
|
balanceLog->SetKey( keyTime, balance ); |
|
} |
|
|
|
static void ConvertLRToVBLog( CDmeFloatLog *pValueLog, CDmeFloatLog *pBalanceLog, CDmeFloatLog *pLeftLog, CDmeFloatLog *pRightLog, DmeTime_t rightOffset, double flRightScale ) |
|
{ |
|
int lc = pLeftLog->GetKeyCount(); |
|
int rc = pRightLog->GetKeyCount(); |
|
|
|
int nLeft = 0, nRight = 0; |
|
while ( nLeft < lc || nRight < rc ) |
|
{ |
|
bool bUseLeft = ( nLeft < lc ); |
|
bool bUseRight = ( nRight < rc ); |
|
|
|
DmeTime_t leftKeyTime = bUseLeft ? pLeftLog->GetKeyTime( nLeft ) : DMETIME_MAXTIME; |
|
DmeTime_t rightKeyTime = bUseRight ? pRightLog->GetKeyTime( nRight ) : DMETIME_MAXTIME; |
|
|
|
// Transform rightKeyTime into leftKeyTime space |
|
if ( bUseRight ) |
|
{ |
|
rightKeyTime.SetSeconds( rightKeyTime.GetSeconds() * flRightScale ); |
|
rightKeyTime += rightOffset; |
|
} |
|
|
|
if ( leftKeyTime == rightKeyTime ) |
|
{ |
|
float lval = pLeftLog->GetKeyValue( nLeft++ ); |
|
float rval = pRightLog->GetKeyValue( nRight++ ); |
|
AddKeyToLogs( pValueLog, pBalanceLog, leftKeyTime, lval, rval ); |
|
continue; |
|
} |
|
|
|
if ( leftKeyTime < rightKeyTime ) |
|
{ |
|
// pull a value from the right log at the leftKeyTime |
|
// and advance to the next sample on the left side |
|
float lval = pLeftLog->GetKeyValue( nLeft++ ); |
|
float rval = pRightLog->GetValue( leftKeyTime ); |
|
AddKeyToLogs( pValueLog, pBalanceLog, leftKeyTime, lval, rval ); |
|
continue; |
|
} |
|
|
|
// Pull a value from the left log at the rightKeyTime |
|
// and advance to the next sample on the right side |
|
float lval = pLeftLog->GetValue( rightKeyTime ); |
|
float rval = pRightLog->GetKeyValue( nRight++ ); |
|
AddKeyToLogs( pValueLog, pBalanceLog, rightKeyTime, lval, rval ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Converts an existing value/balance log |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::ConvertExistingLRLogs( ExistingLogInfo_t *pLogs, |
|
CDmeFilmClip *pClip, CDmeChannel *pLeftChannel, CDmeChannel *pRightChannel ) |
|
{ |
|
CDmeFloatLog *pRightLog = CastElement< CDmeFloatLog >( pRightChannel->GetLog() ); |
|
CDmeFloatLog *pLeftLog = CastElement< CDmeFloatLog >( pLeftChannel->GetLog() ); |
|
if ( !pRightLog || !pLeftLog ) |
|
return; |
|
|
|
// Compute a scale + offset to transform the right log to get it in the same space as the left log |
|
DmeTime_t leftOffset, rightOffset; |
|
double flLeftScale, flRightScale; |
|
if ( !ComputeChannelTimeTransform( &leftOffset, &flLeftScale, pClip, pLeftChannel ) ) |
|
return; |
|
if ( !ComputeChannelTimeTransform( &rightOffset, &flRightScale, pClip, pRightChannel ) ) |
|
return; |
|
|
|
flRightScale = ( flRightScale != 0.0f ) ? flLeftScale / flRightScale : 1.0; |
|
rightOffset = leftOffset - DmeTime_t( rightOffset.GetSeconds() * flRightScale ); |
|
|
|
pLogs[CONTROL_VALUE].m_pLog = CreateElement< CDmeFloatLog >( "value" ); |
|
pLogs[CONTROL_VALUE].m_GlobalOffset = leftOffset; |
|
pLogs[CONTROL_VALUE].m_flGlobalScale = flLeftScale; |
|
|
|
pLogs[CONTROL_BALANCE].m_pLog = CreateElement< CDmeFloatLog >( "balance" ); |
|
pLogs[CONTROL_BALANCE].m_GlobalOffset = leftOffset; // NOTE: This is correct! All logs are transformed into left channel time |
|
pLogs[CONTROL_BALANCE].m_flGlobalScale = flLeftScale; |
|
|
|
ConvertLRToVBLog( pLogs[CONTROL_VALUE].m_pLog, pLogs[CONTROL_BALANCE].m_pLog, |
|
pLeftLog, pRightLog, rightOffset, flRightScale ); |
|
|
|
// DestroyElement( pLeftLog ); |
|
// DestroyElement( pRightLog ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns an existing stereo log, performing conversion if necessary |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::GetExistingStereoLog( ExistingLogInfo_t *pLogs, CDmeFilmClip *pClip, |
|
CDmeGlobalFlexControllerOperator *pRightOp, CDmeGlobalFlexControllerOperator *pLeftOp ) |
|
{ |
|
pLogs[CONTROL_VALUE].m_pLog = NULL; |
|
pLogs[CONTROL_BALANCE].m_pLog = NULL; |
|
|
|
// First, try to grab the channel referring to this op |
|
const static UtlSymId_t symToElement = g_pDataModel->GetSymbol( "toElement" ); |
|
CDmeChannel *pChannel = FindReferringElement< CDmeChannel >( pRightOp, symToElement ); |
|
if ( !pChannel ) |
|
return; |
|
|
|
// Sometimes a stereo op will be read from by this channel |
|
CDmeBalanceToStereoCalculatorOperator *pStereoOp = CastElement< CDmeBalanceToStereoCalculatorOperator >( pChannel->GetFromElement() ); |
|
if ( pStereoOp ) |
|
{ |
|
GetExistingVBLog( pLogs, pClip, pStereoOp ); |
|
return; |
|
} |
|
|
|
// In this case, we recorded game data and we have left/right logs |
|
CDmeChannel *pLeftChannel = FindReferringElement< CDmeChannel >( pLeftOp, symToElement ); |
|
if ( !pLeftChannel ) |
|
return; |
|
ConvertExistingLRLogs( pLogs, pClip, pLeftChannel, pChannel ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Fixup list of existing flex controller logs |
|
// - reattach flex controls that were removed from the gamemodel's list |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::FixupExistingFlexControlLogList( CDmeFilmClip *pCurrentClip, CDmeGameModel *pGameModel ) |
|
{ |
|
int nTrackGroups = pCurrentClip->GetTrackGroupCount(); |
|
for ( int gi = 0; gi < nTrackGroups; ++gi ) |
|
{ |
|
CDmeTrackGroup *pTrackGroup = pCurrentClip->GetTrackGroup( gi ); |
|
if ( !pTrackGroup ) |
|
continue; |
|
|
|
DMETRACKGROUP_FOREACH_CLIP_TYPE_START( CDmeChannelsClip, pTrackGroup, pTrack, pChannelsClip ) |
|
int nChannels = pChannelsClip->m_Channels.Count(); |
|
for ( int ci = 0; ci < nChannels; ++ci ) |
|
{ |
|
CDmeChannel *pChannel = pChannelsClip->m_Channels[ ci ]; |
|
if ( !pChannel ) |
|
continue; |
|
|
|
CDmeGlobalFlexControllerOperator *pOp = CastElement< CDmeGlobalFlexControllerOperator >( pChannel->GetToElement() ); |
|
if ( !pOp ) |
|
continue; |
|
|
|
if ( pOp->m_gameModel != pGameModel->GetHandle() ) |
|
continue; |
|
|
|
int nGlobalIndex = pOp->GetGlobalIndex(); |
|
CDmeGlobalFlexControllerOperator *pFoundOp = pGameModel->FindGlobalFlexController( nGlobalIndex ); |
|
if ( pFoundOp == pOp ) |
|
continue; |
|
|
|
if ( !pFoundOp ) |
|
{ |
|
Msg( "adding missing flex controller %d %s\n", nGlobalIndex, pOp->GetName() ); |
|
pFoundOp = pGameModel->AddGlobalFlexController( pOp->GetName(), nGlobalIndex ); |
|
} |
|
pChannel->SetOutput( pFoundOp, pChannel->GetToAttribute()->GetName() ); |
|
if ( pChannel->GetFromElement() == pOp ) |
|
{ |
|
pChannel->SetInput( pFoundOp, pChannel->GetFromAttribute()->GetName() ); |
|
} |
|
Msg( "removing duplicate flex controller %d %s\n", nGlobalIndex, pOp->GetName() ); |
|
RemoveElementFromRefereringAttributes( pOp ); |
|
DestroyElement( pOp ); |
|
} |
|
DMETRACKGROUP_FOREACH_CLIP_TYPE_END(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Build list of existing flex controller logs |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::BuildExistingFlexControlLogList( CDmeFilmClip *pCurrentClip, CDmeGameModel *pGameModel ) |
|
{ |
|
// These are the current flex controllers that also exist in the desired list |
|
// NOTE: Name of these controllers should match the names of the flex controllers |
|
int nCount = m_ControlInfo.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
ControlInfo_t &info = m_ControlInfo[i]; |
|
|
|
if ( info.m_bIsStereo ) |
|
{ |
|
int nRightFlex = info.m_pControllerIndex[ OUTPUT_RIGHT ]; |
|
int nLeftFlex = info.m_pControllerIndex[ OUTPUT_LEFT ]; |
|
FlexControllerInfo_t *pRightInfo = &m_FlexControllerInfo[nRightFlex]; |
|
FlexControllerInfo_t *pLeftInfo = &m_FlexControllerInfo[nLeftFlex]; |
|
|
|
CDmeGlobalFlexControllerOperator *pRightOp = pGameModel->FindGlobalFlexController( pRightInfo->m_nGlobalIndex ); |
|
CDmeGlobalFlexControllerOperator *pLeftOp = pGameModel->FindGlobalFlexController( pLeftInfo->m_nGlobalIndex ); |
|
if ( pRightOp && pLeftOp ) |
|
{ |
|
Msg( "replacing stereo flex controllers %s and %s\n", pRightOp->GetName(), pRightOp->GetName() ); |
|
|
|
GetExistingStereoLog( info.m_pExistingLog, pCurrentClip, pRightOp, pLeftOp ); |
|
CleanupExistingFlexController( pGameModel, pRightOp ); |
|
CleanupExistingFlexController( pGameModel, pLeftOp ); |
|
} |
|
} |
|
else |
|
{ |
|
int nFlex = info.m_pControllerIndex[ OUTPUT_MONO ]; |
|
FlexControllerInfo_t *pInfo = &m_FlexControllerInfo[nFlex]; |
|
|
|
CDmeGlobalFlexControllerOperator *pMonoOp = pGameModel->FindGlobalFlexController( pInfo->m_nGlobalIndex ); |
|
if ( pMonoOp ) |
|
{ |
|
Msg( "replacing mono flex controller %s\n", pMonoOp->GetName() ); |
|
|
|
GetExistingMonoLog( &info.m_pExistingLog[CONTROL_VALUE], pCurrentClip, pMonoOp ); |
|
CleanupExistingFlexController( pGameModel, pMonoOp ); |
|
} |
|
} |
|
|
|
if ( info.m_bIsMulti ) |
|
{ |
|
int nFlex = info.m_pControllerIndex[ OUTPUT_MULTILEVEL ]; |
|
FlexControllerInfo_t *pMultiInfo = &m_FlexControllerInfo[ nFlex ]; |
|
CDmeGlobalFlexControllerOperator *pMultiOp = pGameModel->FindGlobalFlexController( pMultiInfo->m_nGlobalIndex ); |
|
if ( pMultiOp ) |
|
{ |
|
Msg( "replacing multi flex controller %s\n", pMultiOp->GetName() ); |
|
|
|
GetExistingMonoLog( &info.m_pExistingLog[CONTROL_MULTILEVEL], pCurrentClip, pMultiOp ); |
|
CleanupExistingFlexController( pGameModel, pMultiOp ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a flex controller and a channel connecting it to a control |
|
//----------------------------------------------------------------------------- |
|
struct FlexOpInfo_t |
|
{ |
|
const char *m_pControlAttributeName; |
|
const char *m_pControlLinkAttributeName; |
|
}; |
|
|
|
static FlexOpInfo_t s_pFlexOpInfo[2] = |
|
{ |
|
{ "value", "" }, |
|
{ "multilevel", "multilevel" }, |
|
}; |
|
|
|
void CFlexControlBuilder::BuildFlexControllerOps( CDmeGameModel *pGameModel, CDmeChannelsClip *pChannelsClip, ControlInfo_t &info, ControlField_t field ) |
|
{ |
|
const FlexOpInfo_t& flexInfo = s_pFlexOpInfo[ ( field == CONTROL_VALUE ) ? 0 : 1 ]; |
|
|
|
// Get the global flex controller name and index |
|
const FlexControllerInfo_t& fcInfo = m_FlexControllerInfo[ info.m_pControllerIndex[field] ]; |
|
|
|
// Create operator which drives facial flex setting |
|
CDmeGlobalFlexControllerOperator *pFlexControllerOp = pGameModel->AddGlobalFlexController( |
|
fcInfo.m_pFlexControlName, fcInfo.m_nGlobalIndex ); |
|
|
|
// Create a channel which passes from the control value to the global flex controller |
|
char pName[ 256 ]; |
|
Q_snprintf( pName, sizeof( pName ), "%s_flex_channel", fcInfo.m_pFlexControlName ); |
|
info.m_ppControlChannel[field] = pChannelsClip->CreatePassThruConnection( pName, |
|
info.m_pControl, flexInfo.m_pControlAttributeName, pFlexControllerOp, "flexWeight" ); |
|
|
|
// NOTE: The animation set slider panel looks for these custom attributes |
|
Q_snprintf( pName, sizeof(pName), "%schannel", flexInfo.m_pControlLinkAttributeName ); |
|
info.m_pControl->SetValue( pName, info.m_ppControlChannel[field] ); |
|
|
|
// Switch the channel into play mode by default |
|
info.m_ppControlChannel[field]->SetMode( CM_PLAY ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a flex controller and a channel connecting it to stereo controls |
|
//----------------------------------------------------------------------------- |
|
static const char *s_pStereoOutputPrefix[2] = |
|
{ |
|
"right", |
|
"left", |
|
}; |
|
|
|
static const char *s_pStereoInputPrefix[2] = |
|
{ |
|
"value", |
|
"balance", |
|
}; |
|
|
|
void CFlexControlBuilder::BuildStereoFlexControllerOps( CDmeAnimationSet *pAnimationSet, |
|
CDmeGameModel *pGameModel, CDmeChannelsClip *pChannelsClip, ControlInfo_t &info ) |
|
{ |
|
// Create an operator which converts value/balance to left/right |
|
CDmrElementArray< CDmeOperator > operators = pAnimationSet->GetOperators(); |
|
CDmeBalanceToStereoCalculatorOperator *pStereoCalcOp = |
|
CreateElement< CDmeBalanceToStereoCalculatorOperator >( info.m_pControlName, pAnimationSet->GetFileId() ); |
|
operators.AddToTail( pStereoCalcOp->GetHandle() ); |
|
|
|
pStereoCalcOp->SetValue< float >( "value", info.m_pDefaultValue[CONTROL_VALUE] ); |
|
pStereoCalcOp->SetValue< float >( "balance", info.m_pDefaultValue[CONTROL_BALANCE] ); |
|
|
|
// Connect channels from animation set controls to balance operator to flex controller operators |
|
char pChannelName[ 256 ]; |
|
char pResultName[ 256 ]; |
|
for ( int i = 0; i < 2; ++i ) |
|
{ |
|
// Get the global flex controller name and index |
|
const FlexControllerInfo_t& fcInfo = m_FlexControllerInfo[ info.m_pControllerIndex[i] ]; |
|
|
|
// Create an operator which drives facial flex setting |
|
CDmeGlobalFlexControllerOperator *pFlexControllerOp = pGameModel->AddGlobalFlexController( |
|
fcInfo.m_pFlexControlName, fcInfo.m_nGlobalIndex ); |
|
|
|
// Now create a channel which connects the output of the stereo op to the flex controller op |
|
Q_snprintf( pResultName, sizeof( pResultName ), "result_%s", s_pStereoOutputPrefix[ i ] ); |
|
Q_snprintf( pChannelName, sizeof( pChannelName ), "%s_flex_channel", fcInfo.m_pFlexControlName ); |
|
pChannelsClip->CreatePassThruConnection( pChannelName, pStereoCalcOp, |
|
pResultName, pFlexControllerOp, "flexWeight" ); |
|
|
|
// Create a channel which connects the control to the input of the stereo op |
|
Q_snprintf( pChannelName, sizeof( pChannelName ), "%s_%s_channel", info.m_pControlName, s_pStereoInputPrefix[ i ] ); |
|
info.m_ppControlChannel[i] = pChannelsClip->CreatePassThruConnection( pChannelName, |
|
info.m_pControl, s_pStereoInputPrefix[ i ], pStereoCalcOp, s_pStereoInputPrefix[ i ] ); |
|
|
|
// NOTE: The animation set slider panel looks for these custom attributes |
|
Q_snprintf( pChannelName, sizeof(pChannelName), "%schannel", s_pStereoInputPrefix[ i ] ); |
|
info.m_pControl->SetValue( pChannelName, info.m_ppControlChannel[i] ); |
|
|
|
// Switch the channel into play mode by default |
|
info.m_ppControlChannel[i]->SetMode( CM_PLAY ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Build the infrastructure of the ops that connect that control to the dmegamemodel |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::AttachControlsToGameModel( CDmeAnimationSet *pAnimationSet, |
|
CDmeGameModel *pGameModel, CDmeChannelsClip *pChannelsClip ) |
|
{ |
|
// Build the infrastructure of the ops that connect that control to the dmegamemodel |
|
int c = m_ControlInfo.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
ControlInfo_t &info = m_ControlInfo[i]; |
|
if ( info.m_bIsStereo ) |
|
{ |
|
BuildStereoFlexControllerOps( pAnimationSet, pGameModel, pChannelsClip, info ); |
|
} |
|
else |
|
{ |
|
BuildFlexControllerOps( pGameModel, pChannelsClip, info, CONTROL_VALUE ); |
|
} |
|
|
|
if ( info.m_bIsMulti ) |
|
{ |
|
BuildFlexControllerOps( pGameModel, pChannelsClip, info, CONTROL_MULTILEVEL ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Initializes the fields of a flex control |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::InitializeFlexControl( ControlInfo_t &info ) |
|
{ |
|
CDmElement *pControl = info.m_pControl; |
|
|
|
// Remove these, if they exist... |
|
for ( int i = 0; i < CONTROL_CHANNEL_ATTRIBUTE_COUNT; ++i ) |
|
{ |
|
pControl->RemoveAttribute( s_pChannelControls[i] ); |
|
} |
|
|
|
// Force these to always be up-to-date |
|
pControl->SetValue< bool >( "combo", info.m_bIsStereo ); |
|
pControl->SetValue< bool >( "multi", info.m_bIsMulti ); |
|
pControl->SetValue< float >( "defaultValue", info.m_pDefaultValue[CONTROL_VALUE] ); |
|
pControl->SetValue< float >( "defaultBalance", info.m_pDefaultValue[CONTROL_BALANCE] ); |
|
pControl->SetValue< float >( "defaultMultilevel", info.m_pDefaultValue[CONTROL_MULTILEVEL] ); |
|
|
|
// These can keep their value if they already exist |
|
pControl->InitValue< float >( "value", info.m_pDefaultValue[CONTROL_VALUE] ); |
|
if ( info.m_bIsStereo ) |
|
{ |
|
pControl->InitValue< float >( "balance", info.m_pDefaultValue[CONTROL_BALANCE] ); |
|
} |
|
else |
|
{ |
|
pControl->RemoveAttribute( "balance" ); |
|
} |
|
if ( info.m_bIsMulti ) |
|
{ |
|
pControl->InitValue< float >( "multilevel", info.m_pDefaultValue[CONTROL_MULTILEVEL] ); |
|
} |
|
else |
|
{ |
|
pControl->RemoveAttribute( "multilevel" ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates all controls for flexes |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::CreateFlexControls( CDmeAnimationSet *pAnimationSet ) |
|
{ |
|
// Create a facial control for all input controls |
|
int c = m_ControlInfo.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
ControlInfo_t &info = m_ControlInfo[i]; |
|
|
|
// Check to see if the animation set already has the control |
|
info.m_pControl = pAnimationSet->FindOrAddControl( info.m_pControlName ); |
|
|
|
// Now initialize the fields of the flex control |
|
InitializeFlexControl( info ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Attaches existing logs and sets default values for logs |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::SetupLogs( CDmeChannelsClip *pChannelsClip, bool bUseExistingLogs ) |
|
{ |
|
DmeTime_t targetOffset; |
|
double flTargetScale; |
|
ComputeChannelTimeTransform( &targetOffset, &flTargetScale, pChannelsClip ); |
|
double flOOTargetScale = ( flTargetScale != 0.0 ) ? 1.0 / flTargetScale : 1.0; |
|
|
|
// Build the infrastructure of the ops that connect that control to the dmegamemodel |
|
int c = m_ControlInfo.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
ControlInfo_t &info = m_ControlInfo[i]; |
|
for ( int j = 0; j < CONTROL_FIELD_COUNT; ++j ) |
|
{ |
|
// Can happen for non-multi or non-stereo controls |
|
if ( !info.m_ppControlChannel[j] ) |
|
continue; |
|
|
|
// Replace the existing log if we need to |
|
CDmeFloatLog *pFloatLog = CastElement< CDmeFloatLog >( info.m_ppControlChannel[j]->GetLog() ); |
|
if ( bUseExistingLogs && info.m_pExistingLog[j].m_pLog ) |
|
{ |
|
info.m_ppControlChannel[j]->SetLog( info.m_pExistingLog[j].m_pLog ); |
|
DestroyElement( pFloatLog ); |
|
pFloatLog = info.m_pExistingLog[j].m_pLog; |
|
|
|
// Apply transform to get the log into the space of the current channel |
|
double flTotalScale = info.m_pExistingLog[j].m_flGlobalScale * flOOTargetScale; |
|
DmeTime_t totalOffset = info.m_pExistingLog[j].m_GlobalOffset - targetOffset; |
|
totalOffset.SetSeconds( totalOffset.GetSeconds() * flOOTargetScale ); |
|
pFloatLog->ScaleBiasKeyTimes( flTotalScale, totalOffset ); |
|
} |
|
|
|
// Set the default value for this log |
|
pFloatLog->SetDefaultValue( info.m_pDefaultValue[j] ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Main entry point for creating flex animation set controls |
|
//----------------------------------------------------------------------------- |
|
void CFlexControlBuilder::CreateAnimationSetControls( CDmeFilmClip *pMovie, CDmeAnimationSet *pAnimationSet, |
|
CDmeGameModel *pGameModel, CDmeFilmClip *pSourceClip, CDmeChannelsClip *pDestClip, bool bUseExistingLogs ) |
|
{ |
|
m_pMovie = pMovie; |
|
|
|
FixupExistingFlexControlLogList( pSourceClip, pGameModel ); |
|
|
|
// First, look at the current mdl and determine what are its low-level flexcontrollers |
|
// [these are the outputs eventually driven by the animation set controls] |
|
BuildDesiredFlexControlList( pGameModel ); |
|
|
|
// Next, based on the list of low-level flexcontrollers, determine a high-level set of input controls |
|
BuildDesiredControlList( pGameModel ); |
|
|
|
// Next look at what the animation set currently thinks are the input controls + low-level flexcontrollers |
|
// and remove the unused ones |
|
RemoveUnusedExistingFlexControllers( pGameModel ); |
|
|
|
RemoveUnusedControlsAndChannels( pAnimationSet, pDestClip ); |
|
|
|
if ( bUseExistingLogs ) |
|
{ |
|
// Look at the current input controls + low-level flexcontrollers |
|
// and grab logs that drive them so we can apply them to the new controls |
|
BuildExistingFlexControlLogList( pSourceClip, pGameModel ); |
|
} |
|
|
|
// Create the input controls we decided we needed in BuildDesiredControlList |
|
CreateFlexControls( pAnimationSet ); |
|
|
|
// Build channels + control logis attaching the input controls to the low level flex controls |
|
AttachControlsToGameModel( pAnimationSet, pGameModel, pDestClip ); |
|
|
|
// Attach existing logs to the new input controls created in CreateFlexControls |
|
SetupLogs( pDestClip, bUseExistingLogs ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Initialize default global flex controller |
|
//----------------------------------------------------------------------------- |
|
void SetupDefaultFlexController() |
|
{ |
|
g_pGlobalFlexController = &s_GlobalFlexController; |
|
} |