summaryrefslogtreecommitdiff
path: root/sfmobjects
diff options
context:
space:
mode:
Diffstat (limited to 'sfmobjects')
-rw-r--r--sfmobjects/exportfacialanimation.cpp275
-rw-r--r--sfmobjects/flexcontrolbuilder.cpp951
-rw-r--r--sfmobjects/sfmanimationsetutils.cpp891
-rw-r--r--sfmobjects/sfmobjects.vpc42
-rw-r--r--sfmobjects/sfmphonemeextractor.cpp1186
-rw-r--r--sfmobjects/sfmsession.cpp285
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;
+}
+
+