diff options
Diffstat (limited to 'sfmobjects')
| -rw-r--r-- | sfmobjects/exportfacialanimation.cpp | 275 | ||||
| -rw-r--r-- | sfmobjects/flexcontrolbuilder.cpp | 951 | ||||
| -rw-r--r-- | sfmobjects/sfmanimationsetutils.cpp | 891 | ||||
| -rw-r--r-- | sfmobjects/sfmobjects.vpc | 42 | ||||
| -rw-r--r-- | sfmobjects/sfmphonemeextractor.cpp | 1186 | ||||
| -rw-r--r-- | sfmobjects/sfmsession.cpp | 285 |
6 files changed, 3630 insertions, 0 deletions
diff --git a/sfmobjects/exportfacialanimation.cpp b/sfmobjects/exportfacialanimation.cpp new file mode 100644 index 0000000..df744e3 --- /dev/null +++ b/sfmobjects/exportfacialanimation.cpp @@ -0,0 +1,275 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// A class representing session state for the SFM +// +//============================================================================= + +#include "sfmobjects/exportfacialanimation.h" +#include "movieobjects/dmeclip.h" +#include "movieobjects/dmeanimationset.h" +#include "movieobjects/dmegamemodel.h" +#include "movieobjects/dmetrackgroup.h" +#include "movieobjects/dmetrack.h" +#include "movieobjects/dmesound.h" +#include "movieobjects/dmelog.h" +#include "movieobjects/dmechannel.h" + + +//----------------------------------------------------------------------------- +// Contains export information +//----------------------------------------------------------------------------- +struct ExportInfo_t +{ + CDmeFilmClip *m_pMovie; + CDmeFilmClip *m_pShot; + CDmeAnimationSet *m_pAnimationSet; + DmeTime_t m_tExportStart; + DmeTime_t m_tExportEnd; +}; + + +//----------------------------------------------------------------------------- +// Used to transform channel data into export time +//----------------------------------------------------------------------------- +static void ComputeExportChannelScaleBias( double *pScale, DmeTime_t *pBias, ExportInfo_t &info, CDmeChannel *pChannel ) +{ + DmeClipStack_t channelToGlobal; + if ( pChannel->BuildClipStack( &channelToGlobal, info.m_pMovie, info.m_pShot ) ) + { + DmeTime_t tOffset = CDmeClip::FromChildMediaTime( channelToGlobal, DMETIME_ZERO, false ); + DmeTime_t tScale = CDmeClip::FromChildMediaTime( channelToGlobal, DmeTime_t( 1.0f ), false ); + *pBias = tOffset - info.m_pShot->GetStartTime(); + *pScale = ( tScale - tOffset ).GetSeconds(); + } +} + +static void GetExportTimeRange( DmeTime_t *pExportStart, DmeTime_t *pExportEnd, CDmeFilmClip *pShot ) +{ + *pExportStart = DMETIME_ZERO; + *pExportEnd = pShot->GetDuration(); +} + + +//----------------------------------------------------------------------------- +// Adds a log layer to the list of logs for export +//----------------------------------------------------------------------------- +static void AddLogLayerForExport( ExportInfo_t &info, CDmElement *pRoot, const char *pControlName, CDmeChannel *pChannel ) +{ + CDmeLog *pLog = pChannel->GetLog(); + if ( !pLog || pLog->GetNumLayers() == 0 ) + return; + + CDmrElementArray<> animations( pRoot, "animations" ); + + DmeTime_t tBias; + double flScale; + ComputeExportChannelScaleBias( &flScale, &tBias, info, pChannel ); + + // Only export the base layer + CDmeLogLayer* pLogLayer = pLog->GetLayer( 0 )->Copy(); + pLogLayer->SetName( pControlName ); + pLogLayer->ScaleBiasKeyTimes( flScale, tBias ); + + // Forcibly add keys @ the start + end time + DmeTime_t tStartTime = ( info.m_tExportStart - tBias ) / flScale; + DmeTime_t tEndTime = ( info.m_tExportEnd - tBias ) / flScale; + pLogLayer->InsertKeyFromLayer( info.m_tExportStart, pLog->GetLayer(0), tStartTime ); + pLogLayer->InsertKeyFromLayer( info.m_tExportEnd, pLog->GetLayer(0), tEndTime ); + + pLogLayer->RemoveKeysOutsideRange( info.m_tExportStart, info.m_tExportEnd ); + animations.AddToTail( pLogLayer ); +} + + +//----------------------------------------------------------------------------- +// Exports animations +//----------------------------------------------------------------------------- +static void ExportAnimations( ExportInfo_t &info, CDmElement *pRoot ) +{ + CDmrElementArray<> animations( pRoot, "animations", true ); + + // Build a list of all controls + const CDmaElementArray< CDmElement > &controls = info.m_pAnimationSet->GetControls(); + int nControlCount = controls.Count(); + for ( int i = 0; i < nControlCount; ++i ) + { + CDmElement *pControl = controls[i]; + if ( !pControl || pControl->GetValue<bool>( "transform" ) ) + continue; + bool bIsStereo = pControl->GetValue<bool>( "combo" ); + if ( bIsStereo ) + { + CDmeChannel *pValueChannel = pControl->GetValueElement<CDmeChannel>( "valuechannel" ); + CDmeChannel *pBalanceChannel = pControl->GetValueElement<CDmeChannel>( "balancechannel" ); + + CDmeLog *pValueLog = pValueChannel->GetLog(); + CDmeLog *pBalanceLog = pBalanceChannel->GetLog(); + if ( pValueLog && pBalanceLog && pValueLog->GetNumLayers() != 0 && pBalanceLog->GetNumLayers() != 0 ) + { + DmeTime_t tValueBias, tBalanceBias; + double flValueScale, flBalanceScale; + ComputeExportChannelScaleBias( &flValueScale, &tValueBias, info, pValueChannel ); + ComputeExportChannelScaleBias( &flBalanceScale, &tBalanceBias, info, pBalanceChannel ); + + // Make copy to maintain log layer types + CDmeLogLayer *pValueLogLayer = pValueLog->GetLayer( 0 )->Copy(); + CDmeLogLayer *pBalanceLogLayer = pBalanceLog->GetLayer( 0 )->Copy(); + pValueLogLayer->ScaleBiasKeyTimes( flValueScale, tValueBias ); + pBalanceLogLayer->ScaleBiasKeyTimes( flBalanceScale, tBalanceBias ); + + // Forcibly insert keys @ start + end times. + DmeTime_t tStartTime = ( info.m_tExportStart - tValueBias ) / flValueScale; + DmeTime_t tEndTime = ( info.m_tExportEnd - tValueBias ) / flValueScale; + pValueLogLayer->InsertKeyFromLayer( info.m_tExportStart, pValueLog->GetLayer(0), tStartTime ); + pValueLogLayer->InsertKeyFromLayer( info.m_tExportEnd, pValueLog->GetLayer(0), tEndTime ); + + tStartTime = ( info.m_tExportStart - tBalanceBias ) / flBalanceScale; + tEndTime = ( info.m_tExportEnd - tBalanceBias ) / flBalanceScale; + pBalanceLogLayer->InsertKeyFromLayer( info.m_tExportStart, pBalanceLog->GetLayer(0), tStartTime ); + pBalanceLogLayer->InsertKeyFromLayer( info.m_tExportEnd, pBalanceLog->GetLayer(0), tEndTime ); + + pValueLogLayer->RemoveKeysOutsideRange( info.m_tExportStart, info.m_tExportEnd ); + pBalanceLogLayer->RemoveKeysOutsideRange( info.m_tExportStart, info.m_tExportEnd ); + + char pControlName[512]; + Q_snprintf( pControlName, sizeof(pControlName), "value_%s", pControl->GetName() ); + pValueLogLayer->SetName( pControlName ); + animations.AddToTail( pValueLogLayer ); + + Q_snprintf( pControlName, sizeof(pControlName), "balance_%s", pControl->GetName() ); + pBalanceLogLayer->SetName( pControlName ); + animations.AddToTail( pBalanceLogLayer ); + } + } + else + { + CDmeChannel *pChannel = pControl->GetValueElement<CDmeChannel>( "channel" ); + AddLogLayerForExport( info, pRoot, pControl->GetName(), pChannel ); + } + + if ( pControl->GetValue<bool>( "multi" ) ) + { + char pControlName[512]; + Q_snprintf( pControlName, sizeof(pControlName), "multi_%s", pControl->GetName() ); + CDmeChannel *pMultiChannel = pControl->GetValueElement<CDmeChannel>( "multilevelchannel" ); + AddLogLayerForExport( info, pRoot, pControlName, pMultiChannel ); + } + } +} + + +//----------------------------------------------------------------------------- +// Helper to export sounds +//----------------------------------------------------------------------------- +static void ExportSounds( ExportInfo_t &info, CDmElement *pRoot, CDmeClip *pClip, DmeTime_t tOffset ) +{ + CDmrElementArray<> sounds( pRoot, "sounds", true ); + + DmeClipStack_t soundToGlobal; + int gc = pClip->GetTrackGroupCount(); + for ( int i = 0; i < gc; ++i ) + { + CDmeTrackGroup *pTrackGroup = pClip->GetTrackGroup( i ); + DMETRACKGROUP_FOREACH_CLIP_TYPE_START( CDmeSoundClip, pTrackGroup, pTrack, pSoundClip ) + + const char *pGameSoundName = pSoundClip->m_Sound->m_GameSoundName; + if ( !pGameSoundName || !pGameSoundName[0] ) + continue; + + if ( pSoundClip->IsMute() ) + continue; + + if ( !pSoundClip->BuildClipStack( &soundToGlobal, info.m_pMovie, pClip ) ) + continue; + + DmeTime_t tStart = CDmeClip::FromChildMediaTime( soundToGlobal, DMETIME_ZERO, false ); + DmeTime_t tEnd = CDmeClip::FromChildMediaTime( soundToGlobal, pSoundClip->GetDuration(), false ); + tStart -= tOffset; + tEnd -= tOffset; + if ( tStart >= info.m_tExportEnd || tEnd <= info.m_tExportStart ) + continue; + + const char *pName = pSoundClip->GetName(); + CDmElement *pSoundEvent = CreateElement<CDmElement>( pName ); + pSoundEvent->SetValue( "start", tStart.GetTenthsOfMS() ); + pSoundEvent->SetValue( "end", tEnd.GetTenthsOfMS() ); + pSoundEvent->SetValue( "gamesound", pGameSoundName ); + sounds.AddToTail( pSoundEvent ); + + DMETRACKGROUP_FOREACH_CLIP_TYPE_END() + } +} + +static void ExportSounds_R( ExportInfo_t &info, CDmElement *pRoot, CDmeClip *pClip, DmeTime_t tOffset ) +{ + ExportSounds( info, pRoot, pClip, tOffset ); + + // Recurse + DmeClipStack_t childToGlobal; + int gc = pClip->GetTrackGroupCount(); + for ( int i = 0; i < gc; ++i ) + { + CDmeTrackGroup *pTrackGroup = pClip->GetTrackGroup( i ); + DMETRACKGROUP_FOREACH_CLIP_START( pTrackGroup, pTrack, pChild ) + + if ( !pChild->BuildClipStack( &childToGlobal, info.m_pMovie, pClip ) ) + continue; + + DmeTime_t tStart = CDmeClip::FromChildMediaTime( childToGlobal, DMETIME_ZERO, false ); + DmeTime_t tEnd = CDmeClip::FromChildMediaTime( childToGlobal, pChild->GetDuration(), false ); + tStart -= tOffset; + tEnd -= tOffset; + if ( tStart >= info.m_tExportEnd || tEnd <= info.m_tExportStart ) + continue; + + ExportSounds_R( info, pRoot, pChild, tOffset ); + + DMETRACKGROUP_FOREACH_CLIP_END() + } +} + + +//----------------------------------------------------------------------------- +// Exports sounds, default implementation +//----------------------------------------------------------------------------- +static void ExportSounds( ExportInfo_t &info, CDmElement *pRoot ) +{ + DmeTime_t tOffset = info.m_pShot->GetStartTime(); + ExportSounds( info, pRoot, info.m_pMovie, tOffset ); + ExportSounds_R( info, pRoot, info.m_pShot, tOffset ); +} + + +//----------------------------------------------------------------------------- +// Exports an .fac file +//----------------------------------------------------------------------------- +bool ExportFacialAnimation( const char *pFileName, CDmeFilmClip *pMovie, CDmeFilmClip *pShot, CDmeAnimationSet *pAnimationSet ) +{ + if ( !pMovie || !pShot || !pAnimationSet ) + return false; + + const char *pFileFormat = "facial_animation"; + CDmElement *pRoot = CreateElement< CDmElement >( pAnimationSet->GetName() ); + + ExportInfo_t info; + info.m_pMovie = pMovie; + info.m_pShot = pShot; + info.m_pAnimationSet = pAnimationSet; + GetExportTimeRange( &info.m_tExportStart, &info.m_tExportEnd, pShot ); + + CDmeGameModel *pGameModel = pAnimationSet->GetValueElement<CDmeGameModel>( "gameModel" ); + if ( pGameModel ) + { + pRoot->SetValue( "gamemodel", pGameModel->GetModelName() ); + } + ExportAnimations( info, pRoot ); + ExportSounds( info, pRoot ); + + pRoot->SetFileId( DMFILEID_INVALID, TD_DEEP ); + const char *pEncoding = "keyvalues2_flat"; + bool bOk = g_pDataModel->SaveToFile( pFileName, NULL, pEncoding, pFileFormat, pRoot ); + DestroyElement( pRoot, TD_DEEP ); + return bOk; +} + + diff --git a/sfmobjects/flexcontrolbuilder.cpp b/sfmobjects/flexcontrolbuilder.cpp new file mode 100644 index 0000000..cfa24a4 --- /dev/null +++ b/sfmobjects/flexcontrolbuilder.cpp @@ -0,0 +1,951 @@ +//========= 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; +}
\ No newline at end of file diff --git a/sfmobjects/sfmanimationsetutils.cpp b/sfmobjects/sfmanimationsetutils.cpp new file mode 100644 index 0000000..fb17a46 --- /dev/null +++ b/sfmobjects/sfmanimationsetutils.cpp @@ -0,0 +1,891 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// NOTE: This is a cut-and-paste hack job to get animation set construction +// working from a commandline tool. It came from tools/ifm/createsfmanimation.cpp +// This file needs to die almost immediately + be replaced with a better solution +// that can be used both by the sfm + sfmgen. +// +//============================================================================= + +#include "sfmobjects/sfmanimationsetutils.h" +#include "movieobjects/dmechannel.h" +#include "movieobjects/dmeclip.h" +#include "movieobjects/dmetrackgroup.h" +#include "movieobjects/dmetrack.h" +#include "movieobjects/dmecamera.h" +#include "movieobjects/dmetimeselection.h" +#include "movieobjects/dmeanimationset.h" +#include "movieobjects/dmegamemodel.h" +#include "sfmobjects/flexcontrolbuilder.h" +#include "tier3/tier3.h" +#include "bone_setup.h" +#include "vstdlib/random.h" +#include "tier1/KeyValues.h" +#include "filesystem.h" +#include "movieobjects/timeutils.h" + + +#define ANIMATION_SET_DEFAULT_GROUP_MAPPING_FILE "cfg/SFM_DefaultAnimationGroups.txt" +#define STANDARD_CHANNEL_TRACK_GROUP "channelTrackGroup" +#define STANDARD_ANIMATIONSET_CHANNELS_TRACK "animSetEditorChannels" +#define CLIP_PREROLL_TIME DmeTime_t( 5.0f ) +#define CLIP_POSTROLL_TIME DmeTime_t( 5.0f ) + + +//----------------------------------------------------------------------------- +// Creates channels clip for the animation set +//----------------------------------------------------------------------------- +static CDmeChannelsClip* CreateChannelsClip( CDmeAnimationSet *pAnimationSet, CDmeFilmClip *pOwnerClip ) +{ + CDmeTrackGroup *pTrackGroup = pOwnerClip->FindOrAddTrackGroup( "channelTrackGroup" ); + if ( !pTrackGroup ) + { + Assert( 0 ); + return NULL; + } + + CDmeTrack *pAnimSetEditorTrack = pTrackGroup->FindOrAddTrack( "animSetEditorChannels", DMECLIP_CHANNEL ); + Assert( pAnimSetEditorTrack ); + + CDmeChannelsClip *pChannelsClip = CreateElement< CDmeChannelsClip >( pAnimationSet->GetName(), pAnimationSet->GetFileId() ); + pAnimSetEditorTrack->AddClip( pChannelsClip ); + + DmeTime_t childMediaTime = pOwnerClip->GetStartInChildMediaTime(); + pChannelsClip->SetStartTime( childMediaTime - CLIP_PREROLL_TIME ); + DmeTime_t childMediaDuration = pOwnerClip->ToChildMediaDuration( pOwnerClip->GetDuration() ); + pChannelsClip->SetDuration( childMediaDuration + CLIP_PREROLL_TIME + CLIP_POSTROLL_TIME ); + return pChannelsClip; +} + + +//----------------------------------------------------------------------------- +// Creates a constant valued log +//----------------------------------------------------------------------------- +template < class T > +CDmeChannel *CreateConstantValuedLog( CDmeChannelsClip *channelsClip, const char *basename, const char *pName, CDmElement *pToElement, const char *pToAttr, const T &value ) +{ + char name[ 256 ]; + Q_snprintf( name, sizeof( name ), "%s_%s channel", basename, pName ); + + CDmeChannel *pChannel = CreateElement< CDmeChannel >( name, channelsClip->GetFileId() ); + pChannel->SetMode( CM_PLAY ); + pChannel->CreateLog( CDmAttributeInfo< T >::AttributeType() ); + pChannel->SetOutput( pToElement, pToAttr ); + pChannel->GetLog()->SetValueThreshold( 0.0f ); + + ((CDmeTypedLog< T > *)pChannel->GetLog())->InsertKey( DmeTime_t( 0 ), value ); + + channelsClip->m_Channels.AddToTail( pChannel ); + + return pChannel; +} + + +//----------------------------------------------------------------------------- +// Create channels for transform data +//----------------------------------------------------------------------------- +static void CreateTransformChannels( CDmeTransform *pTransform, const char *pBaseName, int bi, CDmeChannelsClip *pChannelsClip ) +{ + char name[ 256 ]; + + // create, connect and cache bonePos channel + Q_snprintf( name, sizeof( name ), "%s_bonePos channel %d", pBaseName, bi ); + CDmeChannel *pPosChannel = CreateElement< CDmeChannel >( name, pChannelsClip->GetFileId() ); + pPosChannel->SetMode( CM_PLAY ); + pPosChannel->CreateLog( AT_VECTOR3 ); + pPosChannel->SetOutput( pTransform, "position" ); + pPosChannel->GetLog()->SetValueThreshold( 0.0f ); + pChannelsClip->m_Channels.AddToTail( pPosChannel ); + + // create, connect and cache boneRot channel + Q_snprintf( name, sizeof( name ), "%s_boneRot channel %d", pBaseName, bi ); + CDmeChannel *pRotChannel = CreateElement< CDmeChannel >( name, pChannelsClip->GetFileId() ); + pRotChannel->SetMode( CM_PLAY ); + pRotChannel->CreateLog( AT_QUATERNION ); + pRotChannel->SetOutput( pTransform, "orientation" ); + pRotChannel->GetLog()->SetValueThreshold( 0.0f ); + pChannelsClip->m_Channels.AddToTail( pRotChannel ); +} + +static void CreateAnimationLogs( CDmeChannelsClip *channelsClip, CDmeGameModel *pModel, studiohdr_t *pStudioHdr, const char *basename, int sequence, float flStartTime, float flDuration, float flTimeStep = 0.015f ) +{ + Assert( pModel ); + Assert( pStudioHdr ); + + CStudioHdr hdr( pStudioHdr, g_pMDLCache ); + + if ( sequence >= hdr.GetNumSeq() ) + { + sequence = 0; + } + + int numbones = hdr.numbones(); + + // make room for bones + CUtlVector< CDmeDag* > dags; + CUtlVector< CDmeChannel * > poschannels; + CUtlVector< CDmeChannel * > rotchannels; + + dags.EnsureCapacity( numbones ); + poschannels.EnsureCapacity( numbones ); + rotchannels.EnsureCapacity( numbones ); + + Vector pos[ MAXSTUDIOBONES ]; + Quaternion q[ MAXSTUDIOBONES ]; + + float poseparameter[ MAXSTUDIOPOSEPARAM ]; + for ( int pp = 0; pp < MAXSTUDIOPOSEPARAM; ++pp ) + { + poseparameter[ pp ] = 0.0f; + } + + float flSequenceDuration = Studio_Duration( &hdr, sequence, poseparameter ); + mstudioseqdesc_t &seqdesc = hdr.pSeqdesc( sequence ); + + bool created = false; + + for ( float t = flStartTime; t <= flStartTime + flDuration; t += flTimeStep ) + { + int bi; + + if ( t > flStartTime + flDuration ) + t = flStartTime + flDuration; + + float flCycle = t / flSequenceDuration; + + if ( seqdesc.flags & STUDIO_LOOPING ) + { + flCycle = flCycle - (int)flCycle; + if (flCycle < 0) flCycle += 1; + } + else + { + flCycle = max( 0.f, min( flCycle, 0.9999f ) ); + } + + if ( !created ) + { + created = true; + + // create, connect and cache each bone's pos and rot channels + for ( bi = 0; bi < numbones; ++bi ) + { + int nCount = channelsClip->m_Channels.Count(); + + CDmeTransform *pTransform = pModel->GetBone( bi ); + CreateTransformChannels( pTransform, basename, bi, channelsClip ); + + CDmeChannel *pPosChannel = channelsClip->m_Channels[ nCount ]; + CDmeChannel *pRotChannel = channelsClip->m_Channels[ nCount+1 ]; + poschannels.AddToTail( pPosChannel ); + rotchannels.AddToTail( pRotChannel ); + } + } + + // Set up skeleton + IBoneSetup boneSetup( &hdr, BONE_USED_BY_ANYTHING, poseparameter ); + boneSetup.InitPose( pos, q ); + boneSetup.AccumulatePose( pos, q, sequence, flCycle, 1.0f, t, NULL ); + + // Copy bones into recording logs + for ( bi = 0 ; bi < numbones; ++bi ) + { + ((CDmeVector3Log *)poschannels[ bi ]->GetLog())->InsertKey( DmeTime_t( t ), pos[ bi ] ); + ((CDmeQuaternionLog *)rotchannels[ bi ]->GetLog())->InsertKey( DmeTime_t( t ), q[ bi ] ); + } + } +} + + + +static CDmeChannelsClip *FindChannelsClipTargetingDmeGameModel( CDmeFilmClip *pClip, CDmeGameModel *pGameModel ) +{ + uint nBoneCount = pGameModel->NumBones(); + CDmeTransform *pGameModelTransform = pGameModel->GetTransform(); + + 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 ) + + if ( FindChannelTargetingElement( pChannelsClip, pGameModel ) ) + return pChannelsClip; + + if ( FindChannelTargetingElement( pChannelsClip, pGameModelTransform ) ) + return pChannelsClip; + + for ( uint j = 0; j < nBoneCount; ++j ) + { + if ( FindChannelTargetingElement( pChannelsClip, pGameModel->GetBone( j ) ) ) + return pChannelsClip; + } + + DMETRACKGROUP_FOREACH_CLIP_TYPE_END() + } + + return NULL; +} + + +static void RetimeLogData( CDmeChannelsClip *pSrcChannelsClip, CDmeChannelsClip *pDstChannelsClip, CDmeLog *pLog ) +{ + float srcScale = pSrcChannelsClip->GetTimeScale(); + float dstScale = pDstChannelsClip->GetTimeScale(); + DmeTime_t srcStart = pSrcChannelsClip->GetStartTime(); + DmeTime_t dstStart = pDstChannelsClip->GetStartTime(); + DmeTime_t srcOffset = pSrcChannelsClip->GetTimeOffset(); + DmeTime_t dstOffset = pDstChannelsClip->GetTimeOffset(); + srcOffset -= srcStart; + dstOffset -= dstStart; + if ( srcScale != dstScale || srcOffset != dstOffset ) + { + // for speed, I pulled out the math converting out of one timeframe into another: + // t = (t/f0-o0+s0 -s1+o1)*f1 + // = t * f1/f0 + f1 * (o1-o0-s1+s0) + float scale = dstScale / srcScale; + DmeTime_t offset = dstScale * ( dstOffset - srcOffset ); + int nKeys = pLog->GetKeyCount(); + for ( int i = 0; i < nKeys; ++i ) + { + DmeTime_t keyTime = pLog->GetKeyTime( i ); + keyTime = keyTime * scale + offset; + pLog->SetKeyTime( i, keyTime ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Once bones have been setup and flex channels moved, the only things left should be: +// a channel logging the model's "visibility" state +// a channel logging the model's "sequence" +// a channel loggint the model's "viewtarget" position +//----------------------------------------------------------------------------- +static void TransferRemainingChannels( CDmeFilmClip *shot, CDmeChannelsClip *destClip, CDmeChannelsClip *srcClip ) +{ + if ( srcClip == destClip ) + return; + + int channelsCount = srcClip->m_Channels.Count(); + for ( int i = 0; i < channelsCount; ++i ) + { + // Remove channel from channels clip + CDmeChannel *channel = srcClip->m_Channels[ i ]; + Assert( channel ); + if ( !channel ) + continue; + + Msg( "Transferring '%s'\n", channel->GetName() ); + + destClip->m_Channels.AddToTail( channel ); + channel->SetMode( CM_PLAY ); + + // Transfer the logs over to the + CDmeLog *log = channel->GetLog(); + if ( log ) + { + RetimeLogData( srcClip, destClip, log ); + } + } + + srcClip->m_Channels.RemoveAll(); + + // Now find the track which contains the srcClip and remove the srcClip from the track + for ( DmAttributeReferenceIterator_t it = g_pDataModel->FirstAttributeReferencingElement( srcClip->GetHandle() ); + it != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; + it = g_pDataModel->NextAttributeReferencingElement( it ) ) + { + CDmAttribute *attr = g_pDataModel->GetAttribute( it ); + Assert( attr ); + CDmElement *element = attr->GetOwner(); + Assert( element ); + if ( !element ) + continue; + + CDmeTrack *t = CastElement< CDmeTrack >( element ); + if ( !t ) + continue; + + t->RemoveClip( srcClip ); + g_pDataModel->DestroyElement( srcClip->GetHandle() ); + break; + } +} + +static void SetupBoneTransform( CDmeFilmClip *shot, CDmeChannelsClip *srcChannelsClip, CDmeChannelsClip *channelsClip, + CDmElement *control, CDmeGameModel *gameModel, const char *basename, studiohdr_t *hdr, int bonenum, const char *boneName, bool bAttachToGameRecording ) +{ + const char *channelNames[] = { "position", "orientation" }; + const char *valueNames[] = { "valuePosition", "valueOrientation" }; + const char *suffix[] = { "Pos", "Rot" }; + + DmAttributeType_t channelTypes[] = { AT_VECTOR3, AT_QUATERNION }; + int i; + + CDmeTransform *pBoneTxForm = gameModel->GetBone( bonenum ); + + for ( i = 0; i < 2 ; ++i ) + { + char szName[ 512 ]; + Q_snprintf( szName, sizeof( szName ), "%s_bone%s %d", basename, suffix[ i ], bonenum ); + + CDmeChannel *pAttachChannel = NULL; + if ( srcChannelsClip ) + { + pAttachChannel = FindChannelTargetingElement( srcChannelsClip, pBoneTxForm, channelNames[ i ] ); + } + + if ( !pAttachChannel ) + { + // Create one + pAttachChannel = CreateElement< CDmeChannel >( szName, channelsClip->GetFileId() ); + Assert( pAttachChannel ); + pAttachChannel->SetOutput( pBoneTxForm, channelNames[ i ], 0 ); + } + + if ( !pAttachChannel ) + continue; + + if ( bAttachToGameRecording && srcChannelsClip ) + { + // Remove channel from channels clip + int idx = srcChannelsClip->m_Channels.Find( pAttachChannel->GetHandle() ); + if ( idx != srcChannelsClip->m_Channels.InvalidIndex() ) + { + srcChannelsClip->m_Channels.Remove( idx ); + } + channelsClip->m_Channels.AddToTail( pAttachChannel ); + } + + control->SetValue( channelNames[ i ], pAttachChannel ); + control->AddAttribute( valueNames[ i ], channelTypes[ i ] ); + + CDmeLog *pOriginalLog = pAttachChannel->GetLog(); + + pAttachChannel->SetMode( CM_PLAY ); + pAttachChannel->SetInput( control, valueNames[ i ] ); + + // Transfer the logs over to the + if ( bAttachToGameRecording && pOriginalLog && srcChannelsClip ) + { + CDmeLog *pNewLog = pAttachChannel->GetLog(); + if ( pNewLog != pOriginalLog ) + { + pAttachChannel->SetLog( pOriginalLog ); + g_pDataModel->DestroyElement( pNewLog->GetHandle() ); + } + + DmeTime_t tLogToGlobal[ 2 ]; + + Assert(0); + // NOTE: Fix the next 2 lines to look like createsfmanimation.cpp + DmeTime_t curtime = DMETIME_ZERO; //doc->GetTime(); + DmeTime_t cmt = DMETIME_ZERO; //doc->ToCurrentMediaTime( curtime, false ); + DmeTime_t channelscliptime = shot->ToChildMediaTime( cmt, false ); + + DmeTime_t logtime = channelsClip->ToChildMediaTime( channelscliptime, false ); + + tLogToGlobal[ 0 ] = curtime - logtime; + + DmeTime_t attachlogtime = srcChannelsClip->ToChildMediaTime( channelscliptime, false ); + + tLogToGlobal[ 1 ] = curtime - attachlogtime; + + DmeTime_t offset = tLogToGlobal[ 1 ] - tLogToGlobal[ 0 ]; + + if ( DMETIME_ZERO != offset ) + { + int c = pOriginalLog->GetKeyCount(); + for ( int iLog = 0; iLog < c; ++iLog ) + { + DmeTime_t keyTime = pOriginalLog->GetKeyTime( iLog ); + keyTime += offset; + pOriginalLog->SetKeyTime( iLog, keyTime ); + } + } + continue; + } + + if ( pOriginalLog ) + { + pOriginalLog->ClearKeys(); + } + + CDmeLog *log = pAttachChannel->GetLog(); + if ( !log ) + { + log = pAttachChannel->CreateLog( channelTypes[ i ] ); + } + + log->SetValueThreshold( 0.0f ); + if ( bAttachToGameRecording ) + { + Vector pos; + Quaternion rot; + + matrix3x4_t matrix; + pBoneTxForm->GetTransform( matrix ); + MatrixAngles( matrix, rot, pos ); + + if ( i == 0 ) + { + ((CDmeTypedLog< Vector > *)log)->SetKey( DMETIME_ZERO, pos ); + } + else + { + ((CDmeTypedLog< Quaternion > *)log)->SetKey( DMETIME_ZERO, rot ); + } + continue; + } + + CStudioHdr studiohdr( hdr, g_pMDLCache ); + + Vector pos[ MAXSTUDIOBONES ]; + Quaternion q[ MAXSTUDIOBONES ]; + float poseparameter[ MAXSTUDIOPOSEPARAM ]; + for ( int pp = 0; pp < MAXSTUDIOPOSEPARAM; ++pp ) + { + poseparameter[ pp ] = 0.0f; + } + + // Set up skeleton + IBoneSetup boneSetup( &studiohdr, BONE_USED_BY_ANYTHING, poseparameter ); + boneSetup.InitPose( pos, q ); + boneSetup.AccumulatePose( pos, q, 0, 0.0f, 1.0f, 0.0f, NULL ); + + if ( i == 0 ) + { + ((CDmeTypedLog< Vector > *)log)->SetKey( DMETIME_ZERO, pos[ bonenum ] ); + pBoneTxForm->SetPosition( pos[ bonenum ]); + } + else + { + ((CDmeTypedLog< Quaternion > *)log)->SetKey( DMETIME_ZERO, q[ bonenum ] ); + pBoneTxForm->SetOrientation( q[ bonenum ] ); + } + } +} + + +//----------------------------------------------------------------------------- +// Sets up the root transform +//----------------------------------------------------------------------------- +static void SetupRootTransform( CDmeFilmClip *shot, CDmeChannelsClip *srcChannelsClip, + CDmeChannelsClip *channelsClip, CDmElement *control, CDmeGameModel *gameModel, const char *basename, bool bAttachToGameRecording ) +{ + char *channelNames[] = { "position", "orientation" }; + char *valueNames[] = { "valuePosition", "valueOrientation" }; + DmAttributeType_t channelTypes[] = { AT_VECTOR3, AT_QUATERNION }; + const char *suffix[] = { "Pos", "Rot" }; + DmAttributeType_t logType[] = { AT_VECTOR3, AT_QUATERNION }; + + int i; + for ( i = 0; i < 2 ; ++i ) + { + char szName[ 512 ]; + Q_snprintf( szName, sizeof( szName ), "%s_root%s channel", basename, suffix[ i ] ); + + CDmeChannel *pAttachChannel = NULL; + if ( srcChannelsClip ) + { + pAttachChannel = FindChannelTargetingElement( srcChannelsClip, gameModel->GetTransform(), channelNames[ i ] ); + } + + if ( !pAttachChannel ) + { + // Create one + pAttachChannel = CreateElement< CDmeChannel >( szName, channelsClip->GetFileId() ); + Assert( pAttachChannel ); + pAttachChannel->SetOutput( gameModel->GetTransform(), channelNames[ i ], 0 ); + } + + if ( bAttachToGameRecording && srcChannelsClip ) + { + // Remove channel from channels clip + int idx = srcChannelsClip->m_Channels.Find( pAttachChannel->GetHandle() ); + if ( idx != srcChannelsClip->m_Channels.InvalidIndex() ) + { + srcChannelsClip->m_Channels.Remove( idx ); + } + channelsClip->m_Channels.AddToTail( pAttachChannel ); + } + + control->SetValue( channelNames[ i ], pAttachChannel ); + control->AddAttribute( valueNames[ i ], channelTypes[ i ] ); + + CDmeLog *pOriginalLog = pAttachChannel->GetLog(); + + pAttachChannel->SetMode( CM_PLAY ); + pAttachChannel->SetInput( control, valueNames[ i ] ); + + if ( bAttachToGameRecording && pOriginalLog && srcChannelsClip ) + { + CDmeLog *pNewLog = pAttachChannel->GetLog(); + if ( pNewLog != pOriginalLog ) + { + pAttachChannel->SetLog( pOriginalLog ); + g_pDataModel->DestroyElement( pNewLog->GetHandle() ); + } + + RetimeLogData( srcChannelsClip, channelsClip, pOriginalLog ); + } + else + { + Assert( !pOriginalLog ); + CDmeLog *log = pAttachChannel->GetLog(); + if ( !log ) + { + log = pAttachChannel->CreateLog( logType[ i ] ); + } + + log->SetValueThreshold( 0.0f ); + + Vector vecPos; + Quaternion qOrientation; + + matrix3x4_t txform; + gameModel->GetTransform()->GetTransform( txform ); + + MatrixAngles( txform, qOrientation, vecPos ); + + if ( i == 0 ) + { + ((CDmeTypedLog< Vector > *)log)->SetKey( DMETIME_ZERO, vecPos ); + } + else + { + ((CDmeTypedLog< Quaternion > *)log)->SetKey( DMETIME_ZERO, qOrientation ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Creates preset groups for new animation sets +//----------------------------------------------------------------------------- +static bool ShouldRandomize( const char *name ) +{ + if ( !Q_stricmp( name, "eyes_updown" ) ) + return false; + if ( !Q_stricmp( name, "eyes_rightleft" ) ) + return false; + if ( !Q_stricmp( name, "lip_bite" ) ) + return false; + if ( !Q_stricmp( name, "blink" ) ) + return false; + if ( Q_stristr( name, "sneer" ) ) + return false; + return true; +} + +static void CreateProceduralPreset( CDmePresetGroup *pPresetGroup, const char *pPresetName, const CDmaElementArray< CDmElement > &controls, bool bIdentity, float flForceValue = 0.5f ) +{ + CDmePreset *pPreset = pPresetGroup->FindOrAddPreset( pPresetName ); + + int c = controls.Count(); + for ( int i = 0; i < c ; ++i ) + { + CDmElement *pControl = controls[ i ]; + + // Setting values on transforms doesn't make sense right now + if ( pControl->GetValue<bool>( "transform" ) ) + continue; + + bool bIsCombo = pControl->GetValue< bool >( "combo" ); + bool bIsMulti = pControl->GetValue< bool >( "multi" ); + bool bRandomize = ShouldRandomize( pControl->GetName() ); + if ( !bIdentity && !bRandomize ) + continue; + + CDmElement *pControlValue = pPreset->FindOrAddControlValue( pControl->GetName() ); + + if ( !bIdentity ) + { + pControlValue->SetValue< float >( "value", RandomFloat( 0.0f, 1.0f ) ); + if ( bIsCombo ) + { + pControlValue->SetValue< float >( "balance", RandomFloat( 0.25f, 0.75f ) ); + } + if ( bIsMulti ) + { + pControlValue->SetValue< float >( "multilevel", RandomFloat( 0.0f, 1.0f ) ); + } + } + else + { + pControlValue->SetValue< float >( "value", flForceValue ); + if ( bIsCombo ) + { + pControlValue->SetValue< float >( "balance", 0.5f ); + } + if ( bIsMulti ) + { + pControlValue->SetValue< float >( "multilevel", flForceValue ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Creates preset groups for new animation sets +//----------------------------------------------------------------------------- +static void CreatePresetGroups( CDmeAnimationSet *pAnimationSet, const char *pModelName ) +{ + CDmaElementArray< CDmElement > &controls = pAnimationSet->GetControls(); + + // Now create some presets + CDmePresetGroup *pProceduralPresets = pAnimationSet->FindOrAddPresetGroup( "procedural" ); + pProceduralPresets->m_bIsReadOnly = true; + pProceduralPresets->FindOrAddPreset( "Default" ); + CreateProceduralPreset( pProceduralPresets, "Zero", controls, true, 0.0f ); + CreateProceduralPreset( pProceduralPresets, "Half", controls, true, 0.5f ); + CreateProceduralPreset( pProceduralPresets, "One", controls, true, 1.0f ); + + // Add just one fake one for now + CreateProceduralPreset( pProceduralPresets, "Random", controls, false ); + + // These are the truly procedural ones... + pAnimationSet->EnsureProceduralPresets(); + + // Also load the model-specific presets + g_pModelPresetGroupMgr->ApplyModelPresets( pModelName, pAnimationSet ); +} + + +//----------------------------------------------------------------------------- +// Destroys existing group mappings +//----------------------------------------------------------------------------- +static void RemoveExistingGroupMappings( CDmeAnimationSet *pAnimationSet ) +{ + CDmaElementArray<> &groups = pAnimationSet->GetSelectionGroups(); + int nCount = groups.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pGroup = groups[i]; + groups.Set( i, NULL ); + DestroyElement( pGroup ); + } + groups.RemoveAll(); +} + + +void LoadDefaultGroupMappings( CUtlDict< CUtlString, int > &defaultGroupMapping, CUtlVector< CUtlString >& defaultGroupOrdering ) +{ + defaultGroupMapping.RemoveAll(); + defaultGroupOrdering.RemoveAll(); + + KeyValues *pGroupFile = new KeyValues( "groupFile" ); + if ( !pGroupFile ) + return; + + if ( !pGroupFile->LoadFromFile( g_pFullFileSystem, ANIMATION_SET_DEFAULT_GROUP_MAPPING_FILE, "GAME" ) ) + { + pGroupFile->deleteThis(); + return; + } + + // Fill in defaults + for ( KeyValues *sub = pGroupFile->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + const char *pGroupName = sub->GetName(); + if ( !pGroupName ) + { + Warning( "%s is malformed\n", ANIMATION_SET_DEFAULT_GROUP_MAPPING_FILE ); + continue; + } + + int i = defaultGroupOrdering.AddToTail(); + defaultGroupOrdering[i] = pGroupName; + + for ( KeyValues *pControl = sub->GetFirstSubKey(); pControl; pControl = pControl->GetNextKey() ) + { + Assert( !Q_stricmp( pControl->GetName(), "control" ) ); + CUtlString controlName = pControl->GetString(); + defaultGroupMapping.Insert( controlName, pGroupName ); + } + } + + pGroupFile->deleteThis(); +} + +CDmElement *FindOrAddDefaultGroupForControls( const char *pGroupName, CDmaElementArray< CDmElement > &groups, DmFileId_t fileid ) +{ + // Now see if this group exists in the array + int c = groups.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *pGroup = groups[ i ]; + if ( !Q_stricmp( pGroup->GetName(), pGroupName ) ) + return pGroup; + } + + CDmElement *pGroup = CreateElement< CDmElement >( pGroupName, fileid ); + pGroup->AddAttribute( "selectedControls", AT_STRING_ARRAY ); + groups.AddToTail( pGroup ); + return pGroup; +} + +//----------------------------------------------------------------------------- +// Build group mappings +//----------------------------------------------------------------------------- +static void BuildGroupMappings( CDmeAnimationSet *pAnimationSet ) +{ + RemoveExistingGroupMappings( pAnimationSet ); + + // Maps flex controls to first level "groups" by flex controller name + CUtlDict< CUtlString, int > defaultGroupMapping; + CUtlVector< CUtlString > defaultGroupOrdering; + + LoadDefaultGroupMappings( defaultGroupMapping, defaultGroupOrdering ); + + // Create the default groups in order + CDmaElementArray<> &groups = pAnimationSet->GetSelectionGroups(); + int nCount = defaultGroupOrdering.Count(); + for ( int i = 0; i < nCount; ++i ) + { + const char *pGroupName = (const char *)defaultGroupOrdering[ i ]; + if ( !Q_stricmp( pGroupName, "IGNORE" ) ) + continue; + + CDmElement *pGroup = CreateElement< CDmElement >( pGroupName, pAnimationSet->GetFileId() ); + + // Fill in members + pGroup->AddAttribute( "selectedControls", AT_STRING_ARRAY ); + groups.AddToTail( pGroup ); + } + + // Populate the groups with the controls + CDmaElementArray<> &controls = pAnimationSet->GetControls(); + nCount = controls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + const char *pGroupName = "Unknown"; + const char *pControlName = controls[ i ]->GetName(); + + // Find the default if there is one + int idx = defaultGroupMapping.Find( pControlName ); + if ( idx != defaultGroupMapping.InvalidIndex() ) + { + pGroupName = defaultGroupMapping[ idx ]; + } + else if ( Q_stristr( pControlName, "root" ) || Q_stristr( pControlName, "Valve" ) ) + { + pGroupName = "Root"; + } + + if ( !Q_stricmp( pGroupName, "IGNORE" ) ) + continue; + + CDmElement *pGroup = FindOrAddDefaultGroupForControls( pGroupName, groups, pAnimationSet->GetFileId() ); + + // Fill in members + CDmrStringArray selectedControls( pGroup, "selectedControls" ); + Assert( selectedControls.IsValid() ); + if ( selectedControls.IsValid() ) + { + selectedControls.AddToTail( pControlName ); + } + } +} + +void AddIllumPositionAttribute( CDmeGameModel *pGameModel ) +{ + studiohdr_t *pHdr = pGameModel->GetStudioHdr(); + if ( !pHdr ) + return; + + if ( pHdr->IllumPositionAttachmentIndex() > 0 ) + return; // don't add attr if model already has illumposition attachment + + CDmAttribute *pAttr = pGameModel->AddAttributeElement< CDmeDag >( "illumPositionDag" ); + Assert( pAttr ); + if ( !pAttr ) + return; + + Assert( pGameModel->GetChildCount() > 0 ); + pAttr->SetValue( pGameModel->GetChild( 0 ) ); +} + +//----------------------------------------------------------------------------- +// Creates an animation set +//----------------------------------------------------------------------------- +CDmeAnimationSet *CreateAnimationSet( CDmeFilmClip *pMovie, CDmeFilmClip *pShot, + CDmeGameModel *pGameModel, const char *pAnimationSetName, int nSequenceToUse, bool bAttachToGameRecording ) +{ + CDmeAnimationSet *pAnimationSet = CreateElement< CDmeAnimationSet >( pAnimationSetName, pMovie->GetFileId() ); + Assert( pAnimationSet ); + + studiohdr_t *hdr = pGameModel->GetStudioHdr(); + + // Associate this animation set with a specific game model + // FIXME: Should the game model refer back to this set? + pAnimationSet->SetValue( "gameModel", pGameModel ); + + CDmeChannelsClip* pChannelsClip = CreateChannelsClip( pAnimationSet, pShot ); + + // Does everything associated with building facial controls on a model + CFlexControlBuilder builder; + builder.CreateAnimationSetControls( pMovie, pAnimationSet, pGameModel, pShot, pChannelsClip, bAttachToGameRecording ); + + // Create animation data if there wasn't any already in the model + if ( !bAttachToGameRecording ) + { + CreateConstantValuedLog( pChannelsClip, pAnimationSetName, "skin", pGameModel, "skin", (int)0 ); + CreateConstantValuedLog( pChannelsClip, pAnimationSetName, "body", pGameModel, "body", (int)0 ); + CreateConstantValuedLog( pChannelsClip, pAnimationSetName, "sequence", pGameModel, "sequence", (int)0 ); + + CreateAnimationLogs( pChannelsClip, pGameModel, hdr, pAnimationSetName, nSequenceToUse, 0.0f, 1.0f, 0.05f ); + } + + CDmeChannelsClip *srcChannelsClip = FindChannelsClipTargetingDmeGameModel( pShot, pGameModel ); + CDmaElementArray<> &controls = pAnimationSet->GetControls(); + + // First the root transform + { + const char *ctrlName = "rootTransform"; + + // Add the control to the controls group + CDmElement *ctrl = CreateElement< CDmElement >( ctrlName, pMovie->GetFileId() ); + Assert( ctrl ); + ctrl->SetValue< bool >( "transform", true ); + controls.AddToTail( ctrl ); + SetupRootTransform( pShot, srcChannelsClip, pChannelsClip, ctrl, pGameModel, pAnimationSetName, bAttachToGameRecording ); + } + + // Now add the bone transforms as well + { + int numbones = hdr->numbones; + for ( int b = 0; b < numbones; ++b ) + { + mstudiobone_t *bone = hdr->pBone( b ); + const char *name = bone->pszName(); + + // Add the control to the controls group + CDmElement *ctrl = CreateElement< CDmElement >( name, pMovie->GetFileId() ); + Assert( ctrl ); + ctrl->SetValue< bool >( "transform", true ); + controls.AddToTail( ctrl ); + SetupBoneTransform( pShot, srcChannelsClip, pChannelsClip, ctrl, pGameModel, pAnimationSetName, hdr, b, name, bAttachToGameRecording ); + } + } + + // Now copy all remaining logs, and retime them, over to the animation set channels clip... + if ( srcChannelsClip ) + { + TransferRemainingChannels( pShot, pChannelsClip, srcChannelsClip ); + } + + // Create default preset groups for the animation set + CreatePresetGroups( pAnimationSet, pGameModel->GetModelName() ); + + // Builds the preset groups displayed in the upper left of the animation set panel + BuildGroupMappings( pAnimationSet ); + + pShot->AddAnimationSet( pAnimationSet ); + + AddIllumPositionAttribute( pGameModel ); + + return pAnimationSet; +} diff --git a/sfmobjects/sfmobjects.vpc b/sfmobjects/sfmobjects.vpc new file mode 100644 index 0000000..2a6b335 --- /dev/null +++ b/sfmobjects/sfmobjects.vpc @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// SFMOBJECTS.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." + +$Include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;SFMOBJECTS_LIB" + } +} + +$Project "sfmobjects" +{ + $Folder "Header Files" + { + } + + $Folder "Source Files" + { + $File "exportfacialanimation.cpp" + $File "flexcontrolbuilder.cpp" + $File "sfmsession.cpp" + $File "sfmanimationsetutils.cpp" + $File "sfmphonemeextractor.cpp" + } + + $Folder "Interface" + { + $File "$SRCDIR\public\sfmobjects\exportfacialanimation.h" + $File "$SRCDIR\public\sfmobjects\flexcontrolbuilder.h" + $File "$SRCDIR\public\sfmobjects\sfmanimationsetutils.h" + $File "$SRCDIR\public\sfmobjects\sfmphonemeextractor.h" + $File "$SRCDIR\public\sfmobjects\sfmsession.h" + } +} diff --git a/sfmobjects/sfmphonemeextractor.cpp b/sfmobjects/sfmphonemeextractor.cpp new file mode 100644 index 0000000..e6b1c92 --- /dev/null +++ b/sfmobjects/sfmphonemeextractor.cpp @@ -0,0 +1,1186 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "sfmobjects/SFMPhonemeExtractor.h" +#include "tier2/riff.h" +#include "PhonemeConverter.h" +#include "filesystem.h" +#include "tier1/utlbuffer.h" +#include "sentence.h" +#include "movieobjects/dmesound.h" +#include "movieobjects/dmeanimationset.h" +#include "movieobjects/dmebookmark.h" +#include "movieobjects/dmeclip.h" +#include "movieobjects/dmechannel.h" +#include "soundchars.h" +#include "tier2/p4helpers.h" +#include "tier2/soundutils.h" +#include "tier1/utldict.h" + +#include <windows.h> // WAVEFORMATEX, WAVEFORMAT and ADPCM WAVEFORMAT!!! +#include <mmreg.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +static const char *s_pAttributeValueNames[LOG_PREVIEW_FLEX_CHANNEL_COUNT] = +{ + "value", + "balance", + "multilevel" +}; + +static const char *s_pDefaultAttributeValueNames[LOG_PREVIEW_FLEX_CHANNEL_COUNT] = +{ + "defaultValue", + "defaultBalance", + "defaultMultilevel" +}; + + +struct Extractor +{ + PE_APITYPE apitype; + CSysModule *module; + IPhonemeExtractor *extractor; +}; + + +//----------------------------------------------------------------------------- +// Implementations of the phoneme extractor +//----------------------------------------------------------------------------- +class CSFMPhonemeExtractor : public ISFMPhonemeExtractor +{ +public: + CSFMPhonemeExtractor(); + + // Inherited from ISFMPhonemeExtractor + virtual bool Init(); + virtual void Shutdown(); + virtual int GetAPICount(); + virtual void GetAPIInfo( int index, CUtlString* pPrintName, PE_APITYPE *pAPIType ); + virtual void Extract( const PE_APITYPE& apiType, ExtractDesc_t& info, bool bWritePhonemesToWavFiles ); + virtual void ReApply( ExtractDesc_t& info ); + virtual bool GetSentence( CDmeGameSound *gameSound, CSentence& sentence ); + +private: + int FindExtractor( PE_APITYPE type ); + bool GetWaveFormat( const char *filename, CUtlBuffer* pFormat, int *pDataSize, CSentence& sentence, bool &bGotSentence ); + void LogPhonemes( int nItemIndex, ExtractDesc_t& info ); + void ClearInterstitialSpaces( CDmeChannelsClip *pChannelsClip, CUtlDict< LogPreview_t *, int >& controlLookup, ExtractDesc_t& info ); + + void StampControlValueLogs( CDmePreset *preset, DmeTime_t tHeadPosition, float flIntensity, CUtlDict< LogPreview_t *, int > &controlLookup ); + void WriteCurrentValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup ); + void WriteDefaultValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup ); + void BuildPhonemeLogList( CUtlVector< LogPreview_t > &list, CUtlVector< CDmeLog * > &logs ); + CDmeChannelsClip* FindFacialChannelsClip( const CUtlVector< LogPreview_t > &list ); + void BuildPhonemeToPresetMapping( const CUtlVector< CBasePhonemeTag * > &stream, CDmeAnimationSet *pSet, CDmePresetGroup * pPresetGroup, CUtlDict< CDmePreset *, unsigned short > &phonemeToPresetDict ); + + CUtlVector< Extractor > m_Extractors; + int m_nCurrentExtractor; +}; + + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +static CSFMPhonemeExtractor g_ExtractorSingleton; +ISFMPhonemeExtractor *sfm_phonemeextractor = &g_ExtractorSingleton; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CSFMPhonemeExtractor::CSFMPhonemeExtractor() : m_nCurrentExtractor( -1 ) +{ +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +bool CSFMPhonemeExtractor::Init() +{ + // Enumerate modules under bin folder of exe + FileFindHandle_t findHandle; + const char *pFilename = g_pFullFileSystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle ); + while( pFilename ) + { + char fullpath[ 512 ]; + Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename ); + + // Msg( "Loading extractor from %s\n", fullpath ); + + Extractor e; + e.module = g_pFullFileSystem->LoadModule( fullpath ); + if ( !e.module ) + { + pFilename = g_pFullFileSystem->FindNext( findHandle ); + continue; + } + + CreateInterfaceFn factory = Sys_GetFactory( e.module ); + if ( !factory ) + { + pFilename = g_pFullFileSystem->FindNext( findHandle ); + continue; + } + + e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL ); + if ( !e.extractor ) + { + Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath ); + pFilename = g_pFullFileSystem->FindNext( findHandle ); + continue; + } + + e.apitype = e.extractor->GetAPIType(); + + m_Extractors.AddToTail( e ); + pFilename = g_pFullFileSystem->FindNext( findHandle ); + } + + g_pFullFileSystem->FindClose( findHandle ); + return true; +} + +void CSFMPhonemeExtractor::Shutdown() +{ + int c = m_Extractors.Count(); + for ( int i = c - 1; i >= 0; i-- ) + { + Extractor *e = &m_Extractors[ i ]; + g_pFullFileSystem->UnloadModule( e->module ); + } + + m_Extractors.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Finds an extractor of a particular type +//----------------------------------------------------------------------------- +int CSFMPhonemeExtractor::FindExtractor( PE_APITYPE type ) +{ + for ( int i=0; i < m_Extractors.Count(); i++ ) + { + if ( m_Extractors[i].apitype == type ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Iterates over extractors +//----------------------------------------------------------------------------- +int CSFMPhonemeExtractor::GetAPICount() +{ + return m_Extractors.Count(); +} + +void CSFMPhonemeExtractor::GetAPIInfo( int index, CUtlString* pPrintName, PE_APITYPE *pAPIType ) +{ + Assert( pPrintName ); + Assert( pAPIType ); + pPrintName->Set( m_Extractors[ index ].extractor->GetName() ); + *pAPIType = m_Extractors[ index ].apitype; +} + +static void ParseSentence( CSentence& sentence, IterateRIFF &walk ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buf.EnsureCapacity( walk.ChunkSize() ); + walk.ChunkRead( buf.Base() ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() ); + + sentence.InitFromDataChunk( buf.Base(), buf.TellPut() ); +} + +bool CSFMPhonemeExtractor::GetWaveFormat( const char *filename, CUtlBuffer *pBuf, int *pDataSize, CSentence& sentence, bool &bGotSentence ) +{ + InFileRIFF riff( filename, *g_pFSIOReadBinary ); + Assert( riff.RIFFName() == RIFF_WAVE ); + + // set up the iterator for the whole file (root RIFF is a chunk) + IterateRIFF walk( riff, riff.RIFFSize() ); + + bool gotFmt = false; + bool gotData = false; + bGotSentence = false; + + // Walk input chunks and copy to output + while ( walk.ChunkAvailable() ) + { + switch ( walk.ChunkName() ) + { + case WAVE_FMT: + { + pBuf->SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() ); + walk.ChunkRead( pBuf->Base() ); + gotFmt = true; + } + break; + case WAVE_DATA: + { + *pDataSize = walk.ChunkSize(); + gotData = true; + } + break; + case WAVE_VALVEDATA: + { + bGotSentence = true; + ParseSentence( sentence, walk ); + } + break; + default: + break; + } + + // Done + if ( gotFmt && gotData && bGotSentence ) + return true; + + walk.ChunkNext(); + } + return ( gotFmt && gotData ); +} + +bool CSFMPhonemeExtractor::GetSentence( CDmeGameSound *gameSound, CSentence& sentence ) +{ + const char *filename = gameSound->m_SoundName.Get(); + Assert( filename && filename [ 0 ] ); + + char soundname[ 512 ]; + // Note, calling PSkipSoundChars to remove any decorator characters used by the engine!!! + Q_snprintf( soundname, sizeof( soundname ), "sound/%s", PSkipSoundChars( filename ) ); + Q_FixSlashes( soundname ); + + char fullpath[ 512 ]; + g_pFullFileSystem->RelativePathToFullPath( soundname, "GAME", fullpath, sizeof( fullpath ) ); + + // Get sound file metrics of interest + CUtlBuffer buf; + int nDataSize; + bool bValidSentence = false; + if ( !GetWaveFormat( soundname, &buf, &nDataSize, sentence, bValidSentence ) ) + return false; + + return bValidSentence; +} + +static void BuildPhonemeStream( CSentence& in, CUtlVector< CBasePhonemeTag * >& list ) +{ + for ( int i = 0; i < in.m_Words.Count(); ++i ) + { + CWordTag *w = in.m_Words[ i ]; + if ( !w ) + continue; + + for ( int j = 0; j < w->m_Phonemes.Count(); ++j ) + { + CPhonemeTag *ph = w->m_Phonemes[ j ]; + if ( !ph ) + continue; + + CBasePhonemeTag *newTag = new CBasePhonemeTag( *ph ); + list.AddToTail( newTag ); + } + } + + if ( !in.m_Words.Count() && in.m_RunTimePhonemes.Count() ) + { + for ( int i = 0 ; i < in.m_RunTimePhonemes.Count(); ++i ) + { + CBasePhonemeTag *newTag = new CBasePhonemeTag( *in.m_RunTimePhonemes[ i ] ); + list.AddToTail( newTag ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Same the phoneme data into the sound files +//----------------------------------------------------------------------------- +static void StoreValveDataChunk( CSentence& sentence, IterateOutputRIFF& store ) +{ + // Buffer and dump data + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + sentence.SaveToBuffer( buf ); + + // Copy into store + store.ChunkWriteData( buf.Base(), buf.TellPut() ); +} + +static bool SaveSentenceToWavFile( const char *pWavFile, CSentence& sentence ) +{ + char pTempFile[ 512 ]; + + Q_StripExtension( pWavFile, pTempFile, sizeof( pTempFile ) ); + Q_DefaultExtension( pTempFile, ".tmp", sizeof( pTempFile ) ); + + if ( g_pFullFileSystem->FileExists( pTempFile, "GAME" ) ) + { + g_pFullFileSystem->RemoveFile( pTempFile, "GAME" ); + } + + CP4AutoEditAddFile p4Checkout( pWavFile ); + if ( !g_pFullFileSystem->IsFileWritable( pWavFile ) ) + { + Warning( "%s is not writable, can't save sentence data to file\n", pWavFile ); + return false; + } + + // Rename original pWavFile to temp + g_pFullFileSystem->RenameFile( pWavFile, pTempFile, "GAME" ); + + // NOTE: Put this in it's own scope so that the destructor for outfileRFF actually closes the file!!!! + { + // Read from Temp + InFileRIFF riff( pTempFile, *g_pFSIOReadBinary ); + Assert( riff.RIFFName() == RIFF_WAVE ); + + // set up the iterator for the whole file (root RIFF is a chunk) + IterateRIFF walk( riff, riff.RIFFSize() ); + + // And put data back into original pWavFile by name + OutFileRIFF riffout( pWavFile, *g_pFSIOWriteBinary ); + + IterateOutputRIFF store( riffout ); + + bool bWordTrackWritten = false; + + // Walk input chunks and copy to output + while ( walk.ChunkAvailable() ) + { + store.ChunkStart( walk.ChunkName() ); + + switch ( walk.ChunkName() ) + { + case WAVE_VALVEDATA: + { + // Overwrite data + StoreValveDataChunk( sentence, store ); + bWordTrackWritten = true; + } + break; + default: + store.CopyChunkData( walk ); + break; + } + + store.ChunkFinish(); + + walk.ChunkNext(); + } + + // If we didn't write it above, write it now + if ( !bWordTrackWritten ) + { + store.ChunkStart( WAVE_VALVEDATA ); + StoreValveDataChunk( sentence, store ); + store.ChunkFinish(); + } + } + + // Remove temp file + g_pFullFileSystem->RemoveFile( pTempFile, NULL ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Main entry point for phoneme extraction +//----------------------------------------------------------------------------- +void CSFMPhonemeExtractor::Extract( const PE_APITYPE& apiType, ExtractDesc_t& info, bool bWritePhonemesToWavFiles ) +{ + if ( !info.m_pSet ) + return; + + int iExtractor = FindExtractor( apiType ); + if ( iExtractor == -1 ) + return; + + Extractor& extractor = m_Extractors[ iExtractor ]; + + int nWorkItem; + for ( nWorkItem = 0; nWorkItem < info.m_WorkList.Count(); ++nWorkItem ) + { + CExtractInfo& workItem = info.m_WorkList[ nWorkItem ]; + + workItem.m_flDuration = 0.0f; + + CSentence in; + CSentence out; + in.SetText( workItem.m_sHintText.String() ); + out.SetText( workItem.m_sHintText.String() ); + + const char *pFileName = workItem.m_pSound->m_SoundName.Get(); + Assert( pFileName && pFileName [ 0 ] ); + + char pSoundName[ 512 ]; + // Note, calling PSkipSoundChars to remove any decorator characters used by the engine!!! + Q_snprintf( pSoundName, sizeof( pSoundName ), "sound/%s", PSkipSoundChars( pFileName ) ); + Q_FixSlashes( pSoundName ); + + char pFullPath[ 512 ]; + g_pFullFileSystem->RelativePathToFullPath( pSoundName, "GAME", pFullPath, sizeof( pFullPath ) ); + + // Get sound file metrics of interest + CUtlBuffer buf; + WAVEFORMATEX *format; + int nDataSize; + if ( !GetWaveFormat( pSoundName, &buf, &nDataSize, workItem.m_Sentence, workItem.m_bSentenceValid ) ) + continue; + + format = ( WAVEFORMATEX * )buf.Base(); + + if ( !( format->wBitsPerSample > ( 1 << 3 ) ) ) + { + // Have to warn and early-out here to avoid crashing with "integer divide by zero" below + Warning( "Cannot extract phonemes from '%s', %u bits per sample.\n", pSoundName, format->wBitsPerSample ); + continue; + } + + int nBitsPerSample = format->wBitsPerSample; + float flSampleRate = (float)format->nSamplesPerSec; + int nChannels = format->nChannels; + int nSampleCount = nDataSize / ( nBitsPerSample >> 3 ); + + float flTrueSampleSize = ( nBitsPerSample * nChannels ) >> 3; + if ( format->wFormatTag == WAVE_FORMAT_ADPCM ) + { + nBitsPerSample = 16; + flTrueSampleSize = 0.5f; + + ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)buf.Base(); + int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2; + blockSize += 7 * pFormat->wfx.nChannels; + + int blockCount = nDataSize / blockSize; + int blockRem = nDataSize % blockSize; + + // total samples in complete blocks + nSampleCount = blockCount * pFormat->wSamplesPerBlock; + + // add remaining in a short block + if ( blockRem ) + { + nSampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / nChannels); + } + } + + if ( flSampleRate > 0.0f ) + { + workItem.m_flDuration = (float)nSampleCount / flSampleRate; + } + in.CreateEventWordDistribution( workItem.m_sHintText.String(), workItem.m_flDuration ); + if ( !workItem.m_bUseSentence || !workItem.m_bSentenceValid ) + { + extractor.extractor->Extract( pFullPath, + (int)( workItem.m_flDuration * flSampleRate * flTrueSampleSize ), + Msg, in, out ); + + // Tracker 57389: + // Total hack to fix a bug where the Lipsinc extractor is messing up the # channels on 16 bit stereo waves + if ( apiType == SPEECH_API_LIPSINC && nChannels == 2 && nBitsPerSample == 16 ) + { + flTrueSampleSize *= 2.0f; + } + + float bytespersecond = flSampleRate * flTrueSampleSize; + + int i; + // Now convert byte offsets to times + for ( i = 0; i < out.m_Words.Size(); i++ ) + { + CWordTag *tag = out.m_Words[ i ]; + Assert( tag ); + if ( !tag ) + continue; + + tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond; + tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond; + + for ( int j = 0; j < tag->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *ptag = tag->m_Phonemes[ j ]; + Assert( ptag ); + if ( !ptag ) + continue; + + ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond ); + ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond ); + } + } + + if ( bWritePhonemesToWavFiles ) + { + SaveSentenceToWavFile( pFullPath, out ); + } + } + else + { + Msg( "Using .wav file phonemes for (%s)\n", pSoundName ); + out = workItem.m_Sentence; + } + + // Now create channel data + workItem.ClearTags(); + BuildPhonemeStream( out, workItem.m_ApplyTags ); + } + + if ( info.m_bCreateBookmarks ) + { + info.m_pSet->GetBookmarks().RemoveAll(); + } + + for ( nWorkItem = 0; nWorkItem < info.m_WorkList.Count(); ++nWorkItem ) + { + LogPhonemes( nWorkItem, info ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool UniquePhonemeLessFunc( CBasePhonemeTag * const & lhs, CBasePhonemeTag * const & rhs ) +{ + return lhs->GetPhonemeCode() < rhs->GetPhonemeCode(); +} + +void CSFMPhonemeExtractor::BuildPhonemeToPresetMapping( const CUtlVector< CBasePhonemeTag * > &stream, + CDmeAnimationSet *pSet, CDmePresetGroup *pPresetGroup, CUtlDict< CDmePreset *, unsigned short > &phonemeToPresetDict ) +{ + int i; + CUtlRBTree< CBasePhonemeTag * > uniquePhonemes( 0, 0, UniquePhonemeLessFunc ); + for ( i = 0; i < stream.Count(); ++i ) + { + CBasePhonemeTag *tag = stream[ i ]; + if ( uniquePhonemes.Find( tag ) == uniquePhonemes.InvalidIndex() ) + { + uniquePhonemes.Insert( tag ); + } + } + + for ( i = uniquePhonemes.FirstInorder(); i != uniquePhonemes.InvalidIndex(); i = uniquePhonemes.NextInorder( i ) ) + { + CBasePhonemeTag *tag = uniquePhonemes[ i ]; + // Convert phoneme code to text + char ph[ 32 ]; + Q_strncpy( ph, ConvertPhoneme( tag->GetPhonemeCode() ), sizeof( ph ) ); + + char remappedph[ 32 ]; + // By default we search for a preset name p_xxx where xxx is the phoneme string + Q_snprintf( remappedph, sizeof( remappedph ), "p_%s", ph ); + // Now find the preset in the animation set converter + CDmePhonemeMapping *mapping = pSet->FindMapping( ph ); + if ( mapping ) + { + Q_strncpy( remappedph, mapping->GetValueString( "preset" ), sizeof( remappedph ) ); + } + + // Now look up the preset, if it exists + CDmePreset *preset = pPresetGroup->FindPreset( remappedph ); + if ( !preset ) + { + Warning( "Animation set '%s' missing phoneme preset for '%s' -> '%s'\n", + pSet->GetName(), ph, remappedph ); + continue; + } + + // Add to dictionary if it's not already there + if ( phonemeToPresetDict.Find( ph ) == phonemeToPresetDict.InvalidIndex() ) + { + phonemeToPresetDict.Insert( ph, preset ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Finds the channels clip which refers to facial control values +//----------------------------------------------------------------------------- +CDmeChannelsClip* CSFMPhonemeExtractor::FindFacialChannelsClip( const CUtlVector< LogPreview_t > &list ) +{ + CDmeChannelsClip *pChannelsClip = NULL; + + int i; + for ( i = list.Count() - 1; i >= 0; --i ) + { + const LogPreview_t &lp = list[i]; + CDmeChannelsClip *check = FindAncestorReferencingElement< CDmeChannelsClip >( (CDmElement *)lp.m_hChannels[ 0 ].Get() ); + + if ( !pChannelsClip && check ) + { + pChannelsClip = check; + } + else + { + if ( pChannelsClip != check ) + { + Warning( "Selected controls overlap multiple channels clips!!!\n" ); + } + } + } + + if ( !pChannelsClip ) + { + Warning( "Unable to determine destination channels clip!!!\n" ); + } + + return pChannelsClip; +} + + +//----------------------------------------------------------------------------- +// Builds the list of logs which target facial control values +//----------------------------------------------------------------------------- +void CSFMPhonemeExtractor::BuildPhonemeLogList( CUtlVector< LogPreview_t > &list, CUtlVector< CDmeLog * > &logs ) +{ + for ( int i = 0; i < list.Count(); ++i ) + { + LogPreview_t& p = list[ i ]; + + for ( int channel = 0; channel < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++channel ) + { + CDmeChannel *ch = p.m_hChannels[ channel ]; + if ( !ch ) + continue; + + CDmeLog *log = p.m_hChannels[ channel ]->GetLog(); + if ( !log ) + continue; + + logs.AddToTail( log ); + } + } +} + + +//----------------------------------------------------------------------------- +// Writes default values into all log layers targetting facial control values +//----------------------------------------------------------------------------- +void CSFMPhonemeExtractor::WriteDefaultValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup ) +{ + // Write a zero into all relevant log layers + for ( int j = controlLookup.First(); j != controlLookup.InvalidIndex(); j = controlLookup.Next( j ) ) + { + LogPreview_t* lp = controlLookup[ j ]; + + CDmElement *pControl = lp->m_hControl; + + for ( int chIndex = 0; chIndex < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++chIndex ) + { + CDmeChannel *pChannel = lp->m_hChannels[ chIndex ]; + if ( !pChannel ) + continue; + + // Now get the log for the channel + CDmeFloatLog *pFloatLog = CastElement< CDmeFloatLog >( pChannel->GetLog() ); + if ( !pFloatLog ) + continue; + + CDmeFloatLogLayer *pLayer = pFloatLog->GetLayer( pFloatLog->GetTopmostLayer() ); + if ( !pLayer ) + continue; + + float flDefaultValue = pControl->GetValue< float >( s_pDefaultAttributeValueNames[chIndex] ); + pLayer->InsertKey( tHeadPosition, flDefaultValue ); + } + } +} + + +//----------------------------------------------------------------------------- +// Creates a new log key based on the interpolated value at that time +//----------------------------------------------------------------------------- +void CSFMPhonemeExtractor::WriteCurrentValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup ) +{ + // Write a zero into all relevant log layers + for ( int j = controlLookup.First(); j != controlLookup.InvalidIndex(); j = controlLookup.Next( j ) ) + { + LogPreview_t* lp = controlLookup[ j ]; + + for ( int chIndex = 0; chIndex < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++chIndex ) + { + CDmeChannel *pChannel = lp->m_hChannels[ chIndex ]; + if ( !pChannel ) + continue; + + // Now get the log for the channel + CDmeFloatLog *pFloatLog = CastElement< CDmeFloatLog >( pChannel->GetLog() ); + if ( !pFloatLog ) + continue; + + CDmeFloatLogLayer *pLayer = pFloatLog->GetLayer( pFloatLog->GetTopmostLayer() ); + if ( !pLayer ) + continue; + + float flCurrentValue = pLayer->GetValue( tHeadPosition ); + pLayer->InsertKey( tHeadPosition, flCurrentValue ); + } + } +} + + +//----------------------------------------------------------------------------- +// Samples extracted phoneme data and stamps that values into control value logs +//----------------------------------------------------------------------------- +void CSFMPhonemeExtractor::StampControlValueLogs( CDmePreset *preset, DmeTime_t tHeadPosition, float flIntensity, CUtlDict< LogPreview_t *, int > &controlLookup ) +{ + // Now walk the logs required by the preset + const CDmrElementArray< CDmElement > &controlValues = preset->GetControlValues( ); + for ( int j = 0; j < controlValues.Count(); ++j ) + { + // This control contains the preset value + CDmElement *presetControl = controlValues[ j ]; + if ( !presetControl ) + continue; + + int visIndex = controlLookup.Find( presetControl->GetName() ); + if ( visIndex == controlLookup.InvalidIndex() ) + continue; + + LogPreview_t* lp = controlLookup[ visIndex ]; + + for ( int chIndex = 0; chIndex < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++chIndex ) + { + CDmeChannel *ch = lp->m_hChannels[ chIndex ]; + if ( !ch ) + continue; + + // Whereas this control contains the "default" value for the slider (since the presetControl won't have that value) + CDmElement *defaultValueControl = lp->m_hControl.Get(); + if ( !defaultValueControl ) + continue; + + // Now get the log for the channel + CDmeLog *log = ch->GetLog(); + if ( !log ) + { + Assert( 0 ); + continue; + } + + CDmeFloatLog *floatLog = CastElement< CDmeFloatLog >( log ); + if ( !floatLog ) + continue; + + CDmeFloatLogLayer *pLayer = floatLog->GetLayer( floatLog->GetTopmostLayer() ); + if ( !pLayer ) + continue; + + float flDefault = defaultValueControl->GetValue< float >( s_pDefaultAttributeValueNames[chIndex] ); + float flControlValue = presetControl->GetValue< float >( s_pAttributeValueNames[ chIndex ] ); + float flNewValue = flIntensity * ( flControlValue - flDefault ); + float flCurrent = pLayer->GetValue( tHeadPosition ) - flDefault; + // Accumulate new value into topmost layer + pLayer->InsertKey( tHeadPosition, flCurrent + flNewValue + flDefault ); + } + } +} + +void CSFMPhonemeExtractor::ClearInterstitialSpaces( CDmeChannelsClip *pChannelsClip, CUtlDict< LogPreview_t *, int >& controlLookup, ExtractDesc_t& info ) +{ + Assert( info.m_pShot ); + Assert( pChannelsClip ); + + if ( info.m_WorkList.Count() == 0 ) + return; + + // This is handled by the main layering code... + if ( info.m_nExtractType == EXTRACT_WIPE_SOUNDS ) + return; + + // Now walk through all relevant logs + CUtlVector< CDmeLog * > logs; + BuildPhonemeLogList( info.m_ControlList, logs ); + + DmeTime_t tMinTime( DMETIME_MAXTIME ); + DmeTime_t tMaxTime( DMETIME_MINTIME ); + + int i; + // Walk work items and figure out time bounds + for ( i = 0; i < info.m_WorkList.Count(); ++i ) + { + CExtractInfo &item = info.m_WorkList[ i ]; + + CUtlVector< CDmeHandle< CDmeClip > > srcStack; + CUtlVector< CDmeHandle< CDmeClip > > dstStack; + + // Convert original .wav start to animation set channels clip relative time + item.m_pClip->BuildClipStack( &srcStack, info.m_pMovie, info.m_pShot ); + + // NOTE: Time bounds measured in sound media time goes from 0 -> flWaveDuration + DmeTime_t tSoundMediaStartTime = CDmeClip::FromChildMediaTime( srcStack, DMETIME_ZERO, false ); + DmeTime_t tSoundMediaEndTime = CDmeClip::FromChildMediaTime( srcStack, DmeTime_t( item.m_flDuration ), false ); + + // NOTE: Start and end time are measured in sound media time + DmeTime_t tStartTime = item.m_pClip->GetStartInChildMediaTime(); + DmeTime_t tEndTime = item.m_pClip->GetEndInChildMediaTime(); + + // And convert back down into channels clip relative time + pChannelsClip->BuildClipStack( &dstStack, info.m_pMovie, info.m_pShot ); + + // Now convert back down to channels clip relative time + DmeTime_t tChannelMediaStartTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaStartTime, false ); + DmeTime_t tChannelMediaEndTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaEndTime, false ); + + // Find a scale + offset which transforms data in media space of the sound [namely, the phonemes] + // into the media space of the channels [the logs that drive the facial animation] + DmeTime_t tEndDuration = tChannelMediaEndTime - tChannelMediaStartTime; + double flScale = ( item.m_flDuration != 0.0f ) ? tEndDuration.GetSeconds() / item.m_flDuration : 0.0f; + DmeTime_t tOffset = tChannelMediaStartTime; + + DmeTime_t tChannelRelativeStartTime( tStartTime * flScale ); + tChannelRelativeStartTime += tOffset; + DmeTime_t tChannelRelativeEndTime( tEndTime * flScale ); + tChannelRelativeEndTime += tOffset; + + if ( tChannelRelativeStartTime < tMinTime ) + { + tMinTime = tChannelRelativeStartTime; + } + if ( tChannelRelativeEndTime > tMaxTime ) + { + tMaxTime = tChannelRelativeEndTime; + } + } + + // Bloat by one quantum + tMinTime -= DMETIME_MINDELTA; + tMaxTime += DMETIME_MINDELTA; + + for ( i = 0; i < logs.Count(); ++i ) + { + CDmeLog *log = logs[ i ]; + + Assert( log->GetNumLayers() == 1 ); + CDmeLogLayer *layer = log->GetLayer( log->GetTopmostLayer() ); + + if ( info.m_nExtractType == EXTRACT_WIPE_RANGE ) + { + // Write default value keys into log + // Write a default value at that time + WriteDefaultValuesIntoLogLayers( tMinTime, controlLookup ); + + // Write a default value at that time + WriteDefaultValuesIntoLogLayers( tMaxTime, controlLookup ); + + // Now discard all keys > tMinTime and < tMaxTime + for ( int j = layer->GetKeyCount() - 1; j >= 0; --j ) + { + DmeTime_t &t = layer->GetKeyTime( j ); + if ( t <= tMinTime ) + continue; + if ( t >= tMaxTime ) + continue; + + layer->RemoveKey( j ); + } + } + else + { + Assert( info.m_nExtractType == EXTRACT_WIPE_CLIP ); + layer->ClearKeys(); + } + } +} + +void AddAnimSetBookmarkAtSoundMediaTime( const char *pName, DmeTime_t tStart, DmeTime_t tEnd, const CUtlVector< CDmeHandle< CDmeClip > > &srcStack, ExtractDesc_t& info ) +{ + tStart = CDmeClip::FromChildMediaTime( srcStack, tStart, false ); + tEnd = CDmeClip::FromChildMediaTime( srcStack, tEnd, false ); + + tStart = info.m_pShot->ToChildMediaTime( tStart, false ); + tEnd = info.m_pShot->ToChildMediaTime( tEnd, false ); + + CDmeBookmark *pBookmark = CreateElement< CDmeBookmark >( pName ); + pBookmark->SetNote( pName ); + pBookmark->SetTime( tStart ); + pBookmark->SetDuration( tEnd - tStart ); + info.m_pSet->GetBookmarks().AddToTail( pBookmark ); +} + +//----------------------------------------------------------------------------- +// Main entry point for generating phoneme logs +//----------------------------------------------------------------------------- +void CSFMPhonemeExtractor::LogPhonemes( int nItemIndex, ExtractDesc_t& info ) +{ + CExtractInfo &item = info.m_WorkList[ nItemIndex ]; + + // Validate input parameters + Assert( info.m_pSet && item.m_pClip && item.m_pSound ); + if ( !info.m_pSet || !item.m_pClip || !item.m_pSound ) + return; + + CDmePresetGroup *pPresetGroup = info.m_pSet->FindPresetGroup( "phoneme" ); + if ( !pPresetGroup ) + { + Warning( "Animation set '%s' missing preset group 'phoneme'\n", info.m_pSet->GetName() ); + return; + } + + if ( !info.m_pSet->GetPhonemeMap().Count() ) + { + info.m_pSet->RestoreDefaultPhonemeMap(); + } + + // Walk through phoneme stack and build list of unique presets + CUtlDict< CDmePreset *, unsigned short > phonemeToPresetDict; + BuildPhonemeToPresetMapping( item.m_ApplyTags, info.m_pSet, pPresetGroup, phonemeToPresetDict ); + + CDmeChannelsClip *pChannelsClip = FindFacialChannelsClip( info.m_ControlList ); + if ( !pChannelsClip ) + return; + + // Build a fast lookup of the visible sliders + int i; + CUtlDict< LogPreview_t *, int > controlLookup; + for ( i = 0; i < info.m_ControlList.Count(); ++i ) + { + controlLookup.Insert( info.m_ControlList[ i ].m_hControl->GetName(), &info.m_ControlList[ i ] ); + } + + // Only need to do this on the first item and we have multiple .wavs selected + if ( nItemIndex == 0 && info.m_WorkList.Count() > 1 ) + { + ClearInterstitialSpaces( pChannelsClip, controlLookup, info ); + } + + // Set up time selection, put channels into record and stamp out keyframes + + // Convert original .wav start to animation set channels clip relative time + CUtlVector< CDmeHandle< CDmeClip > > srcStack; + item.m_pClip->BuildClipStack( &srcStack, info.m_pMovie, info.m_pShot ); + if ( srcStack.Count() == 0 ) + { + item.m_pClip->BuildClipStack( &srcStack, info.m_pMovie, NULL ); + if ( srcStack.Count() == 0 ) + { + Msg( "Couldn't build stack sound clip to current shot\n" ); + return; + } + } + + // NOTE: Time bounds measured in sound media time goes from 0 -> flWaveDuration + DmeTime_t tSoundMediaStartTime = CDmeClip::FromChildMediaTime( srcStack, DMETIME_ZERO, false ); + DmeTime_t tSoundMediaEndTime = CDmeClip::FromChildMediaTime( srcStack, DmeTime_t( item.m_flDuration ), false ); + + // NOTE: Start and end time are measured in sound media time + DmeTime_t tStartTime = item.m_pClip->GetStartInChildMediaTime(); + DmeTime_t tEndTime = item.m_pClip->GetEndInChildMediaTime(); + + // And convert back down into channels clip relative time + CUtlVector< CDmeHandle< CDmeClip > > dstStack; + pChannelsClip->BuildClipStack( &dstStack, info.m_pMovie, info.m_pShot ); + + // Now convert back down to channels clip relative time + DmeTime_t tChannelMediaStartTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaStartTime, false ); + DmeTime_t tChannelMediaEndTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaEndTime, false ); + + // Find a scale + offset which transforms data in media space of the sound [namely, the phonemes] + // into the media space of the channels [the logs that drive the facial animation] + DmeTime_t tEndDuration = tChannelMediaEndTime - tChannelMediaStartTime; + double flScale = ( item.m_flDuration != 0.0f ) ? tEndDuration.GetSeconds() / item.m_flDuration : 0.0f; + DmeTime_t tOffset = tChannelMediaStartTime; + + CUtlVector< CDmeLog * > logs; + BuildPhonemeLogList( info.m_ControlList, logs ); + + // Add new write layer to each recording log + for ( i = 0; i < logs.Count(); ++i ) + { + logs[ i ]->AddNewLayer(); + } + + // Iterate over the entire range of the sound + double flStartSoundTime = max( 0, tStartTime.GetSeconds() ); + double flEndSoundTime = min( item.m_flDuration, tEndTime.GetSeconds() ); + + // Stamp keys right before and after the sound so as to + // not generate new values outside the import time range + DmeTime_t tPrePhonemeTime( flStartSoundTime * flScale ); + tPrePhonemeTime += tOffset - DMETIME_MINDELTA; + WriteCurrentValuesIntoLogLayers( tPrePhonemeTime, controlLookup ); + + DmeTime_t tPostPhonemeTime( flEndSoundTime * flScale ); + tPostPhonemeTime += tOffset + DMETIME_MINDELTA; + WriteCurrentValuesIntoLogLayers( tPostPhonemeTime, controlLookup ); + + // add bookmarks + if ( info.m_bCreateBookmarks ) + { + AddAnimSetBookmarkAtSoundMediaTime( "start", tPrePhonemeTime, tPrePhonemeTime, srcStack, info ); + + for ( i = 0; i < item.m_ApplyTags.Count() ; ++i ) + { + CBasePhonemeTag *p = item.m_ApplyTags[ i ]; + const char *pPhonemeName = ConvertPhoneme( p->GetPhonemeCode() ); + DmeTime_t tStart = DmeTime_t( p->GetStartTime() ); + DmeTime_t tEnd = DmeTime_t( p->GetEndTime() ); + AddAnimSetBookmarkAtSoundMediaTime( pPhonemeName, tStart, tEnd, srcStack, info ); + } + + AddAnimSetBookmarkAtSoundMediaTime( "end", tPostPhonemeTime, tPostPhonemeTime, srcStack, info ); + } + + if ( info.m_nFilterType == EXTRACT_FILTER_HOLD || info.m_nFilterType == EXTRACT_FILTER_LINEAR ) + { + CDmePreset *pLastPreset = NULL; + + for ( i = 0; i < item.m_ApplyTags.Count() ; ++i ) + { + CBasePhonemeTag *p = item.m_ApplyTags[ i ]; + + DmeTime_t tStart = DmeTime_t( p->GetStartTime() ); + DmeTime_t tEnd = DmeTime_t( p->GetEndTime() ); + + int idx = phonemeToPresetDict.Find( ConvertPhoneme( p->GetPhonemeCode() ) ); + if ( idx == phonemeToPresetDict.InvalidIndex() ) + continue; + + CDmePreset *preset = phonemeToPresetDict[ idx ]; + if ( !preset ) + continue; + + DmeTime_t tKeyTime = tStart * flScale + tOffset; + + if ( info.m_nFilterType == EXTRACT_FILTER_HOLD ) + { + // stamp value at end of phoneme (or default prior to first phoneme) + // NOTE - this ignores phoneme length, but since all phonemes directly abut one another, this doesn't matter + DmeTime_t tLastEnd = tKeyTime - DMETIME_MINDELTA; + if ( tLastEnd > tPrePhonemeTime ) + { + WriteDefaultValuesIntoLogLayers( tKeyTime - DMETIME_MINDELTA, controlLookup ); + if ( pLastPreset ) + { + StampControlValueLogs( pLastPreset, tKeyTime - DMETIME_MINDELTA, 1.0f, controlLookup ); + } + } + pLastPreset = preset; + } + + WriteDefaultValuesIntoLogLayers( tKeyTime, controlLookup ); + StampControlValueLogs( preset, tKeyTime, 1.0f, controlLookup ); + + if ( info.m_nFilterType == EXTRACT_FILTER_HOLD && i == item.m_ApplyTags.Count() - 1 ) + { + // stamp value at end of last phoneme + tKeyTime = tEnd * flScale + tOffset; + tKeyTime = min( tKeyTime, tPostPhonemeTime ); + WriteDefaultValuesIntoLogLayers( tKeyTime - DMETIME_MINDELTA, controlLookup ); + StampControlValueLogs( preset, tKeyTime - DMETIME_MINDELTA, 1.0f, controlLookup ); + + // stamp default just after end of last phoneme to hold silence until tPostPhonemeTime + WriteDefaultValuesIntoLogLayers( tKeyTime, controlLookup ); + } + } + } + else + { + Assert( info.m_nFilterType == EXTRACT_FILTER_FIXED_WIDTH ); + + double tStep = 1.0 / (double)clamp( info.m_flSampleRateHz, 1.0f, 1000.0f ); + + float flFilter = max( info.m_flSampleFilterSize, 0.001f ); + float flOOFilter = 1.0f / flFilter; + + for ( double t = flStartSoundTime; t < flEndSoundTime; t += tStep ) + { + DmeTime_t tPhonemeTime( t ); + + // Determine the location of the sample in the channels clip + DmeTime_t tKeyTime( t * flScale ); + tKeyTime += tOffset; + + // Write a default value at that time + WriteDefaultValuesIntoLogLayers( tKeyTime, controlLookup ); + + // Walk phonemes... + for ( i = 0; i < item.m_ApplyTags.Count() ; ++i ) + { + CBasePhonemeTag *p = item.m_ApplyTags[ i ]; + + DmeTime_t tStart = DmeTime_t( p->GetStartTime() ); + DmeTime_t tEnd = DmeTime_t( p->GetEndTime() ); + + bool bContinue = false; + float flI = 0.0f; + { + DmeTime_t tFilter( flFilter ); + if ( tStart >= tPhonemeTime + tFilter || tEnd <= tPhonemeTime ) + bContinue = true; + + tStart = max( tStart, tPhonemeTime ); + tEnd = min( tEnd, tPhonemeTime + tFilter ); + + flI = ( tEnd - tStart ).GetSeconds() * flOOFilter; + } + + DmeTime_t dStart = tStart - tPhonemeTime; + DmeTime_t dEnd = tEnd - tPhonemeTime; + + float t1 = dStart.GetSeconds() * flOOFilter; + float t2 = dEnd.GetSeconds() * flOOFilter; + + Assert( bContinue == !( t1 < 1.0f && t2 > 0.0f ) ); + if ( !( t1 < 1.0f && t2 > 0.0f ) ) + continue; + + if ( t2 > 1 ) + { + t2 = 1; + } + if ( t1 < 0 ) + { + t1 = 0; + } + + float flIntensity = ( t2 - t1 ); + Assert( fabs( flI - flIntensity ) < 0.000001f ); + + int idx = phonemeToPresetDict.Find( ConvertPhoneme( p->GetPhonemeCode() ) ); + if ( idx == phonemeToPresetDict.InvalidIndex() ) + continue; + + CDmePreset *preset = phonemeToPresetDict[ idx ]; + if ( !preset ) + continue; + + StampControlValueLogs( preset, tKeyTime, flIntensity, controlLookup ); + } + } + } + + // Flatten write layers + for ( i = 0; i < logs.Count(); ++i ) + { + logs[ i ]->FlattenLayers( DMELOG_DEFAULT_THRESHHOLD, CDmeLog::FLATTEN_NODISCONTINUITY_FIXUP ); + } +} + +void CSFMPhonemeExtractor::ReApply( ExtractDesc_t& info ) +{ + if ( info.m_bCreateBookmarks ) + { + info.m_pSet->GetBookmarks().RemoveAll(); + } + + for ( int nWorkItem = 0; nWorkItem < info.m_WorkList.Count(); ++nWorkItem ) + { + LogPhonemes( nWorkItem, info ); + } +} + diff --git a/sfmobjects/sfmsession.cpp b/sfmobjects/sfmsession.cpp new file mode 100644 index 0000000..4cd63f4 --- /dev/null +++ b/sfmobjects/sfmsession.cpp @@ -0,0 +1,285 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// A class representing session state for the SFM +// +//============================================================================= + +#include "sfmobjects/sfmsession.h" +#include "studio.h" +#include "movieobjects/dmechannel.h" +#include "movieobjects/dmetrack.h" +#include "movieobjects/dmeclip.h" +#include "movieobjects/dmecamera.h" +#include "movieobjects/dmetimeselection.h" +#include "movieobjects/dmeanimationset.h" +#include "movieobjects/dmegamemodel.h" + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CSFMSession::CSFMSession() +{ + m_hRoot = NULL; +} + + +//----------------------------------------------------------------------------- +// Sets the root +//----------------------------------------------------------------------------- +void CSFMSession::SetRoot( CDmElement *pRoot ) +{ + m_hRoot = pRoot; +} + + +//----------------------------------------------------------------------------- +// Creates a new (empty) session +//----------------------------------------------------------------------------- +void CSFMSession::Init() +{ + m_hRoot = NULL; + + // a movie currently consists of: (this is all just temporary until clips take over more completely) + // a generic "root" node + // movie - a clip node whose subclips are the movie sequence + // cameras - an array of cameras used throughout the movie + // clips - an array of clips used throughout the movie + CDmElement *pRoot = CreateElement< CDmElement >( "session" ); + Assert( pRoot ); + if ( !pRoot ) + return; + m_hRoot = pRoot; + + // FIXME! + pRoot->SetValue( "editorType", "ifm" ); + + CDmeFilmClip *pFilmClip = CreateElement<CDmeFilmClip>( "sequence" ); + Assert( pFilmClip != NULL ); + pFilmClip->SetDuration( DmeTime_t( 60.0f ) ); + + CDmeTrack *pTrack = pFilmClip->FindOrCreateFilmTrack(); + CDmeClip *pShot = CreateElement<CDmeFilmClip>( "shot" ); + pTrack->AddClip( pShot ); + pShot->SetDuration( DmeTime_t( 60.0f ) ); + + pRoot->SetValue( "activeClip", pFilmClip ); + + pRoot->AddAttributeElementArray< CDmElement >( "miscBin" ); + pRoot->AddAttributeElementArray< CDmeCamera >( "cameraBin" ); + CDmAttribute *pClipBin = pRoot->AddAttributeElementArray< CDmeClip >( "clipBin" ); + + // Don't allow duplicates in the clipBin + pClipBin->AddFlag( FATTRIB_NODUPLICATES ); + + CDmrElementArray<> clipBin( pRoot, "clipBin" ); + clipBin.AddToTail( pFilmClip ); + + CreateSessionSettings(); +} + + +//----------------------------------------------------------------------------- +// Shuts down the session +//----------------------------------------------------------------------------- +void CSFMSession::Shutdown() +{ + if ( m_hRoot.Get() ) + { + if ( m_hRoot->GetFileId() != DMFILEID_INVALID ) + { + g_pDataModel->RemoveFileId( m_hRoot->GetFileId() ); + m_hRoot = NULL; + } + } +} + + +//----------------------------------------------------------------------------- +// Creates session settings +//----------------------------------------------------------------------------- +void CSFMSession::CreateSessionSettings() +{ + if ( !m_hRoot.Get() ) + return; + + m_hRoot->AddAttribute( "settings", AT_ELEMENT ); + + CDmElement *pSettings = m_hRoot->GetValueElement< CDmElement >( "settings" ); + if ( !pSettings ) + { + pSettings = CreateElement< CDmElement >( "sessionSettings", m_hRoot->GetFileId() ); + m_hRoot->SetValue( "settings", pSettings ); + } + + Assert( pSettings ); + + CDmeTimeSelection *ts = NULL; + if ( !pSettings->HasAttribute( "timeSelection" ) ) + { + ts = CreateElement< CDmeTimeSelection >( "timeSelection", m_hRoot->GetFileId() ); + pSettings->SetValue( "timeSelection", ts ); + } + + pSettings->InitValue( "animationSetOverlayBackground", Color( 0, 0, 0, 192 ) ); + + if ( !pSettings->HasAttribute( "standardColors" ) ) + { + CDmrArray<Color> colors( pSettings, "standardColors", true ); + colors.AddToTail( Color( 0, 0, 0, 128 ) ); + colors.AddToTail( Color( 194, 120, 0, 128 ) ); + colors.AddToTail( Color( 255, 0, 100, 128 ) ); + colors.AddToTail( Color( 200, 200, 255, 128 ) ); + colors.AddToTail( Color( 255, 255, 255, 128 ) ); + } + + float flLegacyFrameRate = 24.0f; + if ( pSettings->HasAttribute( "frameRate" ) ) + { + flLegacyFrameRate = pSettings->GetValue<float>( "frameRate" ); + + // remove this from the base level settings area since we're going to add it in renderSettings + pSettings->RemoveAttribute( "frameRate" ); + } + + if ( !pSettings->HasAttribute( "proceduralPresets" ) ) + { + CDmeProceduralPresetSettings *ps = CreateElement< CDmeProceduralPresetSettings >( "proceduralPresets", m_hRoot->GetFileId() ); + pSettings->SetValue( "proceduralPresets", ps ); + } + + CreateRenderSettings( pSettings, flLegacyFrameRate ); +} + + +//----------------------------------------------------------------------------- +// Creates session render settings +// JasonM - remove flLegacyFramerate param eventually (perhaps March 2007) +//----------------------------------------------------------------------------- +void CSFMSession::CreateRenderSettings( CDmElement *pSettings, float flLegacyFramerate ) +{ + pSettings->AddAttribute( "renderSettings", AT_ELEMENT ); + + CDmElement *pRenderSettings = pSettings->GetValueElement< CDmElement >( "renderSettings" ); + if ( !pRenderSettings ) + { + pRenderSettings = CreateElement< CDmElement >( "renderSettings", pSettings->GetFileId() ); + pSettings->SetValue( "renderSettings", pRenderSettings ); + } + Assert( pRenderSettings ); + + pRenderSettings->InitValue( "frameRate", flLegacyFramerate ); // Default framerate + pRenderSettings->InitValue( "lightAverage", 0 ); // Don't light average by default + pRenderSettings->InitValue( "showFocalPlane", 0 ); // Don't show focal plane by default + pRenderSettings->InitValue( "modelLod", 0 ); // Don't do model LOD by default + + CreateProgressiveRefinementSettings( pRenderSettings ); +} + + +//----------------------------------------------------------------------------- +// Creates session progrssing refinement settings +//----------------------------------------------------------------------------- +void CSFMSession::CreateProgressiveRefinementSettings( CDmElement *pRenderSettings ) +{ + // Do we already have Progressive refinement settings? + CDmElement *pRefinementSettings = pRenderSettings->GetValueElement< CDmElement >( "ProgressiveRefinement" ); + if ( !pRefinementSettings ) + { + pRefinementSettings = CreateElement< CDmElement >( "ProgressiveRefinementSettings", pRenderSettings->GetFileId() ); + pRenderSettings->SetValue( "ProgressiveRefinement", pRefinementSettings ); + } + + // Set up defaults for progressive refinement settings... + pRefinementSettings->InitValue( "on", true ); + pRefinementSettings->InitValue( "useDepthOfField", true ); + pRefinementSettings->InitValue( "overrideDepthOfFieldQuality", false ); + pRefinementSettings->InitValue( "overrideDepthOfFieldQualityValue", 1 ); + pRefinementSettings->InitValue( "useMotionBlur", true ); + pRefinementSettings->InitValue( "overrideMotionBlurQuality", false ); + pRefinementSettings->InitValue( "overrideMotionBlurQualityValue", 1 ); + pRefinementSettings->InitValue( "useAntialiasing", false ); + pRefinementSettings->InitValue( "overrideShutterSpeed", false ); + pRefinementSettings->InitValue( "overrideShutterSpeedValue", 1.0f / 48.0f ); +} + + +//----------------------------------------------------------------------------- +// Creates a camera +//----------------------------------------------------------------------------- +CDmeCamera *CSFMSession::CreateCamera( const DmeCameraParams_t& params ) +{ + CDmeCamera *pCamera = CreateElement< CDmeCamera >( params.name, m_hRoot->GetFileId() ); + + // Set parameters + matrix3x4_t txform; + AngleMatrix( params.angles, params.origin, txform ); + + CDmeTransform *pTransform = pCamera->GetTransform(); + if ( pTransform ) + { + pTransform->SetTransform( txform ); + } + + pCamera->SetFOVx( params.fov ); + return pCamera; +} + + +//----------------------------------------------------------------------------- +// Finds or creates a scene +//----------------------------------------------------------------------------- +CDmeDag *CSFMSession::FindOrCreateScene( CDmeFilmClip *pShot, const char *pSceneName ) +{ + CDmeDag *pScene = pShot->GetScene(); + if ( !pScene ) + { + pScene = CreateElement< CDmeDag >( "scene", pShot->GetFileId() ); + pShot->SetScene( pScene ); + } + Assert( pScene ); + + int c = pScene->GetChildCount(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeDag *pChild = pScene->GetChild( i ); + if ( pChild && !Q_stricmp( pChild->GetName(), pSceneName ) ) + return pChild; + } + + CDmeDag *pNewScene = CreateElement< CDmeDag >( pSceneName, pScene->GetFileId() ); + pScene->AddChild( pNewScene ); + + return pNewScene; +} + + +//----------------------------------------------------------------------------- +// Creates a game model +//----------------------------------------------------------------------------- +CDmeGameModel *CSFMSession::CreateEditorGameModel( studiohdr_t *hdr, const Vector &vecOrigin, Quaternion &qOrientation ) +{ + char pBaseName[ 256 ]; + Q_FileBase( hdr->pszName(), pBaseName, sizeof( pBaseName ) ); + + char pGameModelName[ 256 ]; + Q_snprintf( pGameModelName, sizeof( pGameModelName ), "%s_GameModel", pBaseName ); + CDmeGameModel *pGameModel = CreateElement< CDmeGameModel >( pGameModelName, m_hRoot->GetFileId() ); + + char pRelativeModelsFileName[MAX_PATH]; + Q_ComposeFileName( "models", hdr->pszName(), pRelativeModelsFileName, sizeof(pRelativeModelsFileName) ); + pGameModel->SetValue( "modelName", pRelativeModelsFileName ); + + CDmeTransform *pTransform = pGameModel->GetTransform(); + if ( pTransform ) + { + pTransform->SetPosition( vecOrigin ); + pTransform->SetOrientation( qOrientation ); + } + + // create, connect and cache each bone's pos and rot channels + pGameModel->AddBones( hdr, pBaseName, 0, hdr->numbones ); + return pGameModel; +} + + |