diff options
Diffstat (limited to 'movieobjects')
64 files changed, 41665 insertions, 0 deletions
diff --git a/movieobjects/dmeanimationlist.cpp b/movieobjects/dmeanimationlist.cpp new file mode 100644 index 0000000..0540884 --- /dev/null +++ b/movieobjects/dmeanimationlist.cpp @@ -0,0 +1,68 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// An element that contains a list of animations +// +//============================================================================= +#include "movieobjects/dmeanimationlist.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmeclip.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeAnimationList, CDmeAnimationList ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeAnimationList::OnConstruction() +{ + m_Animations.Init( this, "animations" ); +} + +void CDmeAnimationList::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Adds, removes animations +//----------------------------------------------------------------------------- +int CDmeAnimationList::AddAnimation( CDmeChannelsClip *pAnimation ) +{ + return m_Animations.AddToTail( pAnimation ); +} + +void CDmeAnimationList::RemoveAnimation( int nIndex ) +{ + m_Animations.Remove( nIndex ); +} + + +//----------------------------------------------------------------------------- +// Sets the transform +//----------------------------------------------------------------------------- +void CDmeAnimationList::SetAnimation( int nIndex, CDmeChannelsClip *pAnimation ) +{ + m_Animations.Set( nIndex, pAnimation ); +} + + +//----------------------------------------------------------------------------- +// Finds an animation by name +//----------------------------------------------------------------------------- +int CDmeAnimationList::FindAnimation( const char *pAnimName ) +{ + int nCount = m_Animations.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( m_Animations[i]->GetName(), pAnimName ) ) + return i; + } + return -1; +} diff --git a/movieobjects/dmeanimationset.cpp b/movieobjects/dmeanimationset.cpp new file mode 100644 index 0000000..926de45 --- /dev/null +++ b/movieobjects/dmeanimationset.cpp @@ -0,0 +1,1407 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeanimationset.h" +#include "movieobjects/dmebookmark.h" +#include "movieobjects/dmegamemodel.h" +#include "movieobjects/dmecombinationoperator.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmehandle.h" +#include "phonemeconverter.h" +#include "tier1/utlstringmap.h" +#include "tier2/tier2.h" +#include "filesystem.h" +#include "studio.h" +#include "tier3/tier3.h" +#include "tier1/utlbuffer.h" + +//----------------------------------------------------------------------------- +// CDmePresetGroup - container for animation set info +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePreset, CDmePreset ); + +void CDmePreset::OnConstruction() +{ + m_ControlValues.Init( this, "controlValues" ); + m_nProceduralType.InitAndSet( this, "procedural", PROCEDURAL_PRESET_NOT ); +} + +void CDmePreset::OnDestruction() +{ +} + +CDmaElementArray< CDmElement > &CDmePreset::GetControlValues() +{ + return m_ControlValues; +} + +const CDmaElementArray< CDmElement > &CDmePreset::GetControlValues() const +{ + return m_ControlValues; +} + +int CDmePreset::FindControlValueIndex( const char *pControlName ) +{ + int c = m_ControlValues.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *e = m_ControlValues.Get( i ); + if ( !Q_stricmp( e->GetName(), pControlName ) ) + return i; + } + return -1; +} + +CDmElement *CDmePreset::FindControlValue( const char *pControlName ) +{ + int i = FindControlValueIndex( pControlName ); + if ( i >= 0 ) + return m_ControlValues.Get(i); + return NULL; +} + +CDmElement *CDmePreset::FindOrAddControlValue( const char *pControlName ) +{ + CDmElement *pControlValues = FindControlValue( pControlName ); + if ( !pControlValues ) + { + // Create the default groups in order + pControlValues = CreateElement< CDmElement >( pControlName, GetFileId() ); + m_ControlValues.AddToTail( pControlValues ); + } + return pControlValues; +} + +void CDmePreset::RemoveControlValue( const char *pControlName ) +{ + int i = FindControlValueIndex( pControlName ); + if ( i >= 0 ) + { + m_ControlValues.Remove( i ); + } +} + + +//----------------------------------------------------------------------------- +// Is the preset read-only? +//----------------------------------------------------------------------------- +bool CDmePreset::IsReadOnly() +{ + DmAttributeReferenceIterator_t h = g_pDataModel->FirstAttributeReferencingElement( GetHandle() ); + while ( h != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( h ); + CDmePresetGroup *pOwner = CastElement<CDmePresetGroup>( pAttribute->GetOwner() ); + if ( pOwner && pOwner->m_bIsReadOnly ) + return true; + h = g_pDataModel->NextAttributeReferencingElement( h ); + } + return false; +} + + +//----------------------------------------------------------------------------- +// Copies control values +//----------------------------------------------------------------------------- +void CDmePreset::CopyControlValuesFrom( CDmePreset *pSource ) +{ + m_ControlValues.RemoveAll(); + + const CDmaElementArray< CDmElement > &sourceValues = pSource->GetControlValues(); + int nCount = sourceValues.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pCopy = sourceValues[i]->Copy( ); + m_ControlValues.AddToTail( pCopy ); + } +} + +void CDmePreset::SetProceduralPresetType( int nType ) +{ + Assert( nType >= 0 && nType < NUM_PROCEDURAL_PRESET_TYPES ); + m_nProceduralType = nType; +} + +bool CDmePreset::IsProcedural() const +{ + return m_nProceduralType != PROCEDURAL_PRESET_NOT; +} + +int CDmePreset::GetProceduralPresetType() const +{ + return m_nProceduralType; +} + +IMPLEMENT_ELEMENT_FACTORY( DmeProceduralPresetSettings, CDmeProceduralPresetSettings ); + +void CDmeProceduralPresetSettings::OnConstruction() +{ + m_flJitterScale.InitAndSet( this, "jitterscale", 1.0f ); + m_flSmoothScale.InitAndSet( this, "smoothscale", 1.0f ); + m_flSharpenScale.InitAndSet( this, "sharpenscale", 1.0f ); + m_flSoftenScale.InitAndSet( this, "softenscale", 1.0f ); + + m_nJitterIterations.InitAndSet( this, "jitteriterations", 5 ); + m_nSmoothIterations.InitAndSet( this, "smoothiterations", 5 ); + m_nSharpenIterations.InitAndSet( this, "sharpeniterations", 1 ); + m_nSoftenIterations.InitAndSet( this, "softeniterations", 1 ); + + // 1/12 second now ( 833 ten thousandths ) + m_nStaggerInterval.InitAndSet( this, "staggerinterval", 10000 / 12 ); +} + +void CDmeProceduralPresetSettings::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// CDmePresetRemap - copies presets from one group to another +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePresetRemap, CDmePresetRemap ); + +void CDmePresetRemap::OnConstruction() +{ + m_SourcePresetGroup.Init( this, "sourcePresetGroup" ); + m_SrcPresets.Init( this, "srcPresets" ); + m_DestPresets.Init( this, "destPresets" ); +} + +void CDmePresetRemap::OnDestruction() +{ +} + + +const char *CDmePresetRemap::FindSourcePreset( const char *pDestPresetName ) +{ + int nCount = m_DestPresets.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pDestPresetName, m_DestPresets[i] ) ) + return m_SrcPresets[i]; + } + return NULL; +} + +void CDmePresetRemap::AddRemap( const char *pSourcePresetName, const char *pDestPresetName ) +{ + m_SrcPresets.AddToTail( pSourcePresetName ); + m_DestPresets.AddToTail( pDestPresetName ); +} + +void CDmePresetRemap::RemoveAll() +{ + m_SrcPresets.RemoveAll(); + m_DestPresets.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Iteration +//----------------------------------------------------------------------------- +int CDmePresetRemap::GetRemapCount() +{ + return m_SrcPresets.Count(); +} + +const char *CDmePresetRemap::GetRemapSource( int i ) +{ + return m_SrcPresets[i]; +} + +const char *CDmePresetRemap::GetRemapDest( int i ) +{ + return m_DestPresets[i]; +} + + +//----------------------------------------------------------------------------- +// CDmePresetGroup - container for animation set info +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePresetGroup, CDmePresetGroup ); + +void CDmePresetGroup::OnConstruction() +{ + m_Presets.Init( this, "presets" ); + m_bIsVisible.InitAndSet( this, "visible", true ); + m_bIsReadOnly.Init( this, "readonly" ); +} + +void CDmePresetGroup::OnDestruction() +{ +} + +CDmaElementArray< CDmePreset > &CDmePresetGroup::GetPresets() +{ + return m_Presets; +} + +const CDmaElementArray< CDmePreset > &CDmePresetGroup::GetPresets() const +{ + return m_Presets; +} + +//----------------------------------------------------------------------------- +// Finds the index of a particular preset group +//----------------------------------------------------------------------------- +int CDmePresetGroup::FindPresetIndex( CDmePreset *pPreset ) +{ + int c = m_Presets.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmePreset *e = m_Presets.Get( i ); + if ( pPreset == e ) + return i; + } + return -1; +} + + +CDmePreset *CDmePresetGroup::FindPreset( const char *pPresetName ) +{ + int i; + int c = m_Presets.Count(); + for ( i = 0; i < c; ++i ) + { + CDmePreset *e = m_Presets.Get( i ); + if ( !Q_stricmp( e->GetName(), pPresetName ) ) + return e; + } + return NULL; +} + +CDmePreset *CDmePresetGroup::FindOrAddPreset( const char *pPresetName, int nType /*=PROCEDURAL_PRESET_NOT*/ ) +{ + CDmePreset *pPreset = FindPreset( pPresetName ); + if ( !pPreset ) + { + // Create the default groups in order + pPreset = CreateElement< CDmePreset >( pPresetName, GetFileId() ); + pPreset->SetProceduralPresetType( nType ); + m_Presets.AddToTail( pPreset ); + } + return pPreset; +} + +bool CDmePresetGroup::RemovePreset( CDmePreset *pPreset ) +{ + int i = FindPresetIndex( pPreset ); + if ( i >= 0 ) + { + m_Presets.Remove( i ); + return true; + } + return false; +} + +void CDmePresetGroup::MovePresetUp( CDmePreset *pPreset ) +{ + int i = FindPresetIndex( pPreset ); + if ( i >= 1 ) + { + m_Presets.Swap( i, i-1 ); + } +} + +void CDmePresetGroup::MovePresetDown( CDmePreset *pPreset ) +{ + int i = FindPresetIndex( pPreset ); + if ( i >= 0 && i < m_Presets.Count() - 1 ) + { + m_Presets.Swap( i, i+1 ); + } +} + + +//----------------------------------------------------------------------------- +// Reorder presets +//----------------------------------------------------------------------------- +void CDmePresetGroup::MovePresetInFrontOf( CDmePreset *pPreset, CDmePreset *pInFrontOf ) +{ + if ( pPreset == pInFrontOf ) + return; + + int nEnd = pInFrontOf ? FindPresetIndex( pInFrontOf ) : m_Presets.Count(); + Assert( nEnd >= 0 ); + + RemovePreset( pPreset ); + if ( nEnd > m_Presets.Count() ) + { + nEnd = m_Presets.Count(); + } + m_Presets.InsertBefore( nEnd, pPreset ); +} + + +//----------------------------------------------------------------------------- +// The preset remap +//----------------------------------------------------------------------------- +CDmePresetRemap *CDmePresetGroup::GetPresetRemap() +{ + return GetValueElement< CDmePresetRemap >( "presetRemap" ); +} + +CDmePresetRemap *CDmePresetGroup::GetOrAddPresetRemap() +{ + CDmePresetRemap *pPresetRemap = GetPresetRemap(); + if ( !pPresetRemap ) + { + pPresetRemap = CreateElement< CDmePresetRemap >( "PresetRemap", GetFileId() ); + SetValue( "presetRemap", pPresetRemap ); + } + return pPresetRemap; +} + + + +//----------------------------------------------------------------------------- +// Finds a control index +//----------------------------------------------------------------------------- +struct ExportedControl_t +{ + CUtlString m_Name; + bool m_bIsStereo; + bool m_bIsMulti; + int m_nFirstIndex; +}; + + +//----------------------------------------------------------------------------- +// Builds a unique list of controls found in the presets +//----------------------------------------------------------------------------- +static int FindExportedControlIndex( const char *pControlName, CUtlVector< ExportedControl_t > &uniqueControls ) +{ + int nCount = uniqueControls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pControlName, uniqueControls[i].m_Name ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Builds a unique list of controls found in the presets +//----------------------------------------------------------------------------- +static int BuildExportedControlList( CDmeAnimationSet *pAnimationSet, const CDmePresetGroup *pPresetGroup, CUtlVector< ExportedControl_t > &uniqueControls ) +{ + int nGlobalIndex = 0; + const CDmrElementArrayConst< CDmePreset > &presets = pPresetGroup->GetPresets(); + int nPresetCount = presets.Count(); + for ( int iPreset = 0; iPreset < nPresetCount; ++iPreset ) + { + CDmePreset *pPreset = presets[iPreset]; + const CDmrElementArray< CDmElement > &controls = pPreset->GetControlValues(); + + int nControlCount = controls.Count(); + for ( int i = 0; i < nControlCount; ++i ) + { + const char *pControlName = controls[i]->GetName(); + int nIndex = FindExportedControlIndex( pControlName, uniqueControls ); + if ( nIndex >= 0 ) + continue; + CDmAttribute *pValueAttribute = controls[i]->GetAttribute( "value" ); + if ( !pValueAttribute || pValueAttribute->GetType() != AT_FLOAT ) + continue; + + if ( pAnimationSet ) + { + + CDmElement *pControl = pAnimationSet->FindControl( pControlName ); + if ( !pControl ) + continue; + + int j = uniqueControls.AddToTail(); + ExportedControl_t &control = uniqueControls[j]; + control.m_Name = pControlName; + control.m_bIsStereo = pControl->GetValue<bool>( "combo" ); + control.m_bIsMulti = pControl->GetValue<bool>( "multi" ); + control.m_nFirstIndex = nGlobalIndex; + nGlobalIndex += 1 + control.m_bIsStereo + control.m_bIsMulti; + } + else + { + int j = uniqueControls.AddToTail(); + ExportedControl_t &control = uniqueControls[j]; + control.m_Name = pControlName; + // this isn't quite as reliable as querying the animation set but if we don't have one... + control.m_bIsStereo = controls[ i ]->GetAttribute( "balance" ) ? true : false; + control.m_bIsMulti = controls[ i ]->GetAttribute( "multilevel" ) ? true : false; + control.m_nFirstIndex = nGlobalIndex; + nGlobalIndex += 1 + control.m_bIsStereo + control.m_bIsMulti; + } + } + } + return nGlobalIndex; +} + + +//----------------------------------------------------------------------------- +// Exports this preset group to a faceposer .txt expression file +// Either an animation set or a combination operator are required so that +// the default value for unspecified +//----------------------------------------------------------------------------- +bool CDmePresetGroup::ExportToTXT( const char *pFileName, CDmeAnimationSet *pAnimationSet /* = NULL */, CDmeCombinationOperator *pComboOp /* = NULL */ ) const +{ + const CDmePresetGroup *pPresetGroup = this; + + // find all used controls + CUtlVector< ExportedControl_t > exportedControls; + BuildExportedControlList( pAnimationSet, pPresetGroup, exportedControls ); + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + // Output the unique keys + buf.Printf( "$keys " ); + int nExportedControlCount = exportedControls.Count(); + for ( int i = 0; i < nExportedControlCount; ++i ) + { + char pTempBuf[MAX_PATH]; + + ExportedControl_t &control = exportedControls[i]; + if ( !control.m_bIsStereo ) + { + buf.Printf("%s ", control.m_Name.Get() ); + } + else + { + Q_snprintf( pTempBuf, sizeof(pTempBuf), "right_%s", control.m_Name.Get() ); + buf.Printf("%s ", pTempBuf ); + Q_snprintf( pTempBuf, sizeof(pTempBuf), "left_%s", control.m_Name.Get() ); + buf.Printf("%s ", pTempBuf ); + } + + if ( control.m_bIsMulti ) + { + Q_snprintf( pTempBuf, sizeof(pTempBuf), "multi_%s", control.m_Name.Get() ); + buf.Printf("%s ", pTempBuf ); + } + } + buf.Printf( "\n" ); + buf.Printf( "$hasweighting\n" ); + buf.Printf( "$normalized\n" ); + + // Output all presets + const CDmrElementArrayConst< CDmePreset > &presets = pPresetGroup->GetPresets(); + int nPresetCount = presets.Count(); + for ( int iPreset = 0; iPreset < nPresetCount; ++iPreset ) + { + CDmePreset *pPreset = presets[iPreset]; + const char *pPresetName = pPreset->GetName(); + + // Hack for 'silence' and for p_ naming scheme + if ( !Q_stricmp( pPresetName, "p_silence" ) ) + { + pPresetName = "<sil>"; + } + if ( pPresetName[0] == 'p' && pPresetName[1] == '_' ) + { + pPresetName = &pPresetName[2]; + } + + buf.Printf( "\"%s\" \t", pPresetName ); + + int nPhonemeIndex = TextToPhonemeIndex( pPresetName ); + int nCode = CodeForPhonemeByIndex( nPhonemeIndex ); + if ( nCode < 128 ) + { + buf.Printf( "\"%c\" \t", nCode ); + } + else + { + buf.Printf( "\"0x%x\"\t", nCode ); + } + + for ( int i = 0; i < nExportedControlCount; ++i ) + { + ExportedControl_t &control = exportedControls[i]; + CDmElement *pControlValue = pPreset->FindControlValue( control.m_Name ); + if ( !pControlValue ) + { + CDmElement *pControl = pAnimationSet ? pAnimationSet->FindControl( control.m_Name ) : NULL; + if ( !pControl ) + { + const ControlIndex_t nIndex = pComboOp ? pComboOp->FindControlIndex( control.m_Name ) : -1; + if ( nIndex >= 0 ) + { + buf.Printf( "%.5f\t0.000\t", pComboOp->GetControlDefaultValue( nIndex ) ); + if ( control.m_bIsStereo ) + { + buf.Printf( "%.5f\t0.000\t", pComboOp->GetControlDefaultValue( nIndex ) ); + } + if ( control.m_bIsMulti ) + { + buf.Printf( "%.5f\t0.000\t", pComboOp->GetControlDefaultValue( nIndex ) ); + } + } + else + { + buf.Printf( "0.000\t0.000\t" ); + if ( control.m_bIsStereo ) + { + buf.Printf( "0.000\t0.000\t" ); + } + if ( control.m_bIsMulti ) + { + buf.Printf( "0.000\t0.000\t" ); + } + } + + continue; + } + + if ( !control.m_bIsStereo ) + { + buf.Printf( "%.5f\t1.000\t", pControl->GetValue<float>( "defaultValue" ) ); + } + else + { + float flValue, flBalance, flLeft, flRight; + flValue = pControl->GetValue<float>( "defaultValue", 0.0f ); + flBalance = pControl->GetValue<float>( "defaultBalance", 0.5f ); + ValueBalanceToLeftRight( &flLeft, &flRight, flValue, flBalance ); + buf.Printf( "%.5f\t1.000\t", flRight ); + buf.Printf( "%.5f\t1.000\t", flLeft ); + } + + if ( control.m_bIsMulti ) + { + buf.Printf( "%.5f\t1.000\t", pControl->GetValue<float>( "defaultMultilevel" ) ); + } + continue; + } + + if ( !control.m_bIsStereo ) + { + buf.Printf( "%.5f\t1.000\t", pControlValue->GetValue<float>( "value" ) ); + } + else + { + float flValue, flBalance, flLeft, flRight; + flValue = pControlValue->GetValue<float>( "value" ); + flBalance = pControlValue->GetValue<float>( "balance" ); + ValueBalanceToLeftRight( &flLeft, &flRight, flValue, flBalance ); + buf.Printf( "%.5f\t1.000\t", flRight ); + buf.Printf( "%.5f\t1.000\t", flLeft ); + } + + if ( control.m_bIsMulti ) + { + buf.Printf( "%.5f\t1.000\t", pControlValue->GetValue<float>( "multilevel" ) ); + } + } + const char *pDesc = DescForPhonemeByIndex( nPhonemeIndex ); + buf.Printf( "\"%s\"\n", pDesc ? pDesc : pPresetName ); + } + + return g_pFullFileSystem->WriteFile( pFileName, NULL, buf ); +} + +#ifdef ALIGN4 +#undef ALIGN4 +#endif // #ifdef ALIGN4 +#define ALIGN4( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3) + + +//----------------------------------------------------------------------------- +// Exports this preset group to a faceposer .vfe expression file +//----------------------------------------------------------------------------- +bool CDmePresetGroup::ExportToVFE( const char *pFileName, CDmeAnimationSet *pAnimationSet /* = NULL */, CDmeCombinationOperator *pComboOp /* = NULL */ ) const +{ + const CDmePresetGroup *pPresetGroup = this; + + int i; + const CDmrElementArrayConst< CDmePreset > &presets = pPresetGroup->GetPresets(); + + // find all used controls + CUtlVector< ExportedControl_t > exportedControls; + int nTotalControlCount = BuildExportedControlList( pAnimationSet, pPresetGroup, exportedControls ); + const int nExportedControlCount = exportedControls.Count(); + + byte *pData = (byte *)calloc( 1024 * 1024, 1 ); + byte *pDataStart = pData; + + flexsettinghdr_t *fhdr = (flexsettinghdr_t *)pData; + + fhdr->id = ('V' << 16) + ('F' << 8) + ('E'); + fhdr->version = 0; + if ( !g_pFullFileSystem->FullPathToRelativePathEx( pFileName, "GAME", fhdr->name, sizeof(fhdr->name) ) ) + { + Q_strncpy( fhdr->name, pFileName, sizeof(fhdr->name) ); + } + + // allocate room for header + pData += sizeof( flexsettinghdr_t ); + ALIGN4( pData ); + + // store flex settings + flexsetting_t *pSetting = (flexsetting_t *)pData; + fhdr->numflexsettings = presets.Count(); + fhdr->flexsettingindex = pData - pDataStart; + pData += sizeof( flexsetting_t ) * fhdr->numflexsettings; + ALIGN4( pData ); + for ( i = 0; i < fhdr->numflexsettings; i++ ) + { + CDmePreset *pPreset = presets[i]; + Assert( pPreset ); + + pSetting[i].index = i; + pSetting[i].settingindex = pData - (byte *)(&pSetting[i]); + + flexweight_t *pFlexWeights = (flexweight_t *)pData; + + for ( int j = 0; j < nExportedControlCount; j++ ) + { + ExportedControl_t &control = exportedControls[ j ]; + CDmElement *pControlValue = pPreset->FindControlValue( control.m_Name ); + if ( !pControlValue ) + { + const ControlIndex_t nIndex = pComboOp ? pComboOp->FindControlIndex( control.m_Name ) : -1; + if ( nIndex >= 0 ) + { + if ( !control.m_bIsStereo ) + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex; + pFlexWeights->weight = pComboOp->GetControlDefaultValue( nIndex ); + pFlexWeights->influence = 1.0f; + pFlexWeights++; + } + else + { + float flValue, flBalance, flLeft, flRight; + flValue = pComboOp->GetControlDefaultValue( nIndex ); + flBalance = 0.5; + ValueBalanceToLeftRight( &flLeft, &flRight, flValue, flBalance ); + + pSetting[i].numsettings += 2; + pFlexWeights->key = control.m_nFirstIndex; + pFlexWeights->weight = flRight; + pFlexWeights->influence = 1.0f; + pFlexWeights++; + pFlexWeights->key = control.m_nFirstIndex + 1; + pFlexWeights->weight = flLeft; + pFlexWeights->influence = 1.0f; + pFlexWeights++; + } + + if ( control.m_bIsMulti ) + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex + 1 + control.m_bIsStereo; + pFlexWeights->weight = 0.5f; + pFlexWeights->influence = 1.0f; + pFlexWeights++; + } + } + else + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex; + pFlexWeights->weight = 0.0f; + pFlexWeights->influence = 0.0f; + pFlexWeights++; + + if ( control.m_bIsStereo ) + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex + 1; + pFlexWeights->weight = 0.0f; + pFlexWeights->influence = 0.0f; + pFlexWeights++; + } + + if ( control.m_bIsMulti ) + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex + 1 + control.m_bIsStereo; + pFlexWeights->weight = 0.5f; + pFlexWeights->influence = 0.0f; + pFlexWeights++; + } + } + + continue; + } + + if ( !control.m_bIsStereo ) + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex; + pFlexWeights->weight = pControlValue->GetValue<float>( "value" ); + pFlexWeights->influence = 1.0f; + pFlexWeights++; + } + else + { + float flValue, flBalance, flLeft, flRight; + flValue = pControlValue->GetValue<float>( "value" ); + flBalance = pControlValue->GetValue<float>( "balance" ); + ValueBalanceToLeftRight( &flLeft, &flRight, flValue, flBalance ); + + pSetting[i].numsettings += 2; + pFlexWeights->key = control.m_nFirstIndex; + pFlexWeights->weight = flRight; + pFlexWeights->influence = 1.0f; + pFlexWeights++; + pFlexWeights->key = control.m_nFirstIndex + 1; + pFlexWeights->weight = flLeft; + pFlexWeights->influence = 1.0f; + pFlexWeights++; + } + + if ( control.m_bIsMulti ) + { + pSetting[i].numsettings++; + pFlexWeights->key = control.m_nFirstIndex + 1 + control.m_bIsStereo; + pFlexWeights->weight = pControlValue->GetValue<float>( "multilevel" ); + pFlexWeights->influence = 1.0f; + pFlexWeights++; + } + } + + pData = (byte *)pFlexWeights; + ALIGN4( pData ); + } + + int numindexes = 1; + for (i = 0; i < fhdr->numflexsettings; i++) + { + if ( pSetting[i].index >= numindexes ) + { + numindexes = pSetting[i].index + 1; + } + } + + // store indexed table + int *pIndex = (int *)pData; + fhdr->numindexes = numindexes; + fhdr->indexindex = pData - pDataStart; + pData += sizeof( int ) * numindexes; + ALIGN4( pData ); + for (i = 0; i < numindexes; i++) + { + pIndex[i] = -1; + } + for (i = 0; i < fhdr->numflexsettings; i++) + { + pIndex[pSetting[i].index] = i; + } + + // store flex setting names + for (i = 0; i < fhdr->numflexsettings; i++) + { + CDmePreset *pPreset = presets[i]; + const char *pPresetName = pPreset->GetName(); + + // Hack for 'silence' and for p_ naming scheme + if ( pPresetName[0] == 'p' && pPresetName[1] == '_' ) + { + pPresetName = &pPresetName[2]; + } + if ( !Q_stricmp( pPresetName, "silence" ) ) + { + pPresetName = "<sil>"; + } + + pSetting[i].nameindex = pData - (byte *)(&pSetting[i]); + strcpy( (char *)pData, pPresetName ); + pData += Q_strlen( pPresetName ) + 1; + } + ALIGN4( pData ); + + // store key names + char **pKeynames = (char **)pData; + fhdr->numkeys = nTotalControlCount; + fhdr->keynameindex = pData - pDataStart; + pData += sizeof(char *) * nTotalControlCount; + int j = 0; + for ( i = 0; i < nExportedControlCount; ++i ) + { + char pTempBuf[MAX_PATH]; + + ExportedControl_t &control = exportedControls[i]; + if ( !control.m_bIsStereo ) + { + pKeynames[j++] = (char *)(pData - pDataStart); + strcpy( (char *)pData, control.m_Name ); + pData += Q_strlen( control.m_Name ) + 1; + } + else + { + pKeynames[j++] = (char *)(pData - pDataStart); + Q_snprintf( pTempBuf, sizeof(pTempBuf), "right_%s", control.m_Name.Get() ); + strcpy( (char *)pData, pTempBuf ); + pData += Q_strlen( pTempBuf ) + 1; + + pKeynames[j++] = (char *)(pData - pDataStart); + Q_snprintf( pTempBuf, sizeof(pTempBuf), "left_%s", control.m_Name.Get() ); + strcpy( (char *)pData, pTempBuf ); + pData += Q_strlen( pTempBuf ) + 1; + } + + if ( control.m_bIsMulti ) + { + pKeynames[j++] = (char *)(pData - pDataStart); + Q_snprintf( pTempBuf, sizeof(pTempBuf), "multi_%s", control.m_Name.Get() ); + strcpy( (char *)pData, pTempBuf ); + pData += Q_strlen( pTempBuf ) + 1; + } + } + Assert( j == nTotalControlCount ); + ALIGN4( pData ); + + // allocate room for remapping + int *keymapping = (int *)pData; + fhdr->keymappingindex = pData - pDataStart; + pData += sizeof( int ) * nTotalControlCount; + for (i = 0; i < nTotalControlCount; i++) + { + keymapping[i] = -1; + } + ALIGN4( pData ); + + fhdr->length = pData - pDataStart; + + FileHandle_t fh = g_pFullFileSystem->Open( pFileName, "wb" ); + if ( !fh ) + { + ConWarning( "Unable to write to %s (read-only?)\n", pFileName ); + free( pDataStart ); + return false; + } + + g_pFullFileSystem->Write( pDataStart, fhdr->length, fh ); + g_pFullFileSystem->Close( fh ); + free( pDataStart ); + return true; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// CDmeAnimationSet - container for animation set info +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeAnimationSet, CDmeAnimationSet ); + +void CDmeAnimationSet::OnConstruction() +{ + m_Controls.Init( this, "controls" ); + m_PresetGroups.Init( this, "presetGroups" ); + m_SelectionGroups.Init( this, "selectionGroups" ); + m_PhonemeMap.Init( this, "phonememap" ); + m_Operators.Init( this, "operators" ); + m_Bookmarks.Init( this, "bookmarks" ); +} + +void CDmeAnimationSet::OnDestruction() +{ +} + +CDmaElementArray< CDmElement > &CDmeAnimationSet::GetControls() +{ + return m_Controls; +} + +CDmaElementArray< CDmePresetGroup > &CDmeAnimationSet::GetPresetGroups() +{ + return m_PresetGroups; +} + +CDmaElementArray< CDmeOperator > &CDmeAnimationSet::GetOperators() +{ + return m_Operators; +} + +void CDmeAnimationSet::AddOperator( CDmeOperator *pOperator ) +{ + m_Operators.AddToTail( pOperator ); +} + +void CDmeAnimationSet::RemoveOperator( CDmeOperator *pOperator ) +{ + int nCount = m_Operators.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Operators[i] == pOperator ) + { + m_Operators.Remove(i); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Finds the index of a particular preset group +//----------------------------------------------------------------------------- +void CDmeAnimationSet::OnElementUnserialized() +{ + BaseClass::OnElementUnserialized(); + + CDmeGameModel *pGameModel = GetValueElement< CDmeGameModel >( "gameModel" ); + if ( pGameModel ) + { + // NOTE: The model preset manager can't possibly have the right + // file id at this point; it's up to the preset group manager to queue + // application requests until it gets one + g_pModelPresetGroupMgr->ApplyModelPresets( pGameModel->GetModelName(), this ); + } +} + + +//----------------------------------------------------------------------------- +// Finds the index of a particular preset group +//----------------------------------------------------------------------------- +int CDmeAnimationSet::FindPresetGroupIndex( CDmePresetGroup *pPresetGroup ) +{ + int c = m_PresetGroups.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmePresetGroup *e = m_PresetGroups.Get( i ); + if ( pPresetGroup == e ) + return i; + } + return -1; +} + +int CDmeAnimationSet::FindPresetGroupIndex( const char *pGroupName ) +{ + int c = m_PresetGroups.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmePresetGroup *e = m_PresetGroups.Get( i ); + if ( e && !Q_stricmp( e->GetName(), pGroupName ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Find by name +//----------------------------------------------------------------------------- +CDmePresetGroup *CDmeAnimationSet::FindPresetGroup( const char *pGroupName ) +{ + int nIndex = FindPresetGroupIndex( pGroupName ); + if ( nIndex >= 0 ) + return m_PresetGroups[nIndex]; + return NULL; +} + + +//----------------------------------------------------------------------------- +// Find or add by name +//----------------------------------------------------------------------------- +CDmePresetGroup *CDmeAnimationSet::FindOrAddPresetGroup( const char *pGroupName ) +{ + CDmePresetGroup *pPresetGroup = FindPresetGroup( pGroupName ); + if ( !pPresetGroup ) + { + // Create the default groups in order + pPresetGroup = CreateElement< CDmePresetGroup >( pGroupName, GetFileId() ); + m_PresetGroups.AddToTail( pPresetGroup ); + } + return pPresetGroup; +} + + +//----------------------------------------------------------------------------- +// Remove preset group +//----------------------------------------------------------------------------- +bool CDmeAnimationSet::RemovePresetGroup( CDmePresetGroup *pPresetGroup ) +{ + int i = FindPresetGroupIndex( pPresetGroup ); + if ( i >= 0 ) + { + m_PresetGroups.Remove( i ); + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Move preset group up/down in the list +//----------------------------------------------------------------------------- +void CDmeAnimationSet::MovePresetGroupUp( CDmePresetGroup *pPresetGroup ) +{ + int i = FindPresetGroupIndex( pPresetGroup ); + if ( i >= 1 ) + { + m_PresetGroups.Swap( i, i-1 ); + } +} + +void CDmeAnimationSet::MovePresetGroupDown( CDmePresetGroup *pPresetGroup ) +{ + int i = FindPresetGroupIndex( pPresetGroup ); + if ( i >= 0 && i < m_PresetGroups.Count() - 1 ) + { + m_PresetGroups.Swap( i, i+1 ); + } +} + + +//----------------------------------------------------------------------------- +// Reorder preset groups +//----------------------------------------------------------------------------- +void CDmeAnimationSet::MovePresetGroupInFrontOf( CDmePresetGroup *pPresetGroup, CDmePresetGroup *pInFrontOf ) +{ + if ( pPresetGroup == pInFrontOf ) + return; + +#ifdef DBGFLAG_ASSERT + int nStart = FindPresetGroupIndex( pPresetGroup ); +#endif + + int nEnd = pInFrontOf ? FindPresetGroupIndex( pInFrontOf ) : m_PresetGroups.Count(); + Assert( nStart >= 0 && nEnd >= 0 ); + + RemovePresetGroup( pPresetGroup ); + if ( nEnd > m_PresetGroups.Count() ) + { + nEnd = m_PresetGroups.Count(); + } + m_PresetGroups.InsertBefore( nEnd, pPresetGroup ); +} + + +CDmePreset *CDmeAnimationSet::FindOrAddPreset( const char *pGroupName, const char *pPresetName, int nType /*=PROCEDURAL_PRESET_NOT*/ ) +{ + CDmePresetGroup *pPresetGroup = FindOrAddPresetGroup( pGroupName ); + return pPresetGroup->FindOrAddPreset( pPresetName, nType ); +} + +bool CDmeAnimationSet::RemovePreset( CDmePreset *pPreset ) +{ + int c = m_PresetGroups.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( m_PresetGroups[i]->RemovePreset( pPreset ) ) + return true; + } + return false; +} + + +const CDmaElementArray< CDmeBookmark > &CDmeAnimationSet::GetBookmarks() const +{ + return m_Bookmarks; +} + +CDmaElementArray< CDmeBookmark > &CDmeAnimationSet::GetBookmarks() +{ + return m_Bookmarks; +} + + +CDmaElementArray< CDmElement > &CDmeAnimationSet::GetSelectionGroups() +{ + return m_SelectionGroups; +} + +CDmaElementArray< CDmePhonemeMapping > &CDmeAnimationSet::GetPhonemeMap() +{ + return m_PhonemeMap; +} + +void CDmeAnimationSet::RestoreDefaultPhonemeMap() +{ + CUndoScopeGuard guard( "RestoreDefaultPhonemeMap" ); + + int i; + int c = m_PhonemeMap.Count(); + for ( i = 0; i < c; ++i ) + { + g_pDataModel->DestroyElement( m_PhonemeMap[ i ]->GetHandle() ); + } + m_PhonemeMap.Purge(); + + int phonemeCount = NumPhonemes(); + for ( i = 0; i < phonemeCount; ++i ) + { + const char *pName = NameForPhonemeByIndex( i ); + CDmePhonemeMapping *mapping = CreateElement< CDmePhonemeMapping >( pName, GetFileId() ); + char presetName[ 256 ]; + Q_snprintf( presetName, sizeof( presetName ), "p_%s", pName ); + mapping->m_Preset = presetName; + mapping->m_Weight = 1.0f; + + m_PhonemeMap.AddToTail( mapping ); + } +} + +CDmePhonemeMapping *CDmeAnimationSet::FindMapping( const char *pRawPhoneme ) +{ + int c = m_PhonemeMap.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmePhonemeMapping *e = m_PhonemeMap.Get( i ); + Assert( e ); + if ( !e ) + continue; + + if ( !Q_stricmp( e->GetName(), pRawPhoneme ) ) + return e; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Finds a control +//----------------------------------------------------------------------------- +CDmElement *CDmeAnimationSet::FindControl( const char *pControlName ) +{ + int c = m_Controls.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *e = m_Controls.Get( i ); + if ( !Q_stricmp( e->GetName(), pControlName ) ) + return e; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Finds or adds a control +//----------------------------------------------------------------------------- +CDmElement *CDmeAnimationSet::FindOrAddControl( const char *pControlName ) +{ + CDmElement *pControl = FindControl( pControlName ); + if ( !pControl ) + { + // If not, then create one + pControl = CreateElement< CDmElement >( pControlName, GetFileId() ); + m_Controls.AddToTail( pControl ); + } + return pControl; +} + + +CDmElement *CDmeAnimationSet::FindSelectionGroup( const char *pSelectionGroupName ) +{ + int c = m_SelectionGroups.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *e = m_SelectionGroups.Get( i ); + if ( !Q_stricmp( e->GetName(), pSelectionGroupName ) ) + return e; + } + return NULL; +} + +CDmElement *CDmeAnimationSet::FindOrAddSelectionGroup( const char *pSelectionGroupName ) +{ + CDmElement *pSelectionGroup = FindSelectionGroup( pSelectionGroupName ); + if ( !pSelectionGroup ) + { + // Create the default groups in order + pSelectionGroup = CreateElement< CDmElement >( pSelectionGroupName, GetFileId() ); + pSelectionGroup->AddAttribute( "selectedControls", AT_STRING_ARRAY ); + m_SelectionGroups.AddToTail( pSelectionGroup ); + } + return pSelectionGroup; +} + +void CDmeAnimationSet::CollectOperators( CUtlVector< DmElementHandle_t > &operators ) +{ + int numOperators = m_Operators.Count(); + for ( int i = 0; i < numOperators; ++i ) + { + DmElementHandle_t h = m_Operators.GetHandle( i ); + if ( h != DMELEMENT_HANDLE_INVALID ) + { + operators.AddToTail( h ); + } + } +} + +struct PPType_t +{ + int type; + char const *name; +}; + +static PPType_t g_PresetNames[ NUM_PROCEDURAL_PRESET_TYPES ] = +{ + { PROCEDURAL_PRESET_NOT, "NotProcedural!!!" }, + { PROCEDURAL_PRESET_IN_CROSSFADE, "In" }, + { PROCEDURAL_PRESET_OUT_CROSSFADE, "Out" }, + { PROCEDURAL_PRESET_REVEAL, "Reveal" }, + { PROCEDURAL_PRESET_PASTE, "Paste" }, + { PROCEDURAL_PRESET_JITTER, "Jitter" }, + { PROCEDURAL_PRESET_SMOOTH, "Smooth" }, + { PROCEDURAL_PRESET_SHARPEN, "Sharpen" }, + { PROCEDURAL_PRESET_SOFTEN, "Soften" }, + { PROCEDURAL_PRESET_STAGGER, "Stagger" }, +}; + +void CDmeAnimationSet::EnsureProceduralPresets() +{ + // Note: Starts at index 1 to skip the PROCEDURAL_PRESET_NOT case + for ( int i = 1; i < NUM_PROCEDURAL_PRESET_TYPES; ++i ) + { + FindOrAddPreset( "Procedural", g_PresetNames[ i ].name, g_PresetNames[ i ].type ); + } +} + +//----------------------------------------------------------------------------- +// A cache of preset groups to be associated with specific models +//----------------------------------------------------------------------------- +class CModelPresetGroupManager : public IModelPresetGroupManager +{ +public: + CModelPresetGroupManager(); + virtual void AssociatePresetsWithFile( DmFileId_t fileId ); + virtual void ApplyModelPresets( const char *pModelName, CDmeAnimationSet *pAnimationSet ); + +private: + struct QueuedPresetRequest_t + { + CUtlString m_ModelName; + CDmeHandle< CDmeAnimationSet > m_hAnimationSet; + }; + + typedef CUtlVector< CDmeHandle< CDmePresetGroup, true > > PresetGroupList_t; + + // Loads model presets from .pre files matching the model name + void LoadModelPresets( const char *pModelName, PresetGroupList_t &list ); + + CUtlStringMap< PresetGroupList_t > m_Lookup; + DmFileId_t m_FileId; + CUtlVector< QueuedPresetRequest_t > m_QueuedPresetRequest; +}; + + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +static CModelPresetGroupManager s_ModelPresetGroupManager; +IModelPresetGroupManager *g_pModelPresetGroupMgr = &s_ModelPresetGroupManager; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CModelPresetGroupManager::CModelPresetGroupManager() +{ + m_FileId = DMFILEID_INVALID; +} + + +//----------------------------------------------------------------------------- +// Associates presets in the cache with a particular file +//----------------------------------------------------------------------------- +void CModelPresetGroupManager::AssociatePresetsWithFile( DmFileId_t fileId ) +{ + m_FileId = fileId; + m_Lookup.Clear(); + if ( m_FileId != DMFILEID_INVALID ) + { + int nCount = m_QueuedPresetRequest.Count(); + for ( int i = 0; i < nCount; ++i ) + { + QueuedPresetRequest_t &request = m_QueuedPresetRequest[i]; + if ( request.m_hAnimationSet.Get() ) + { + ApplyModelPresets( request.m_ModelName, request.m_hAnimationSet.Get() ); + } + } + m_QueuedPresetRequest.Purge(); + } +} + + +//----------------------------------------------------------------------------- +// Loads model presets from .pre files matching the model name +//----------------------------------------------------------------------------- +void CModelPresetGroupManager::LoadModelPresets( const char *pModelName, PresetGroupList_t &list ) +{ + list.RemoveAll(); + + char pPresetPath[MAX_PATH]; + Q_ExtractFilePath( pModelName, pPresetPath, sizeof(pPresetPath) ); + + char pPresetNameBuf[MAX_PATH]; + Q_StripExtension( pModelName, pPresetNameBuf, sizeof(pPresetNameBuf) ); + int nLen = Q_strlen( pPresetNameBuf ); + Q_snprintf( &pPresetNameBuf[nLen], MAX_PATH - nLen, "*.pre" ); + + CDisableUndoScopeGuard sg; + + FileFindHandle_t fh; + const char *pFileName = g_pFullFileSystem->FindFirstEx( pPresetNameBuf, "GAME", &fh ); + for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( fh ) ) + { + char pRelativePresetPath[MAX_PATH]; + Q_ComposeFileName(pPresetPath, pFileName, pRelativePresetPath, sizeof(pRelativePresetPath) ); + + CDmElement* pRoot = NULL; + DmFileId_t fileid = g_pDataModel->RestoreFromFile( pRelativePresetPath, "GAME", NULL, &pRoot, CR_FORCE_COPY ); + if ( fileid == DMFILEID_INVALID || !pRoot ) + continue; + + CDmePresetGroup *pPresetGroup = CastElement<CDmePresetGroup>( pRoot ); + if ( !pPresetGroup ) + { + if ( pRoot ) + { + g_pDataModel->RemoveFileId( pRoot->GetFileId() ); + } + continue; + } + + pPresetGroup->SetFileId( m_FileId, TD_DEEP ); + + // Presets used through the model preset manager must be read only + shared + pPresetGroup->m_bIsReadOnly = true; + pPresetGroup->SetShared( true ); + + int i = list.AddToTail(); + list[i] = pPresetGroup; + } + g_pFullFileSystem->FindClose( fh ); +} + + +//----------------------------------------------------------------------------- +// Applies model presets associated with a particular model to an animation set +//----------------------------------------------------------------------------- +void CModelPresetGroupManager::ApplyModelPresets( const char *pModelName, CDmeAnimationSet *pAnimationSet ) +{ + if ( m_FileId == DMFILEID_INVALID ) + { + int i = m_QueuedPresetRequest.AddToTail(); + m_QueuedPresetRequest[i].m_ModelName = pModelName; + m_QueuedPresetRequest[i].m_hAnimationSet = pAnimationSet; + return; + } + + if ( !m_Lookup.Defined( pModelName ) ) + { + LoadModelPresets( pModelName, m_Lookup[pModelName] ); + } + + PresetGroupList_t &list = m_Lookup[pModelName]; + int nCount = list.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmePresetGroup *pPresetGroup = list[i]; + int nIndex = pAnimationSet->FindPresetGroupIndex( pPresetGroup->GetName() ); + if ( nIndex >= 0 ) + { + pAnimationSet->GetPresetGroups().Set( nIndex, pPresetGroup ); + } + else + { + pAnimationSet->GetPresetGroups().AddToTail( pPresetGroup ); + } + } +} diff --git a/movieobjects/dmeattachment.cpp b/movieobjects/dmeattachment.cpp new file mode 100644 index 0000000..3601ba0 --- /dev/null +++ b/movieobjects/dmeattachment.cpp @@ -0,0 +1,103 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Dme version of a model attachment point +// +//============================================================================= +#include "movieobjects/dmeattachment.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imesh.h" +#include "tier1/KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeAttachment, CDmeAttachment ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeAttachment::OnConstruction() +{ + if ( !g_pMaterialSystem ) + return; + + m_bIsRigid.Init( this, "isRigid" ); + m_bIsWorldAligned.Init( this, "isWorldAligned" ); + + KeyValues *pVMTKeyValues = new KeyValues( "wireframe" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + pVMTKeyValues->SetInt( "$ignorez", 0 ); + m_AttachmentMaterial.Init( "__DmeJointMaterial", pVMTKeyValues ); +} + +void CDmeAttachment::OnDestruction() +{ + if ( !g_pMaterialSystem ) + return; + + m_AttachmentMaterial.Shutdown(); +} + + +//----------------------------------------------------------------------------- +// For rendering joints +//----------------------------------------------------------------------------- +#define AXIS_SIZE 6.0f + +//----------------------------------------------------------------------------- +// Rendering method for the dag +//----------------------------------------------------------------------------- +void CDmeAttachment::Draw( const matrix3x4_t &shapeToWorld, CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + if ( !g_pMaterialSystem ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadMatrix( shapeToWorld ); + + pRenderContext->Bind( m_AttachmentMaterial ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 3 ); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( AXIS_SIZE, 0.0f, 0.0f ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, AXIS_SIZE, 0.0f ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, 0.0f, AXIS_SIZE ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PopMatrix(); +} + + diff --git a/movieobjects/dmebalancetostereocalculatoroperator.cpp b/movieobjects/dmebalancetostereocalculatoroperator.cpp new file mode 100644 index 0000000..a8fc482 --- /dev/null +++ b/movieobjects/dmebalancetostereocalculatoroperator.cpp @@ -0,0 +1,105 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The expression operator class - scalar math calculator +// for a good list of operators and simple functions, see: +// \\fileserver\user\MarcS\boxweb\aliveDistLite\v4.2.0\doc\alive\functions.txt +// (although we'll want to implement elerp as the standard 3x^2 - 2x^3 with rescale) +// +//============================================================================= +#include "movieobjects/dmebalancetostereocalculatoroperator.h" +#include "movieobjects/dmechannel.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmattribute.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeBalanceToStereoCalculatorOperator, CDmeBalanceToStereoCalculatorOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeBalanceToStereoCalculatorOperator::OnConstruction() +{ + m_result_left.Init( this, "result_left" ); + m_result_right.Init( this, "result_right" ); + m_result_multi.Init( this, "result_multi" ); + + m_value.Init( this, "value" ); + m_balance.InitAndSet( this, "balance", 0.5f ); + m_multilevel.InitAndSet( this, "multilevel", 0.5f ); + + m_bSpewResult.Init( this, "spewresult" ); + + m_flDefaultValue = FLT_MAX; +} + +void CDmeBalanceToStereoCalculatorOperator::OnDestruction() +{ +} + +void CDmeBalanceToStereoCalculatorOperator::GetInputAttributes ( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_value.GetAttribute() ); + attrs.AddToTail( m_balance.GetAttribute() ); + attrs.AddToTail( m_multilevel.GetAttribute() ); +} + +void CDmeBalanceToStereoCalculatorOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_result_left.GetAttribute() ); + attrs.AddToTail( m_result_right.GetAttribute() ); + attrs.AddToTail( m_result_multi.GetAttribute() ); +} + +float CDmeBalanceToStereoCalculatorOperator::ComputeDefaultValue() +{ + // NOTE: This is a total hack, which we expect to remove soon, when the balance work is done. + const static UtlSymId_t symToElement = g_pDataModel->GetSymbol( "toElement" ); + CDmeChannel *pChannel = FindReferringElement<CDmeChannel>( this, symToElement ); + if ( !pChannel ) + return 0.0f; + + CDmElement *pControl = pChannel->GetFromElement(); + if ( !pControl ) + return 0.0f; + + return pControl->GetValue<float>( "defaultValue" ); +} + +void CDmeBalanceToStereoCalculatorOperator::Operate() +{ + if ( m_flDefaultValue == FLT_MAX ) + { + m_flDefaultValue = ComputeDefaultValue(); + } + + float flValue = m_value - m_flDefaultValue; + if ( m_balance > 0.5f ) + { + m_result_right = m_value; + m_result_left = ( ( 1.0f - m_balance ) / 0.5f ) * flValue + m_flDefaultValue; + } + else + { + m_result_right = ( m_balance / 0.5f ) * flValue + m_flDefaultValue; + m_result_left = m_value; + } + + m_result_multi = m_multilevel; + + if ( m_bSpewResult ) + { + Msg( "%s = l('%f') r('%f') m('%f')\n", GetName(), (float)m_result_left, (float)m_result_right, (float)m_result_multi ); + } +} + +void CDmeBalanceToStereoCalculatorOperator::SetSpewResult( bool state ) +{ + m_bSpewResult = state; +} diff --git a/movieobjects/dmebookmark.cpp b/movieobjects/dmebookmark.cpp new file mode 100644 index 0000000..35063c4 --- /dev/null +++ b/movieobjects/dmebookmark.cpp @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmebookmark.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeBookmark, CDmeBookmark ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeBookmark::OnConstruction() +{ + m_Time.InitAndSet( this, "time", 0 ); + m_Duration.InitAndSet( this, "duration", 0 ); + m_Note.Init( this, "note" ); +} + +void CDmeBookmark::OnDestruction() +{ +} diff --git a/movieobjects/dmecamera.cpp b/movieobjects/dmecamera.cpp new file mode 100644 index 0000000..f593602 --- /dev/null +++ b/movieobjects/dmecamera.cpp @@ -0,0 +1,288 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmecamera.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "mathlib/vector.h" +#include "movieobjects/dmetransform.h" +#include "materialsystem/imaterialsystem.h" +#include "movieobjects_interfaces.h" +#include "tier2/tier2.h" + +// FIXME: REMOVE +#include "istudiorender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeCamera, CDmeCamera ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCamera::OnConstruction() +{ + m_fieldOfView.InitAndSet( this, "fieldOfView", 30.0f ); + + // FIXME: This currently matches the client DLL for HL2 + // but we probably need a way of getting this state from the client DLL + m_zNear.InitAndSet( this, "znear", 3.0f ); + m_zFar.InitAndSet( this, "zfar", 16384.0f * 1.73205080757f ); + + m_fFocalDistance.InitAndSet( this, "focalDistance", 72.0f); + m_fAperture.InitAndSet( this, "aperture", 0.2f); + m_fShutterSpeed.InitAndSet( this, "shutterSpeed", 1.0f / 48.0f ); + m_fToneMapScale.InitAndSet( this, "toneMapScale", 1.0f ); + m_fBloomScale.InitAndSet( this, "bloomScale", 0.28f ); + m_nDoFQuality.InitAndSet( this, "depthOfFieldQuality", 0 ); + m_nMotionBlurQuality.InitAndSet( this, "motionBlurQuality", 0 ); +} + +void CDmeCamera::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// Loads the material system view matrix based on the transform +//----------------------------------------------------------------------------- +void CDmeCamera::LoadViewMatrix( bool bUseEngineCoordinateSystem ) +{ + if ( !g_pMaterialSystem ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + VMatrix view; + GetViewMatrix( view, bUseEngineCoordinateSystem ); + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadMatrix( view ); +} + +//----------------------------------------------------------------------------- +// Loads the material system projection matrix based on the fov, etc. +//----------------------------------------------------------------------------- +void CDmeCamera::LoadProjectionMatrix( int nDisplayWidth, int nDisplayHeight ) +{ + if ( !g_pMaterialSystem ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + + VMatrix proj; + GetProjectionMatrix( proj, nDisplayWidth, nDisplayHeight ); + pRenderContext->LoadMatrix( proj ); +} + +//----------------------------------------------------------------------------- +// Sets up studiorender camera state +//----------------------------------------------------------------------------- +void CDmeCamera::LoadStudioRenderCameraState() +{ + // FIXME: Remove this! This should automatically happen in DrawModel + // in studiorender. + if ( !g_pStudioRender ) + return; + + matrix3x4_t transform; + GetTransform()->GetTransform( transform ); + + Vector vecOrigin, vecRight, vecUp, vecForward; + MatrixGetColumn( transform, 0, vecRight ); + MatrixGetColumn( transform, 1, vecUp ); + MatrixGetColumn( transform, 2, vecForward ); + MatrixGetColumn( transform, 3, vecOrigin ); + g_pStudioRender->SetViewState( vecOrigin, vecRight, vecUp, vecForward ); +} + +//----------------------------------------------------------------------------- +// Returns the x FOV (the full angle) +//----------------------------------------------------------------------------- +float CDmeCamera::GetFOVx() const +{ + return m_fieldOfView; +} + +void CDmeCamera::SetFOVx( float fov ) +{ + m_fieldOfView = fov; +} + +//----------------------------------------------------------------------------- +// Returns the focal distance in inches +//----------------------------------------------------------------------------- +float CDmeCamera::GetFocalDistance() const +{ + return m_fFocalDistance; +} + +//----------------------------------------------------------------------------- +// Sets the focal distance in inches +//----------------------------------------------------------------------------- +void CDmeCamera::SetFocalDistance( const float &fFocalDistance ) +{ + m_fFocalDistance = fFocalDistance; +} + +//----------------------------------------------------------------------------- +// Returns the camera aperture in inches +//----------------------------------------------------------------------------- +float CDmeCamera::GetAperture() const +{ + return m_fAperture; +} + +//----------------------------------------------------------------------------- +// Returns the camera aperture in inches +//----------------------------------------------------------------------------- +float CDmeCamera::GetShutterSpeed() const +{ + return m_fShutterSpeed; +} + +//----------------------------------------------------------------------------- +// Returns the tone map scale +//----------------------------------------------------------------------------- +float CDmeCamera::GetToneMapScale() const +{ + return m_fToneMapScale; +} + +//----------------------------------------------------------------------------- +// Returns the bloom scale +//----------------------------------------------------------------------------- +float CDmeCamera::GetBloomScale() const +{ + return m_fBloomScale; +} + +//----------------------------------------------------------------------------- +// Returns the number of Depth of Field samples +//----------------------------------------------------------------------------- +int CDmeCamera::GetDepthOfFieldQuality() const +{ + return m_nDoFQuality; +} + +//----------------------------------------------------------------------------- +// Returns the number of Motion Blur samples +//----------------------------------------------------------------------------- +int CDmeCamera::GetMotionBlurQuality() const +{ + return m_nMotionBlurQuality; +} + +//----------------------------------------------------------------------------- +// Returns the view direction +//----------------------------------------------------------------------------- +void CDmeCamera::GetViewDirection( Vector *pDirection ) +{ + matrix3x4_t transform; + GetTransform()->GetTransform( transform ); + MatrixGetColumn( transform, 2, *pDirection ); + + // We look down the -z axis + *pDirection *= -1.0f; +} + +//----------------------------------------------------------------------------- +// Sets up render state in the material system for rendering +//----------------------------------------------------------------------------- +void CDmeCamera::SetupRenderState( int nDisplayWidth, int nDisplayHeight, bool bUseEngineCoordinateSystem /* = false */ ) +{ + LoadViewMatrix( bUseEngineCoordinateSystem ); + LoadProjectionMatrix( nDisplayWidth, nDisplayHeight ); + LoadStudioRenderCameraState( ); +} + +//----------------------------------------------------------------------------- +// accessors for generated matrices +//----------------------------------------------------------------------------- +void CDmeCamera::GetViewMatrix( VMatrix &view, bool bUseEngineCoordinateSystem /* = false */ ) +{ + matrix3x4_t transform, invTransform; + CDmeTransform *pTransform = GetTransform(); + pTransform->GetTransform( transform ); + + if ( bUseEngineCoordinateSystem ) + { + VMatrix matRotate( transform ); + VMatrix matRotateZ; + MatrixBuildRotationAboutAxis( matRotateZ, Vector(0,0,1), -90 ); + MatrixMultiply( matRotate, matRotateZ, matRotate ); + + VMatrix matRotateX; + MatrixBuildRotationAboutAxis( matRotateX, Vector(1,0,0), 90 ); + MatrixMultiply( matRotate, matRotateX, matRotate ); + transform = matRotate.As3x4(); + } + + MatrixInvert( transform, invTransform ); + view = invTransform; +} + +void CDmeCamera::GetProjectionMatrix( VMatrix &proj, int width, int height ) +{ + float flFOV = m_fieldOfView.Get(); + float flZNear = m_zNear.Get(); + float flZFar = m_zFar.Get(); + float flApsectRatio = (float)width / (float)height; + +// MatrixBuildPerspective( proj, flFOV, flFOV * flApsectRatio, flZNear, flZFar ); + +#if 1 + float halfWidth = tan( flFOV * M_PI / 360.0 ); + float halfHeight = halfWidth / flApsectRatio; +#else + float halfHeight = tan( flFOV * M_PI / 360.0 ); + float halfWidth = flApsectRatio * halfHeight; +#endif + memset( proj.Base(), 0, sizeof( proj ) ); + proj[0][0] = 1.0f / halfWidth; + proj[1][1] = 1.0f / halfHeight; + proj[2][2] = flZFar / ( flZNear - flZFar ); + proj[3][2] = -1.0f; + proj[2][3] = flZNear * flZFar / ( flZNear - flZFar ); +} + +void CDmeCamera::GetViewProjectionInverse( VMatrix &viewprojinv, int width, int height ) +{ + VMatrix view, proj; + GetViewMatrix( view ); + GetProjectionMatrix( proj, width, height ); + + VMatrix viewproj; + MatrixMultiply( proj, view, viewproj ); + bool success = MatrixInverseGeneral( viewproj, viewprojinv ); + if ( !success ) + { + Assert( 0 ); + MatrixInverseTR( viewproj, viewprojinv ); + } +} + +//----------------------------------------------------------------------------- +// Computes the screen space position given a screen size +//----------------------------------------------------------------------------- +void CDmeCamera::ComputeScreenSpacePosition( const Vector &vecWorldPosition, int width, int height, Vector2D *pScreenPosition ) +{ + VMatrix view, proj, viewproj; + GetViewMatrix( view ); + GetProjectionMatrix( proj, width, height ); + MatrixMultiply( proj, view, viewproj ); + + Vector vecScreenPos; + Vector3DMultiplyPositionProjective( viewproj, vecWorldPosition, vecScreenPos ); + + pScreenPosition->x = ( vecScreenPos.x + 1.0f ) * width / 2.0f; + pScreenPosition->y = ( -vecScreenPos.y + 1.0f ) * height / 2.0f; +} + + diff --git a/movieobjects/dmechannel.cpp b/movieobjects/dmechannel.cpp new file mode 100644 index 0000000..81e5e9d --- /dev/null +++ b/movieobjects/dmechannel.cpp @@ -0,0 +1,1007 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmechannel.h" +#include "movieobjects/dmelog.h" +#include "movieobjects/dmeclip.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmehandle.h" +#include "datamodel/dmattribute.h" +#include "tier0/vprof.h" +#include "tier1/KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// +// CDmeChannelRecordingMgr +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +static CDmeChannelRecordingMgr s_ChannelRecordingMgr; +CDmeChannelRecordingMgr *g_pChannelRecordingMgr = &s_ChannelRecordingMgr; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDmeChannelRecordingMgr::CDmeChannelRecordingMgr() +{ + m_bActive = false; + m_bSavedUndoState = false; + m_bUseTimeSelection = false; + m_nRevealType = PROCEDURAL_PRESET_NOT; + m_pRevealTarget = NULL; +} + + +//----------------------------------------------------------------------------- +// Activates, deactivates layer recording. +//----------------------------------------------------------------------------- +void CDmeChannelRecordingMgr::StartLayerRecording( const char *pUndoRedoDesc, const DmeLog_TimeSelection_t *pTimeSelection ) +{ + g_pDataModel->StartUndo( pUndoRedoDesc, pUndoRedoDesc ); + m_bSavedUndoState = g_pDataModel->IsUndoEnabled(); + g_pDataModel->SetUndoEnabled( false ); + + Assert( !m_bActive ); + Assert( m_LayerChannels.Count() == 0 ); + m_LayerChannels.Purge(); + m_bActive = true; + m_bUseTimeSelection = ( pTimeSelection != NULL ); + if ( pTimeSelection ) + { + m_TimeSelection = *pTimeSelection; + } + else + { + // Slam to default value + m_TimeSelection = DmeLog_TimeSelection_t(); + } + m_TimeSelection.ResetTimeAdvancing(); +} + +void CDmeChannelRecordingMgr::FinishLayerRecording( float flThreshhold, bool bFlattenLayers /*=true*/ ) +{ + Assert( m_bActive ); + + RemoveAllChannelsFromRecordingLayer(); + m_bUseTimeSelection = false; + m_TimeSelection.ResetTimeAdvancing(); + + g_pDataModel->SetUndoEnabled( m_bSavedUndoState ); + if ( bFlattenLayers ) + { + FlattenLayers( flThreshhold ); + } + g_pDataModel->FinishUndo(); + + m_bActive = false; + m_LayerChannels.Purge(); + m_nRevealType = PROCEDURAL_PRESET_NOT; + m_pRevealTarget = NULL; + m_PasteTarget.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Adds a channel to the recording layer +//----------------------------------------------------------------------------- +void CDmeChannelRecordingMgr::AddChannelToRecordingLayer( CDmeChannel *pChannel, CDmeClip *pRoot, CDmeClip *pShot ) +{ + Assert( pChannel->m_nRecordLayerIndex == -1 ); + + CDmeLog *pLog = pChannel->GetLog(); + if ( !pLog ) + return; + + int nRecordLayerIndex = m_LayerChannels.AddToTail(); + LayerChannelInfo_t& info = m_LayerChannels[nRecordLayerIndex]; + info.m_Channel = pChannel; + if ( pRoot ) + { + if ( !pChannel->BuildClipStack( &info.m_ClipStack, pRoot, pShot ) ) + { + m_LayerChannels.Remove( nRecordLayerIndex ); + return; + } + } + + pChannel->m_nRecordLayerIndex = nRecordLayerIndex; + + // This operation is undoable + CEnableUndoScopeGuard guard; + pLog->AddNewLayer(); + pChannel->SetMode( CM_RECORD ); +} + + +//----------------------------------------------------------------------------- +// Removes all channels from the recording layer +//----------------------------------------------------------------------------- +void CDmeChannelRecordingMgr::RemoveAllChannelsFromRecordingLayer( ) +{ + int c = m_LayerChannels.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeChannel *pChannel = m_LayerChannels[ i ].m_Channel.Get(); + if ( !pChannel ) + continue; + + CDmeLog *pLog = pChannel->GetLog(); + if ( pLog && IsUsingTimeSelection() ) + { + // Computes local times for the time selection + DmeLog_TimeSelection_t timeSelection; + GetLocalTimeSelection( timeSelection, pChannel->m_nRecordLayerIndex ); + pLog->FinishTimeSelection( pChannel->GetCurrentTime(), timeSelection ); + } + pChannel->m_nRecordLayerIndex = -1; + pChannel->SetMode( CM_PLAY ); + } +} + + +//----------------------------------------------------------------------------- +// Flattens recorded layers into the base layer +//----------------------------------------------------------------------------- +void CDmeChannelRecordingMgr::FlattenLayers( float flThreshhold ) +{ + int nFlags = 0; + if ( IsUsingDetachedTimeSelection() && IsTimeAdvancing() ) + { + nFlags |= CDmeLog::FLATTEN_NODISCONTINUITY_FIXUP; + } + + int c = m_LayerChannels.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeChannel *pChannel = m_LayerChannels[ i ].m_Channel.Get(); + if ( !pChannel ) + continue; + + CDmeLog *pLog = pChannel->GetLog(); + Assert( pLog ); + if ( !pLog ) + continue; + + pLog->FlattenLayers( flThreshhold, nFlags ); + } +} + + +//----------------------------------------------------------------------------- +// Used to iterate over all channels currently being recorded +//----------------------------------------------------------------------------- +int CDmeChannelRecordingMgr::GetLayerRecordingChannelCount() +{ + return m_LayerChannels.Count(); +} + +CDmeChannel* CDmeChannelRecordingMgr::GetLayerRecordingChannel( int nIndex ) +{ + return m_LayerChannels[nIndex].m_Channel.Get(); +} + + +//----------------------------------------------------------------------------- +// Computes time selection info in log time for a particular recorded channel +//----------------------------------------------------------------------------- +void CDmeChannelRecordingMgr::GetLocalTimeSelection( DmeLog_TimeSelection_t& selection, int nIndex ) +{ + Assert( m_bUseTimeSelection ); + LayerChannelInfo_t& info = m_LayerChannels[nIndex]; + selection = m_TimeSelection; + for ( int i = 0; i < TS_TIME_COUNT; ++i ) + { + selection.m_nTimes[i] = CDmeClip::ToChildMediaTime( info.m_ClipStack, selection.m_nTimes[i], false ); + } + selection.m_pPresetValue = info.m_pPresetValue; +} + + +//----------------------------------------------------------------------------- +// Methods which control various aspects of recording +//----------------------------------------------------------------------------- +void CDmeChannelRecordingMgr::UpdateTimeAdvancing( bool bPaused, DmeTime_t tCurTime ) +{ + Assert( m_bActive && m_bUseTimeSelection ); + if ( !bPaused && !m_TimeSelection.IsTimeAdvancing() ) + { + m_TimeSelection.StartTimeAdvancing(); + + // blow away logs after curtime + int nCount = m_LayerChannels.Count(); + for ( int i = 0; i < nCount; ++i ) + { + LayerChannelInfo_t& info = m_LayerChannels[i]; + DmeTime_t t = CDmeClip::ToChildMediaTime( info.m_ClipStack, tCurTime, false ); + info.m_Channel->GetLog()->RemoveKeys( t, DMETIME_MAXTIME ); + } + } +} + +void CDmeChannelRecordingMgr::UpdateRecordingTimeSelectionTimes( const DmeLog_TimeSelection_t& timeSelection ) +{ + Assert( m_bActive ); + for ( int i = 0; i < TS_TIME_COUNT; ++i ) + { + m_TimeSelection.m_nTimes[i] = timeSelection.m_nTimes[i]; + } + m_TimeSelection.m_nResampleInterval = timeSelection.m_nResampleInterval; +} + +void CDmeChannelRecordingMgr::SetIntensityOnAllLayers( float flIntensity ) +{ + m_TimeSelection.m_flIntensity = flIntensity; +} + +void CDmeChannelRecordingMgr::SetRecordingMode( RecordingMode_t mode ) +{ + m_TimeSelection.SetRecordingMode( mode ); +} + +void CDmeChannelRecordingMgr::SetPresetValue( CDmeChannel* pChannel, CDmAttribute *pPresetValue ) +{ + Assert( pChannel->m_nRecordLayerIndex != -1 ); + m_LayerChannels[ pChannel->m_nRecordLayerIndex ].m_pPresetValue = pPresetValue; +} + + +//----------------------------------------------------------------------------- +// Methods to query aspects of recording +//----------------------------------------------------------------------------- +bool CDmeChannelRecordingMgr::IsUsingDetachedTimeSelection() const +{ + Assert( m_bActive ); + return !m_TimeSelection.m_bAttachedMode; +} + +bool CDmeChannelRecordingMgr::IsTimeAdvancing() const +{ + Assert( m_bActive ); + return m_TimeSelection.IsTimeAdvancing(); +} + +bool CDmeChannelRecordingMgr::IsUsingTimeSelection() const +{ + return m_bUseTimeSelection; +} + +bool CDmeChannelRecordingMgr::ShouldRecordUsingTimeSelection() const +{ + return m_bUseTimeSelection && m_bActive; +} + +void CDmeChannelRecordingMgr::SetProceduralTarget( int nProceduralMode, const CDmAttribute *pTarget ) +{ + m_nRevealType = nProceduralMode; + m_pRevealTarget = pTarget; + m_PasteTarget.RemoveAll(); +} + +void CDmeChannelRecordingMgr::SetProceduralTarget( int nProceduralMode, const CUtlVector< KeyValues * >& list ) +{ + m_nRevealType = nProceduralMode; + m_pRevealTarget = NULL; + m_PasteTarget.RemoveAll(); + for ( int i = 0; i < list.Count(); ++i ) + { + m_PasteTarget.AddToTail( list[ i ] ); + } +} + +int CDmeChannelRecordingMgr::GetProceduralType() const +{ + return m_nRevealType; +} + +const CDmAttribute *CDmeChannelRecordingMgr::GetProceduralTarget() const +{ + Assert( m_pRevealTarget ); + return m_pRevealTarget; +} + +const CUtlVector< KeyValues * > &CDmeChannelRecordingMgr::GetPasteTarget() const +{ + return m_PasteTarget; +} + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeChannel, CDmeChannel ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeChannel::OnConstruction() +{ + m_nRecordLayerIndex = -1; + m_nNextCurveType = CURVE_DEFAULT; + m_tCurrentTime = DMETIME_INVALID; + m_tPreviousTime = DMETIME_INVALID; + m_timeOutsideTimeframe = DMETIME_INVALID; + + m_fromElement .Init( this, "fromElement", FATTRIB_HAS_CALLBACK | FATTRIB_NEVERCOPY ); + m_fromAttribute .Init( this, "fromAttribute", FATTRIB_TOPOLOGICAL | FATTRIB_HAS_CALLBACK ); + m_fromIndex .Init( this, "fromIndex", FATTRIB_TOPOLOGICAL ); + m_toElement .Init( this, "toElement", FATTRIB_HAS_CALLBACK | FATTRIB_NEVERCOPY ); + m_toAttribute .Init( this, "toAttribute", FATTRIB_TOPOLOGICAL | FATTRIB_HAS_CALLBACK ); + m_toIndex .Init( this, "toIndex", FATTRIB_TOPOLOGICAL ); + m_mode .InitAndSet( this, "mode", (int)CM_PASS ); + m_log .Init( this, "log" ); + m_FromAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; + m_ToAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; +} + +void CDmeChannel::OnDestruction() +{ +} + +int CDmeChannel::GetFromArrayIndex() const +{ + return m_fromIndex; +} + +int CDmeChannel::GetToArrayIndex() const +{ + return m_toIndex; +} + +void CDmeChannel::Play() +{ + CDmAttribute *pToAttr = GetToAttribute(); + + if ( pToAttr == NULL ) + return; + + CDmeLog *pLog = GetLog(); + if ( !pLog ) + return; + + DmeTime_t time = GetCurrentTime(); + + DmeTime_t t0 = pLog->GetBeginTime(); + DmeTime_t tn = pLog->GetEndTime(); + + PlayMode_t pmode = PM_HOLD; + switch ( pmode ) + { + case PM_HOLD: + time = clamp( time, t0, tn ); + break; + + case PM_LOOP: + if ( tn == t0 ) + { + time = t0; + } + else + { + time -= t0; + time = time % ( tn - t0 ); + time += t0; + } + break; + } + + // We might not want to do it this way, but this makes empty logs not get in the way if there is a valid pFromAttr +#if 1 + if ( pLog->IsEmpty() && !pLog->HasDefaultValue() && + GetFromAttribute() != NULL ) + { + Pass(); + return; + } +#endif + + pLog->GetValue( time, pToAttr, m_toIndex.Get() ); +} + +void CDmeChannel::Pass() +{ + CDmAttribute *pFromAttr = GetFromAttribute(); + CDmAttribute *pToAttr = GetToAttribute(); + if ( !pFromAttr || !pToAttr ) + return; + + if ( pFromAttr == pToAttr ) + return; + + DmAttributeType_t type = pFromAttr->GetType(); + const void *pValue = NULL; + if ( IsArrayType( type ) ) + { + CDmrGenericArray array( pFromAttr ); + pValue = array.GetUntyped( m_fromIndex.Get() ); + type = ArrayTypeToValueType( type ); + } + else + { + pValue = pFromAttr->GetValueUntyped(); + } + + if ( IsArrayType( pToAttr->GetType() ) ) + { + CDmrGenericArray array( pToAttr ); + array.Set( m_toIndex.Get(), type, pValue ); + } + else + { + pToAttr->SetValue( type, pValue ); + } +} + +//----------------------------------------------------------------------------- +// IsDirty - ie needs to operate +//----------------------------------------------------------------------------- +bool CDmeChannel::IsDirty() +{ + if ( BaseClass::IsDirty() ) + return true; + + switch( GetMode() ) + { + case CM_PLAY: + return true; + + case CM_RECORD: + if ( m_nRecordLayerIndex != -1 ) + return true; + + // NOTE: Fall through! + case CM_PASS: + { + CDmAttribute *pFromAttr = GetFromAttribute(); + if ( pFromAttr && pFromAttr->IsFlagSet( FATTRIB_OPERATOR_DIRTY ) ) + return true; + } + break; + + default: + break; + } + return false; +} + + +void CDmeChannel::Operate() +{ + VPROF( "CDmeChannel::Operate" ); + + switch ( GetMode() ) + { + case CM_OFF: + return; + + case CM_PLAY: + Play(); + return; + + case CM_RECORD: + Record(); + return; + + case CM_PASS: + Pass(); + return; + } +} + +void CDmeChannel::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + ChannelMode_t mode = GetMode(); + if ( mode == CM_OFF || mode == CM_PLAY ) + return; // off and play ignore inputs + + CDmAttribute *pAttr = GetFromAttribute(); + if ( pAttr != NULL ) + { + attrs.AddToTail( pAttr ); + } +} + +void CDmeChannel::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + ChannelMode_t mode = GetMode(); + if ( mode == CM_OFF ) + return; // off ignores inputs + + if ( mode == CM_RECORD || mode == CM_PASS ) + { + if ( GetFromAttribute() == GetToAttribute() ) + return; // record/pass from and to the same attribute doesn't write anything + } + + CDmAttribute *pAttr = GetToAttribute(); + if ( pAttr != NULL ) + { + attrs.AddToTail( pAttr ); + } +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +CDmElement *CDmeChannel::GetFromElement() const +{ + return m_fromElement; +} + +CDmElement *CDmeChannel::GetToElement() const +{ + return m_toElement; +} + +void CDmeChannel::SetLog( CDmeLog *pLog ) +{ + m_log = pLog; +} + +CDmeLog *CDmeChannel::CreateLog( DmAttributeType_t type ) +{ + CDmeLog *log = CDmeLog::CreateLog( type, GetFileId() ); + m_log.Set( log ); + return log; +} + +// HACK: This is an evil hack since the element and attribute change sequentially, but they really need to change in lockstep or else you're looking +// up an attribute from some other element or vice versa. + + +void CDmeChannel::SetInput( CDmElement* pElement, const char* pAttribute, int index ) +{ + m_fromElement.Set( pElement ); + m_fromAttribute.Set( pAttribute ); + m_fromIndex.Set( index ); + SetupFromAttribute(); +} + +void CDmeChannel::SetOutput( CDmElement* pElement, const char* pAttribute, int index ) +{ + m_toElement.Set( pElement ); + m_toAttribute.Set( pAttribute ); + m_toIndex.Set( index ); + SetupToAttribute(); +} + +void CDmeChannel::SetInput( CDmAttribute *pAttribute, int index ) +{ + if ( pAttribute ) + { + SetInput( pAttribute->GetOwner(), pAttribute->GetName(), index ); + } + else + { + SetInput( NULL, "", index ); + } +} + +void CDmeChannel::SetOutput( CDmAttribute *pAttribute, int index ) +{ + if ( pAttribute ) + { + SetOutput( pAttribute->GetOwner(), pAttribute->GetName(), index ); + } + else + { + SetOutput( NULL, "", index ); + } +} + + +ChannelMode_t CDmeChannel::GetMode() +{ + return static_cast< ChannelMode_t >( m_mode.Get() ); +} + +void CDmeChannel::SetMode( ChannelMode_t mode ) +{ + if ( mode != m_mode ) + { + m_mode.Set( static_cast< int >( mode ) ); + m_tPreviousTime = DMETIME_INVALID; + } +} + +void CDmeChannel::ClearLog() +{ + GetLog()->ClearKeys(); +} + +CDmeLog *CDmeChannel::GetLog() +{ + if ( !m_log.GetElement() && ( m_FromAttributeHandle == DMATTRIBUTE_HANDLE_INVALID ) ) + { + // NOTE: This will generate a new log based on the from attribute + SetupFromAttribute(); + } + return m_log.GetElement(); +} + + +//----------------------------------------------------------------------------- +// Used to cache off handles to attributes +//----------------------------------------------------------------------------- +CDmAttribute *CDmeChannel::SetupFromAttribute() +{ + m_FromAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; + + CDmElement *pObject = m_fromElement.GetElement(); + const char *pName = m_fromAttribute.Get(); + if ( pObject == NULL || pName == NULL || !pName[0] ) + return NULL; + + CDmAttribute *pAttr = pObject->GetAttribute( pName ); + if ( !pAttr ) + return NULL; + + m_FromAttributeHandle = pAttr->GetHandle(); + + DmAttributeType_t fromType = pAttr->GetType(); + if ( IsArrayType( fromType ) ) + { + fromType = ArrayTypeToValueType( fromType ); + } + + CDmeLog *pLog = m_log.GetElement(); + if ( pLog == NULL ) + { + CreateLog( fromType ); + return pAttr; + } + + DmAttributeType_t logType = pLog->GetDataType(); + if ( IsArrayType( logType ) ) + { + logType = ArrayTypeToValueType( logType ); + } + + if ( logType != fromType ) + { + // NOTE: This will release the current log + CreateLog( fromType ); + } + + return pAttr; +} + +CDmAttribute *CDmeChannel::SetupToAttribute() +{ + m_ToAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; + + CDmElement *pObject = m_toElement.GetElement(); + const char *pName = m_toAttribute.Get(); + if ( pObject == NULL || pName == NULL || !pName[0] ) + return NULL; + + CDmAttribute *pAttr = pObject->GetAttribute( pName ); + if ( !pAttr ) + return NULL; + + m_ToAttributeHandle = pAttr->GetHandle(); + return pAttr; +} + + +//----------------------------------------------------------------------------- +// This function gets called whenever an attribute changes +//----------------------------------------------------------------------------- +void CDmeChannel::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + if ( ( pAttribute == m_fromElement .GetAttribute() ) || + ( pAttribute == m_fromAttribute.GetAttribute() ) ) + { + // NOTE: This will force a recache of the attribute handle + m_FromAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; + return; + } + + if ( ( pAttribute == m_toElement .GetAttribute() ) || + ( pAttribute == m_toAttribute.GetAttribute() ) ) + { + m_ToAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; + return; + } + + BaseClass::OnAttributeChanged( pAttribute ); +} + + +DmeTime_t CDmeChannel::GetCurrentTime() const +{ + return m_tCurrentTime; +} + +//----------------------------------------------------------------------------- +// Simple version. Only works if multiple active channels clips +// do not reference the same channels +//----------------------------------------------------------------------------- +void CDmeChannel::SetCurrentTime( DmeTime_t time ) +{ + m_tPreviousTime = m_tCurrentTime; + m_tCurrentTime = time; + m_timeOutsideTimeframe = DMETIME_ZERO; +} + +//----------------------------------------------------------------------------- +// SetCurrentTime sets the current time on the clip, +// choosing the time closest to (and after) a timeframe if multiple sets in a frame +//----------------------------------------------------------------------------- +void CDmeChannel::SetCurrentTime( DmeTime_t time, DmeTime_t start, DmeTime_t end ) +{ + m_tPreviousTime = m_tCurrentTime; + + DmeTime_t dt( 0 ); + if ( time < start ) + { + dt = time - start; + time = start; + } + else if ( time >= end ) + { + dt = time - end; + time = end; + } + DmeTime_t totf = m_timeOutsideTimeframe; + + const DmeTime_t t0( 0 ); + if ( ( dt < t0 && totf < t0 && dt < totf ) || // both prior to clip, old totf closer + ( dt < t0 && totf >= t0 ) || // new dt prior to clip, old totf in or after + ( dt >= t0 && totf >= t0 && dt > totf ) ) // both after clip, old totf closer + return; // if old todt is a better match, don't update channel time + + m_tCurrentTime = time; + m_timeOutsideTimeframe = dt; +} + +//----------------------------------------------------------------------------- +// ClearTimeMetric marks m_timeOutsideTimeframe invalid (-inf is the worst possible match) +//----------------------------------------------------------------------------- +void CDmeChannel::ClearTimeMetric() +{ + m_timeOutsideTimeframe = DmeTime_t::MinTime(); +} + +void CDmeChannel::SetChannelToPlayToSelf( const char *outputAttributeName, float defaultValue, bool force /*= false*/ ) +{ + if ( !HasAttribute( outputAttributeName ) ) + { + AddAttribute( outputAttributeName, AT_FLOAT ); + } + + CDmeTypedLog< bool > *log = static_cast< CDmeTypedLog< bool >* >( GetLog() ); + // Usually we won't put it into playback if it's empty, we'll just read the default value continously + if ( force || ( log && !log->IsEmpty() && !log->HasDefaultValue() ) ) + { + SetMode( CM_PLAY ); + SetOutput( this, outputAttributeName ); + } + SetValue( outputAttributeName, defaultValue ); +} + +void CDmeChannel::SetNextKeyCurveType( int nCurveType ) +{ + m_nNextCurveType = nCurveType; +} + +CDmeLogLayer *FindLayerInSnapshot( const CDmrElementArray<CDmElement>& snapshotArray, CDmeLog *origLog ) +{ + if ( !snapshotArray.IsValid() ) + return NULL; + + int c = snapshotArray.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmeLogLayer *layer = CastElement< CDmeLogLayer >( snapshotArray[ i ] ); + if ( !layer ) + continue; + + CDmeLog *pLog = layer->GetValueElement< CDmeLog >( "origLog" ); + if ( !pLog ) + { + Assert( 0 ); + continue; + } + + if ( pLog == origLog ) + return layer; + } + + return NULL; +} + +KeyValues *FindLayerInPasteData( const CUtlVector< KeyValues * > &list, CDmeLog *log ) +{ + int c = list.Count(); + for ( int i = 0; i < c; ++i ) + { + CDisableUndoScopeGuard noundo; + + KeyValues *kv = list[ i ]; + Assert( kv ); + + if ( Q_stricmp( kv->GetName(), "ControlLayers" ) ) + continue; + + LayerSelectionData_t *data = reinterpret_cast< LayerSelectionData_t * >( kv->GetPtr( "LayerData" ) ); + if ( !data ) + continue; + + CDmeChannel *ch = data->m_hChannel; + if ( !ch ) + continue; + + CDmeLog *chLog = ch->GetLog(); + if ( chLog == log ) + return kv; + } + + return NULL; +} + +static int FindSpanningLayerAndSetIntensity( DmeLog_TimeSelection_t &ts, LayerSelectionData_t *data ) +{ + Assert( data->m_vecData.Count() >= 2 ); + + float frac = ts.m_flIntensity; + int i = 0; + for ( ; i < data->m_vecData.Count() - 1; ++i ) + { + LayerSelectionData_t::DataLayer_t *current = &data->m_vecData[ i ]; + LayerSelectionData_t::DataLayer_t *next = &data->m_vecData[ i + 1 ]; + + if ( frac >= current->m_flStartFraction && frac <= next->m_flStartFraction ) + { + frac = RemapVal( frac, current->m_flStartFraction, next->m_flStartFraction, 0.0f, 1.0f ); + ts.m_flIntensity = frac; + break; + } + } + + return i; +} + +void CDmeChannel::Record() +{ + VPROF( "CDmeChannel::Record" ); + + CDmAttribute *pFromAttr = GetFromAttribute(); + if ( pFromAttr == NULL ) + return; // or clear out the log? + + CDmeLog *pLog = GetLog(); + DmeTime_t time = GetCurrentTime(); + if ( m_tPreviousTime == DMETIME_INVALID ) + { + m_tPreviousTime = time; + } + + if ( g_pChannelRecordingMgr->ShouldRecordUsingTimeSelection() ) + { + Assert( m_nRecordLayerIndex != -1 ); + + // Computes local times for the time selection + DmeLog_TimeSelection_t timeSelection; + g_pChannelRecordingMgr->GetLocalTimeSelection( timeSelection, m_nRecordLayerIndex ); + + int nType = g_pChannelRecordingMgr->GetProceduralType(); + switch ( nType ) + { + default: + case PROCEDURAL_PRESET_NOT: + { + pLog->StampKeyAtHead( time, m_tPreviousTime, timeSelection, pFromAttr, m_fromIndex.Get() ); + } + break; + case PROCEDURAL_PRESET_REVEAL: + { + // Find the matching layer in the "target" data array + const CDmrElementArray<CDmElement> snapshotArray = const_cast< CDmAttribute * >( g_pChannelRecordingMgr->GetProceduralTarget() ); + CDmeLogLayer *snapshotLayer = FindLayerInSnapshot( snapshotArray, pLog ); + if ( snapshotLayer ) + { + Assert( pLog ); + pLog->RevealUsingTimeSelection( timeSelection, snapshotLayer ); + } + } + break; + case PROCEDURAL_PRESET_JITTER: + case PROCEDURAL_PRESET_SMOOTH: + case PROCEDURAL_PRESET_SHARPEN: + case PROCEDURAL_PRESET_SOFTEN: + case PROCEDURAL_PRESET_STAGGER: + case PROCEDURAL_PRESET_PASTE: + { + const CUtlVector< KeyValues * > &pasteTarget = g_pChannelRecordingMgr->GetPasteTarget(); + KeyValues *layer = FindLayerInPasteData( pasteTarget, pLog ); + if ( layer ) + { + LayerSelectionData_t *data = reinterpret_cast< LayerSelectionData_t * >( layer->GetPtr( "LayerData" ) ); + Assert( data ); + + int iSourceLayer = FindSpanningLayerAndSetIntensity( timeSelection, data ); + + CDmeLogLayer *sourceLayer = data->m_vecData[ iSourceLayer ].m_hData.Get(); + CDmeLogLayer *targetLayer = data->m_vecData[ iSourceLayer + 1 ].m_hData.Get(); + if ( sourceLayer && sourceLayer->GetKeyCount() > 0 && + targetLayer && targetLayer->GetKeyCount() > 0 && + sourceLayer->GetKeyCount() == targetLayer->GetKeyCount() ) + { + Assert( pLog->GetNumLayers() >= 2 ); + CDmeLogLayer *outputLayer = pLog->GetLayer( pLog->GetTopmostLayer() ); + if ( nType == PROCEDURAL_PRESET_STAGGER ) + { + pLog->BlendTimesUsingTimeSelection( sourceLayer, targetLayer, outputLayer, timeSelection, data->m_tStartOffset ); + } + else + { + pLog->BlendLayersUsingTimeSelection( sourceLayer, targetLayer, outputLayer, timeSelection, false, data->m_tStartOffset ); + } + } + } + } + break; + } + } + else + { + if ( m_tPreviousTime != time ) + { + pLog->SetDuplicateKeyAtTime( m_tPreviousTime ); + } + pLog->SetKey( time, pFromAttr, m_fromIndex.Get(), m_nNextCurveType ); + m_nNextCurveType = CURVE_DEFAULT; + } + + // Output the data that's in the log + Play(); +} + + +//----------------------------------------------------------------------------- +// Builds a clip stack that passes through root + shot +// Returns true if it succeeded +//----------------------------------------------------------------------------- +bool CDmeChannel::BuildClipStack( DmeClipStack_t *pClipStack, CDmeClip *pRoot, CDmeClip *pShot ) +{ + DmAttributeReferenceIterator_t it; + for ( it = g_pDataModel->FirstAttributeReferencingElement( GetHandle() ); + it != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; + it = g_pDataModel->NextAttributeReferencingElement( it ) ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( it ); + CDmElement *pElement = pAttribute->GetOwner(); + CDmeChannelsClip *pChannelsClip = CastElement< CDmeChannelsClip >( pElement ); + if ( !pChannelsClip ) + continue; + + if ( pChannelsClip->BuildClipStack( pClipStack, pRoot, pShot ) ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Finds the owner clip for a channel which passes through the root +//----------------------------------------------------------------------------- +CDmeClip* CDmeChannel::FindOwnerClipForChannel( CDmeClip *pRoot ) +{ + DmeClipStack_t stack; + if ( BuildClipStack( &stack, pRoot, pRoot ) ) + return stack[ stack.Count() - 1 ]; + return NULL; +} + diff --git a/movieobjects/dmeclip.cpp b/movieobjects/dmeclip.cpp new file mode 100644 index 0000000..b97c5cb --- /dev/null +++ b/movieobjects/dmeclip.cpp @@ -0,0 +1,1725 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeclip.h" + +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmehandle.h" + +#include "movieobjects/dmetimeframe.h" +#include "movieobjects/dmebookmark.h" +#include "movieobjects/dmesound.h" +#include "movieobjects/dmechannel.h" +#include "movieobjects/dmecamera.h" +#include "movieobjects/dmelight.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmeinput.h" +#include "movieobjects/dmeoperator.h" +#include "movieobjects/dmematerial.h" +#include "movieobjects/dmetrack.h" +#include "movieobjects/dmetrackgroup.h" +#include "movieobjects/dmematerialoverlayfxclip.h" +#include "movieobjects/dmeanimationset.h" +#include "movieobjects_interfaces.h" + +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imesh.h" +#include "tier3/tier3.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// String to clip type + back +//----------------------------------------------------------------------------- +static const char *s_pClipTypeNames[DMECLIP_TYPE_COUNT] = +{ + "Channel", + "Audio", + "Effects", + "Film", +}; + +DmeClipType_t ClipTypeFromString( const char *pName ) +{ + for ( DmeClipType_t i = DMECLIP_FIRST; i <= DMECLIP_LAST; ++i ) + { + if ( !Q_stricmp( pName, s_pClipTypeNames[i] ) ) + return i; + } + return DMECLIP_UNKNOWN; +} + +const char *ClipTypeToString( DmeClipType_t type ) +{ + if ( type >= DMECLIP_FIRST && type <= DMECLIP_LAST ) + return s_pClipTypeNames[ type ]; + return "Unknown"; +} + + +//----------------------------------------------------------------------------- +// CDmeClip - common base class for filmclips, soundclips, and channelclips +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeClip, CDmeClip ); + +void CDmeClip::OnConstruction() +{ + m_TimeFrame.InitAndCreate( this, "timeFrame" ); + m_ClipColor.InitAndSet( this, "color", Color( 0, 0, 0, 0 ) ); + m_ClipText.Init( this, "text" ); + m_bMute.Init( this, "mute" ); + m_TrackGroups.Init( this, "trackGroups", FATTRIB_MUSTCOPY | FATTRIB_HAS_ARRAY_CALLBACK ); +} + +void CDmeClip::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Inherited from IDmElement +//----------------------------------------------------------------------------- +void CDmeClip::OnAttributeArrayElementAdded( CDmAttribute *pAttribute, int nFirstElem, int nLastElem ) +{ + BaseClass::OnAttributeArrayElementAdded( pAttribute, nFirstElem, nLastElem ); + if ( pAttribute == m_TrackGroups.GetAttribute() ) + { + for ( int i = nFirstElem; i <= nLastElem; ++i ) + { + CDmeTrackGroup *pTrackGroup = m_TrackGroups[ i ]; + if ( pTrackGroup ) + { + pTrackGroup->SetOwnerClip( this ); + } + } + return; + } +} + +void CDmeClip::OnAttributeArrayElementRemoved( CDmAttribute *pAttribute, int nFirstElem, int nLastElem ) +{ + BaseClass::OnAttributeArrayElementRemoved( pAttribute, nFirstElem, nLastElem ); + if ( pAttribute == m_TrackGroups.GetAttribute() ) + { + for ( int i = nFirstElem; i <= nLastElem; ++i ) + { + CDmeTrackGroup *pTrackGroup = m_TrackGroups[ i ]; + if ( pTrackGroup ) + { + pTrackGroup->SetOwnerClip( NULL ); + } + } + return; + } +} + + +//----------------------------------------------------------------------------- +// Clip color +//----------------------------------------------------------------------------- +void CDmeClip::SetClipColor( const Color& clr ) +{ + m_ClipColor.Set( clr ); +} + +Color CDmeClip::GetClipColor() const +{ + return m_ClipColor.Get(); +} + + +//----------------------------------------------------------------------------- +// Clip text +//----------------------------------------------------------------------------- +void CDmeClip::SetClipText( const char *pText ) +{ + m_ClipText = pText; +} + +const char* CDmeClip::GetClipText() const +{ + return m_ClipText; +} + + +//----------------------------------------------------------------------------- +// Returns the time frame +//----------------------------------------------------------------------------- +CDmeTimeFrame *CDmeClip::GetTimeFrame() const +{ + return m_TimeFrame.GetElement(); +} + +DmeTime_t CDmeClip::ToChildMediaTime( DmeTime_t t, bool bClamp ) const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->ToChildMediaTime( t, bClamp ) : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::FromChildMediaTime( DmeTime_t t, bool bClamp ) const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->FromChildMediaTime( t, bClamp ) : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::ToChildMediaDuration( DmeTime_t dt ) const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->ToChildMediaDuration( dt ) : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::FromChildMediaDuration( DmeTime_t dt ) const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->FromChildMediaDuration( dt ) : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::GetTimeOffset() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetTimeOffset() : DmeTime_t( 0 ); +} + +float CDmeClip::GetTimeScale() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetTimeScale() : 0.0f; +} + +DmeTime_t CDmeClip::GetStartTime() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetStartTime() : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::GetEndTime() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetStartTime() + tf->GetDuration() : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::GetDuration() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetDuration() : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::GetStartInChildMediaTime() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetStartInChildMediaTime() : DmeTime_t( 0 ); +} + +DmeTime_t CDmeClip::GetEndInChildMediaTime() const +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + return tf ? tf->GetEndInChildMediaTime() : DmeTime_t( 0 ); +} + +void CDmeClip::SetTimeOffset( DmeTime_t t ) +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + if ( tf ) + { + tf->SetTimeOffset( t ); + } +} + +void CDmeClip::SetTimeScale( float s ) +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + if ( tf ) + { + tf->SetTimeScale( s ); + } +} + +void CDmeClip::SetStartTime( DmeTime_t t ) +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + if ( tf ) + { + tf->SetStartTime( t ); + } +} + +void CDmeClip::SetDuration( DmeTime_t t ) +{ + CDmeTimeFrame *tf = m_TimeFrame.GetElement(); + if ( tf ) + { + tf->SetDuration( t ); + } +} + + +//----------------------------------------------------------------------------- +// Track iteration +//----------------------------------------------------------------------------- +const CUtlVector< DmElementHandle_t > &CDmeClip::GetTrackGroups( ) const +{ + return m_TrackGroups.Get(); +} + +int CDmeClip::GetTrackGroupCount( ) const +{ + // Make sure no invalid clip types have snuck in + return m_TrackGroups.Count(); +} + +CDmeTrackGroup *CDmeClip::GetTrackGroup( int nIndex ) const +{ + if ( ( nIndex >= 0 ) && ( nIndex < m_TrackGroups.Count() ) ) + return m_TrackGroups[ nIndex ]; + return NULL; +} + +//----------------------------------------------------------------------------- +// Is a track group valid to add? +//----------------------------------------------------------------------------- +bool CDmeClip::IsTrackGroupValid( CDmeTrackGroup *pTrackGroup ) +{ + // FIXME: If track groups have allowed types, we can check for validity + for ( DmeClipType_t i = DMECLIP_FIRST; i <= DMECLIP_LAST; ++i ) + { + if ( !IsSubClipTypeAllowed( i ) && pTrackGroup->IsSubClipTypeAllowed( i ) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Track group addition/removal +//----------------------------------------------------------------------------- +void CDmeClip::AddTrackGroup( CDmeTrackGroup *pTrackGroup ) +{ + if ( !IsTrackGroupValid( pTrackGroup ) ) + return; + + // FIXME: Should check if track with same name already exists??? + if ( GetTrackGroupIndex( pTrackGroup ) < 0 ) + { + m_TrackGroups.AddToTail( pTrackGroup ); + } +} + +void CDmeClip::AddTrackGroupBefore( CDmeTrackGroup *pTrackGroup, CDmeTrackGroup *pBefore ) +{ + if ( !IsTrackGroupValid( pTrackGroup ) ) + return; + + // FIXME: Should check if track with same name already exists??? + if ( GetTrackGroupIndex( pTrackGroup ) < 0 ) + { + int nBeforeIndex = pBefore ? GetTrackGroupIndex( pBefore ) : GetTrackGroupCount(); + if ( nBeforeIndex >= 0 ) + { + m_TrackGroups.InsertBefore( nBeforeIndex, pTrackGroup ); + } + } +} + + +CDmeTrackGroup *CDmeClip::AddTrackGroup( const char *pTrackGroupName ) +{ + CDmeTrackGroup *pTrackGroup = CreateElement< CDmeTrackGroup >( pTrackGroupName, GetFileId() ); + pTrackGroup->SetMinimized( false ); + m_TrackGroups.AddToTail( pTrackGroup ); + return pTrackGroup; +} + +void CDmeClip::RemoveTrackGroup( int nIndex ) +{ + Assert( nIndex >= 0 && nIndex < m_TrackGroups.Count() ); + + m_TrackGroups.Remove( nIndex ); +} + +void CDmeClip::RemoveTrackGroup( CDmeTrackGroup *pTrackGroup ) +{ + int i = GetTrackGroupIndex( pTrackGroup ); + if ( i < 0 ) + return; + + m_TrackGroups.Remove( i ); +} + +void CDmeClip::RemoveTrackGroup( const char *pTrackGroupName ) +{ + if ( !pTrackGroupName ) + { + pTrackGroupName = DMETRACKGROUP_DEFAULT_NAME; + } + + int c = m_TrackGroups.Count(); + for ( int i = c; --i >= 0; ) + { + if ( !Q_strcmp( m_TrackGroups[i]->GetName(), pTrackGroupName ) ) + { + m_TrackGroups.Remove( i ); + return; + } + } +} + + +//----------------------------------------------------------------------------- +// Swap track groups +//----------------------------------------------------------------------------- +void CDmeClip::SwapOrder( CDmeTrackGroup *pTrackGroup1, CDmeTrackGroup *pTrackGroup2 ) +{ + if ( pTrackGroup1 == pTrackGroup2 ) + return; + if ( pTrackGroup1->IsFilmTrackGroup() || pTrackGroup2->IsFilmTrackGroup() ) + return; + + int nIndex1 = -1, nIndex2 = -1; + int c = m_TrackGroups.Count(); + for ( int i = c; --i >= 0; ) + { + if ( m_TrackGroups[i] == pTrackGroup1 ) + { + nIndex1 = i; + } + if ( m_TrackGroups[i] == pTrackGroup2 ) + { + nIndex2 = i; + } + } + if ( ( nIndex1 < 0 ) || ( nIndex2 < 0 ) ) + return; + + m_TrackGroups.Swap( nIndex1, nIndex2 ); +} + + +//----------------------------------------------------------------------------- +// Track group finding +//----------------------------------------------------------------------------- +CDmeTrackGroup *CDmeClip::FindTrackGroup( const char *pTrackGroupName ) const +{ + if ( !pTrackGroupName ) + { + pTrackGroupName = DMETRACKGROUP_DEFAULT_NAME; + } + + int c = m_TrackGroups.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeTrackGroup *pTrackGroup = m_TrackGroups[i]; + if ( !pTrackGroup ) + continue; + + if ( !Q_strcmp( pTrackGroup->GetName(), pTrackGroupName ) ) + return pTrackGroup; + } + return NULL; +} + +int CDmeClip::GetTrackGroupIndex( CDmeTrackGroup *pTrackGroup ) const +{ + int nTrackGroups = m_TrackGroups.Count(); + for ( int i = 0 ; i < nTrackGroups; ++i ) + { + if ( pTrackGroup == m_TrackGroups[i] ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Find or create a track group +//----------------------------------------------------------------------------- +CDmeTrackGroup *CDmeClip::FindOrAddTrackGroup( const char *pTrackGroupName ) +{ + CDmeTrackGroup *pTrackGroup = FindTrackGroup( pTrackGroupName ); + if ( !pTrackGroup ) + { + pTrackGroup = AddTrackGroup( pTrackGroupName ); + } + return pTrackGroup; +} + + +//----------------------------------------------------------------------------- +// Finding clips in track groups +//----------------------------------------------------------------------------- +CDmeTrack *CDmeClip::FindTrackForClip( CDmeClip *pClip, CDmeTrackGroup **ppTrackGroup /*= NULL*/ ) const +{ +// DmeClipType_t type = pClip->GetClipType(); + int c = m_TrackGroups.Count(); + for ( int i = 0 ; i < c; ++i ) + { + // FIXME: If trackgroups have valid types, can early out here + CDmeTrack *pTrack = m_TrackGroups[i]->FindTrackForClip( pClip ); + if ( pTrack ) + { + if ( ppTrackGroup ) + { + *ppTrackGroup = m_TrackGroups[i]; + } + return pTrack; + } + } + + return NULL; +} + +bool CDmeClip::FindMultiTrackGroupForClip( CDmeClip *pClip, int *pTrackGroupIndex, int *pTrackIndex, int *pClipIndex ) const +{ + int nTrackGroups = m_TrackGroups.Count(); + for ( int gi = 0 ; gi < nTrackGroups; ++gi ) + { + CDmeTrackGroup *pTrackGroup = m_TrackGroups[ gi ]; + if ( !pTrackGroup ) + continue; + + if ( !pTrackGroup->FindTrackForClip( pClip, pTrackIndex, pClipIndex ) ) + continue; + + if ( pTrackGroupIndex ) + { + *pTrackGroupIndex = gi; + } + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Finding clips in tracks by time +//----------------------------------------------------------------------------- +void CDmeClip::FindClipsAtTime( DmeClipType_t clipType, DmeTime_t time, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( clipType == DMECLIP_FILM ) + return; + + int gc = GetTrackGroupCount(); + for ( int i = 0; i < gc; ++i ) + { + CDmeTrackGroup *pTrackGroup = GetTrackGroup( i ); + if ( !pTrackGroup ) + continue; + + pTrackGroup->FindClipsAtTime( clipType, time, flags, clips ); + } +} + +void CDmeClip::FindClipsWithinTime( DmeClipType_t clipType, DmeTime_t startTime, DmeTime_t endTime, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( clipType == DMECLIP_FILM ) + return; + + int gc = GetTrackGroupCount(); + for ( int i = 0; i < gc; ++i ) + { + CDmeTrackGroup *pTrackGroup = GetTrackGroup( i ); + if ( !pTrackGroup ) + continue; + + pTrackGroup->FindClipsWithinTime( clipType, startTime, endTime, flags, clips ); + } +} + + +//----------------------------------------------------------------------------- +// Build a list of all referring clips +//----------------------------------------------------------------------------- +static int BuildReferringClipList( CDmeClip *pClip, CDmeClip** ppParents, int nMaxCount ) +{ + int nCount = 0; + + DmAttributeReferenceIterator_t it, it2, it3; + for ( it = g_pDataModel->FirstAttributeReferencingElement( pClip->GetHandle() ); + it != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; + it = g_pDataModel->NextAttributeReferencingElement( it ) ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( it ); + const char *pName = pAttribute->GetName(); + CDmElement *pElement = pAttribute->GetOwner(); + CDmeTrack *pTrack = CastElement< CDmeTrack >( pElement ); + if ( !pTrack ) + continue; + + for ( it2 = g_pDataModel->FirstAttributeReferencingElement( pTrack->GetHandle() ); + it2 != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; + it2 = g_pDataModel->NextAttributeReferencingElement( it2 ) ) + { + pAttribute = g_pDataModel->GetAttribute( it2 ); + pName = pAttribute->GetName(); + pElement = pAttribute->GetOwner(); + CDmeTrackGroup *pTrackGroup = CastElement< CDmeTrackGroup >( pElement ); + if ( !pTrackGroup ) + continue; + + for ( it3 = g_pDataModel->FirstAttributeReferencingElement( pTrackGroup->GetHandle() ); + it3 != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; + it3 = g_pDataModel->NextAttributeReferencingElement( it3 ) ) + { + pAttribute = g_pDataModel->GetAttribute( it3 ); + pName = pAttribute->GetName(); + pElement = pAttribute->GetOwner(); + CDmeClip *pParent = CastElement< CDmeClip >( pElement ); + if ( !pParent ) + continue; + + Assert( nCount < nMaxCount ); + if ( nCount >= nMaxCount ) + return nCount; + ppParents[nCount++] = pParent; + } + } + } + return nCount; +} + + +//----------------------------------------------------------------------------- +// Clip stack +//----------------------------------------------------------------------------- +static bool BuildClipStack_R( DmeClipStack_t* pStack, CDmeClip *pMovie, CDmeClip *pShot, CDmeClip *pCurrent ) +{ + // Add this clip to the stack + int nIndex = pStack->AddToHead( CDmeHandle< CDmeClip >( pCurrent ) ); + + // Is this clip the shot? We don't need to look for it any more. + if ( pCurrent == pShot ) + { + pShot = NULL; + } + + // Is this clip the movie? We succeeded if we already found the shot! + if ( pCurrent == pMovie ) + { + if ( !pShot ) + return true; + } + else + { + // NOTE: This algorithm assumes a clip can never appear twice under another clip + // at a single level of hierarchy. + CDmeClip* ppParents[1024]; + int nCount = BuildReferringClipList( pCurrent, ppParents, 1024 ); + for ( int i = 0; i < nCount; ++i ) + { + // Can we find a path to the root through the shot? We succeeded! + if ( BuildClipStack_R( pStack, pMovie, pShot, ppParents[i] ) ) + return true; + } + } + + // This clip didn't work out for us. Remove it. + pStack->Remove( nIndex ); + + return false; +} + +bool CDmeClip::BuildClipStack( DmeClipStack_t* pStack, CDmeClip *pMovie, CDmeClip *pShot ) +{ + // Walk through each shot in the movie and look for the subClip, if don't find it recurse into each shot + return BuildClipStack_R( pStack, pMovie, pShot, this ); +} + +DmeTime_t CDmeClip::ToChildMediaTime( const DmeClipStack_t& stack, DmeTime_t globalTime, bool bClamp /* = true */ ) +{ + DmeTime_t time = globalTime; + + int nClips = stack.Count(); + for ( int i = 0; i < nClips; ++i ) + { + time = stack[ i ]->ToChildMediaTime( time, bClamp ); + } + + return time; +} + +DmeTime_t CDmeClip::FromChildMediaTime( const DmeClipStack_t& stack, DmeTime_t localTime, bool bClamp /* = true */ ) +{ + DmeTime_t time = localTime; + + int nClips = stack.Count(); + for ( int i = nClips-1; i >= 0; --i ) + { + time = stack[ i ]->FromChildMediaTime( time, bClamp ); + } + + return time; +} + +DmeTime_t CDmeClip::ToChildMediaDuration( const DmeClipStack_t& stack, DmeTime_t globalDuration ) +{ + DmeTime_t duration = globalDuration; + + int nClips = stack.Count(); + for ( int i = 0; i < nClips; ++i ) + { + duration = stack[ i ]->ToChildMediaDuration( duration ); + } + + return duration; +} + +DmeTime_t CDmeClip::FromChildMediaDuration( const DmeClipStack_t& stack, DmeTime_t localDuration ) +{ + DmeTime_t duration = localDuration; + + int nClips = stack.Count(); + for ( int i = nClips-1; i >= 0; --i ) + { + duration = stack[ i ]->FromChildMediaDuration( duration ); + } + + return duration; +} + + +void CDmeClip::ToChildMediaTime( DmeLog_TimeSelection_t ¶ms, const DmeClipStack_t& stack ) +{ + for ( int i = 0; i < TS_TIME_COUNT; ++i ) + { + params.m_nTimes[i] = ToChildMediaTime( stack, params.m_nTimes[i], false ); + } +} + + +//----------------------------------------------------------------------------- +// +// CDmeSoundClip - timeframe view into a dmesound +// +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSoundClip, CDmeSoundClip ); + +void CDmeSoundClip::OnConstruction() +{ + m_Sound.Init( this, "sound" ); + m_bShowWave.InitAndSet( this, "showwave", false ); +} + +void CDmeSoundClip::OnDestruction() +{ +} + +void CDmeSoundClip::SetShowWave( bool state ) +{ + m_bShowWave = state; +} + +bool CDmeSoundClip::ShouldShowWave( ) const +{ + return m_bShowWave; +} + +//----------------------------------------------------------------------------- +// CDmeChannelsClip - timeframe view into a set of channels +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeChannelsClip, CDmeChannelsClip ); + +void CDmeChannelsClip::OnConstruction() +{ + m_Channels.Init( this, "channels" ); +} + +void CDmeChannelsClip::OnDestruction() +{ +} + +CDmeChannel *CDmeChannelsClip::CreatePassThruConnection( char const *passThruName, + CDmElement *pFrom, char const *pFromAttribute, + CDmElement *pTo, char const *pToAttribute, int index /*= 0*/ ) +{ + CDmeChannel *helper = CreateElement< CDmeChannel >( passThruName, GetFileId() ); + Assert( helper ); + + helper->SetMode( CM_PASS ); + helper->SetInput( pFrom, pFromAttribute ); + helper->SetOutput( pTo, pToAttribute, index ); + + m_Channels.AddToTail( helper ); + + return helper; +} + +void CDmeChannelsClip::RemoveChannel( CDmeChannel *pChannel ) +{ + int nCount = m_Channels.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( pChannel == m_Channels[i] ) + { + m_Channels.Remove( i ); + break; + } + } +} + + + +//----------------------------------------------------------------------------- +// CDmeFXClip - timeframe view into an effect +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeFXClip, CDmeFXClip ); + +void CDmeFXClip::OnConstruction() +{ +} + +void CDmeFXClip::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Global list of FX clip types +//----------------------------------------------------------------------------- +const char *CDmeFXClip::s_pFXClipTypes[MAX_FXCLIP_TYPES]; +const char *CDmeFXClip::s_pFXClipDescriptions[MAX_FXCLIP_TYPES]; +int CDmeFXClip::s_nFXClipTypeCount = 0; + +void CDmeFXClip::InstallFXClipType( const char *pElementType, const char *pDescription ) +{ + s_pFXClipTypes[s_nFXClipTypeCount] = pElementType; + s_pFXClipDescriptions[s_nFXClipTypeCount] = pDescription; + ++s_nFXClipTypeCount; +} + +int CDmeFXClip::FXClipTypeCount() +{ + return s_nFXClipTypeCount; +} + +const char *CDmeFXClip::FXClipType( int nIndex ) +{ + Assert( s_nFXClipTypeCount > nIndex ); + return s_pFXClipTypes[nIndex]; +} + +const char *CDmeFXClip::FXClipDescription( int nIndex ) +{ + Assert( s_nFXClipTypeCount > nIndex ); + return s_pFXClipDescriptions[nIndex]; +} + + +//----------------------------------------------------------------------------- +// CDmeFilmClip - hierarchical clip (movie, sequence or shot) w/ scene info +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeFilmClip, CDmeFilmClip ); + +void CDmeFilmClip::OnConstruction() +{ + m_MaterialOverlayEffect.Init( this, "materialOverlay" ); + + m_MapName.Init( this, "mapname" ); + m_Camera.Init( this, "camera" ); + m_MonitorCameras.Init( this, "monitorCameras" ); + m_nActiveMonitor.InitAndSet( this, "activeMonitor", -1 ); + m_Lights.Init( this, "lights" ); + m_Scene.Init( this, "scene" ); + m_AVIFile.Init( this, "aviFile" ); + m_fadeInDuration .InitAndSet( this, "fadeIn", 0 ); + m_fadeOutDuration.InitAndSet( this, "fadeOut", 0 ); + + m_Inputs.Init( this, "inputs" ); + m_Operators.Init( this, "operators" ); + m_bIsUsingCachedVersion.Init( this, "useAviFile" ); + m_AnimationSets.Init( this, "animationSets" ); + m_Bookmarks.Init( this, "bookmarks" ); + m_FilmTrackGroup.Init( this, "subClipTrackGroup", FATTRIB_HAS_CALLBACK | FATTRIB_HAS_PRE_CALLBACK ); + m_Volume.InitAndSet( this, "volume", 1.0); + + m_pCachedVersion = NULL; + m_bIsUsingCachedVersion = false; + m_bReloadCachedVersion = false; +} + +void CDmeFilmClip::OnDestruction() +{ + if ( g_pVideo != NULL && m_pCachedVersion != NULL ) + { + g_pVideo->DestroyVideoMaterial( m_pCachedVersion ); + m_pCachedVersion = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Returns the special film track group +//----------------------------------------------------------------------------- +CDmeTrackGroup *CDmeFilmClip::GetFilmTrackGroup() const +{ + return m_FilmTrackGroup; +} + +//----------------------------------------------------------------------------- +// Returns the special film track +//----------------------------------------------------------------------------- +CDmeTrack *CDmeFilmClip::GetFilmTrack() const +{ + CDmeTrackGroup *pTrackGroup = m_FilmTrackGroup.GetElement(); + if ( pTrackGroup ) + return pTrackGroup->GetFilmTrack(); + return NULL; +} + +CDmeTrackGroup *CDmeFilmClip::FindOrCreateFilmTrackGroup() +{ + if ( !m_FilmTrackGroup ) + { + m_FilmTrackGroup = CreateElement< CDmeTrackGroup >( "subClipTrackGroup", GetFileId() ); + m_FilmTrackGroup->SetMinimized( false ); + m_FilmTrackGroup->SetMaxTrackCount( 1 ); + m_FilmTrackGroup->SetOwnerClip( this ); + } + return m_FilmTrackGroup; +} + +CDmeTrack *CDmeFilmClip::FindOrCreateFilmTrack() +{ + CDmeTrackGroup *pTrackGroup = FindOrCreateFilmTrackGroup(); + CDmeTrack *pTrack = pTrackGroup->GetFilmTrack(); + return pTrack ? pTrack : m_FilmTrackGroup->CreateFilmTrack(); +} + + +//----------------------------------------------------------------------------- +// Finding clips in track groups +//----------------------------------------------------------------------------- +CDmeTrack *CDmeFilmClip::FindTrackForClip( CDmeClip *pClip, CDmeTrackGroup **ppTrackGroup /*= NULL*/ ) const +{ + if ( m_FilmTrackGroup.GetElement() ) + { + CDmeTrack *pTrack = m_FilmTrackGroup->FindTrackForClip( pClip ); + if ( pTrack ) + { + if ( ppTrackGroup ) + { + *ppTrackGroup = m_FilmTrackGroup; + } + return pTrack; + } + } + + return CDmeClip::FindTrackForClip( pClip, ppTrackGroup ); +} + +//----------------------------------------------------------------------------- +// Finding clips in tracks by time +//----------------------------------------------------------------------------- +void CDmeFilmClip::FindClipsAtTime( DmeClipType_t clipType, DmeTime_t time, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( clipType == DMECLIP_FILM ) || ( clipType == DMECLIP_UNKNOWN ) ) + { + if ( m_FilmTrackGroup ) + { + m_FilmTrackGroup->FindClipsAtTime( clipType, time, flags, clips ); + } + } + + CDmeClip::FindClipsAtTime( clipType, time, flags, clips ); +} + +void CDmeFilmClip::FindClipsWithinTime( DmeClipType_t clipType, DmeTime_t startTime, DmeTime_t endTime, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( clipType == DMECLIP_FILM ) || ( clipType == DMECLIP_UNKNOWN ) ) + { + if ( m_FilmTrackGroup ) + { + m_FilmTrackGroup->FindClipsWithinTime( clipType, startTime, endTime, flags, clips ); + } + } + + CDmeClip::FindClipsWithinTime( clipType, startTime, endTime, flags, clips ); +} + + +//----------------------------------------------------------------------------- +// Volume +//----------------------------------------------------------------------------- +void CDmeFilmClip::SetVolume( float state ) +{ + m_Volume = state; +} +float CDmeFilmClip::GetVolume() const +{ + return m_Volume.Get(); +} + +//----------------------------------------------------------------------------- +// mapname helper methods +//----------------------------------------------------------------------------- +const char *CDmeFilmClip::GetMapName() +{ + return m_MapName.Get(); +} + +void CDmeFilmClip::SetMapName( const char *pMapName ) +{ + m_MapName.Set( pMapName ); +} + + +//----------------------------------------------------------------------------- +// Attribute changed +//----------------------------------------------------------------------------- +void CDmeFilmClip::PreAttributeChanged( CDmAttribute *pAttribute ) +{ + BaseClass::PreAttributeChanged( pAttribute ); + if ( pAttribute == m_FilmTrackGroup.GetAttribute() ) + { + if ( m_FilmTrackGroup.GetElement() ) + { + m_FilmTrackGroup->SetOwnerClip( NULL ); + } + return; + } +} + +void CDmeFilmClip::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + BaseClass::OnAttributeChanged( pAttribute ); + if ( pAttribute == m_FilmTrackGroup.GetAttribute() ) + { + if ( m_FilmTrackGroup.GetElement() ) + { + m_FilmTrackGroup->SetMaxTrackCount( 1 ); + m_FilmTrackGroup->SetOwnerClip( this ); + } + } + else if ( pAttribute->GetOwner() == m_TimeFrame.GetElement() ) + { + InvokeOnAttributeChangedOnReferrers( GetHandle(), pAttribute ); + } +} + +void CDmeFilmClip::OnElementUnserialized( ) +{ + BaseClass::OnElementUnserialized(); + + CDmeTrackGroup *pFilmTrackGroup = m_FilmTrackGroup.GetElement(); + if ( pFilmTrackGroup ) + { + pFilmTrackGroup->SetMaxTrackCount( 1 ); + if ( pFilmTrackGroup->GetTrackCount() == 0 ) + { + pFilmTrackGroup->CreateFilmTrack(); + } + } + + // this conversion code went in on 10/31/2005 + // I'm hoping we don't care about any files that old - if we ever hit this, we should move this code into an unserialization converter + Assert( !HasAttribute( "overlay" ) && !HasAttribute( "overlayalpha" ) ); + if ( HasAttribute( "overlay" ) || HasAttribute( "overlayalpha" ) ) + { + Warning( "CDmeFilmClip %s is an old version that has overlay and/or overlayalpha attributes!\n", GetName() ); + + // Backward compat conversion + // If this is an older file with an overlay attribute, strip it out into materialoverlay + CDmAttribute *pOverlayAttribute = GetAttribute( "overlay" ); + if ( !pOverlayAttribute ) + goto cleanUp; + + const char *pName = pOverlayAttribute->GetValueString(); + if ( !pName || !pName[0] ) + goto cleanUp; + + // If we don't yet have a material overlay, create one + if ( m_MaterialOverlayEffect.GetElement() == NULL ) + { + m_MaterialOverlayEffect = CreateElement<CDmeMaterialOverlayFXClip>( "materialOverlay", GetFileId() ); + } + + m_MaterialOverlayEffect->SetOverlayEffect( pName ); + + // If this is an older file with an overlayalpha attribute, strip it out into materialoverlay + CDmAttribute *pOverlayAlphaAttribute = GetAttribute( "overlayalpha" ); + if ( pOverlayAlphaAttribute ) + { + float alpha = pOverlayAlphaAttribute->GetValue<float>(); + m_MaterialOverlayEffect->SetAlpha( alpha ); + } + +cleanUp: + // Always strip out the old overlay attribute + RemoveAttribute( "overlay" ); + RemoveAttribute( "overlayalpha" ); + } +} + + +//----------------------------------------------------------------------------- +// Resolve +//----------------------------------------------------------------------------- +void CDmeFilmClip::Resolve() +{ + BaseClass::Resolve(); + if ( m_AVIFile.IsDirty() ) + { + m_bReloadCachedVersion = true; + m_AVIFile.GetAttribute()->RemoveFlag( FATTRIB_DIRTY ); + } +} + + +//----------------------------------------------------------------------------- +// Helper for overlays +//----------------------------------------------------------------------------- +void CDmeFilmClip::SetOverlay( const char *pMaterialName ) +{ + if ( pMaterialName && pMaterialName[0] ) + { + if ( !m_MaterialOverlayEffect.GetElement() ) + { + m_MaterialOverlayEffect = CreateElement<CDmeMaterialOverlayFXClip>( "materialOverlay", GetFileId() ); + } + + m_MaterialOverlayEffect->SetOverlayEffect( pMaterialName ); + } + else + { + m_MaterialOverlayEffect.Set( NULL ); + } +} + +IMaterial *CDmeFilmClip::GetOverlayMaterial() +{ + return m_MaterialOverlayEffect.GetElement() ? m_MaterialOverlayEffect->GetMaterial() : NULL; +} + +float CDmeFilmClip::GetOverlayAlpha() +{ + return m_MaterialOverlayEffect.GetElement() ? m_MaterialOverlayEffect->GetAlpha() : 0.0f; +} + +void CDmeFilmClip::SetOverlayAlpha( float alpha ) +{ + if ( m_MaterialOverlayEffect.GetElement() ) + { + m_MaterialOverlayEffect->SetAlpha( alpha ); + } +} + +bool CDmeFilmClip::HasOpaqueOverlay( void ) +{ + if ( m_MaterialOverlayEffect->GetMaterial()->IsTranslucent() || + ( m_MaterialOverlayEffect->GetAlpha() < 1.0f ) ) + { + return false; + } + + return true; +} + +void CDmeFilmClip::DrawOverlay( DmeTime_t time, Rect_t ¤tRect, Rect_t &totalRect ) +{ + if ( m_MaterialOverlayEffect.GetElement() ) + { + m_MaterialOverlayEffect->ApplyEffect( ToChildMediaTime( time ), currentRect, totalRect, NULL ); + } + + DmeTime_t fadeIn( m_fadeInDuration ); + DmeTime_t fadeOut( m_fadeOutDuration ); + + float fade = 1.0f; + if ( time < GetStartTime() + fadeIn ) + { + fade = ( time - GetStartTime() ) / fadeIn; + } + if ( time > GetEndTime() - fadeOut ) + { + fade = min( fade, ( GetEndTime() - time ) / fadeOut ); + } + if ( fade < 1.0f ) + { + if ( !m_FadeMaterial.IsValid() ) + { + m_FadeMaterial.Init( "engine\\singlecolor.vmt", NULL, false ); + } + + float r, g, b; + m_FadeMaterial->GetColorModulation( &r, &g, &b ); + float a = m_FadeMaterial->GetAlphaModulation(); + + m_FadeMaterial->ColorModulate( 0.0f, 0.0f, 0.0f ); + m_FadeMaterial->AlphaModulate( 1.0f - fade ); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_FadeMaterial ); + + float w = currentRect.width; + float h = currentRect.height; + + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, 2 ); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, h, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( w, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( w, h, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + m_FadeMaterial->ColorModulate( r, g, b ); + m_FadeMaterial->AlphaModulate( a ); + } +} + + +//----------------------------------------------------------------------------- +// AVI tape out +//----------------------------------------------------------------------------- +void CDmeFilmClip::UseCachedVersion( bool bUseCachedVersion ) +{ + m_bIsUsingCachedVersion = bUseCachedVersion; +} + +bool CDmeFilmClip::IsUsingCachedVersion() const +{ + return m_bIsUsingCachedVersion; +} + +IVideoMaterial *CDmeFilmClip::GetCachedVideoMaterial() +{ + if ( m_bReloadCachedVersion ) + { + if ( g_pVideo ) + { + if ( m_pCachedVersion != NULL ) + { + g_pVideo->DestroyVideoMaterial( m_pCachedVersion ); + m_pCachedVersion = NULL; + } + if ( m_AVIFile[0] ) + { + m_pCachedVersion = g_pVideo->CreateVideoMaterial( m_AVIFile, m_AVIFile, "MOD" ); + } + + } + m_bReloadCachedVersion = false; + } + return m_pCachedVersion; +} + +void CDmeFilmClip::SetCachedAVI( const char *pAVIFile ) +{ + m_AVIFile = pAVIFile; + m_bReloadCachedVersion = true; +} + + +//----------------------------------------------------------------------------- +// Camera helper methods +//----------------------------------------------------------------------------- +CDmeCamera *CDmeFilmClip::GetCamera() +{ + return m_Camera; +} + +void CDmeFilmClip::SetCamera( CDmeCamera *pCamera ) +{ + m_Camera = pCamera; +} + + +//----------------------------------------------------------------------------- +// Returns the monitor camera associated with the clip (for now, only 1 supported) +//----------------------------------------------------------------------------- +CDmeCamera *CDmeFilmClip::GetMonitorCamera() +{ + if ( m_nActiveMonitor < 0 ) + return NULL; + return m_MonitorCameras[ m_nActiveMonitor ]; +} + +void CDmeFilmClip::AddMonitorCamera( CDmeCamera *pCamera ) +{ + m_MonitorCameras.AddToTail( pCamera ); +} + +int CDmeFilmClip::FindMonitorCamera( CDmeCamera *pCamera ) +{ + return m_MonitorCameras.Find( pCamera->GetHandle() ); +} + +void CDmeFilmClip::RemoveMonitorCamera( CDmeCamera *pCamera ) +{ + int i = m_MonitorCameras.Find( pCamera->GetHandle() ); + if ( i >= 0 ) + { + if ( m_nActiveMonitor == i ) + { + m_nActiveMonitor = -1; + } + m_MonitorCameras.FastRemove( i ); + } +} + +void CDmeFilmClip::SelectMonitorCamera( CDmeCamera *pCamera ) +{ + m_nActiveMonitor = pCamera ? m_MonitorCameras.Find( pCamera->GetHandle() ) : -1; +} + + +//----------------------------------------------------------------------------- +// Light helper methods +//----------------------------------------------------------------------------- +int CDmeFilmClip::GetLightCount() +{ + return m_Lights.Count(); +} + +CDmeLight *CDmeFilmClip::GetLight( int nIndex ) +{ + if ( ( nIndex < 0 ) || ( nIndex >= m_Lights.Count() ) ) + return NULL; + + return m_Lights[ nIndex ]; +} + +void CDmeFilmClip::AddLight( CDmeLight *pLight ) +{ + m_Lights.AddToTail( pLight ); +} + + +//----------------------------------------------------------------------------- +// Scene / Dag helper methods +//----------------------------------------------------------------------------- +CDmeDag *CDmeFilmClip::GetScene() +{ + return m_Scene.GetElement(); +} + +void CDmeFilmClip::SetScene( CDmeDag *pDag ) +{ + m_Scene.Set( pDag ); +} + + +//----------------------------------------------------------------------------- +// helper for inputs and operators +//----------------------------------------------------------------------------- +int CDmeFilmClip::GetInputCount() +{ + return m_Inputs.Count(); +} + +CDmeInput *CDmeFilmClip::GetInput( int nIndex ) +{ + if ( nIndex < 0 || nIndex >= m_Inputs.Count() ) + return NULL; + + return m_Inputs[ nIndex ]; +} + +void CDmeFilmClip::AddInput( CDmeInput *pInput ) +{ + m_Inputs.AddToTail( pInput ); +} + +void CDmeFilmClip::RemoveAllInputs() +{ + m_Inputs.RemoveAll(); +} + +void CDmeFilmClip::AddOperator( CDmeOperator *pOperator ) +{ + m_Operators.AddToTail( pOperator ); +} + +void CDmeFilmClip::CollectOperators( CUtlVector< DmElementHandle_t > &operators ) +{ + int numInputs = m_Inputs.Count(); + for ( int i = 0; i < numInputs; ++i ) + { + operators.AddToTail( m_Inputs[ i ]->GetHandle() ); + } + + int numOperators = m_Operators.Count(); + for ( int i = 0; i < numOperators; ++i ) + { + operators.AddToTail( m_Operators[ i ]->GetHandle() ); + } +} + +int CDmeFilmClip::GetAnimationSetCount() +{ + // yes, this is nasty perf-wise, but since we only have a dozen or so animation sets, + // and we're about to rewrite the entire structure of animation sets vs. clips vs. scene + // it's not worth polluting the leaf code just to save a couple cycles short term + + int nCount = 0; + + int nElements = m_AnimationSets.Count(); + for ( int i = 0; i < nElements; ++i ) + { + CDmElement *pElement = m_AnimationSets.Get( i ); + if ( !pElement ) + continue; + + CDmeAnimationSet *pAnimSet = CastElement< CDmeAnimationSet >( pElement ); + if ( pAnimSet ) + { + ++nCount; + } + else + { + const CDmAttribute *pChildren = pElement->GetAttribute( "children", AT_ELEMENT_ARRAY ); + if ( !pChildren ) + continue; + + CDmrElementArrayConst< CDmElement > array( pChildren ); + nCount += array.Count(); + } + } + + return nCount; +} + +CDmeAnimationSet *CDmeFilmClip::GetAnimationSet( int idx ) +{ + // yes, this is nasty perf-wise, but since we only have a dozen or so animation sets, + // and we're about to rewrite the entire structure of animation sets vs. clips vs. scene + // it's not worth polluting the leaf code just to save a couple cycles short term + + int nElements = m_AnimationSets.Count(); + for ( int i = 0; i < nElements; ++i ) + { + CDmElement *pElement = m_AnimationSets.Get( i ); + if ( !pElement ) + continue; + + CDmeAnimationSet *pAnimSet = CastElement< CDmeAnimationSet >( pElement ); + if ( pAnimSet ) + { + if ( idx == 0 ) + return pAnimSet; + --idx; + } + else + { + const CDmAttribute *pChildren = pElement->GetAttribute( "children", AT_ELEMENT_ARRAY ); + if ( !pChildren ) + continue; + + CDmrElementArrayConst< CDmElement > array( pChildren ); + int nChildren = array.Count(); + + if ( idx < nChildren ) + return CastElement< CDmeAnimationSet >( array[ idx ] ); + idx -= nChildren; + } + } + + return NULL; +} + +void CDmeFilmClip::AddAnimationSet( CDmeAnimationSet *element ) +{ + m_AnimationSets.AddToTail( element ); +} + +void CDmeFilmClip::RemoveAllAnimationSets() +{ + m_AnimationSets.RemoveAll(); +} + +CDmaElementArray< CDmElement > &CDmeFilmClip::GetAnimationSets() +{ + return m_AnimationSets; +} + +const CDmaElementArray< CDmElement > &CDmeFilmClip::GetAnimationSets() const +{ + return m_AnimationSets; +} + + +const CDmaElementArray< CDmeBookmark > &CDmeFilmClip::GetBookmarks() const +{ + return m_Bookmarks; +} + +CDmaElementArray< CDmeBookmark > &CDmeFilmClip::GetBookmarks() +{ + return m_Bookmarks; +} + + +//----------------------------------------------------------------------------- +// Used to move clips in non-film track groups with film clips +// Call BuildClipAssociations before modifying the film track, +// then UpdateAssociatedClips after modifying it. +//----------------------------------------------------------------------------- +void CDmeFilmClip::BuildClipAssociations( CUtlVector< ClipAssociation_t > &association, bool bHandleGaps ) +{ + association.RemoveAll(); + + CDmeTrack *pFilmTrack = GetFilmTrack(); + if ( !pFilmTrack ) + return; + + int c = pFilmTrack->GetClipCount(); + int gc = GetTrackGroupCount(); + if ( c == 0 || gc == 0 ) + return; + + DmeTime_t clipStartTime = GetStartInChildMediaTime(); + DmeTime_t clipEndTime = GetEndInChildMediaTime(); + + // These slugs will be removed in UpdateAssociatedClips + if ( bHandleGaps ) + { + pFilmTrack->FillAllGapsWithSlugs( "__tempSlug__", clipStartTime, clipEndTime ); + } + + for ( int i = 0; i < gc; ++i ) + { + CDmeTrackGroup *pTrackGroup = GetTrackGroup( i ); + int tc = pTrackGroup->GetTrackCount(); + for ( int j = 0; j < tc; ++j ) + { + CDmeTrack *pTrack = pTrackGroup->GetTrack( j ); + if ( !pTrack->IsSynched() ) + continue; + + // Only have visible tracks now + int cc = pTrack->GetClipCount(); + association.EnsureCapacity( association.Count() + cc ); + for ( int k = 0; k < cc; ++k ) + { + CDmeClip *pClip = pTrack->GetClip( k ); + CDmeClip *pFilmClip = pFilmTrack->FindFilmClipAtTime( pClip->GetStartTime() ); + + int nIndex = association.AddToTail(); + association[nIndex].m_hClip = pClip; + association[nIndex].m_hAssociation = pFilmClip; + if ( pFilmClip ) + { + association[nIndex].m_offset = pClip->GetStartTime() - pFilmClip->GetStartTime(); + association[nIndex].m_nType = ClipAssociation_t::HAS_CLIP; + continue; + } + + // Handle edge cases + if ( pClip->GetStartTime() <= clipStartTime ) + { + association[nIndex].m_offset = pClip->GetStartTime() - clipStartTime; + association[nIndex].m_nType = ClipAssociation_t::BEFORE_START; + continue; + } + + if ( pClip->GetStartTime() >= clipEndTime ) + { + association[nIndex].m_offset = pClip->GetStartTime() - clipEndTime; + association[nIndex].m_nType = ClipAssociation_t::AFTER_END; + continue; + } + + association[nIndex].m_offset = DmeTime_t( 0 ); + association[nIndex].m_nType = ClipAssociation_t::NO_MOVEMENT; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Rolls associated clips so they remain in the same relative time +//----------------------------------------------------------------------------- +void CDmeFilmClip::RollAssociatedClips( CDmeClip *pClip, CUtlVector< ClipAssociation_t > &association, DmeTime_t dt ) +{ + int c = association.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( association[i].m_nType != ClipAssociation_t::HAS_CLIP ) + continue; + + if ( association[i].m_hAssociation.Get() != pClip ) + continue; + + DmeTime_t newStartTime = association[i].m_hClip->GetStartTime() - dt; + association[i].m_hClip->SetStartTime( newStartTime ); + } +} + +//----------------------------------------------------------------------------- +// Rolls associated clips so they remain in the same relative time +//----------------------------------------------------------------------------- +void CDmeFilmClip::ScaleAssociatedClips( CDmeClip *pClip, CUtlVector< ClipAssociation_t > &association, float ratio, DmeTime_t oldOffset ) +{ + int c = association.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( association[i].m_nType != ClipAssociation_t::HAS_CLIP ) + continue; + + if ( association[i].m_hAssociation.Get() != pClip ) + continue; + + DmeTime_t clipStartTime = pClip->GetStartTime(); + DmeTime_t oldStartTime = association[i].m_hClip->GetStartTime(); + DmeTime_t newStartTime = ( oldStartTime - clipStartTime + oldOffset ) / ratio + clipStartTime - pClip->GetTimeOffset(); + association[i].m_hClip->SetStartTime( newStartTime ); + } +} + +void CDmeFilmClip::UpdateAssociatedClips( CUtlVector< ClipAssociation_t > &association ) +{ + int i; + + CDmeTrack *pFilmTrack = GetFilmTrack(); + if ( !pFilmTrack ) + return; + + int c = association.Count(); + if ( c > 0 ) + { + DmeTime_t clipStartTime = GetStartInChildMediaTime(); + DmeTime_t clipEndTime = GetEndInChildMediaTime(); + + for ( i = 0; i < c; ++i ) + { + ClipAssociation_t &curr = association[i]; + if ( !curr.m_hClip.Get() ) + continue; + + switch( curr.m_nType ) + { + case ClipAssociation_t::HAS_CLIP: + if ( curr.m_hAssociation.Get() ) + { + curr.m_hClip->SetStartTime( curr.m_hAssociation->GetStartTime() + curr.m_offset ); + } + break; + + case ClipAssociation_t::BEFORE_START: + curr.m_hClip->SetStartTime( clipStartTime + curr.m_offset ); + break; + + case ClipAssociation_t::AFTER_END: + curr.m_hClip->SetStartTime( clipEndTime + curr.m_offset ); + break; + } + } + } + + c = pFilmTrack->GetClipCount(); + for ( i = c; --i >= 0; ) + { + CDmeClip *pClip = pFilmTrack->GetClip(i); + if ( !Q_strcmp( pClip->GetName(), "__tempSlug__" ) ) + { + pFilmTrack->RemoveClip( i ); + } + } +} + + +//----------------------------------------------------------------------------- +// Creates a slug clip +//----------------------------------------------------------------------------- +CDmeFilmClip *CreateSlugClip( const char *pClipName, DmeTime_t startTime, DmeTime_t endTime, DmFileId_t fileid ) +{ + CDmeFilmClip *pSlugClip = CreateElement<CDmeFilmClip>( pClipName, fileid ); + pSlugClip->GetTimeFrame()->SetName( "timeframe" ); + pSlugClip->SetStartTime( startTime ); + pSlugClip->SetDuration( endTime - startTime ); + pSlugClip->SetTimeOffset( DmeTime_t( 0 ) ); + pSlugClip->SetTimeScale( 1.0f ); + pSlugClip->SetClipColor( Color( 0, 0, 0, 128 ) ); + pSlugClip->SetOverlay( "vgui/black" ); + return pSlugClip; +} + +//----------------------------------------------------------------------------- +// helper methods +//----------------------------------------------------------------------------- +CDmeTrack *GetParentTrack( CDmeClip *pClip ) +{ + DmAttributeReferenceIterator_t hAttr = g_pDataModel->FirstAttributeReferencingElement( pClip->GetHandle() ); + for ( ; hAttr != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; hAttr = g_pDataModel->NextAttributeReferencingElement( hAttr ) ) + { + CDmAttribute *pAttr = g_pDataModel->GetAttribute( hAttr ); + if ( !pAttr ) + continue; + + CDmeTrack *pTrack = CastElement< CDmeTrack >( pAttr->GetOwner() ); + if ( pTrack ) + return pTrack; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Finds a channel in a channel or film clip targetting a particular element +//----------------------------------------------------------------------------- +CDmeChannel *FindChannelTargetingElement( CDmeChannelsClip *pChannelsClip, CDmElement *pElement, const char *pAttributeName ) +{ + int nChannels = pChannelsClip->m_Channels.Count(); + for ( int i = 0; i < nChannels; ++i ) + { + CDmeChannel *pChannel = pChannelsClip->m_Channels[ i ]; + CDmElement *toElement = pChannel->GetToElement(); + if ( toElement != pElement ) + continue; + + if ( pAttributeName && ( Q_stricmp( pChannel->GetToAttribute()->GetName(), pAttributeName ) != 0 ) ) + continue; + + return pChannel; + } + + return NULL; +} + +CDmeChannel *FindChannelTargetingElement( CDmeFilmClip *pClip, CDmElement *pElement, const char *pAttributeName, CDmeChannelsClip **ppChannelsClip, CDmeTrack **ppTrack, CDmeTrackGroup **ppTrackGroup ) +{ + 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 ) + + CDmeChannel *pChannel = FindChannelTargetingElement( pChannelsClip, pElement, pAttributeName ); + if ( !pChannel ) + continue; + + if ( ppChannelsClip ) + { + *ppChannelsClip = pChannelsClip; + } + if ( ppTrack ) + { + *ppTrack = pTrack; + } + if ( ppTrackGroup ) + { + *ppTrackGroup = pTrackGroup; + } + return pChannel; + + DMETRACKGROUP_FOREACH_CLIP_TYPE_END() + } + + return NULL; +} diff --git a/movieobjects/dmecombinationoperator.cpp b/movieobjects/dmecombinationoperator.cpp new file mode 100644 index 0000000..abc4bdc --- /dev/null +++ b/movieobjects/dmecombinationoperator.cpp @@ -0,0 +1,2184 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmecombinationoperator.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmattribute.h" +#include "movieobjects/dmechannel.h" +#include "movieobjects/dmemodel.h" +#include "movieobjects/dmeshape.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmeclip.h" +#include "movieobjects/dmelog.h" +#include "movieobjects/dmevertexdata.h" +#include "movieobjects/dmemesh.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeCombinationInputControl, CDmeCombinationInputControl ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationInputControl::OnConstruction() +{ + m_RawControlNames.Init( this, "rawControlNames" ); + m_bIsStereo.Init( this, "stereo" ); + m_bIsEyelid.InitAndSet( this, "eyelid", false ); + m_WrinkleScales.Init( this, "wrinkleScales" ); +} + +void CDmeCombinationInputControl::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Backward compat +//----------------------------------------------------------------------------- +void CDmeCombinationInputControl::OnElementUnserialized() +{ + BaseClass::OnElementUnserialized(); + + int nWrinkleCount = m_WrinkleScales.Count(); + int nControlCount = m_RawControlNames.Count(); + if ( nWrinkleCount < nControlCount ) + { + for ( int i = nWrinkleCount; i < nControlCount; ++i ) + { + m_WrinkleScales.AddToTail( 0.0f ); + } + } + else if ( nWrinkleCount > nControlCount ) + { + m_WrinkleScales.RemoveMultiple( nControlCount, nWrinkleCount - nControlCount ); + } +} + + +//----------------------------------------------------------------------------- +// Adds a control, returns the control index +//----------------------------------------------------------------------------- +bool CDmeCombinationInputControl::AddRawControl( const char *pRawControlName ) +{ + Assert( !strchr( pRawControlName, '_' ) && !strchr( pRawControlName, ' ' ) ); + int nCount = m_RawControlNames.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pRawControlName, m_RawControlNames[i] ) ) + return false; + } + + m_RawControlNames.AddToTail( pRawControlName ); + m_WrinkleScales.AddToTail( 0.0f ); + return true; +} + + +//----------------------------------------------------------------------------- +// Finds a raw control by name +//----------------------------------------------------------------------------- +int CDmeCombinationInputControl::FindRawControl( const char *pRawControlName ) +{ + Assert( !strchr( pRawControlName, '_' ) && !strchr( pRawControlName, ' ' ) ); + int nCount = m_RawControlNames.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pRawControlName, m_RawControlNames[i] ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Removes controls +//----------------------------------------------------------------------------- +bool CDmeCombinationInputControl::RemoveRawControl( const char *pRawControlName ) +{ + int i = FindRawControl( pRawControlName ); + if ( i >= 0 ) + { + m_RawControlNames.FastRemove( i ); + m_WrinkleScales.FastRemove( i ); + return true; + } + + return false; +} + +void CDmeCombinationInputControl::RemoveAllRawControls() +{ + m_RawControlNames.RemoveAll(); + m_WrinkleScales.RemoveAll( ); +} + + +//----------------------------------------------------------------------------- +// Iterates remapped controls +//----------------------------------------------------------------------------- +int CDmeCombinationInputControl::RawControlCount() const +{ + return m_RawControlNames.Count(); +} + +const char *CDmeCombinationInputControl::RawControlName( int nIndex ) const +{ + return m_RawControlNames[ nIndex ]; +} + + +//----------------------------------------------------------------------------- +// Is this control a stereo control? +//----------------------------------------------------------------------------- +bool CDmeCombinationInputControl::IsStereo() const +{ + return m_bIsStereo; +} + +void CDmeCombinationInputControl::SetStereo( bool bStereo ) +{ + m_bIsStereo = bStereo; +} + + +//----------------------------------------------------------------------------- +// Is this control an eyelid control? +//----------------------------------------------------------------------------- +bool CDmeCombinationInputControl::IsEyelid() const +{ + return m_bIsEyelid; +} + +void CDmeCombinationInputControl::SetEyelid( bool bEyelid ) +{ + m_bIsEyelid = bEyelid; +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationInputControl::MoveRawControlUp( const char *pRawControlName ) +{ + int nIndex = FindRawControl( pRawControlName ); + if ( nIndex > 0 ) + { + m_RawControlNames.Swap( nIndex, nIndex - 1 ); + m_WrinkleScales.Swap( nIndex, nIndex - 1 ); + } +} + +void CDmeCombinationInputControl::MoveRawControlDown( const char *pRawControlName ) +{ + int nIndex = FindRawControl( pRawControlName ); + int nLastIndex = m_RawControlNames.Count() - 1; + if ( nIndex >= 0 && nIndex < nLastIndex ) + { + m_RawControlNames.Swap( nIndex, nIndex + 1 ); + m_WrinkleScales.Swap( nIndex, nIndex + 1 ); + } +} + + +//----------------------------------------------------------------------------- +// Returns the wrinkle scale for a particular control +//----------------------------------------------------------------------------- +float CDmeCombinationInputControl::WrinkleScale( const char *pRawControlName ) +{ + int nIndex = FindRawControl( pRawControlName ); + return WrinkleScale( nIndex ); +} + +float CDmeCombinationInputControl::WrinkleScale( int nIndex ) +{ + if ( nIndex < 0 || ( nIndex >= m_WrinkleScales.Count() ) ) + return 0.0f; + return m_WrinkleScales[ nIndex ]; +} + +void CDmeCombinationInputControl::SetWrinkleScale( const char *pRawControlName, float flWrinkleScale ) +{ + int nIndex = FindRawControl( pRawControlName ); + if ( nIndex < 0 || ( nIndex >= m_WrinkleScales.Count() ) ) + return; + m_WrinkleScales.Set( nIndex, flWrinkleScale ); +} + + +//----------------------------------------------------------------------------- +// The default value of an input control is the value the UI has by default +//----------------------------------------------------------------------------- +float CDmeCombinationInputControl::GetDefaultValue() const +{ + return RawControlCount() == 2 ? 0.5f : 0.0f; +} + + +//----------------------------------------------------------------------------- +// The base value of an input control will set the data to the base state +// i.e. The state upon which the deltas are relative. Normally this is the +// same as GetDefaultValue() except for EyeLid controls +//----------------------------------------------------------------------------- +float CDmeCombinationInputControl::GetBaseValue() const +{ + if ( IsEyelid() ) + return 0.0f; + + return GetDefaultValue(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDmeCombinationInputControl::GetEyesUpDownFlexName() const +{ + const CDmAttribute *pEyesUpDownFlexAttr = GetAttribute( "eyesUpDownFlex", AT_STRING ); + + if ( pEyesUpDownFlexAttr ) + return pEyesUpDownFlexAttr->GetValueString(); + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeCombinationDominationRule, CDmeCombinationDominationRule ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationDominationRule::OnConstruction() +{ + m_Dominators.Init( this, "dominators", FATTRIB_HAS_CALLBACK | FATTRIB_HAS_ARRAY_CALLBACK ); + m_Suppressed.Init( this, "suppressed", FATTRIB_HAS_CALLBACK | FATTRIB_HAS_ARRAY_CALLBACK ); +} + +void CDmeCombinationDominationRule::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Notify parent that one of our attributes has changed +//----------------------------------------------------------------------------- +void CDmeCombinationDominationRule::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + BaseClass::OnAttributeChanged( pAttribute ); + + if ( pAttribute == m_Dominators.GetAttribute() || pAttribute == m_Suppressed.GetAttribute() ) + { + InvokeOnAttributeChangedOnReferrers( GetHandle(), pAttribute ); + } +} + + +//----------------------------------------------------------------------------- +// Do we have this string already? +//----------------------------------------------------------------------------- +bool CDmeCombinationDominationRule::HasString( const char *pString, const CDmaStringArray& attr ) +{ + int nCount = attr.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pString, attr[i] ) ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Adds a dominating control +//----------------------------------------------------------------------------- +void CDmeCombinationDominationRule::AddDominator( const char *pDominatorControl ) +{ + if ( HasString( pDominatorControl, m_Dominators ) ) + { + Warning( "Domination rule already contains dominating control %s\n", pDominatorControl ); + return; + } + if ( HasString( pDominatorControl, m_Suppressed ) ) + { + Warning( "Attemped to add a control as both dominator + suppressed %s\n", pDominatorControl ); + return; + } + m_Dominators.AddToTail( pDominatorControl ); +} + + +//----------------------------------------------------------------------------- +// Add a suppressed control +//----------------------------------------------------------------------------- +void CDmeCombinationDominationRule::AddSuppressed( const char *pSuppressedControl ) +{ + if ( HasString( pSuppressedControl, m_Suppressed ) ) + { + Warning( "Domination rule already contains suppressed control %s\n", pSuppressedControl ); + return; + } + if ( HasString( pSuppressedControl, m_Dominators ) ) + { + Warning( "Attemped to add a control as both dominator + suppressed %s\n", pSuppressedControl ); + return; + } + m_Suppressed.AddToTail( pSuppressedControl ); +} + + +//----------------------------------------------------------------------------- +// Remove all dominatior + suppressed controls +//----------------------------------------------------------------------------- +void CDmeCombinationDominationRule::RemoveAllDominators() +{ + m_Dominators.RemoveAll(); +} + +void CDmeCombinationDominationRule::RemoveAllSuppressed() +{ + m_Suppressed.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Iteration +//----------------------------------------------------------------------------- +int CDmeCombinationDominationRule::DominatorCount() const +{ + return m_Dominators.Count(); +} + +const char *CDmeCombinationDominationRule::GetDominator( int i ) const +{ + return m_Dominators[i]; +} + +int CDmeCombinationDominationRule::SuppressedCount() const +{ + return m_Suppressed.Count(); +} + +const char *CDmeCombinationDominationRule::GetSuppressed( int i ) const +{ + return m_Suppressed[i]; +} + + +//----------------------------------------------------------------------------- +// Search +//----------------------------------------------------------------------------- +bool CDmeCombinationDominationRule::HasDominatorControl( const char *pDominatorControl ) const +{ + int nCount = DominatorCount(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( GetDominator(i), pDominatorControl ) ) + return true; + } + return false; +} + +bool CDmeCombinationDominationRule::HasSuppressedControl( const char *pSuppressedControl ) const +{ + int nCount = SuppressedCount(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( GetSuppressed(i), pSuppressedControl ) ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeCombinationOperator, CDmeCombinationOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::OnConstruction() +{ + m_InputControls.Init( this, "controls" ); + m_ControlValues[COMBO_CONTROL_NORMAL].Init( this, "controlValues" ); + m_ControlValues[COMBO_CONTROL_LAGGED].Init( this, "controlValuesLagged" ); + m_bSpecifyingLaggedData.Init( this, "usesLaggedValues" ); + + m_Dominators.Init( this, "dominators", FATTRIB_HAS_CALLBACK | FATTRIB_HAS_ARRAY_CALLBACK ); + + m_Targets.Init( this, "targets" ); + m_flLastLaggedComputationTime = FLT_MIN; +} + +void CDmeCombinationOperator::OnDestruction() +{ + m_RawControlInfo.RemoveAll(); + m_CombinationInfo.RemoveAll(); + m_DominatorInfo.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + m_Dominators.GetAttribute()->AddFlag( FATTRIB_DIRTY ); +} + + +//----------------------------------------------------------------------------- +// Finds the index of the input control with the specified name (and creates one if necessary) +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::OnElementUnserialized() +{ + BaseClass::OnElementUnserialized(); + + // Mark all of the input as not being in their default state since we read it from a file + int nCount = m_InputControls.Count(); + m_IsDefaultValue.SetCount( nCount ); + for ( int i = 0; i < nCount; ++i ) + { + m_IsDefaultValue[i] = false; + } +} + + +//----------------------------------------------------------------------------- +// Finds the index of the input control with the specified name (and creates one if necessary) +//----------------------------------------------------------------------------- +ControlIndex_t CDmeCombinationOperator::FindOrCreateControl( const char *pControlName, bool bStereo, bool bAutoAddRawControl ) +{ + Assert( !strchr( pControlName, '_' ) && !strchr( pControlName, ' ' ) ); + int nCount = m_InputControls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !m_InputControls[i] ) + continue; + + if ( !Q_stricmp( pControlName, m_InputControls[i]->GetName() ) ) + return i; + } + + // NOTE: the y coordinate of the control value is -1 if it's not a stereo control + CDmeCombinationInputControl *pInputControl = CreateElement< CDmeCombinationInputControl >( pControlName, GetFileId() ); + pInputControl->SetStereo( bStereo ); + int nIndex = m_InputControls.AddToTail( pInputControl ); + m_ControlValues[COMBO_CONTROL_NORMAL].AddToTail( Vector( 0.0f, 0.5f, 0.5f ) ); + m_ControlValues[COMBO_CONTROL_LAGGED].AddToTail( Vector( 0.0f, 0.5f, 0.5f ) ); + m_IsDefaultValue.AddToTail( true ); + Assert( m_InputControls.Count() == m_ControlValues[COMBO_CONTROL_NORMAL].Count() ); + Assert( m_InputControls.Count() == m_ControlValues[COMBO_CONTROL_LAGGED].Count() ); + Assert( m_InputControls.Count() == m_IsDefaultValue.Count() ); + + if ( bAutoAddRawControl ) + { + AddRawControl( nIndex, pControlName ); + } + + return nIndex; +} + + +//----------------------------------------------------------------------------- +// Finds the index of the input control with the specified name +//----------------------------------------------------------------------------- +ControlIndex_t CDmeCombinationOperator::FindControlIndex( const char *pControlName ) +{ + int nCount = m_InputControls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !m_InputControls[i] ) + continue; + + if ( !Q_stricmp( pControlName, m_InputControls[i]->GetName() ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Removes a control +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::RemoveControl( const char *pControlName ) +{ + ControlIndex_t nIndex = FindControlIndex( pControlName ); + if ( nIndex >= 0 ) + { + DestroyElement( m_InputControls[nIndex] ); + m_InputControls.FastRemove( nIndex ); + m_ControlValues[COMBO_CONTROL_NORMAL].FastRemove( nIndex ); + m_ControlValues[COMBO_CONTROL_LAGGED].FastRemove( nIndex ); + m_IsDefaultValue.FastRemove( nIndex ); + + Assert( m_InputControls.Count() == m_ControlValues[COMBO_CONTROL_NORMAL].Count() ); + Assert( m_InputControls.Count() == m_ControlValues[COMBO_CONTROL_LAGGED].Count() ); + Assert( m_InputControls.Count() == m_IsDefaultValue.Count() ); + + RebuildRawControlList(); + } +} + +void CDmeCombinationOperator::RemoveAllControls() +{ + int nCount = m_InputControls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmeCombinationInputControl *pInputControl = m_InputControls[i]; + m_InputControls.Set( i, NULL ); + DestroyElement( pInputControl ); + } + m_InputControls.RemoveAll(); + m_ControlValues[COMBO_CONTROL_NORMAL].RemoveAll( ); + m_ControlValues[COMBO_CONTROL_LAGGED].RemoveAll( ); + m_IsDefaultValue.RemoveAll( ); + + RebuildRawControlList(); +} + + +//----------------------------------------------------------------------------- +// Changes a control's name +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetControlName( ControlIndex_t nControl, const char *pControlName ) +{ + ControlIndex_t nFoundIndex = FindControlIndex( pControlName ); + if ( nFoundIndex >= 0 && nFoundIndex != nControl ) + { + Warning( "A control with name \"%s\" already exists!\n", pControlName ); + return; + } + m_InputControls[nControl]->SetName( pControlName ); +} + + +//----------------------------------------------------------------------------- +// Updates the default value associated with a control +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::UpdateDefaultValue( ControlIndex_t nControlIndex ) +{ + if ( m_IsDefaultValue[ nControlIndex ] ) + { + float flDefaultValue = GetRawControlCount( nControlIndex ) == 2 ? 0.5f : 0.0f; + const Vector& vec = m_ControlValues[COMBO_CONTROL_NORMAL][nControlIndex]; + m_ControlValues[COMBO_CONTROL_NORMAL].Set( nControlIndex, Vector( flDefaultValue, vec.y, vec.z ) ); + const Vector& vec2 = m_ControlValues[COMBO_CONTROL_LAGGED][nControlIndex]; + m_ControlValues[COMBO_CONTROL_LAGGED].Set( nControlIndex, Vector( flDefaultValue, vec2.y, vec2.z ) ); + } +} + + +//----------------------------------------------------------------------------- +// Adds an output control to a input control +// If force is true, removes the raw control from any existing controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::AddRawControl( ControlIndex_t nControl, const char *pRawControlName ) +{ + if ( FindRawControlIndex( pRawControlName, true ) >= 0 ) + { + Warning( "Attempted to add the same remapped control \"%s\" twice!\n", pRawControlName ); + return; + } + + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + if ( pInputControl->AddRawControl( pRawControlName ) ) + { + UpdateDefaultValue( nControl ); + RebuildRawControlList(); + } +} + + +//----------------------------------------------------------------------------- +// Removes an output control from an input control +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::RemoveRawControl( ControlIndex_t nControl, const char *pRawControlName ) +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + if ( pInputControl->RemoveRawControl( pRawControlName ) ) + { + UpdateDefaultValue( nControl ); + RebuildRawControlList(); + } +} + +void CDmeCombinationOperator::RemoveAllRawControls( ControlIndex_t nControl ) +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + pInputControl->RemoveAllRawControls( ); + + int nCount = m_InputControls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + UpdateDefaultValue( i ); + } + + RebuildRawControlList(); +} + + +//----------------------------------------------------------------------------- +// Iterates remapped controls +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::GetRawControlCount( ControlIndex_t nControl ) const +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + return pInputControl->RawControlCount(); +} + +const char *CDmeCombinationOperator::GetRawControlName( ControlIndex_t nControl, int nIndex ) const +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + return pInputControl->RawControlName( nIndex ); +} + +float CDmeCombinationOperator::GetRawControlWrinkleScale( ControlIndex_t nControl, int nIndex ) const +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + return pInputControl->WrinkleScale( nIndex ); +} + +float CDmeCombinationOperator::GetRawControlWrinkleScale( ControlIndex_t nControl, const char *pRawControlName ) const +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + return pInputControl->WrinkleScale( pRawControlName ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +float CDmeCombinationOperator::GetControlDefaultValue( ControlIndex_t nControl ) const +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + return pInputControl->GetDefaultValue(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +float CDmeCombinationOperator::GetControlBaseValue( ControlIndex_t nControl ) const +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + return pInputControl->GetBaseValue(); +} + + +//----------------------------------------------------------------------------- +// Iterates a global list of output controls +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::GetRawControlCount( ) const +{ + return m_RawControlInfo.Count(); +} + +const char *CDmeCombinationOperator::GetRawControlName( int nIndex ) const +{ + return m_RawControlInfo[nIndex].m_Name; +} + +float CDmeCombinationOperator::GetRawControlWrinkleScale( int nIndex ) const +{ + return m_RawControlInfo[nIndex].m_flWrinkleScale; +} + + +//----------------------------------------------------------------------------- +// Sets the wrinkle scale for a particular raw control +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetWrinkleScale( ControlIndex_t nControl, const char *pRawControlName, float flWrinkleScale ) +{ + CDmeCombinationInputControl *pInputControl = m_InputControls[nControl]; + Assert( pInputControl ); + pInputControl->SetWrinkleScale( pRawControlName, flWrinkleScale ); + RebuildRawControlList(); +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveControlUp( const char *pControlName ) +{ + + int nIndex = FindControlIndex( pControlName ); + if ( nIndex > 0 ) + { + m_InputControls.Swap( nIndex, nIndex - 1 ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + m_ControlValues[ i ].Swap( nIndex, nIndex - 1 ); + } + RebuildRawControlList(); + } +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveControlDown( const char *pControlName ) +{ + int nIndex = FindControlIndex( pControlName ); + int nLastIndex = m_InputControls.Count() - 1; + if ( nIndex >= 0 && nIndex < nLastIndex ) + { + m_InputControls.Swap( nIndex, nIndex + 1 ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + m_ControlValues[ i ].Swap( nIndex, nIndex + 1 ); + } + RebuildRawControlList(); + } +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveControlBefore( const char *pDragControlName, const char *pDropControlName ) +{ + int pDragIndex( FindControlIndex( pDragControlName ) ); + int pDropIndex( FindControlIndex( pDropControlName ) ); + + // Have to copy because InsertAfter may reallocate memory before doing the copy and therefore might be referencing garabage + CDmeCombinationInputControl *inputControlCopy( m_InputControls[ pDragIndex ] ); + m_InputControls.InsertBefore( pDropIndex, inputControlCopy ); + if ( pDragIndex <= pDropIndex ) + { + m_InputControls.Remove( pDragIndex ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + const Vector controlValueCopy( m_ControlValues[ i ][ pDragIndex ] ); + m_ControlValues[ i ].InsertBefore( pDropIndex, controlValueCopy ); + m_ControlValues[ i ].Remove( pDragIndex ); + } + } + else + { + m_InputControls.Remove( pDragIndex + 1 ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + const Vector controlValueCopy( m_ControlValues[ i ][ pDragIndex ] ); + m_ControlValues[ i ].InsertBefore( pDropIndex, controlValueCopy ); + m_ControlValues[ i ].Remove( pDragIndex + 1 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveControlAfter( const char *pDragControlName, const char *pDropControlName ) +{ + int nDragIndex = FindControlIndex( pDragControlName ); + int nDropIndex = FindControlIndex( pDropControlName ); + + // Have to copy because InsertAfter may reallocate memory before doing the copy and therefore might be referencing garabage + CDmeCombinationInputControl *inputControlCopy( m_InputControls[ nDragIndex ] ); + m_InputControls.InsertBefore( nDropIndex + 1, inputControlCopy ); + if ( nDragIndex < nDropIndex ) + { + m_InputControls.Remove( nDragIndex ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + const Vector controlValueCopy( m_ControlValues[ i ][ nDragIndex ] ); + m_ControlValues[ i ].InsertBefore( nDropIndex + 1, controlValueCopy ); + m_ControlValues[ i ].Remove( nDragIndex ); + } + } + else + { + m_InputControls.Remove( nDragIndex + 1 ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + const Vector controlValueCopy( m_ControlValues[ i ][ nDragIndex ] ); + m_ControlValues[ i ].InsertBefore( nDropIndex + 1, controlValueCopy ); + m_ControlValues[ i ].Remove( nDragIndex + 1 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveRawControlUp( ControlIndex_t nControlIndex, const char *pRawControlName ) +{ + m_InputControls[nControlIndex]->MoveRawControlUp( pRawControlName ); +} + + +//----------------------------------------------------------------------------- +// Reordering controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveRawControlDown( ControlIndex_t nControlIndex, const char *pRawControlName ) +{ + m_InputControls[nControlIndex]->MoveRawControlDown( pRawControlName ); +} + + +//----------------------------------------------------------------------------- +// Returns true if a control is a stereo control +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetStereoControl( ControlIndex_t nControlIndex, bool bIsStereo ) +{ + m_InputControls[nControlIndex]->SetStereo( bIsStereo ); +} + +bool CDmeCombinationOperator::IsStereoControl( ControlIndex_t nControlIndex ) const +{ + return m_InputControls[nControlIndex]->IsStereo(); +} + +bool CDmeCombinationOperator::IsStereoRawControl( int nIndex ) const +{ + return IsStereoControl( m_RawControlInfo[nIndex].m_InputControl ); +} + +void CDmeCombinationOperator::SetEyelidControl( ControlIndex_t nControlIndex, bool bIsEyelid ) +{ + m_InputControls[nControlIndex]->SetEyelid( bIsEyelid ); +} + +bool CDmeCombinationOperator::IsEyelidControl( ControlIndex_t nControlIndex ) const +{ + return m_InputControls[nControlIndex]->IsEyelid(); +} + +bool CDmeCombinationOperator::IsEyelidRawControl( int nIndex ) const +{ + return IsEyelidControl( m_RawControlInfo[nIndex].m_InputControl ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDmeCombinationOperator::GetEyesUpDownFlexName( ControlIndex_t nControlIndex ) const +{ + return m_InputControls[ nControlIndex ]->GetEyesUpDownFlexName(); +} + + +//----------------------------------------------------------------------------- +// Sets the value of a control +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetControlValue( ControlIndex_t nControlIndex, float flValue, CombinationControlType_t type ) +{ + m_IsDefaultValue[ nControlIndex ] = false; + float flMultiLevel = m_ControlValues[type][nControlIndex].z; + m_ControlValues[type].Set( nControlIndex, Vector( flValue, flValue, flMultiLevel ) ); +} + +void CDmeCombinationOperator::SetControlValue( ControlIndex_t nControlIndex, float flLevel, float flBalance, CombinationControlType_t type ) +{ + Assert( IsStereoControl( nControlIndex ) ); + m_IsDefaultValue[ nControlIndex ] = false; + float flMultiLevel = m_ControlValues[type][nControlIndex].z; + m_ControlValues[type].Set( nControlIndex, Vector( flLevel, flBalance, flMultiLevel ) ); +} + +void CDmeCombinationOperator::SetControlValue( ControlIndex_t nControlIndex, const Vector2D& vec, CombinationControlType_t type ) +{ + Assert( IsStereoControl( nControlIndex ) ); + m_IsDefaultValue[ nControlIndex ] = false; + float flMultiLevel = m_ControlValues[type][nControlIndex].z; + m_ControlValues[type].Set( nControlIndex, Vector( vec.x, vec.y, flMultiLevel ) ); +} + + +//----------------------------------------------------------------------------- +// Sets the value of a control +//----------------------------------------------------------------------------- +float CDmeCombinationOperator::GetControlValue( ControlIndex_t nControlIndex, CombinationControlType_t type ) const +{ + Assert( !IsStereoControl( nControlIndex ) ); + return m_ControlValues[type].Get( nControlIndex ).x; +} + + +//----------------------------------------------------------------------------- +// Sets the value of a control +//----------------------------------------------------------------------------- +const Vector2D& CDmeCombinationOperator::GetStereoControlValue( ControlIndex_t nControlIndex, CombinationControlType_t type ) const +{ + return m_ControlValues[type].Get( nControlIndex ).AsVector2D(); +} + + +//----------------------------------------------------------------------------- +// Sets the level of a control (only used by controls w/ 3 or more remappings) +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetMultiControlLevel( ControlIndex_t nControlIndex, float flMultiLevel, CombinationControlType_t type ) +{ + m_IsDefaultValue[ nControlIndex ] = false; + const Vector2D &value = m_ControlValues[type][nControlIndex].AsVector2D(); + m_ControlValues[type].Set( nControlIndex, Vector( value.x, value.y, flMultiLevel ) ); +} + +float CDmeCombinationOperator::GetMultiControlLevel( ControlIndex_t nControlIndex, CombinationControlType_t type ) const +{ + Assert( IsMultiControl( nControlIndex ) ); + return m_ControlValues[type][nControlIndex].z; +} + + +//----------------------------------------------------------------------------- +// Returns true if a control is a multi control (a control w/ 3 or more remappings) +//----------------------------------------------------------------------------- +bool CDmeCombinationOperator::IsMultiControl( ControlIndex_t nControlIndex ) const +{ + return m_InputControls[nControlIndex]->RawControlCount() >= 3 || IsEyelidControl( nControlIndex ); +} + + +//----------------------------------------------------------------------------- +// Iterates controls +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::GetControlCount() const +{ + return m_InputControls.Count(); +} + +const char *CDmeCombinationOperator::GetControlName( ControlIndex_t i ) const +{ + return m_InputControls[i]->GetName(); +} + + +//----------------------------------------------------------------------------- +// Do we have a raw control? +//----------------------------------------------------------------------------- +bool CDmeCombinationOperator::HasRawControl( const char *pRawControlName ) const +{ + return FindRawControlIndex( pRawControlName ) >= 0; +} + + +//----------------------------------------------------------------------------- +// Finds the index of the remapped control with the specified name +//----------------------------------------------------------------------------- +CDmeCombinationOperator::RawControlIndex_t CDmeCombinationOperator::FindRawControlIndex( const char *pControlName, bool bIgnoreDefaultControls ) const +{ + int nRawControlCount = m_RawControlInfo.Count(); + for ( int i = 0; i < nRawControlCount; ++i ) + { + if ( bIgnoreDefaultControls && m_RawControlInfo[i].m_bIsDefaultControl ) + continue; + + if ( !Q_stricmp( pControlName, m_RawControlInfo[i].m_Name ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Is a particular remapped control stereo? +//----------------------------------------------------------------------------- +bool CDmeCombinationOperator::IsRawControlStereo( const char *pRawControlName ) +{ + RawControlIndex_t nIndex = FindRawControlIndex( pRawControlName ); + if ( nIndex < 0 ) + return false; + + CDmeCombinationInputControl *pInputControl = m_InputControls[ m_RawControlInfo[nIndex].m_InputControl ]; + return pInputControl->IsStereo(); +} + + +//----------------------------------------------------------------------------- +// Would a particular delta state attached to this combination operator end up stereo? +//----------------------------------------------------------------------------- +bool CDmeCombinationOperator::IsDeltaStateStereo( const char *pDeltaStateName ) +{ + int *pTemp = (int*)_alloca( m_RawControlInfo.Count() * sizeof(int) ); + int nCount = ParseDeltaName( pDeltaStateName, pTemp ); + for ( int i = 0; i < nCount; ++i ) + { + CDmeCombinationInputControl *pInputControl = m_InputControls[ m_RawControlInfo[ pTemp[i] ].m_InputControl ]; + if ( pInputControl->IsStereo() ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Does one of the targets we refer to contain a particular delta state? +//----------------------------------------------------------------------------- +bool CDmeCombinationOperator::DoesTargetContainDeltaState( const char *pSearchName ) +{ + int nTargetCount = m_Targets.Count(); + for ( int i = 0; i < nTargetCount; ++i ) + { + const CDmrElementArray<> deltaArray( m_Targets[i], "deltaStates" ); + if ( !deltaArray.IsValid() ) + continue; + + int nDeltaCount = deltaArray.Count(); + for ( int j = 0; j < nDeltaCount; ++j ) + { + if ( !deltaArray[j] ) + continue; + + char pBuf[512]; + Q_strncpy( pBuf, deltaArray[j]->GetName(), sizeof(pBuf) ); + + char *pEnd; + for ( char *pName = pBuf; *pName; pName = pEnd ) + { + pEnd = strchr( pName, '_' ); + if ( !pEnd ) + { + pEnd = pName + Q_strlen( pName ); + } + else + { + // Null-terminate + *pEnd = 0; + ++pEnd; + } + + if ( !Q_stricmp( pSearchName, pName ) ) + return true; + } + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Computes list of all remapped controls +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::RebuildRawControlList() +{ + m_RawControlInfo.RemoveAll(); + + int nControlCount = m_InputControls.Count(); + for ( int i = 0; i < nControlCount; ++i ) + { + CDmeCombinationInputControl *pInputControl = m_InputControls[i]; + Assert( pInputControl ); + int nRemapCount = pInputControl->RawControlCount(); + + const bool bIsEyelid = pInputControl->IsEyelid(); + + float flStep = ( nRemapCount > 2 ) ? 1.0f / ( nRemapCount - 1 ) : 0.0f; + for ( int j = 0; j < nRemapCount; ++j ) + { + int k = m_RawControlInfo.AddToTail( ); + RawControlInfo_t &info = m_RawControlInfo[k]; + + info.m_Name = pInputControl->RawControlName( j ); + info.m_InputControl = i; + info.m_bIsDefaultControl = false; + info.m_flWrinkleScale = pInputControl->WrinkleScale( j ); + info.m_bLowerEyelid = false; + + if ( bIsEyelid ) + { + info.m_FilterRamp.Init( 0.0f, 1.0f, 10.0f, 11.0f ); + // TODO: Right now it's implicit that the lower eyelid is first... + if ( j == 0 ) + { + // Close Lower Lid + info.m_bLowerEyelid = true; + } + continue; + } + + switch( nRemapCount ) + { + case 1: + info.m_FilterRamp.Init( 0.0f, 1.0f, 10.0f, 11.0f ); + break; + + case 2: + if ( j == 0 ) + { + info.m_FilterRamp.Init( -11.0f, -10.0f, 0.0f, 0.5f ); + } + else + { + info.m_FilterRamp.Init( 0.5f, 1.0f, 10.0f, 11.0f ); + } + break; + + default: + { + if ( j == 0 ) + { + info.m_FilterRamp.Init( -11.0f, -10.0f, 0, flStep ); + } + else if ( j == nRemapCount-1 ) + { + info.m_FilterRamp.Init( 1.0f - flStep, 1.0f, 10.0f, 11.0f ); + } + else + { + float flPeak = j * flStep; + info.m_FilterRamp.Init( flPeak - flStep, flPeak, flPeak, flPeak + flStep ); + } + } + break; + } + } + } + + RebuildDominatorInfo(); +} + + +//----------------------------------------------------------------------------- +// Computes lists of dominators and suppressors +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::RebuildDominatorInfo() +{ + m_DominatorInfo.RemoveAll(); + int nCount = m_Dominators.Count(); + int *pDominators = (int*)_alloca( m_RawControlInfo.Count() * sizeof(int) ); + int *pSuppressed = (int*)_alloca( m_RawControlInfo.Count() * sizeof(int) ); + for ( int i = 0; i < nCount; ++i ) + { + CDmeCombinationDominationRule *pRule = m_Dominators[i]; + bool bRuleOk = true; + + int nDominatorCount = pRule->DominatorCount(); + int nSuppressedCount = pRule->SuppressedCount(); + if ( ( nDominatorCount == 0 ) || ( nSuppressedCount == 0 ) ) + continue; + + for ( int j = 0; j < nDominatorCount; ++j ) + { + int nControlIndex = FindRawControlIndex( pRule->GetDominator(j) ); + if ( nControlIndex < 0 ) + { + bRuleOk = false; + break; + } + pDominators[j] = nControlIndex; + } + + for ( int j = 0; j < nSuppressedCount; ++j ) + { + int nControlIndex = FindRawControlIndex( pRule->GetSuppressed(j) ); + if ( nControlIndex < 0 ) + { + bRuleOk = false; + break; + } + pSuppressed[j] = nControlIndex; + } + + if ( !bRuleOk ) + continue; + + int k = m_DominatorInfo.AddToTail(); + m_DominatorInfo[k].m_DominantIndices.AddMultipleToTail( nDominatorCount, pDominators ); + m_DominatorInfo[k].m_SuppressedIndices.AddMultipleToTail( nSuppressedCount, pSuppressed ); + } +} + + +//----------------------------------------------------------------------------- +// Adds a dominator. Dominators are specified using raw control names +//----------------------------------------------------------------------------- +CDmeCombinationDominationRule *CDmeCombinationOperator::AddDominationRule( ) +{ + CDmeCombinationDominationRule *pDominationRule = CreateElement< CDmeCombinationDominationRule >( "rule", GetFileId() ); + m_Dominators.AddToTail( pDominationRule ); + return pDominationRule; +} + + +//----------------------------------------------------------------------------- +// Adds a dominator. Dominators are specified using raw control names +//----------------------------------------------------------------------------- +CDmeCombinationDominationRule *CDmeCombinationOperator::AddDominationRule( CDmeCombinationDominationRule *pSrcRule ) +{ + CDmeCombinationDominationRule *pDestRule = pSrcRule->Copy( ); + pDestRule->SetFileId( GetFileId(), TD_DEEP ); + m_Dominators.AddToTail( pDestRule ); + return pDestRule; +} + + +//----------------------------------------------------------------------------- +// Adds a dominator. Dominators are specified using raw control names +//----------------------------------------------------------------------------- +CDmeCombinationDominationRule *CDmeCombinationOperator::AddDominationRule( int nDominatorCount, const char **ppDominatorControlNames, int nSuppressedCount, const char **ppSuppressedControlNames ) +{ + CDmeCombinationDominationRule *pDominationRule = AddDominationRule(); + for ( int i = 0; i < nDominatorCount; ++i ) + { + pDominationRule->AddDominator( ppDominatorControlNames[i] ); + } + for ( int i = 0; i < nSuppressedCount; ++i ) + { + pDominationRule->AddSuppressed( ppSuppressedControlNames[i] ); + } + return pDominationRule; +} + + +//----------------------------------------------------------------------------- +// Adds a dominator. Dominators are specified using raw control names +//----------------------------------------------------------------------------- +CDmeCombinationDominationRule *CDmeCombinationOperator::AddDominationRule( const CUtlVector< const char * > dominators, const CUtlVector< const char * > suppressed ) +{ + return AddDominationRule( dominators.Count(), (const char **)dominators.Base(), suppressed.Count(), (const char **)suppressed.Base() ); +} + + +//----------------------------------------------------------------------------- +// Removes a domination rule +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::RemoveDominationRule( int nIndex ) +{ + CDmeCombinationDominationRule *pRule = m_Dominators[nIndex]; + m_Dominators.Remove( nIndex ); + DestroyElement( pRule ); +} + +void CDmeCombinationOperator::RemoveDominationRule( CDmeCombinationDominationRule *pRule ) +{ + int nCount = m_Dominators.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Dominators[i] == pRule ) + { + RemoveDominationRule( i ); + break; + } + } +} + +void CDmeCombinationOperator::RemoveAllDominationRules() +{ + int nCount = m_Dominators.Count(); + for ( int i = nCount; --i >= 0; ) + { + RemoveDominationRule( i ); + } +} + + +//----------------------------------------------------------------------------- +// Iteration +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::DominationRuleCount() const +{ + return m_Dominators.Count(); +} + +CDmeCombinationDominationRule *CDmeCombinationOperator::GetDominationRule( int i ) +{ + return m_Dominators[i]; +} + + +//----------------------------------------------------------------------------- +// Finds a domination rule +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::FindDominationRule( CDmeCombinationDominationRule *pRule ) +{ + int nCount = m_Dominators.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Dominators[i] == pRule ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Rule reordering +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::MoveDominationRuleUp( CDmeCombinationDominationRule* pRule ) +{ + int nIndex = FindDominationRule( pRule ); + if ( nIndex > 0 ) + { + m_Dominators.Swap( nIndex, nIndex - 1 ); + return; + } +} + +void CDmeCombinationOperator::MoveDominationRuleDown( CDmeCombinationDominationRule* pRule ) +{ + int nIndex = FindDominationRule( pRule ); + if ( nIndex >= 0 && nIndex < m_Dominators.Count() - 1 ) + { + m_Dominators.Swap( nIndex, nIndex + 1 ); + return; + } +} + + +//----------------------------------------------------------------------------- +// Attaches a channel to an input +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::AttachChannelToControlValue( ControlIndex_t nControlIndex, CombinationControlType_t type, CDmeChannel *pChannel ) +{ + pChannel->SetOutput( m_ControlValues[type].GetAttribute(), nControlIndex ); +} + + +//----------------------------------------------------------------------------- +// Determines the weighting of input controls based on the deltaState name +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::FindDeltaStateIndex( CDmAttribute *pDeltaArray, const char *pDeltaStateName ) +{ + const CDmrElementArray<> deltas( pDeltaArray ); + int nDeltaArrayCount = deltas.Count(); + for ( int i = 0; i < nDeltaArrayCount; ++i ) + { + CDmElement *pDeltaElement = deltas[i]; + if ( pDeltaElement && !Q_stricmp( pDeltaElement->GetName(), pDeltaStateName ) ) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Determines which combination to use based on the deltaState name +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::ParseDeltaName(const char *pDeltaStateName, int *pControlIndices ) +{ + char pBuf[512]; + Q_strncpy( pBuf, pDeltaStateName, sizeof(pBuf) ); + + int nComboCount = 0; + char *pEnd; + for ( char *pName = pBuf; *pName; pName = pEnd ) + { + pEnd = strchr( pName, '_' ); + if ( !pEnd ) + { + pEnd = pName + Q_strlen( pName ); + } + else + { + // Null-terminate + *pEnd = 0; + ++pEnd; + } + + int nControlIndex = FindRawControlIndex( pName ); + if ( nControlIndex < 0 ) + return 0; + + pControlIndices[ nComboCount++ ] = nControlIndex; + } + + return nComboCount; +} + + +//----------------------------------------------------------------------------- +// Finds dominators +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::FindDominators( CombinationOperation_t& op ) +{ + // Dominators are sets of inputs, which, when set to 1, will + // supress another set of inputs. Only combinations which contain *all* + // dominators will suppress any combinatation that contains *all* suppressors + int nDominatorCount = m_DominatorInfo.Count(); + for ( int i = 0; i < nDominatorCount; ++i ) + { + DominatorInfo_t &info = m_DominatorInfo[i]; + + // Look for suppressor indices in the control indices list + int nCount = info.m_SuppressedIndices.Count(); + int j; + for ( j = 0; j < nCount; ++j ) + { + if ( op.m_ControlIndices.Find( info.m_SuppressedIndices[j] ) < 0 ) + break; + } + if ( j != nCount ) + continue; + + op.m_DominatorIndices.AddToTail( i ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::ComputeCombinationInfo( int nIndex ) +{ + CleanUpCombinationInfo( nIndex ); + + int nCurrentCount = m_CombinationInfo.Count(); + while ( nIndex >= nCurrentCount ) + { + int j = m_CombinationInfo.AddToTail(); + CombinationInfo_t &info = m_CombinationInfo[j]; + for ( int k = 0; k < COMBO_CONTROL_TYPE_COUNT; ++k ) + { + info.m_hDestAttribute[k] = DMATTRIBUTE_HANDLE_INVALID; + } + ++nCurrentCount; + } + + CombinationInfo_t &info = m_CombinationInfo[nIndex]; + + CDmElement *pSource = m_Targets[ nIndex ]; + if ( !pSource ) + return; + + CDmrElementArray<> deltas( pSource, "deltaStates" ); + if ( !deltas.IsValid() ) + return; + + CDmrArray<Vector2D> weights( pSource, "deltaStateWeights" ); + if ( !weights.IsValid() ) + return; + + CDmrArray<Vector2D> weightsLagged( pSource, "deltaStateWeightsLagged" ); + + // This is an error state + if ( deltas.Count() > weights.Count() ) + return; + + // This is an error state + if ( weightsLagged.IsValid() && deltas.Count() > weightsLagged.Count() ) + return; + + info.m_hDestAttribute[COMBO_CONTROL_NORMAL] = weights.GetAttribute()->GetHandle(); + info.m_hDestAttribute[COMBO_CONTROL_LAGGED] = weightsLagged.IsValid() ? weightsLagged.GetAttribute()->GetHandle() : DMATTRIBUTE_HANDLE_INVALID; + + int *pTemp = (int*)_alloca( m_RawControlInfo.Count() * sizeof(int) ); + int nDeltaCount = deltas.Count(); + for ( int i = 0; i < nDeltaCount; ++i ) + { + CDmElement *pDeltaElement = deltas[i]; + if ( !pDeltaElement ) + continue; + + int nControlCount = ParseDeltaName( pDeltaElement->GetName(), pTemp ); + if ( nControlCount == 0 ) + continue; + + int j = info.m_Outputs.AddToTail(); + CombinationOperation_t &op = info.m_Outputs[j]; + op.m_nDeltaStateIndex = i; + op.m_ControlIndices.AddMultipleToTail( nControlCount, pTemp ); + + // Find dominators + FindDominators( op ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::ComputeCombinationInfo() +{ + int nTargetCount = m_Targets.Count(); + for ( int i = 0; i < nTargetCount; ++i ) + { + ComputeCombinationInfo( i ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::CleanUpCombinationInfo( int nIndex ) +{ + if ( nIndex >= m_CombinationInfo.Count() ) + return; + + CombinationInfo_t &info = m_CombinationInfo[ nIndex ]; + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + info.m_hDestAttribute[i] = DMATTRIBUTE_HANDLE_INVALID; + } + info.m_Outputs.RemoveAll(); +} + + +void CDmeCombinationOperator::CleanUpCombinationInfo( ) +{ + int nCount = m_CombinationInfo.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CleanUpCombinationInfo( i ); + } + m_CombinationInfo.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// helpers for finding corrector names +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::Resolve() +{ + BaseClass::Resolve(); + + if ( m_InputControls.IsDirty() || m_Dominators.IsDirty() ) + { + RebuildRawControlList(); + ComputeCombinationInfo(); + } +} + + +//----------------------------------------------------------------------------- +// Adds a target for the combination operator +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::AddTarget( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + int i = m_Targets.AddToTail( pElement ); + ComputeCombinationInfo( i ); +} + + +//----------------------------------------------------------------------------- +// Sets targets +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::AddTarget( CDmeDag *pDag ) +{ + if ( !pDag ) + return; + + CDmeShape *pShape = pDag->GetShape(); + AddTarget( pShape ); + + int nChildCount = pDag->GetChildCount(); + for ( int i = 0; i < nChildCount; ++i ) + { + CDmeDag *pChild = pDag->GetChild(i); + + // Do not traverse into models + if ( CastElement< CDmeModel >( pChild ) ) + continue; + + AddTarget( pChild ); + } +} + + +//----------------------------------------------------------------------------- +// Remaps non-stereo -> stereo, stereo ->left/right, also adds multilevel + filter +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::ComputeInternalControlValue( RawControlIndex_t nRawControlIndex, CombinationControlType_t type, Vector2D &value ) +{ + const RawControlInfo_t &info = m_RawControlInfo[nRawControlIndex]; + const Vector &vecControlValue = m_ControlValues[ type ][ info.m_InputControl ]; + + const bool bMultiControl = IsMultiControl( info.m_InputControl ); + float flValue = bMultiControl ? vecControlValue.z : vecControlValue.x; + + // Apply multicontrol remapping + if ( flValue <= info.m_FilterRamp.x || flValue >= info.m_FilterRamp.w ) + { + flValue = 0.0f; + } + else if ( flValue < info.m_FilterRamp.y ) + { + flValue = RemapVal( flValue, info.m_FilterRamp.x, info.m_FilterRamp.y, 0.0f, 1.0f ); + } + else if ( flValue > info.m_FilterRamp.z ) + { + flValue = RemapVal( flValue, info.m_FilterRamp.z, info.m_FilterRamp.w, 1.0f, 0.0f ); + } + else + { + flValue = 1.0f; + } + + if ( IsEyelidControl( info.m_InputControl ) ) + { + if ( info.m_bLowerEyelid ) + { + flValue = ( 1.0f - flValue ) * vecControlValue.x; + } + else + { + flValue *= vecControlValue.x; + } + } + else if ( bMultiControl ) + { + flValue *= vecControlValue.x; + } + + value.x = value.y = flValue; + if ( IsStereoControl( info.m_InputControl ) ) + { + if ( vecControlValue.y < 0.5f ) + { + float flRightAmount = RemapVal( vecControlValue.y, 0.0f, 0.5f, 0.0f, 1.0f ); + value.y *= flRightAmount; + } + else + { + float flLeftAmount = RemapVal( vecControlValue.y, 0.5f, 1.0f, 1.0f, 0.0f ); + value.x *= flLeftAmount; + } + } +} + + +//----------------------------------------------------------------------------- +// Computes lagged input values from non-lagged input +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::ComputeLaggedInputValues() +{ + if ( !m_bSpecifyingLaggedData ) + return; + + float t = Plat_FloatTime(); + if ( m_flLastLaggedComputationTime == FLT_MIN ) + { + m_flLastLaggedComputationTime = t; + } + + float dt = t - m_flLastLaggedComputationTime; + m_flLastLaggedComputationTime = t; + float flFactor = ExponentialDecay( 0.8, 0.033, dt ); + + int nCount = m_ControlValues[COMBO_CONTROL_NORMAL].Count(); + for ( int i = 0; i < nCount; ++i ) + { + Vector vecLerp; + VectorLerp( m_ControlValues[COMBO_CONTROL_NORMAL][i], m_ControlValues[COMBO_CONTROL_LAGGED][i], flFactor, vecLerp ); + m_ControlValues[COMBO_CONTROL_LAGGED].Set( i, vecLerp ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::Operate() +{ + ComputeLaggedInputValues(); + + int nCount = m_CombinationInfo.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CombinationInfo_t &info = m_CombinationInfo[i]; + + for ( CombinationControlType_t type = COMBO_CONTROL_FIRST; type < COMBO_CONTROL_TYPE_COUNT; type = (CombinationControlType_t)(type+1) ) + { + if ( ( info.m_hDestAttribute[type] == DMATTRIBUTE_HANDLE_INVALID ) || !g_pDataModel->IsAttributeHandleValid( info.m_hDestAttribute[type] ) ) + continue; + + CDmAttribute* pAttribute = g_pDataModel->GetAttribute( info.m_hDestAttribute[type] ); + if ( !pAttribute || pAttribute->GetType() != AT_VECTOR2_ARRAY ) + continue; + + CDmrArray< Vector2D > vec2D( pAttribute ); + + CombinationControlType_t useType = m_bSpecifyingLaggedData ? type : COMBO_CONTROL_NORMAL; + int nOutputCount = info.m_Outputs.Count(); + for ( int j = 0; j < nOutputCount; ++j ) + { + CombinationOperation_t &op = info.m_Outputs[j]; + + // Compute the core combination + Vector2D vecValue( 1.0f, 1.0f ); + int nCombinationCount = op.m_ControlIndices.Count(); + for ( int k = 0; k < nCombinationCount; ++k ) + { + Vector2D v; + ComputeInternalControlValue( op.m_ControlIndices[k], useType, v ); + vecValue *= v; + } + + + // Compute the dominators + int nDominatorCount = op.m_DominatorIndices.Count(); + for ( int k = 0; k < nDominatorCount; ++k ) + { + const CUtlVector< int > &dominantIndices = m_DominatorInfo[ op.m_DominatorIndices[k] ].m_DominantIndices; + int nDominantCount = dominantIndices.Count(); + + Vector2D suppressor( -1.0f, -1.0f ); + for ( int l = 0; l < nDominantCount; ++l ) + { + Vector2D v; + ComputeInternalControlValue( dominantIndices[l], useType, v ); + suppressor *= v; + } + suppressor.x += 1.0f; suppressor.y += 1.0f; + + vecValue *= suppressor; + } + + vec2D.Set( op.m_nDeltaStateIndex, vecValue ); + } + } + } +} + +void CDmeCombinationOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + attrs.AddToTail( m_ControlValues[i].GetAttribute() ); + } +} + +void CDmeCombinationOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + int nCount = m_CombinationInfo.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CombinationInfo_t &info = m_CombinationInfo[i]; + for ( int j = 0; j < COMBO_CONTROL_TYPE_COUNT; ++j ) + { + if ( ( info.m_hDestAttribute[j] == DMATTRIBUTE_HANDLE_INVALID ) || !g_pDataModel->IsAttributeHandleValid( info.m_hDestAttribute[j] ) ) + continue; + + CDmAttribute* pAttribute = g_pDataModel->GetAttribute( info.m_hDestAttribute[j] ); + if ( !pAttribute || pAttribute->GetType() != AT_VECTOR2_ARRAY ) + continue; + + attrs.AddToTail( pAttribute ); + } + } +} + + +//----------------------------------------------------------------------------- +// Used by studiomdl to discover the various combination rules +//----------------------------------------------------------------------------- +int CDmeCombinationOperator::GetOperationTargetCount() const +{ + return m_Targets.Count(); +} + +CDmElement *CDmeCombinationOperator::GetOperationTarget( int nTargetIndex ) +{ + return m_Targets[ nTargetIndex ]; +} + +int CDmeCombinationOperator::GetOperationCount( int nTargetIndex ) const +{ + return m_CombinationInfo[ nTargetIndex ].m_Outputs.Count(); +} + +CDmElement *CDmeCombinationOperator::GetOperationDeltaState( int nTargetIndex, int nOpIndex ) +{ + CDmElement *pElement = GetOperationTarget( nTargetIndex ); + const CDmrElementArray<> deltaArray( pElement, "deltaStates" ); + if ( !deltaArray.IsValid() ) + return NULL; + + int nDeltaStateIndex = m_CombinationInfo[ nTargetIndex ].m_Outputs[ nOpIndex ].m_nDeltaStateIndex; + return deltaArray[ nDeltaStateIndex ]; +} + +const CUtlVector< int > &CDmeCombinationOperator::GetOperationControls( int nTargetIndex, int nOpIndex ) const +{ + return m_CombinationInfo[ nTargetIndex ].m_Outputs[ nOpIndex ].m_ControlIndices; +} + +int CDmeCombinationOperator::GetOperationDominatorCount( int nTargetIndex, int nOpIndex ) const +{ + return m_CombinationInfo[ nTargetIndex ].m_Outputs[ nOpIndex ].m_DominatorIndices.Count(); +} + +const CUtlVector< int > &CDmeCombinationOperator::GetOperationDominator( int nTargetIndex, int nOpIndex, int nDominatorIndex ) const +{ + int nIndex = m_CombinationInfo[ nTargetIndex ].m_Outputs[ nOpIndex ].m_DominatorIndices[ nDominatorIndex ]; + return m_DominatorInfo[ nIndex ].m_DominantIndices; +} + +void CDmeCombinationOperator::CopyControls( CDmeCombinationOperator *pSrc ) +{ + // Clean Me Out! + RemoveAllControls(); + + // Copy The Source Controls + for ( int i = 0; i < pSrc->m_InputControls.Count(); ++i ) + { + CDmeCombinationInputControl *pSrcInput = pSrc->m_InputControls[ i ]; + if ( pSrcInput ) + { + CDmeCombinationInputControl *pDstInput = pSrcInput->Copy( ); + pDstInput->SetFileId( GetFileId(), TD_DEEP ); + m_InputControls.AddToTail( pDstInput ); + } + } + + // Copy The Control Values + m_ControlValues[ COMBO_CONTROL_NORMAL ].CopyArray( + pSrc->m_ControlValues[ COMBO_CONTROL_NORMAL ].Base(), + pSrc->m_ControlValues[ COMBO_CONTROL_NORMAL ].Count() ); + + m_ControlValues[ COMBO_CONTROL_LAGGED ].CopyArray( + pSrc->m_ControlValues[ COMBO_CONTROL_LAGGED ].Base(), + pSrc->m_ControlValues[ COMBO_CONTROL_LAGGED ].Count() ); + + RebuildRawControlList(); + + m_Dominators.Purge(); + + // Copy The Source Controls + for ( int i = 0; i < pSrc->m_Dominators.Count(); ++i ) + { + CDmeCombinationDominationRule *pSrcDom = pSrc->m_Dominators[ i ]; + if ( pSrcDom ) + { + CDmeCombinationDominationRule *pDstDom = pSrcDom->Copy( ); + pDstDom->SetFileId( GetFileId(), TD_DEEP ); + m_Dominators.AddToTail( pDstDom ); + } + } + + RebuildRawControlList(); +} + + +//----------------------------------------------------------------------------- +// Generates wrinkle deltas for the uncorrected controls +// NOTE: This is only being used because we have no authoring path for wrinkle data yet +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::GenerateWrinkleDeltas( CDmeShape *pShape, bool bOverwrite ) +{ + CDmeMesh* pMesh = CastElement< CDmeMesh >( pShape ); + CDmeVertexData *pBindState = pMesh ? pMesh->FindBaseState( "bind" ) : NULL; + if ( pBindState ) + { + int nRawControlCount = m_RawControlInfo.Count(); + for ( int i = 0; i < nRawControlCount; ++i ) + { + CDmeVertexDeltaData* pDelta = pMesh->FindDeltaState( m_RawControlInfo[i].m_Name ); + if ( pDelta ) + { + pDelta->GenerateWrinkleDelta( pBindState, m_RawControlInfo[i].m_flWrinkleScale, bOverwrite ); + } + } + } +} + +void CDmeCombinationOperator::GenerateWrinkleDeltas( bool bOverwrite /* = true */ ) +{ + int nTargetCount = m_Targets.Count(); + for ( int i = 0; i < nTargetCount; ++i ) + { + GenerateWrinkleDeltas( CastElement< CDmeShape >( m_Targets[i] ), bOverwrite ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::RemoveAllTargets() +{ + m_Targets.RemoveAll(); + + CleanUpCombinationInfo(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetToDefault() +{ + const int nControlsCount = m_InputControls.Count(); + for ( int i = 0; i < nControlsCount; ++i ) + { + m_IsDefaultValue[ i ] = true; + UpdateDefaultValue( i ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::SetToBase() +{ + const int nControlsCount = m_InputControls.Count(); + for ( int i = 0; i < nControlsCount; ++i ) + { + const float flBaseValue = GetControlBaseValue( i ); + for ( int j = 0; j < COMBO_CONTROL_TYPE_COUNT; ++j ) + { + const Vector &v = m_ControlValues[ j ][ i ]; + m_ControlValues[ j ].Set( i, Vector( flBaseValue, v.y, v.z ) ); + } + } +} + + +//----------------------------------------------------------------------------- +// * Remove all controls which refer to raw controls which refer to delta +// states which do not exist in any target +// * Remove all dominators which refer to states which no longer exist +//----------------------------------------------------------------------------- +void CDmeCombinationOperator::Purge() +{ + bool bDelete = true; + + const int nTargetCount = m_Targets.Count(); + for ( int i = 0; i < nTargetCount; ++i ) + { + CDmElement *pSource = m_Targets[ i ]; + if ( !pSource ) + continue; + + CDmrElementArray<> deltas( pSource, "deltaStates" ); + if ( !deltas.IsValid() ) + continue; + + const int nDeltaCount = deltas.Count(); + + do + { + bDelete = false; + + const int nControlCount = GetControlCount(); + for ( int j = 0; j < nControlCount; ++j ) + { + bDelete = true; + const int nRawControlCount = GetRawControlCount( j ); + for ( int k = 0; bDelete && k < nRawControlCount; ++k ) + { + for ( int l = 0; l < nDeltaCount; ++l ) + { + if ( !Q_strcmp( GetRawControlName( j, k ), deltas[ l ]->GetName() ) ) + { + bDelete = false; + break; + } + } + } + + if ( bDelete ) + { + RemoveControl( GetControlName( j ) ); + break; + } + } + } while ( bDelete ); + } + + do + { + bDelete = false; + + const int nDomCount = DominationRuleCount(); + for ( int i = 0; i < nDomCount; ++i ) + { + bDelete = true; + CDmeCombinationDominationRule *pDom = GetDominationRule( i ); + + const int nDCount = pDom->DominatorCount(); + for ( int j = 0; j < nDCount; ++j ) + { + if ( HasRawControl( pDom->GetDominator( j ) ) ) + { + bDelete = false; + break; + } + } + + if ( !bDelete ) + { + const int nSCount = pDom->SuppressedCount(); + for ( int j = 0; j < nSCount; ++j ) + { + if ( HasRawControl( pDom->GetSuppressed( j ) ) ) + { + bDelete = false; + break; + } + } + } + + if ( bDelete ) + { + RemoveDominationRule( i ); + break; + } + } + } while ( bDelete ); +} + + +//----------------------------------------------------------------------------- +// Creates lagged log data from an input log +//----------------------------------------------------------------------------- +static void CreateLaggedLog( CDmeVector2Log *pLog, CDmeVector2Log *pLaggedLog, int nSamplesPerSec ) +{ +} + + +//----------------------------------------------------------------------------- +// Helper method to create a lagged version of channel data from source data +//----------------------------------------------------------------------------- +void CreateLaggedVertexAnimation( CDmeChannelsClip *pClip, int nSamplesPerSec ) +{ + if ( !pClip ) + return; + + int nChannelCount = pClip->m_Channels.Count(); + for ( int i = 0; i < nChannelCount; ++i ) + { + CDmeChannel *pChannel = pClip->m_Channels[i]; + CDmElement *pDest = pChannel->GetToElement(); + CDmAttribute *pDestAttr = pChannel->GetToAttribute(); + int nArrayIndex = pChannel->GetToArrayIndex(); + + if ( pChannel->GetLog()->GetDataType() != AT_VECTOR2 ) + continue; + + if ( Q_stricmp( pDestAttr->GetName(), "controlValues" ) ) + continue; + + if ( !pDest->GetValue<bool>( "usesLaggedValues" ) ) + continue; + + CDmAttribute *pLaggedAttr = pDest->GetAttribute( "controlValuesLagged" ); + if ( !pLaggedAttr || pLaggedAttr->GetType() != AT_VECTOR2_ARRAY ) + continue; + + int nLen = Q_strlen( pChannel->GetName() ); + char *pNewChannelName = (char*)_alloca( nLen + 10 ); + memcpy( pNewChannelName, pChannel->GetName(), nLen+1 ); + Q_strncpy( &pNewChannelName[nLen], "_lagged", 10 ); + CDmeChannel *pNewChannel = CreateElement< CDmeChannel >( pNewChannelName, pClip->GetFileId() ); + pNewChannel->SetOutput( pLaggedAttr, nArrayIndex ); + CDmeVector2Log *pNewLog = pNewChannel->CreateLog< Vector2D >( ); + pClip->m_Channels.AddToTail( pNewChannel ); + + CreateLaggedLog( static_cast<CDmeVector2Log*>( pChannel->GetLog() ), pNewLog, nSamplesPerSec ); + } +} + + +//----------------------------------------------------------------------------- +// +// A class used to edit combination operators in Maya.. doesn't connect to targets +// +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMayaCombinationOperator, CDmeMayaCombinationOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMayaCombinationOperator::OnConstruction() +{ + m_DeltaStates.Init( this, "deltaStates" ); + m_DeltaStateWeights[COMBO_CONTROL_NORMAL].Init( this, "deltaStateWeights" ); + m_DeltaStateWeights[COMBO_CONTROL_LAGGED].Init( this, "deltaStateWeightsLagged" ); + AddTarget( this ); +} + +void CDmeMayaCombinationOperator::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Add, remove delta states +//----------------------------------------------------------------------------- +void CDmeMayaCombinationOperator::AddDeltaState( const char *pDeltaStateName ) +{ + m_DeltaStates.AddToTail( CreateElement<CDmElement>( pDeltaStateName ) ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + m_DeltaStateWeights[i].AddToTail( Vector2D( 1.0f, 1.0f ) ); + } + ComputeCombinationInfo( 0 ); +} + +void CDmeMayaCombinationOperator::RemoveDeltaState( const char *pDeltaStateName ) +{ + int nIndex = FindDeltaState( pDeltaStateName ); + if ( nIndex >= 0 ) + { + m_DeltaStates.Remove( nIndex ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + m_DeltaStateWeights[i].Remove( nIndex ); + } + } + ComputeCombinationInfo( 0 ); +} + +void CDmeMayaCombinationOperator::RemoveAllDeltaStates() +{ + m_DeltaStates.RemoveAll( ); + for ( int i = 0; i < COMBO_CONTROL_TYPE_COUNT; ++i ) + { + m_DeltaStateWeights[i].RemoveAll(); + } + ComputeCombinationInfo( 0 ); +} + +int CDmeMayaCombinationOperator::FindDeltaState( const char *pDeltaStateName ) +{ + int nCount = m_DeltaStates.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pDeltaStateName, m_DeltaStates[i]->GetName() ) ) + return i; + } + return -1; +} + +int CDmeMayaCombinationOperator::DeltaStateCount() const +{ + return m_DeltaStates.Count(); +} + +const char *CDmeMayaCombinationOperator::GetDeltaState( int nIndex ) const +{ + return m_DeltaStates[nIndex]->GetName(); +} + +const Vector2D& CDmeMayaCombinationOperator::GetDeltaStateWeight( int nIndex, CombinationControlType_t type ) const +{ + return m_DeltaStateWeights[type][nIndex]; +} diff --git a/movieobjects/dmedag.cpp b/movieobjects/dmedag.cpp new file mode 100644 index 0000000..79eb21d --- /dev/null +++ b/movieobjects/dmedag.cpp @@ -0,0 +1,438 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmedag.h" +#include "movieobjects/dmeshape.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmetransform.h" +#include "movieobjects_interfaces.h" +#include "movieobjects/dmedrawsettings.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeDag, CDmeDag ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CUtlStack<CDmeDag::TransformInfo_t> CDmeDag::s_TransformStack; +bool CDmeDag::s_bDrawUsingEngineCoordinates = false; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeDag::OnConstruction() +{ + m_Transform.InitAndCreate( this, "transform", GetName() ); + m_Shape.Init( this, "shape" ); + m_Visible.InitAndSet( this, "visible", true, FATTRIB_HAS_CALLBACK ); + m_Children.Init( this, "children" ); +} + +void CDmeDag::OnDestruction() +{ + g_pDataModel->DestroyElement( m_Transform.Get() ); +} + + +//----------------------------------------------------------------------------- +// Accessors +//----------------------------------------------------------------------------- +CDmeTransform *CDmeDag::GetTransform() +{ + return m_Transform.GetElement(); +} + +CDmeShape *CDmeDag::GetShape() +{ + return m_Shape.GetElement(); +} + +void CDmeDag::SetShape( CDmeShape *pShape ) +{ + m_Shape = pShape; +} + + +bool CDmeDag::IsVisible() const +{ + return m_Visible; +} + +void CDmeDag::SetVisible( bool bVisible ) +{ + m_Visible = bVisible; +} + + +//----------------------------------------------------------------------------- +// Returns the visibility attribute for DmeRenderable support +//----------------------------------------------------------------------------- +CDmAttribute *CDmeDag::GetVisibilityAttribute() +{ + return m_Visible.GetAttribute(); +} + + +//----------------------------------------------------------------------------- +// child helpers +//----------------------------------------------------------------------------- +const CUtlVector< DmElementHandle_t > &CDmeDag::GetChildren() const +{ + return m_Children.Get(); +} + +int CDmeDag::GetChildCount() const +{ + return m_Children.Count(); +} + +CDmeDag *CDmeDag::GetChild( int i ) const +{ + if ( i < 0 || i >= m_Children.Count() ) + return NULL; + + return m_Children.Get( i ); +} + +void CDmeDag::AddChild( CDmeDag* pDag ) +{ + m_Children.AddToTail( pDag ); +} + +void CDmeDag::RemoveChild( int i ) +{ + m_Children.FastRemove( i ); +} + +void CDmeDag::RemoveChild( const CDmeDag *pChild, bool bRecurse ) +{ + int i = FindChild( pChild ); + if ( i >= 0 ) + { + RemoveChild( i ); + } +} + +int CDmeDag::FindChild( const CDmeDag *pChild ) const +{ + return m_Children.Find( pChild->GetHandle() ); +} + +// recursive +int CDmeDag::FindChild( CDmeDag *&pParent, const CDmeDag *pChild ) +{ + int index = FindChild( pChild ); + if ( index >= 0 ) + { + pParent = this; + return index; + } + + int nChildren = m_Children.Count(); + for ( int ci = 0; ci < nChildren; ++ci ) + { + index = m_Children[ ci ]->FindChild( pParent, pChild ); + if ( index >= 0 ) + return index; + } + + pParent = NULL; + return -1; +} + +int CDmeDag::FindChild( const char *name ) const +{ + int nChildren = m_Children.Count(); + for ( int ci = 0; ci < nChildren; ++ci ) + { + if ( V_strcmp( m_Children[ ci ]->GetName(), name ) == 0 ) + return ci; + } + return -1; +} + +CDmeDag *CDmeDag::FindOrAddChild( const char *name ) +{ + int i = FindChild( name ); + if ( i >= 0 ) + return GetChild( i ); + + CDmeDag *pChild = CreateElement< CDmeDag >( name, GetFileId() ); + AddChild( pChild ); + return pChild; +} + + +//----------------------------------------------------------------------------- +// Recursively render the Dag hierarchy +//----------------------------------------------------------------------------- +void CDmeDag::PushDagTransform() +{ + int i = s_TransformStack.Push(); + TransformInfo_t &info = s_TransformStack[i]; + info.m_pTransform = GetTransform(); + info.m_bComputedDagToWorld = false; +} + +void CDmeDag::PopDagTransform() +{ + Assert( s_TransformStack.Top().m_pTransform == GetTransform() ); + s_TransformStack.Pop(); +} + + +//----------------------------------------------------------------------------- +// Transform from DME to engine coordinates +//----------------------------------------------------------------------------- +void CDmeDag::DmeToEngineMatrix( matrix3x4_t& dmeToEngine ) +{ + VMatrix rotation, rotationZ; + MatrixBuildRotationAboutAxis( rotation, Vector( 1, 0, 0 ), 90 ); + MatrixBuildRotationAboutAxis( rotationZ, Vector( 0, 1, 0 ), 90 ); + ConcatTransforms( rotation.As3x4(), rotationZ.As3x4(), dmeToEngine ); +} + +//----------------------------------------------------------------------------- +// Transform from engine to DME coordinates +//----------------------------------------------------------------------------- +void CDmeDag::EngineToDmeMatrix( matrix3x4_t& engineToDme ) +{ + VMatrix rotation, rotationZ; + MatrixBuildRotationAboutAxis( rotation, Vector( 1, 0, 0 ), -90 ); + MatrixBuildRotationAboutAxis( rotationZ, Vector( 0, 1, 0 ), -90 ); + ConcatTransforms( rotationZ.As3x4(), rotation.As3x4(), engineToDme ); +} + + +void CDmeDag::GetShapeToWorldTransform( matrix3x4_t &mat ) +{ + int nCount = s_TransformStack.Count(); + if ( nCount == 0 ) + { + if ( !s_bDrawUsingEngineCoordinates ) + { + SetIdentityMatrix( mat ); + } + else + { + DmeToEngineMatrix( mat ); + } + return; + } + + if ( s_TransformStack.Top().m_bComputedDagToWorld ) + { + MatrixCopy( s_TransformStack.Top().m_DagToWorld, mat ); + return; + } + + // Compute all uncomputed dag to worls + int i; + for ( i = 0; i < nCount; ++i ) + { + TransformInfo_t &info = s_TransformStack[i]; + if ( !info.m_bComputedDagToWorld ) + break; + } + + // Set up the initial transform + if ( i == 0 ) + { + if ( !s_bDrawUsingEngineCoordinates ) + { + SetIdentityMatrix( mat ); + } + else + { + DmeToEngineMatrix( mat ); + } + } + else + { + MatrixCopy( s_TransformStack[i-1].m_DagToWorld, mat ); + } + + // Compute all transforms + for ( ; i < nCount; ++i ) + { + matrix3x4_t localToParent; + TransformInfo_t &info = s_TransformStack[i]; + info.m_pTransform->GetTransform( localToParent ); + ConcatTransforms( mat, localToParent, info.m_DagToWorld ); + info.m_bComputedDagToWorld = true; + MatrixCopy( info.m_DagToWorld, mat ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDag::GetLocalMatrix( matrix3x4_t &m ) +{ + CDmeTransform *pTransform = GetTransform(); + if ( pTransform ) + { + pTransform->GetTransform( m ); + } + else + { + SetIdentityMatrix( m ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDag::GetWorldMatrix( matrix3x4_t &m ) +{ + GetLocalMatrix( m ); + const static UtlSymId_t symChildren = g_pDataModel->GetSymbol( "children" ); + CDmeDag *pParent = FindReferringElement< CDmeDag >( this, symChildren ); + if ( pParent ) + { + matrix3x4_t localMatrix; + GetLocalMatrix( localMatrix ); + + matrix3x4_t parentWorldMatrix; + pParent->GetWorldMatrix( parentWorldMatrix ); + ConcatTransforms( parentWorldMatrix, localMatrix, m ); + } + else + { + GetLocalMatrix( m ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDag::GetParentWorldMatrix( matrix3x4_t &m ) +{ + const static UtlSymId_t symChildren = g_pDataModel->GetSymbol( "children" ); + CDmeDag *pParent = FindReferringElement< CDmeDag >( this, symChildren ); + if ( pParent ) + { + pParent->GetWorldMatrix( m ); + } + else + { + SetIdentityMatrix( m ); + } +} + + +//----------------------------------------------------------------------------- +// Recursively render the Dag hierarchy +//----------------------------------------------------------------------------- +void CDmeDag::DrawUsingEngineCoordinates( bool bEnable ) +{ + s_bDrawUsingEngineCoordinates = bEnable; +} + + +//----------------------------------------------------------------------------- +// Recursively render the Dag hierarchy +//----------------------------------------------------------------------------- +void CDmeDag::Draw( CDmeDrawSettings *pDrawSettings ) +{ + if ( !m_Visible ) + return; + + PushDagTransform(); + + CDmeShape *pShape = GetShape(); + if ( pShape ) + { + matrix3x4_t shapeToWorld; + GetShapeToWorldTransform( shapeToWorld ); + pShape->Draw( shapeToWorld, pDrawSettings ); + } + + uint cn = m_Children.Count(); + for ( uint ci = 0; ci < cn; ++ci ) + { + m_Children[ ci ]->Draw( pDrawSettings ); + } + + PopDagTransform(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDag::GetBoundingSphere( Vector &c0, float &r0, const matrix3x4_t &pMat ) const +{ + matrix3x4_t lMat; + m_Transform.GetElement()->GetTransform( lMat ); + matrix3x4_t wMat; + ConcatTransforms( pMat, lMat, wMat ); + + c0.Zero(); + r0 = 0.0f; + + const CDmeShape *pShape = m_Shape.GetElement(); + if ( pShape ) + { + pShape->GetBoundingSphere( c0, r0 ); + } + + // No scale in Dme! :) + Vector vTemp; + VectorTransform( c0, pMat, vTemp ); + + const int nChildren = m_Children.Count(); + if ( nChildren > 0 ) + { + Vector c1; // Child center + float r1; // Child radius + + Vector v01; // c1 - c0 + float l01; // |v01| + + for ( int i = 0; i < nChildren; ++i ) + { + m_Children[ i ]->GetBoundingSphere( c1, r1, wMat ); + + if ( r0 == 0.0f ) + { + c0 = c1; + r0 = r1; + continue; + } + + v01 = c1 - c0; + l01 = v01.NormalizeInPlace(); + + if ( r0 < l01 + r1 ) + { + // Current sphere doesn't contain both spheres + if ( r1 < l01 + r0 ) + { + // Child sphere doesn't contain both spheres + c0 = c0 + 0.5f * ( r1 + l01 - r0 ) * v01; + r0 = 0.5f * ( r0 + l01 + r1 ); + } + else + { + // Child sphere contains both spheres + c0 = c1; + r0 = r1; + } + } + } + } +}
\ No newline at end of file diff --git a/movieobjects/dmedccmakefile.cpp b/movieobjects/dmedccmakefile.cpp new file mode 100644 index 0000000..458fd33 --- /dev/null +++ b/movieobjects/dmedccmakefile.cpp @@ -0,0 +1,323 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Describes an asset: something that is compiled from sources, +// in potentially multiple steps, to a compiled resource +// +//============================================================================= + + +#include "movieobjects/dmedccmakefile.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "tier2/fileutils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSourceDCCFile, CDmeSourceDCCFile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSourceDCCFile::OnConstruction() +{ + m_RootDCCObjects.Init( this, "rootDCCObjects" ); + m_ExportType.InitAndSet( this, "exportType", 0 ); + m_FrameStart.InitAndSet( this, "frameStart", 0.0f ); + m_FrameEnd.InitAndSet( this, "frameEnd", 0.0f ); + m_FrameIncrement.InitAndSet( this, "frameIncrement", 1.0f ); +} + +void CDmeSourceDCCFile::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSourceMayaFile, CDmeSourceMayaFile ); +IMPLEMENT_ELEMENT_FACTORY( DmeSourceMayaModelFile, CDmeSourceMayaModelFile ); +IMPLEMENT_ELEMENT_FACTORY( DmeSourceMayaAnimationFile, CDmeSourceMayaAnimationFile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSourceMayaFile::OnConstruction() +{ +} + +void CDmeSourceMayaFile::OnDestruction() +{ +} + +void CDmeSourceMayaModelFile::OnConstruction() +{ + m_ExportType = 0; +} + +void CDmeSourceMayaModelFile::OnDestruction() +{ +} + +void CDmeSourceMayaAnimationFile::OnConstruction() +{ + m_ExportType = 1; +} + +void CDmeSourceMayaAnimationFile::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSourceXSIFile, CDmeSourceXSIFile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSourceXSIFile::OnConstruction() +{ +} + +void CDmeSourceXSIFile::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeDCCMakefile, CDmeDCCMakefile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeDCCMakefile::OnConstruction() +{ + m_bFlushFile = false; +} + +void CDmeDCCMakefile::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Compile assets +//----------------------------------------------------------------------------- +void CDmeDCCMakefile::GetOutputs( CUtlVector<CUtlString> &fullPaths ) +{ + fullPaths.RemoveAll(); + + char pOutputName[MAX_PATH]; + Q_FileBase( GetFileName(), pOutputName, sizeof(pOutputName) ); + if ( !pOutputName[0] ) + return; + + // FIXME: We need to come up with an appropriate directory structure for export + char pOutputDir[MAX_PATH]; + GetMakefilePath( pOutputDir, sizeof(pOutputDir) ); + if ( !pOutputDir[0] ) + return; + + Q_StripTrailingSlash( pOutputDir ); + char pFullPath[MAX_PATH]; + Q_snprintf( pFullPath, sizeof(pFullPath), "%s\\%s.dmx", pOutputDir, pOutputName ); + fullPaths.AddToTail( pFullPath ); +} + + +//----------------------------------------------------------------------------- +// Creates, destroys the output element associated with this makefile +//----------------------------------------------------------------------------- +CDmElement *CDmeDCCMakefile::CreateOutputElement( ) +{ + if ( m_bFlushFile ) + { + m_bFlushFile = false; + if ( GetFileId() != DMFILEID_INVALID ) + { + // NOTE: CDmeHandles will correctly re-hook up to the new makefile after load + // If the file fails to load, we have the copy. If the file correctly has the make in it + // it will replace this copy I made + CDmeHandle< CDmeDCCMakefile > hMakefileOld; + hMakefileOld = this; + + // NOTE NOTE NOTE + // UnloadFile essentially calls delete this! + // So don't refer to any state in this DmElement after that + DmFileId_t fileId = GetFileId(); + g_pDataModel->UnloadFile( fileId ); + + CDmElement *pRoot = NULL; + if ( g_pDataModel->RestoreFromFile( g_pDataModel->GetFileName( fileId ), NULL, NULL, &pRoot, CR_DELETE_OLD ) != DMFILEID_INVALID ) + { + // NOTE: Unload/restore kills the this pointer, we need to redo this + if ( hMakefileOld.Get() ) + { + hMakefileOld->SetDirty( false ); + return hMakefileOld->CreateOutputElement(); + } + } + + // NOTE: We expect file backup prior to compile to avoid really fatal errors + // This case happens if the file failed to load. In this case, we must use + // the copy of the makefile + Assert( 0 ); + return NULL; + } + } + + // The output element is the root element containing the makefile + return FindReferringElement< CDmElement >( this, "makefile" ); +} + +void CDmeDCCMakefile::DestroyOutputElement( CDmElement *pOutput ) +{ + m_bFlushFile = true; +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMayaMakefile, CDmeMayaMakefile ); +IMPLEMENT_ELEMENT_FACTORY( DmeMayaModelMakefile, CDmeMayaModelMakefile ); +IMPLEMENT_ELEMENT_FACTORY( DmeMayaAnimationMakefile, CDmeMayaAnimationMakefile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMayaMakefile::OnConstruction() +{ +} + +void CDmeMayaMakefile::OnDestruction() +{ +} + +void CDmeMayaModelMakefile::OnConstruction() +{ +} + +void CDmeMayaModelMakefile::OnDestruction() +{ +} + +void CDmeMayaAnimationMakefile::OnConstruction() +{ +} + +void CDmeMayaAnimationMakefile::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Returns source types +//----------------------------------------------------------------------------- +static DmeMakefileType_t s_pMayaModelSourceTypes[] = +{ + { "DmeSourceMayaModelFile", "Maya Model File", true, "makefiledir:../maya", "*.ma;*.mb", "Maya File (*.ma,*.mb)" }, + { NULL, NULL, false, NULL, NULL, NULL }, +}; + +DmeMakefileType_t* CDmeMayaModelMakefile::GetSourceTypes() +{ + return s_pMayaModelSourceTypes; +} + +static DmeMakefileType_t s_pMayaAnimationSourceTypes[] = +{ + { "DmeSourceMayaAnimationFile", "Maya Animation File", true, "makefiledir:../maya", "*.ma;*.mb", "Maya File (*.ma,*.mb)" }, + { NULL, NULL, false, NULL, NULL, NULL }, +}; + +DmeMakefileType_t* CDmeMayaAnimationMakefile::GetSourceTypes() +{ + return s_pMayaAnimationSourceTypes; +} + + +//----------------------------------------------------------------------------- +// Makefile type +//----------------------------------------------------------------------------- +static DmeMakefileType_t s_MayaModelMakefileType = +{ + "DmeMayaModelMakefile", "Maya Model Component", true, "contentdir:models", "*.dmx", "DMX File (*.dmx)" +}; + +DmeMakefileType_t *CDmeMayaModelMakefile::GetMakefileType() +{ + return &s_MayaModelMakefileType; +} + +static DmeMakefileType_t s_MayaAnimationMakefileType = +{ + "DmeMayaAnimationMakefile", "Maya Animation Component", true, "contentdir:models", "*.dmx", "DMX File (*.dmx)" +}; + +DmeMakefileType_t *CDmeMayaAnimationMakefile::GetMakefileType() +{ + return &s_MayaAnimationMakefileType; +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeXSIMakefile, CDmeXSIMakefile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeXSIMakefile::OnConstruction() +{ +} + +void CDmeXSIMakefile::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Returns source types +//----------------------------------------------------------------------------- +static DmeMakefileType_t s_pXSISourceTypes[] = +{ + { "DmeSourceXSIFile", "XSI File", true, "makefiledir:../xsi", "*.xsi", "XSI File (*.xsi)" }, + { NULL, NULL, false, NULL, NULL, NULL }, +}; + +DmeMakefileType_t* CDmeXSIMakefile::GetSourceTypes() +{ + return s_pXSISourceTypes; +} + + +//----------------------------------------------------------------------------- +// Makefile type +//----------------------------------------------------------------------------- +static DmeMakefileType_t s_XSIMakefileType = +{ + "DmeXSIMakefile", "XSI Model Component", true, "contentdir:models", "*.dmx", "DMX File (*.dmx)", +}; + +DmeMakefileType_t *CDmeXSIMakefile::GetMakefileType() +{ + return &s_XSIMakefileType; +} + + diff --git a/movieobjects/dmedrawsettings.cpp b/movieobjects/dmedrawsettings.cpp new file mode 100644 index 0000000..59b25f4 --- /dev/null +++ b/movieobjects/dmedrawsettings.cpp @@ -0,0 +1,265 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + + +#include "datamodel/dmelementfactoryhelper.h" +#include "tier1/KeyValues.h" +#include "tier1/utlrbtree.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmedrawsettings.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeDrawSettings, CDmeDrawSettings ); + + +//----------------------------------------------------------------------------- +// Statics +//----------------------------------------------------------------------------- +bool CDmeDrawSettings::s_bWireframeMaterialInitialized( false ); +CMaterialReference CDmeDrawSettings::s_WireframeMaterial; +bool CDmeDrawSettings::s_bWireframeOnShadedMaterialInitialized( false ); +CMaterialReference CDmeDrawSettings::s_WireframeOnShadedMaterial; +bool CDmeDrawSettings::s_bFlatGrayMaterial( false ); +CMaterialReference CDmeDrawSettings::s_FlatGrayMaterial; +bool CDmeDrawSettings::s_bUnlitGrayMaterial( false ); +CMaterialReference CDmeDrawSettings::s_UnlitGrayMaterial; + +CUtlRBTree< CUtlSymbol > CDmeDrawSettings::s_KnownDrawableTypes; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDrawSettings::OnConstruction() +{ + if ( s_KnownDrawableTypes.Count() == 0 ) + { + BuildKnownDrawableTypes(); + } + + SetDefLessFunc< CUtlRBTree< CUtlSymbol > >( m_NotDrawable ); + m_NotDrawable.RemoveAll(); + + m_DrawType.InitAndSet( this, "drawType", static_cast< int >( DRAW_SMOOTH ) ); + m_bBackfaceCulling.InitAndSet( this, "backfaceCulling", true ); + m_bWireframeOnShaded.InitAndSet( this, "wireframeOnShaded", false ); + m_bXRay.InitAndSet( this, "xray", false ); + m_bGrayShade.InitAndSet( this, "grayShade", false ); + m_bNormals.InitAndSet( this, "normals", false ); + m_NormalLength.InitAndSet( this, "normalLength", 1.0 ); + m_Color.InitAndSet( this, "color", Color( 0, 0, 0, 1 ) ); + m_bDeltaHighlight.InitAndSet( this, "highlightDeltas", false ); + m_flHighlightSize.InitAndSet( this, "highlightSize", 1.5f ); + m_cHighlightColor.InitAndSet( this, "highlightColor", Color( 0xff, 0x14, 0x93, 0xff ) ); // Deep Pink + + m_IsAMaterialBound = false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDrawSettings::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDrawSettings::Resolve() +{ +} + + +//----------------------------------------------------------------------------- +// Wireframe +//----------------------------------------------------------------------------- +void CDmeDrawSettings::BindWireframe() +{ + if ( !s_bWireframeMaterialInitialized ) + { + s_bWireframeMaterialInitialized = true; + + KeyValues *pKeyValues = new KeyValues( "wireframe" ); + pKeyValues->SetInt( "$decal", 0 ); + pKeyValues->SetInt( "$vertexcolor", 1 ); + pKeyValues->SetInt( "$ignorez", 1 ); + s_WireframeMaterial.Init( "__DmeWireframe", pKeyValues ); + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( s_WireframeMaterial ); + m_IsAMaterialBound = true; +} + + +//----------------------------------------------------------------------------- +// Wireframe as a decal +//----------------------------------------------------------------------------- +void CDmeDrawSettings::BindWireframeOnShaded() +{ + if ( !s_bWireframeOnShadedMaterialInitialized ) + { + s_bWireframeOnShadedMaterialInitialized = true; + + KeyValues *pKeyValues = new KeyValues( "wireframe" ); + pKeyValues->SetInt( "$decal", 0 ); + pKeyValues->SetInt( "$vertexcolor", 1 ); + pKeyValues->SetInt( "$ignorez", 0 ); + s_WireframeOnShadedMaterial.Init( "__DmeWireframeOnShaded", pKeyValues ); + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( s_WireframeOnShadedMaterial ); + m_IsAMaterialBound = true; +} + + +//----------------------------------------------------------------------------- +// Flat Gray Shaded +//----------------------------------------------------------------------------- +void CDmeDrawSettings::BindGray() +{ + if ( !s_bFlatGrayMaterial ) + { + s_bFlatGrayMaterial = true; + + KeyValues *pKeyValues = new KeyValues( "VertexLitGeneric" ); + pKeyValues->SetInt( "$model", 1 ); + s_FlatGrayMaterial.Init( "__DmeFlatGray", pKeyValues ); + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( s_FlatGrayMaterial ); + m_IsAMaterialBound = true; +} + + +//----------------------------------------------------------------------------- +// Flat Gray Shaded +//----------------------------------------------------------------------------- +void CDmeDrawSettings::BindUnlitGray() +{ + if ( !s_bUnlitGrayMaterial ) + { + s_bUnlitGrayMaterial = true; + + KeyValues *pKeyValues = new KeyValues( "UnlitGeneric" ); + pKeyValues->SetInt( "$model", 1 ); + pKeyValues->SetInt( "$vertexcolor", 1 ); + s_UnlitGrayMaterial.Init( "__DmeUnlitGray", pKeyValues ); + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( s_UnlitGrayMaterial ); + m_IsAMaterialBound = true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeDrawSettings::Drawable( CDmElement *pElement ) +{ + if ( m_NotDrawable.IsValidIndex( m_NotDrawable.Find( pElement->GetType() ) ) ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDrawSettings::BuildKnownDrawableTypes() +{ + SetDefLessFunc< CUtlRBTree< CUtlSymbol > >( s_KnownDrawableTypes ); + + s_KnownDrawableTypes.RemoveAll(); + + s_KnownDrawableTypes.InsertIfNotFound( g_pDataModel->GetSymbol( "DmeMesh" ) ); + s_KnownDrawableTypes.InsertIfNotFound( g_pDataModel->GetSymbol( "DmeJoint" ) ); + s_KnownDrawableTypes.InsertIfNotFound( g_pDataModel->GetSymbol( "DmeModel" ) ); + s_KnownDrawableTypes.InsertIfNotFound( g_pDataModel->GetSymbol( "DmeAttachment" ) ); + + m_NotDrawable.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeDrawSettings::DrawDag( CDmeDag *pDag ) +{ + if ( !pDag ) + return; + + m_vHighlightPoints.RemoveAll(); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + m_IsAMaterialBound = false; + + if ( GetDeltaHighlight() ) + { + BindUnlitGray(); + } + else + { + if ( !Shaded() ) + { + BindWireframe(); + } + else if ( GetGrayShade() ) + { + BindGray(); + } + } + + pDag->Draw( this ); + + m_IsAMaterialBound = false; + + if ( GetDeltaHighlight() || ( GetWireframeOnShaded() && Shaded() ) ) + { + VMatrix m; + + pRenderContext->GetMatrix( MATERIAL_PROJECTION, &m ); + + /* Extract the near and far clipping plane values from projection matrix + float c = m[ 2 ][ 2 ]; + float d = m[ 2 ][ 3 ]; + + const float near = d / ( c - 1.0f ); + const float far = d / ( c + 1.0f ); + */ + + const float zBias = 0.00025; + m[ 2 ][ 2 ] += zBias; + m[ 2 ][ 3 ] += zBias; + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadMatrix( m ); + + BindWireframeOnShaded(); + PushDrawType(); + SetDrawType( CDmeDrawSettings::DRAW_WIREFRAME ); + pDag->Draw( this ); + PopDrawType(); + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); + } +}
\ No newline at end of file diff --git a/movieobjects/dmeeditortypedictionary.cpp b/movieobjects/dmeeditortypedictionary.cpp new file mode 100644 index 0000000..f4efc00 --- /dev/null +++ b/movieobjects/dmeeditortypedictionary.cpp @@ -0,0 +1,328 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Contains a bunch of information about editor types +// Editor types are arbitrary +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "movieobjects/dmeeditortypedictionary.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose DmeEditorAttributeInfo to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeEditorAttributeInfo, CDmeEditorAttributeInfo ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeEditorAttributeInfo::OnConstruction() +{ + m_Widget.Init( this, "widget" ); + m_bIsVisible.Init( this, "isVisible" ); + m_bIsReadOnly.Init( this, "isReadOnly" ); + m_ArrayEntries.Init( this, "arrayEntries" ); + m_bHideType.Init( this, "hideType" ); + m_bHideValue.Init( this, "hideValue" ); + m_Help.Init( this, "help" ); + + m_bIsVisible = true; + m_bIsReadOnly = false; +} + +void CDmeEditorAttributeInfo::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Returns the attribute name this info is associated with +//----------------------------------------------------------------------------- +const char *CDmeEditorAttributeInfo::GetAttributeName() const +{ + return GetName(); +} + + +//----------------------------------------------------------------------------- +// Returns the widget name +//----------------------------------------------------------------------------- +const char *CDmeEditorAttributeInfo::GetWidgetName() const +{ + return m_Widget.Get(); +} + + +//----------------------------------------------------------------------------- +// Returns the info for a entry in an attribute array, if this attribute is an array type +//----------------------------------------------------------------------------- +CDmeEditorAttributeInfo *CDmeEditorAttributeInfo::GetArrayInfo() +{ + return m_ArrayEntries; +} + + +//----------------------------------------------------------------------------- +// Sets the info for an entry in an attribute array +//----------------------------------------------------------------------------- +void CDmeEditorAttributeInfo::SetArrayInfo( CDmeEditorAttributeInfo *pInfo ) +{ + m_ArrayEntries = pInfo; +} + + +//----------------------------------------------------------------------------- +// +// CDmeEditorChoicesInfo, Base class for configuration for choices +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Expose DmeEditorChoicesInfo to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeEditorChoicesInfo, CDmeEditorChoicesInfo ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeEditorChoicesInfo::OnConstruction() +{ + m_Choices.Init( this, "choices" ); + m_ChoiceType.Init( this, "choicetype" ); +} + +void CDmeEditorChoicesInfo::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets/gets choice type +//----------------------------------------------------------------------------- +void CDmeEditorChoicesInfo::SetChoiceType( const char *pChoiceType ) +{ + m_ChoiceType = pChoiceType; +} + +const char *CDmeEditorChoicesInfo::GetChoiceType() const +{ + return m_ChoiceType; +} + +bool CDmeEditorChoicesInfo::HasChoiceType() const +{ + return m_ChoiceType.Length() > 0; +} + + +//----------------------------------------------------------------------------- +// Gets the choices +//----------------------------------------------------------------------------- +int CDmeEditorChoicesInfo::GetChoiceCount() const +{ + return m_Choices.Count(); +} + +CDmElement *CDmeEditorChoicesInfo::CreateChoice( const char *pChoiceString ) +{ + CDmElement *pChoice = CreateElement< CDmElement >( "", GetFileId() ); + m_Choices.AddToTail( pChoice ); + pChoice->SetValue<CUtlString>( "string", pChoiceString ); + return pChoice; +} + +const char *CDmeEditorChoicesInfo::GetChoiceString( int nIndex ) const +{ + Assert( ( nIndex < GetChoiceCount() ) && ( nIndex >= 0 ) ); + CDmElement *pChoice = m_Choices[nIndex]; + if ( !pChoice ) + return ""; + + return pChoice->GetValueString( "string" ); +} + + +//----------------------------------------------------------------------------- +// Expose DmeEditorType class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeEditorType, CDmeEditorType ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeEditorType::OnConstruction() +{ +} + +void CDmeEditorType::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Computes the actual attribute name stored in the type +//----------------------------------------------------------------------------- +const char *CDmeEditorType::GetActualAttributeName( const char *pAttributeName ) +{ + // Fixup the names of the attribute info for the 3 standard fields (name, type, id) + if ( !V_stricmp( "name", pAttributeName ) ) + return "__name"; + + if ( !V_stricmp( "id", pAttributeName ) ) + return "__id"; + + if ( !V_stricmp( "type", pAttributeName ) ) + return "__type"; + + return pAttributeName; +} + + +//----------------------------------------------------------------------------- +// Returns the editor info associated with an editor type +//----------------------------------------------------------------------------- +void CDmeEditorType::AddAttributeInfo( const char *pAttributeName, CDmeEditorAttributeInfo *pInfo ) +{ + pAttributeName = GetActualAttributeName( pAttributeName ); + SetValue( pAttributeName, pInfo ); +} + + +//----------------------------------------------------------------------------- +// Removes a editor type associated with a particular attribute +//----------------------------------------------------------------------------- +void CDmeEditorType::RemoveAttributeInfo( const char *pAttributeName ) +{ + pAttributeName = GetActualAttributeName( pAttributeName ); + if ( HasAttribute( pAttributeName ) ) + { + RemoveAttribute( pAttributeName ); + } +} + + +//----------------------------------------------------------------------------- +// Returns the editor info associated with an editor type +//----------------------------------------------------------------------------- +CDmeEditorAttributeInfo *CDmeEditorType::GetAttributeInfo( const char *pAttributeName ) +{ + pAttributeName = GetActualAttributeName( pAttributeName ); + if ( !HasAttribute( pAttributeName ) ) + return NULL; + + return GetValueElement< CDmeEditorAttributeInfo >( pAttributeName ); +} + + +//----------------------------------------------------------------------------- +// Returns the editor info associated with a single entry in an attribute array +//----------------------------------------------------------------------------- +CDmeEditorAttributeInfo *CDmeEditorType::GetAttributeArrayInfo( const char *pAttributeName ) +{ + CDmeEditorAttributeInfo *pAttributeInfo = GetAttributeInfo( pAttributeName ); + if ( !pAttributeInfo ) + return NULL; + + return pAttributeInfo->GetArrayInfo(); +} + + +//----------------------------------------------------------------------------- +// Expose DmeEditorTypeDictionary class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeEditorTypeDictionary, CDmeEditorTypeDictionary ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeEditorTypeDictionary::OnConstruction() +{ +} + +void CDmeEditorTypeDictionary::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeEditorTypeDictionary::AddEditorType( CDmeEditorType *pEditorType ) +{ + const char *pEditorTypeName = pEditorType->GetName(); + if ( HasAttribute( pEditorTypeName ) ) + { + Warning( "Editor type %s is already defined! Ignoring...\n", pEditorTypeName ); + return; + } + + SetValue( pEditorTypeName, pEditorType->GetHandle() ); +} + +void CDmeEditorTypeDictionary::AddEditorTypesFromFile( const char *pFileName, const char *pPathID ) +{ +} + + +//----------------------------------------------------------------------------- +// Returns the editor type to use with an element +//----------------------------------------------------------------------------- +CDmeEditorType *CDmeEditorTypeDictionary::GetEditorType( CDmElement *pElement ) +{ + if ( !pElement ) + return NULL; + + const char *pEditorTypeName = NULL; + if ( pElement->HasAttribute( "editorType" ) ) + { + pEditorTypeName = pElement->GetValueString( "editorType" ); + } + + if ( !pEditorTypeName || !pEditorTypeName[0] ) + { + // Try to use the type name as an editor + pEditorTypeName = pElement->GetTypeString(); + } + + if ( !pEditorTypeName || !pEditorTypeName[0] ) + return NULL; + + if ( !HasAttribute( pEditorTypeName ) ) + return NULL; + + return GetValueElement< CDmeEditorType >( pEditorTypeName ); +} + + +//----------------------------------------------------------------------------- +// Returns the editor info associated with an editor type +//----------------------------------------------------------------------------- +CDmeEditorAttributeInfo *CDmeEditorTypeDictionary::GetAttributeInfo( CDmElement *pElement, const char *pAttributeName ) +{ + CDmeEditorType *pEditorType = GetEditorType( pElement ); + if ( !pEditorType ) + return NULL; + return pEditorType->GetAttributeInfo( pAttributeName ); +} + + +//----------------------------------------------------------------------------- +// Returns the editor info associated with an editor type +//----------------------------------------------------------------------------- +CDmeEditorAttributeInfo *CDmeEditorTypeDictionary::GetAttributeArrayInfo( CDmElement *pElement, const char *pAttributeName ) +{ + CDmeEditorType *pEditorType = GetEditorType( pElement ); + if ( !pEditorType ) + return NULL; + return pEditorType->GetAttributeArrayInfo( pAttributeName ); +} diff --git a/movieobjects/dmeexpressionoperator.cpp b/movieobjects/dmeexpressionoperator.cpp new file mode 100644 index 0000000..424a344 --- /dev/null +++ b/movieobjects/dmeexpressionoperator.cpp @@ -0,0 +1,942 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The expression operator class - scalar math calculator +// for a good list of operators and simple functions, see: +// \\fileserver\user\MarcS\boxweb\aliveDistLite\v4.2.0\doc\alive\functions.txt +// (although we'll want to implement elerp as the standard 3x^2 - 2x^3 with rescale) +// +//============================================================================= +#include "movieobjects/dmeexpressionoperator.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmattribute.h" +#include "mathlib/noise.h" +#include "mathlib/vector.h" +#include <ctype.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Parsing helper methods +//----------------------------------------------------------------------------- +bool ParseLiteral( const char *&expr, float &value ) +{ + const char *startExpr = expr; + value = ( float )strtod( startExpr, const_cast< char** >( &expr ) ); + return ( startExpr != expr ); +} + +bool ParseString( const char *&expr, const char *str ) +{ + const char *startExpr = expr; + while ( ( *expr == ' ' ) || ( *expr == '\t' ) ) + expr++; // skip whitespace + + expr = StringAfterPrefix( expr, str ); + if ( expr ) + return true; + + expr = startExpr; + return false; +} + +bool ParseStringList( const char *&expr, const char **pOps, int &nOp ) +{ + while ( nOp-- ) + { + if ( ParseString( expr, pOps[ nOp ] ) ) + return true; + } + return false; +} + +bool ParseStringList( const char *&expr, const CUtlVector< CUtlString > &strings, int &nOp ) +{ + while ( nOp-- ) + { + if ( ParseString( expr, strings[ nOp ] ) ) + return true; + } + return false; +} + +int FindString( const CUtlVector< CUtlString > &strings, const char *str ) +{ + uint sn = strings.Count(); + for ( uint si = 0; si < sn; ++si ) + { + if ( !Q_strcmp( str, strings[ si ] ) ) + return si; + } + return -1; +} + +class ParseState_t +{ +public: + ParseState_t( const CUtlStack<float> &stack, const char *expr ) + : m_stacksize( stack.Count() ), m_startingExpr( expr ) {} + void Reset( CUtlStack<float> &stack, const char *&expr ) + { + Assert( m_stacksize <= stack.Count() ); + stack.PopMultiple( stack.Count() - m_stacksize ); + expr = m_startingExpr; + } + +private: + int m_stacksize; + const char* m_startingExpr; +}; + + + + +void CExpressionCalculator::SetVariable( int nVariableIndex, float value ) +{ + m_varValues[ nVariableIndex ] = value; +} + +void CExpressionCalculator::SetVariable( const char *var, float value ) +{ + int vi = FindString( m_varNames, var ); + if ( vi >= 0 ) + { + m_varValues[ vi ] = value; + } + else + { + m_varNames.AddToTail( var ); + m_varValues.AddToTail( value ); + } +} + +int CExpressionCalculator::FindVariableIndex( const char *var ) +{ + return FindString( m_varNames, var ); +} + +bool CExpressionCalculator::Evaluate( float &value ) +{ + m_bIsBuildingArgumentList = false; + m_stack.PopMultiple( m_stack.Count() ); + const char *pExpr = m_expr.Get(); + bool success = ParseExpr( pExpr ); + if ( success && m_stack.Count() == 1 ) + { + value = m_stack.Top(); + return true; + } + + value = 0.0f; + return false; +} + + +//----------------------------------------------------------------------------- +// Builds a list of variable names from the expression +//----------------------------------------------------------------------------- +bool CExpressionCalculator::BuildVariableListFromExpression( ) +{ + m_bIsBuildingArgumentList = true; + m_stack.PopMultiple( m_stack.Count() ); + const char *pExpr = m_expr.Get(); + bool bSuccess = ParseExpr( pExpr ); + m_bIsBuildingArgumentList = false; + if ( !bSuccess || m_stack.Count() != 1 ) + { + m_varNames.RemoveAll(); + return false; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Iterate over variables +//----------------------------------------------------------------------------- +int CExpressionCalculator::VariableCount() +{ + return m_varNames.Count(); +} + +const char *CExpressionCalculator::VariableName( int nIndex ) +{ + return m_varNames[nIndex]; +} + + + +bool CExpressionCalculator::ParseExpr( const char *&expr ) +{ + return ( expr != NULL ) && ParseConditional( expr ); +} + +bool CExpressionCalculator::ParseConditional( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseOr( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + ParseState_t ps1( m_stack, expr ); + if ( ParseString( expr, "?" ) && + ParseExpr( expr ) && + ParseString( expr, ":" ) && + ParseExpr( expr ) ) + { + float f3 = m_stack.Top(); + m_stack.Pop(); + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + m_stack.Push( f1 != 0.0f ? f2 : f3 ); + return true; // and matched + } + ps1.Reset( m_stack, expr ); + return true; // equality (or lower) matched +} + +bool CExpressionCalculator::ParseOr( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseAnd( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + ParseState_t ps1( m_stack, expr ); + if ( ParseString( expr, "||" ) && + ParseOr( expr ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + m_stack.Push( ( f1 != 0.0f ) || ( f2 != 0.0f ) ? 1 : 0 ); + return true; // and matched + } + ps1.Reset( m_stack, expr ); + return true; // equality (or lower) matched +} + +bool CExpressionCalculator::ParseAnd( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseEquality( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + ParseState_t ps1( m_stack, expr ); + if ( ParseString( expr, "&&" ) && + ParseAnd( expr ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + m_stack.Push( ( f1 != 0.0f ) && ( f2 != 0.0f ) ? 1 : 0 ); + return true; // and matched + } + ps1.Reset( m_stack, expr ); + return true; // equality (or lower) matched +} + +bool CExpressionCalculator::ParseEquality( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseLessGreater( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + const char *pOps[] = { "==", "!=" }; + int nOp = 2; + + ParseState_t ps1( m_stack, expr ); + if ( ParseStringList( expr, pOps, nOp ) && + ParseEquality( expr ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nOp ) + { + case 0: // == + m_stack.Push( f1 == f2 ? 1 : 0 ); + break; + case 1: // != + m_stack.Push( f1 != f2 ? 1 : 0 ); + break; + } + return true; // equality matched + } + ps1.Reset( m_stack, expr ); + return true; // lessgreater (or lower) matched +} + +bool CExpressionCalculator::ParseLessGreater( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseAddSub( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + const char *pOps[] = { "<", ">", "<=", ">=" }; + int nOp = 4; + + ParseState_t ps1( m_stack, expr ); + if ( ParseStringList( expr, pOps, nOp ) && + ParseLessGreater( expr ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nOp ) + { + case 0: // < + m_stack.Push( f1 < f2 ? 1 : 0 ); + break; + case 1: // > + m_stack.Push( f1 > f2 ? 1 : 0 ); + break; + case 2: // <= + m_stack.Push( f1 <= f2 ? 1 : 0 ); + break; + case 3: // >= + m_stack.Push( f1 >= f2 ? 1 : 0 ); + break; + } + return true; // inequality matched + } + ps1.Reset( m_stack, expr ); + return true; // addsub (or lower) matched +} + +bool CExpressionCalculator::ParseAddSub( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseDivMul( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + const char *pOps[] = { "+", "-" }; + int nOp = 2; + + ParseState_t ps1( m_stack, expr ); + if ( ParseStringList( expr, pOps, nOp ) && + ParseAddSub( expr ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nOp ) + { + case 0: // + + m_stack.Push( f1 + f2 ); + break; + case 1: // - + m_stack.Push( f1 - f2 ); + break; + } + return true; // addsub matched + } + ps1.Reset( m_stack, expr ); + return true; // divmul (or lower) matched +} + +bool CExpressionCalculator::ParseDivMul( const char *&expr ) +{ + ParseState_t ps0( m_stack, expr ); + if ( !ParseUnary( expr ) ) + { + ps0.Reset( m_stack, expr ); + return false; // nothing matched + } + + const char *pOps[] = { "*", "/", "%" }; + int nOp = 3; + + ParseState_t ps1( m_stack, expr ); + if ( ParseStringList( expr, pOps, nOp ) && + ParseDivMul( expr ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nOp ) + { + case 0: // * + m_stack.Push( f1 * f2 ); + break; + case 1: // / + m_stack.Push( f1 / f2 ); + break; + case 2: // % + m_stack.Push( fmod( f1, f2 ) ); + break; + } + return true; // divmul matched + } + ps1.Reset( m_stack, expr ); + return true; // unary (or lower) matched +} + +bool CExpressionCalculator::ParseUnary( const char *&expr ) +{ + ParseState_t ps( m_stack, expr ); + + const char *pOps[] = { "+", "-", "!" }; + int nOp = 3; + + if ( ParseStringList( expr, pOps, nOp ) && + ParseUnary( expr ) ) + { + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nOp ) + { + case 0: // + + m_stack.Push( f1 ); + break; + case 1: // - + m_stack.Push( -f1 ); + break; + case 2: // ! + m_stack.Push( f1 == 0 ? 1 : 0 ); + break; + } + return true; + } + + ps.Reset( m_stack, expr ); + if ( ParsePrimary( expr ) ) + return true; + + ps.Reset( m_stack, expr ); + return false; +} + +bool CExpressionCalculator::ParsePrimary( const char *&expr ) +{ + ParseState_t ps( m_stack, expr ); + + float value = 0.0f; + if ( ParseLiteral( expr, value ) ) + { + m_stack.Push( value ); + return true; + } + + ps.Reset( m_stack, expr ); + int nVar = m_varNames.Count(); + if ( ParseStringList( expr, m_varNames, nVar) ) + { + m_stack.Push( m_varValues[ nVar ] ); + return true; + } + + ps.Reset( m_stack, expr ); + if ( ParseString( expr, "(" ) && + ParseExpr( expr ) && + ParseString( expr, ")" ) ) + { + return true; + } + + ps.Reset( m_stack, expr ); + if ( Parse1ArgFunc( expr ) || + Parse2ArgFunc( expr ) || + Parse3ArgFunc( expr ) || +// Parse4ArgFunc( expr ) || + Parse5ArgFunc( expr ) ) + { + return true; + } + + // If we're parsing it to discover names of variable names, add them here + if ( !m_bIsBuildingArgumentList ) + return false; + + // Variables can't start with a number + if ( isdigit( *expr ) ) + return false; + + const char *pStart = expr; + while ( isalnum( *expr ) || *expr == '_' ) + { + ++expr; + } + + int nLen = (size_t)expr - (size_t)pStart; + char *pVariableName = (char*)_alloca( nLen+1 ); + memcpy( pVariableName, pStart, nLen ); + pVariableName[nLen] = 0; + + SetVariable( pVariableName, 0.0f ); + m_stack.Push( 0.0f ); + return true; +} + +/* +dtor(d) : converts degrees to radians +rtod(r) : converts radians to degrees + +abs(a) : absolute value +floor(a) : rounds down to the nearest integer +ceiling(a) : rounds up to the nearest integer +round(a) : rounds to the nearest integer +sgn(a) : if a < 0 returns -1 else 1 +sqr(a) : returns a * a +sqrt(a) : returns sqrt(a) + +sin(a) : sin(a), a is in degrees +asin(a) : asin(a) returns degrees +cos(a) : cos(a), a is in degrees +acos(a) : acos(a) returns degrees +tan(a) : tan(a), a is in degrees + +exp(a) : returns the exponential function of a +log(a) : returns the natural logaritm of a +*/ +bool CExpressionCalculator::Parse1ArgFunc( const char *&expr ) +{ + ParseState_t ps( m_stack, expr ); + + const char *pFuncs[] = + { + "abs", "sqr", "sqrt", "sin", "asin", "cos", "acos", "tan", + "exp", "log", "dtor", "rtod", "floor", "ceiling", "round", "sign" + }; + int nFunc = 16; + + if ( ParseStringList( expr, pFuncs, nFunc ) && + ParseString( expr, "(" ) && + ParseExpr( expr ) && + ParseString( expr, ")" ) ) + { + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nFunc ) + { + case 0: // abs + m_stack.Push( fabs( f1 ) ); + break; + case 1: // sqr + m_stack.Push( f1 * f1 ); + break; + case 2: // sqrt + m_stack.Push( sqrt( f1 ) ); + break; + case 3: // sin + m_stack.Push( sin( f1 ) ); + break; + case 4: // asin + m_stack.Push( asin( f1 ) ); + break; + case 5: // cos + m_stack.Push( cos( f1 ) ); + break; + case 6: // acos + m_stack.Push( acos( f1 ) ); + break; + case 7: // tan + m_stack.Push( tan( f1 ) ); + break; + case 8: // exp + m_stack.Push( exp( f1 ) ); + break; + case 9: // log + m_stack.Push( log( f1 ) ); + break; + case 10: // dtor + m_stack.Push( DEG2RAD( f1 ) ); + break; + case 11: // rtod + m_stack.Push( RAD2DEG( f1 ) ); + break; + case 12: // floor + m_stack.Push( floor( f1 ) ); + break; + case 13: // ceiling + m_stack.Push( ceil( f1 ) ); + break; + case 14: // round + m_stack.Push( floor( f1 + 0.5f ) ); + break; + case 15: // sign + m_stack.Push( f1 >= 0.0f ? 1.0f : -1.0f ); + break; + } + return true; + } + return false; +} + +/* +min(a,b) : if a<b returns a else b +max(a,b) : if a>b returns a else b +atan2(a,b) : atan2(a/b) returns degrees +pow(a,b) : function returns a raised to the power of b +*/ +bool CExpressionCalculator::Parse2ArgFunc( const char *&expr ) +{ + ParseState_t ps( m_stack, expr ); + + const char *pFuncs[] = { "min", "max", "atan2", "pow" }; + int nFunc = 4; + + if ( ParseStringList( expr, pFuncs, nFunc ) && + ParseString( expr, "(" ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, ")" ) ) + { + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nFunc ) + { + case 0: // min + m_stack.Push( min( f1, f2 ) ); + break; + case 1: // max + m_stack.Push( max( f1, f2 ) ); + break; + case 2: // atan2 + m_stack.Push( atan2( f1, f2 ) ); + break; + case 3: // pow + m_stack.Push( pow( f1, f2 ) ); + break; + } + return true; + } + return false; +} + +/* +inrange(x,a,b) : if x is between a and b, returns 1 else returns 0 +clamp(x,a,b) : see bound() above + +ramp(value,a,b) : returns 0 -> 1 as value goes from a to b +lerp(factor,a,b) : returns a -> b as value goes from 0 to 1 + +cramp(value,a,b) : clamp(ramp(value,a,b),0,1) +clerp(factor,a,b) : clamp(lerp(factor,a,b),a,b) + +elerp(x,a,b) : ramp( 3*x*x - 2*x*x*x, a, b) +//elerp(factor,a,b) : lerp(lerp(sind(clerp(factor,-90,90)),0.5,1.0),a,b) + +noise(a,b,c) : { solid noise pattern (improved perlin noise) indexed with three numbers } +*/ + +float ramp( float x, float a, float b ) +{ + return ( x - a ) / ( b - a ); +} + +float lerp( float x, float a, float b ) +{ + return a + x * ( b - a ); +} + +float smoothstep( float x ) +{ + return 3*x*x - 2*x*x*x; +} + +bool CExpressionCalculator::Parse3ArgFunc( const char *&expr ) +{ + ParseState_t ps( m_stack, expr ); + + const char *pFuncs[] = { "inrange", "clamp", "ramp", "lerp", "cramp", "clerp", "elerp", "noise" }; + int nFunc = 8; + + if ( ParseStringList( expr, pFuncs, nFunc ) && + ParseString( expr, "(" ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, ")" ) ) + { + float f3 = m_stack.Top(); + m_stack.Pop(); + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nFunc ) + { + case 0: // inrange + m_stack.Push( ( f1 >= f2 ) && ( f1 <= f3 ) ? 1.0f : 0.0f ); + break; + case 1: // clamp + m_stack.Push( clamp( f1, f2, f3 ) ); + break; + case 2: // ramp + m_stack.Push( ramp( f1, f2, f3 ) ); + break; + case 3: // lerp + m_stack.Push( lerp( f1, f2, f3 ) ); + break; + case 4: // cramp + m_stack.Push( clamp( ramp( f1, f2, f3 ), 0, 1 ) ); + break; + case 5: // clerp + m_stack.Push( clamp( lerp( f1, f2, f3 ), f2, f3 ) ); + break; + case 6: // elerp + m_stack.Push( lerp( smoothstep( f1 ), f2, f3 ) ); + break; + case 7: // noise + m_stack.Push( ImprovedPerlinNoise( Vector( f1, f2, f3 ) ) ); + break; + } + return true; + } + return false; +} + +//bool CExpressionCalculator::Parse4ArgFunc( const char *&expr ); + +/* +rescale (X,Xa,Xb,Ya,Yb) : lerp(ramp(X,Xa,Xb),Ya,Yb) +crescale(X,Xa,Xb,Ya,Yb) : clamp(rescale(X,Xa,Xb,Ya,Yb),Ya,Yb) +*/ +float rescale( float x, float a, float b, float c, float d ) +{ + return lerp( ramp( x, a, b ), c, d ); +} + +bool CExpressionCalculator::Parse5ArgFunc( const char *&expr ) +{ + ParseState_t ps( m_stack, expr ); + + const char *pFuncs[] = { "rescale", "crescale" }; + int nFunc = 2; + + if ( ParseStringList( expr, pFuncs, nFunc ) && + ParseString( expr, "(" ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, "," ) && + ParseExpr( expr ) && + ParseString( expr, ")" ) ) + { + float f5 = m_stack.Top(); + m_stack.Pop(); + float f4 = m_stack.Top(); + m_stack.Pop(); + float f3 = m_stack.Top(); + m_stack.Pop(); + float f2 = m_stack.Top(); + m_stack.Pop(); + float f1 = m_stack.Top(); + m_stack.Pop(); + switch ( nFunc ) + { + case 0: // rescale + m_stack.Push( rescale( f1, f2, f3, f4, f5 ) ); + break; + case 1: // crescale + m_stack.Push( clamp( rescale( f1, f2, f3, f4, f5 ), f4, f5 ) ); + break; + } + return true; + } + return false; +} + +void TestCalculator( const char *expr, float answer ) +{ + CExpressionCalculator calc( expr ); + float result = 0.0f; + +#ifdef DBGFLAG_ASSERT + bool success = +#endif + calc.Evaluate( result ); + Assert( success && ( result == answer ) ); +} + +void TestCalculator( const char *expr, float answer, const char *var, float value ) +{ + CExpressionCalculator calc( expr ); + calc.SetVariable( var, value ); + float result = 0.0f; + +#ifdef DBGFLAG_ASSERT + bool success = +#endif + calc.Evaluate( result ); + Assert( success && ( result == answer ) ); +} + +void TestCalculator() +{ +// TestCalculator( "-1", 1 ); + TestCalculator( "2 * 3 + 4", 10 ); + TestCalculator( "2 + 3 * 4", 14 ); + TestCalculator( "2 * 3 * 4", 24 ); + TestCalculator( "2 * -3 + 4", -2 ); + TestCalculator( "12.0 / 2.0", 6 ); + TestCalculator( "(2*3)+4", 10 ); + TestCalculator( "( 1 + 2 ) / (1+2)", 1 ); + TestCalculator( "(((5)))", 5 ); + TestCalculator( "--5", 5 ); + TestCalculator( "3.5 % 2", 1.5 ); + TestCalculator( "1e-2", 0.01 ); + TestCalculator( "9 == ( 3 * ( 1 + 2 ) )", 1 ); + TestCalculator( "9 != ( 3 * ( 1 + 2 ) )", 0 ); + TestCalculator( "9 <= ( 3 * ( 1 + 2 ) )", 1 ); + TestCalculator( "9 < ( 3 * ( 1 + 2 ) )", 0 ); + TestCalculator( "9 < 3", 0 ); + TestCalculator( "10 >= 5", 1 ); +// TestCalculator( "9 < ( 3 * ( 2 + 2 ) )", 0 ); + TestCalculator( "x + 1", 5, "x", 4 ); + TestCalculator( "pi - 3.14159", 0, "pi", 3.14159 ); +// TestCalculator( "pi / 2", 0, "pi", 3.14159 ); + TestCalculator( "abs(-10)", 10 ); + TestCalculator( "sqr(-5)", 25 ); + TestCalculator( "sqrt(9)", 3 ); +// TestCalculator( "sqrt(-9)", -3 ); + TestCalculator( "pow(2,3)", 8 ); + TestCalculator( "min(abs(-4),2+3/2)", 3.5 ); + TestCalculator( "round(0.5)", 1 ); + TestCalculator( "round(0.49)", 0 ); + TestCalculator( "round(-0.5)", 0 ); + TestCalculator( "round(-0.51)", -1 ); + TestCalculator( "inrange( 5, -8, 10 )", 1 ); + TestCalculator( "inrange( 5, 5, 10 )", 1 ); + TestCalculator( "inrange( 5, 6, 10 )", 0 ); + TestCalculator( "elerp( 1/4, 0, 1 )", 3/16.0f - 1/32.0f ); + TestCalculator( "rescale( 0.5, -1, 1, 0, 100 )", 75 ); + TestCalculator( "1 > 2 ? 6 : 9", 9 ); + TestCalculator( "1 ? 1 ? 2 : 4 : 1 ? 6 : 8", 2 ); + TestCalculator( "0 ? 1 ? 2 : 4 : 1 ? 6 : 8", 6 ); + TestCalculator( "noise( 0.123, 4.56, 78.9 )", ImprovedPerlinNoise( Vector( 0.123, 4.56, 78.9 ) ) ); +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeExpressionOperator, CDmeExpressionOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeExpressionOperator::OnConstruction() +{ + m_result.Init( this, "result" ); + m_expr.Init( this, "expr" ); + m_bSpewResult.Init( this, "spewresult" ); + +#ifdef _DEBUG + TestCalculator(); +#endif _DEBUG +} + +void CDmeExpressionOperator::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDmeExpressionOperator::IsInputAttribute( CDmAttribute *pAttribute ) +{ + const char *pName = pAttribute->GetName( ); +#if 0 // skip this test, since none of these are float attributes, but leave the code as a reminder + if ( Q_strcmp( pName, "name" ) == 0 ) + return false; + + if ( Q_strcmp( pName, "expr" ) == 0 ) + return false; +#endif + + if ( Q_strcmp( pName, "result" ) == 0 ) + return false; + + if ( pAttribute->GetType() != AT_FLOAT ) + return false; + + return true; +} + +void CDmeExpressionOperator::Operate() +{ + CExpressionCalculator calc( m_expr.Get() ); + + for ( CDmAttribute *pAttribute = FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( IsInputAttribute( pAttribute ) ) + { + const char *pName = pAttribute->GetName( ); + calc.SetVariable( pName, pAttribute->GetValue< float >() ); + } + } + + float oldValue = m_result; + + calc.Evaluate( oldValue ); + + m_result = oldValue; + + if ( m_bSpewResult ) + { + Msg( "%s = '%f'\n", GetName(), (float)m_result ); + } +} + +void CDmeExpressionOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + for ( CDmAttribute *pAttribute = FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( IsInputAttribute( pAttribute ) ) + { + attrs.AddToTail( pAttribute ); + } + } + + attrs.AddToTail( m_expr.GetAttribute() ); +} + +void CDmeExpressionOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_result.GetAttribute() ); +} + + +void CDmeExpressionOperator::SetSpewResult( bool state ) +{ + m_bSpewResult = state; +} diff --git a/movieobjects/dmeeyeball.cpp b/movieobjects/dmeeyeball.cpp new file mode 100644 index 0000000..8d38c1d --- /dev/null +++ b/movieobjects/dmeeyeball.cpp @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//============================================================================= + + +// Valve includes +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmeeyeball.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeEyeball, CDmeEyeball ); + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeEyeball::OnConstruction() +{ + m_flDiameter.InitAndSet( this, "diameter", 1.0 ); + m_flYawAngle.InitAndSet( this, "angle", 2.0 ); + m_flPupilScale.InitAndSet( this, "pupilScale", 1.0 ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeEyeball::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Returns the model relative position of the eyeball Position +//----------------------------------------------------------------------------- +void CDmeEyeball::GetWorldPosition( Vector &worldPosition ) +{ + matrix3x4_t mWorld; + GetShapeToWorldTransform( mWorld ); + + MatrixPosition( mWorld, worldPosition ); +} diff --git a/movieobjects/dmeeyeposition.cpp b/movieobjects/dmeeyeposition.cpp new file mode 100644 index 0000000..1cd86dd --- /dev/null +++ b/movieobjects/dmeeyeposition.cpp @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//============================================================================= + + +// Valve includes +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmeeyeposition.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeEyePosition, CDmeEyePosition ); + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeEyePosition::OnConstruction() +{ +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeEyePosition::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Returns the model relative position of the eyePosition +//----------------------------------------------------------------------------- +void CDmeEyePosition::GetWorldPosition( Vector &worldPosition ) +{ + matrix3x4_t mWorld; + GetShapeToWorldTransform( mWorld ); + + MatrixPosition( mWorld, worldPosition ); +} diff --git a/movieobjects/dmefaceset.cpp b/movieobjects/dmefaceset.cpp new file mode 100644 index 0000000..5fecdfd --- /dev/null +++ b/movieobjects/dmefaceset.cpp @@ -0,0 +1,187 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmefaceset.h" +#include "movieobjects/dmematerial.h" +#include "tier0/dbg.h" +#include "UtlBuffer.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeFaceSet, CDmeFaceSet ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeFaceSet::OnConstruction() +{ + m_indices.Init( this, "faces" ); + m_material.Init( this, "material" ); +} + +void CDmeFaceSet::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +CDmeMaterial *CDmeFaceSet::GetMaterial() +{ + return m_material.GetElement(); +} + +void CDmeFaceSet::SetMaterial( CDmeMaterial *pMaterial ) +{ + m_material = pMaterial; +} + +int CDmeFaceSet::AddIndices( int nCount ) +{ + int nCurrentCount = m_indices.Count(); + m_indices.EnsureCount( nCount + nCurrentCount ); + return nCurrentCount; +} + +void CDmeFaceSet::SetIndices( int nFirstIndex, int nCount, int *pIndices ) +{ + m_indices.SetMultiple( nFirstIndex, nCount, pIndices ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeFaceSet::SetIndex( int i, int nValue ) +{ + m_indices.Set( i, nValue ); +} + + +//----------------------------------------------------------------------------- +// Returns the number of triangulated indices +//----------------------------------------------------------------------------- +int CDmeFaceSet::GetNextPolygonVertexCount( int nFirstIndex ) const +{ + int nCurrIndex = nFirstIndex; + int nTotalCount = m_indices.Count(); + while( nCurrIndex < nTotalCount ) + { + if ( m_indices[nCurrIndex] == -1 ) + break; + ++nCurrIndex; + } + + return nCurrIndex - nFirstIndex; +} + + +//----------------------------------------------------------------------------- +// Returns the number of triangulated indices total +//----------------------------------------------------------------------------- +int CDmeFaceSet::GetTriangulatedIndexCount() const +{ + int nIndexCount = 0; + int nVertexCount = 0; + int nTotalCount = m_indices.Count(); + for ( int nCurrIndex = 0; nCurrIndex < nTotalCount; ++nCurrIndex ) + { + if ( m_indices[nCurrIndex] == -1 ) + { + if ( nVertexCount >= 3 ) + { + nIndexCount += ( nVertexCount - 2 ) * 3; + } + nVertexCount = 0; + continue; + } + + ++nVertexCount; + } + + if ( nVertexCount >= 3 ) + { + nIndexCount += ( nVertexCount - 2 ) * 3; + } + + return nIndexCount; +} + + +//----------------------------------------------------------------------------- +// Returns the number of indices total +//----------------------------------------------------------------------------- +int CDmeFaceSet::GetIndexCount() const +{ + int nIndexCount = 0; + int nVertexCount = 0; + int nTotalCount = m_indices.Count(); + + for ( int nCurrIndex = 0; nCurrIndex < nTotalCount; ++nCurrIndex ) + { + if ( m_indices[nCurrIndex] == -1 ) + { + nIndexCount += nVertexCount; + nVertexCount = 0; + continue; + } + + ++nVertexCount; + } + + nIndexCount += nVertexCount; + + return nIndexCount; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeFaceSet::RemoveMultiple( int elem, int num ) +{ + m_indices.RemoveMultiple( elem, num ); +} + + +//----------------------------------------------------------------------------- +// Returns the number of faces in the face set +//----------------------------------------------------------------------------- +int CDmeFaceSet::GetFaceCount() const +{ + int nFaceCount = 0; + int nVertexCount = 0; + + const int nIndexCount = NumIndices(); + for ( int i = 0; i < nIndexCount; ++i ) + { + if ( GetIndex( i ) < 0 ) + { + if ( nVertexCount > 0 ) + { + ++nFaceCount; + } + nVertexCount = 0; + continue; + } + + ++nVertexCount; + } + + if ( nVertexCount > 0 ) + { + ++nFaceCount; + } + + return nFaceCount; +}
\ No newline at end of file diff --git a/movieobjects/dmegamemodel.cpp b/movieobjects/dmegamemodel.cpp new file mode 100644 index 0000000..324f744 --- /dev/null +++ b/movieobjects/dmegamemodel.cpp @@ -0,0 +1,694 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Dme version of a game model (MDL) +// +//============================================================================= +#include "movieobjects/dmegamemodel.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datacache/imdlcache.h" +#include "studio.h" +#include "tier3/tier3.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGlobalFlexControllerOperator, CDmeGlobalFlexControllerOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGlobalFlexControllerOperator::OnConstruction() +{ + m_flexWeight.Init( this, "flexWeight" ); + m_gameModel.Init( this, "gameModel", FATTRIB_HAS_CALLBACK ); + + m_ToAttributeHandle = DMATTRIBUTE_HANDLE_INVALID; + + m_nFlexControllerIndex = -1; +} + +void CDmeGlobalFlexControllerOperator::OnDestruction() +{ +} + +void CDmeGlobalFlexControllerOperator::Resolve() +{ + if ( m_nFlexControllerIndex < 0 ) + { + m_nFlexControllerIndex = FindGlobalFlexControllerIndex(); + } +} + +void CDmeGlobalFlexControllerOperator::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + // Don't have the required interface... + if ( !g_pGlobalFlexController ) + return; + + if ( pAttribute == m_gameModel.GetAttribute() && m_gameModel.GetElement() ) + { + m_nFlexControllerIndex = FindGlobalFlexControllerIndex(); + + SetupToAttribute(); + } +} + +void CDmeGlobalFlexControllerOperator::Operate() +{ + CDmAttribute *pToAttr = g_pDataModel->GetAttribute( m_ToAttributeHandle ); + if ( !pToAttr ) + return; + + DmAttributeType_t type = m_flexWeight.GetAttribute()->GetType(); + + const void *pValue = m_flexWeight.GetAttribute()->GetValueUntyped(); + if ( IsArrayType( pToAttr->GetType() ) ) + { + if ( m_nFlexControllerIndex == -1 ) + return; + + CDmrGenericArray array( pToAttr ); + array.Set( m_nFlexControllerIndex, type, pValue ); + } + else + { + pToAttr->SetValue( type, pValue ); + } +} + +void CDmeGlobalFlexControllerOperator::SetGameModel( CDmeGameModel *gameModel ) +{ + m_gameModel = gameModel; +} + +void CDmeGlobalFlexControllerOperator::SetWeight( float flWeight ) +{ + m_flexWeight = flWeight; +} + +void CDmeGlobalFlexControllerOperator::SetMapping( int globalIndex ) +{ + m_nFlexControllerIndex = globalIndex; + if ( m_gameModel.GetElement() ) + { + if ( (uint)globalIndex >= m_gameModel->NumFlexWeights() ) + { + m_gameModel->SetNumFlexWeights( (uint)( globalIndex + 1 ) ); + } + } +} + +int CDmeGlobalFlexControllerOperator::GetGlobalIndex() const +{ + return m_nFlexControllerIndex; +} + +void CDmeGlobalFlexControllerOperator::GetInputAttributes ( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_flexWeight.GetAttribute() ); +} + +void CDmeGlobalFlexControllerOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + CDmAttribute *toAttribute = g_pDataModel->GetAttribute( m_ToAttributeHandle ); + if ( toAttribute ) + { + attrs.AddToTail( toAttribute ); + } +} + +void CDmeGlobalFlexControllerOperator::SetupToAttribute() +{ + CDmElement *pObject = m_gameModel.GetElement(); + if ( pObject == NULL) + return; + + CDmAttribute *pAttr = pObject->GetAttribute( "flexWeights" ); + Assert( pAttr ); + if ( !pAttr ) + return; + + m_ToAttributeHandle = pAttr->GetHandle(); + return; +} + + +//----------------------------------------------------------------------------- +// Connect up stuff by index +//----------------------------------------------------------------------------- +int CDmeGlobalFlexControllerOperator::FindGlobalFlexControllerIndex() const +{ + int nGlobalFlexControllerIndex = -1; + + const char *pModelName = m_gameModel->GetModelName(); + MDLHandle_t h = pModelName && pModelName[0] ? g_pMDLCache->FindMDL( pModelName ) : MDLHANDLE_INVALID; + if ( h != MDLHANDLE_INVALID ) + { + studiohdr_t *hdr = g_pMDLCache->GetStudioHdr( h ); + Assert( hdr ); + if ( hdr ) + { + int fc = hdr->numflexcontrollers; + for ( LocalFlexController_t i = LocalFlexController_t(0) ; i < fc; ++i ) + { + mstudioflexcontroller_t *flex = hdr->pFlexcontroller( i ); + if ( flex->localToGlobal == -1 ) + { + flex->localToGlobal = g_pGlobalFlexController->FindGlobalFlexController( flex->pszName() ); + } + + if ( !Q_stricmp( flex->pszName(), GetName() ) ) + { + nGlobalFlexControllerIndex = flex->localToGlobal; + // Grow the array + if ( (uint)flex->localToGlobal >= m_gameModel->NumFlexWeights() ) + { + m_gameModel->SetNumFlexWeights( (uint)( flex->localToGlobal + 1 ) ); + } + break; + } + } + } + + g_pMDLCache->Release( h ); + } + + return nGlobalFlexControllerIndex; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGameModel, CDmeGameModel ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGameModel::OnConstruction() +{ + m_flexWeights.Init( this, "flexWeights" ); + m_viewTarget.Init( this, "viewTarget" ); + m_modelName.Init( this, "modelName", FATTRIB_HAS_CALLBACK ); + m_skin.Init( this, "skin" ); + m_body.Init( this, "body" ); + m_sequence.Init( this, "sequence" ); + m_flags.Init( this, "flags" ); + m_bones.Init( this, "bones" ); + m_globalFlexControllers.Init( this, "globalFlexControllers" ); + m_bComputeBounds.Init( this, "computeBounds" ); +} + +void CDmeGameModel::OnDestruction() +{ +} + +CDmeGlobalFlexControllerOperator *CDmeGameModel::AddGlobalFlexController( const char *controllerName, int globalIndex ) +{ + int i, c; + c = m_globalFlexControllers.Count(); + for ( i = 0; i < c; ++i ) + { + CDmeGlobalFlexControllerOperator *op = m_globalFlexControllers.Get( i ); + Assert( op ); + if ( op && !Q_stricmp( op->GetName(), controllerName ) ) + break; + } + + if ( i >= c ) + { + CDmeGlobalFlexControllerOperator *newOperator = CreateElement< CDmeGlobalFlexControllerOperator >( controllerName, GetFileId() ); + Assert( newOperator ); + if ( !newOperator ) + return NULL; + + i = m_globalFlexControllers.AddToTail( newOperator ); + } + + Assert( m_globalFlexControllers.IsValidIndex( i ) ); + CDmeGlobalFlexControllerOperator *op = m_globalFlexControllers.Get( i ); + Assert( op ); + if ( op ) + { + op->SetMapping( globalIndex ); + op->SetGameModel( this ); + } + + if ( (uint)globalIndex >= NumFlexWeights() ) + { + SetNumFlexWeights( globalIndex + 1 ); + } + + return op; +} + + +//----------------------------------------------------------------------------- +// Find a flex controller by its global index +//----------------------------------------------------------------------------- +CDmeGlobalFlexControllerOperator *CDmeGameModel::FindGlobalFlexController( int nGlobalIndex ) +{ + int i, c; + c = m_globalFlexControllers.Count(); + for ( i = 0; i < c; ++i ) + { + CDmeGlobalFlexControllerOperator *op = m_globalFlexControllers.Get( i ); + Assert( op ); + if ( op && op->GetGlobalIndex() == nGlobalIndex ) + return op; + } + + return NULL; +} + +studiohdr_t* CDmeGameModel::GetStudioHdr() const +{ + const char *pModelName = GetModelName(); + MDLHandle_t h = pModelName && pModelName[0] ? g_pMDLCache->FindMDL( pModelName ) : MDLHANDLE_INVALID; + return ( h != MDLHANDLE_INVALID ) ? g_pMDLCache->GetStudioHdr( h ) : NULL; +} + + +// A src bone transform transforms pre-compiled data (.dmx or .smd files, for example) +// into post-compiled data (.mdl or .ani files) +bool CDmeGameModel::GetSrcBoneTransforms( matrix3x4_t *pPreTransform, matrix3x4_t *pPostTransform, int nBoneIndex ) const +{ + studiohdr_t *pStudioHdr = GetStudioHdr(); + if ( !pStudioHdr ) + return false; + + if ( pStudioHdr->numbones <= nBoneIndex ) + return false; + + const char *pBoneName = pStudioHdr->pBone( nBoneIndex )->pszName(); + int nCount = pStudioHdr->NumSrcBoneTransforms(); + for ( int i = 0; i < nCount; ++i ) + { + const mstudiosrcbonetransform_t *pSrcTransform = pStudioHdr->SrcBoneTransform( i ); + if ( Q_stricmp( pSrcTransform->pszName(), pBoneName ) ) + continue; + + MatrixCopy( pSrcTransform->pretransform, *pPreTransform ); + MatrixCopy( pSrcTransform->posttransform, *pPostTransform ); + return true; + } + return false; +} + +bool CDmeGameModel::IsRootTransform( int nBoneIndex ) const +{ + studiohdr_t *pStudioHdr = GetStudioHdr(); + if ( !pStudioHdr ) + return true; + + if ( pStudioHdr->numbones <= nBoneIndex ) + return true; + + mstudiobone_t *pBone = pStudioHdr->pBone( nBoneIndex ); + return pBone->parent == -1; +} + +int CDmeGameModel::NumGlobalFlexControllers() const +{ + return m_globalFlexControllers.Count(); +} + +CDmeGlobalFlexControllerOperator *CDmeGameModel::GetGlobalFlexController( int localIndex ) +{ + return m_globalFlexControllers.Get( localIndex ); +} + +void CDmeGameModel::RemoveGlobalFlexController( CDmeGlobalFlexControllerOperator *controller ) +{ + int c = m_globalFlexControllers.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmeGlobalFlexControllerOperator *check = m_globalFlexControllers.Get( i ); + if ( check == controller ) + { + m_globalFlexControllers.Remove( i ); + break; + } + } +} + +void CDmeGameModel::AppendGlobalFlexControllerOperators( CUtlVector< IDmeOperator * >& list ) +{ + int c = m_globalFlexControllers.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeOperator *op = m_globalFlexControllers.Get( i ); + if ( !op ) + continue; + list.AddToTail( op ); + } +} + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +void CDmeGameModel::AddBone( CDmeTransform* pTransform ) +{ + m_bones.AddToTail( pTransform ); +} + + +//----------------------------------------------------------------------------- +// Is this dag under the game model? +//----------------------------------------------------------------------------- +static bool IsDagUnderGameModel( CDmeDag *pDag, CDmeGameModel *pGameModel ) +{ + if ( pDag == pGameModel ) + return true; + + DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( pDag->GetHandle() ); + while ( i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); + CDmElement *pDmeParent = pAttribute->GetOwner(); + const static UtlSymId_t symChildren = g_pDataModel->GetSymbol( "children" ); + if ( pDmeParent && pAttribute->GetNameSymbol() == symChildren ) + { + CDmeDag *pParent = CastElement< CDmeDag >( pDmeParent ); + if ( pParent && ( pParent->GetFileId() == pDag->GetFileId() ) ) + { + if ( IsDagUnderGameModel( pParent, pGameModel ) ) + return true; + } + } + i = g_pDataModel->NextAttributeReferencingElement( i ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Is this dag under the game model? +//----------------------------------------------------------------------------- +static CDmeDag* GetDagForTransform( CDmeTransform *pTransform, CDmeGameModel *pGameModel ) +{ + DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( pTransform->GetHandle() ); + while ( i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); + CDmElement *pDmeParent = pAttribute->GetOwner(); + const static UtlSymId_t symTransform = g_pDataModel->GetSymbol( "transform" ); + if ( pDmeParent && pAttribute->GetNameSymbol() == symTransform ) + { + CDmeDag *pParent = CastElement< CDmeDag >( pDmeParent ); + if ( pParent && ( pParent->GetFileId() == pTransform->GetFileId() ) ) + { + if ( IsDagUnderGameModel( pParent, pGameModel ) ) + return pParent; + } + } + i = g_pDataModel->NextAttributeReferencingElement( i ); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Finds existing dags +//----------------------------------------------------------------------------- +void CDmeGameModel::PopulateExistingDagList( CDmeDag** pDags, int nCount ) +{ + int nCurrentBoneCount = m_bones.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( i >= nCurrentBoneCount ) + { + pDags[ i ] = NULL; + continue; + } + + CDmeTransform *pTransform = GetBone( i ); + Assert( pTransform ); + pDags[ i ] = pTransform ? GetDagForTransform( pTransform, this ) : NULL; + } +} + + +//----------------------------------------------------------------------------- +// Adds bones to the game model +//----------------------------------------------------------------------------- +void CDmeGameModel::AddBones( studiohdr_t *pStudioHdr, const char *pBaseName, int nFirstBone, int nCount ) +{ + if ( nFirstBone + nCount > pStudioHdr->numbones ) + { + nCount = pStudioHdr->numbones - nFirstBone; + if ( nCount <= 0 ) + return; + } + + // make room for bones + CDmeDag** pDags = ( CDmeDag** )_alloca( pStudioHdr->numbones * sizeof(CDmeDag*) ); + int nDagCount = nFirstBone; + PopulateExistingDagList( pDags, nFirstBone ); + + char name[ 256 ]; + for ( int i = 0; i < nCount; ++i ) + { + int bi = i + nFirstBone; + + // get parent + mstudiobone_t *pBone = pStudioHdr->pBone( bi ); + int parentIndex = pBone->parent; + Assert( parentIndex < nDagCount ); + + // build dag hierarchy to match bone hierarchy + CDmeDag *pParent = ( parentIndex < 0 ) ? this : pDags[ parentIndex ]; + Q_snprintf( name, sizeof( name ), "%s_bone %d (%s)", pBaseName, bi, pBone->pszName() ); + CDmeDag *pDag = CreateElement< CDmeDag >( name, GetFileId() ); + pDags[nDagCount++] = pDag; + pParent->AddChild( pDag ); + + CDmeTransform *pTransform = pDag->GetTransform(); + pTransform->SetName( name ); + + // add different bone representations to dme model and input + AddBone( pTransform ); + } +} + + +void CDmeGameModel::SetBone( uint index, const Vector& pos, const Quaternion& rot ) +{ + m_bones[ index ]->SetPosition( pos ); + m_bones[ index ]->SetOrientation( rot ); +} + +void CDmeGameModel::RemoveAllBones() +{ + m_bones.RemoveAll(); +} + +uint CDmeGameModel::NumBones() const +{ + return m_bones.Count(); +} + +CDmeTransform *CDmeGameModel::GetBone( uint index ) const +{ + return m_bones[ index ]; +} + +int CDmeGameModel::FindBone( CDmeTransform *pTransform ) const +{ + return m_bones.Find( pTransform ); +} + +uint CDmeGameModel::NumFlexWeights() const +{ + return m_flexWeights.Count(); +} + +const CUtlVector< float >& CDmeGameModel::GetFlexWeights() const +{ + return m_flexWeights.Get(); +} + +void CDmeGameModel::SetNumFlexWeights( uint nFlexWeights ) +{ + if ( nFlexWeights > (uint)m_flexWeights.Count() ) + { + while ( (uint)m_flexWeights.Count() < nFlexWeights ) + { + m_flexWeights.AddToTail( 0.0f ); + } + } + else + { + while ( (uint)m_flexWeights.Count() > nFlexWeights ) + { + m_flexWeights.Remove( (uint)m_flexWeights.Count() - 1 ); + } + } +} + +void CDmeGameModel::SetFlexWeights( uint nFlexWeights, const float* flexWeights ) +{ + m_flexWeights.CopyArray( flexWeights, nFlexWeights ); +} + +const Vector& CDmeGameModel::GetViewTarget() const +{ + return m_viewTarget.Get(); +} + +void CDmeGameModel::SetViewTarget( const Vector &viewTarget ) +{ + m_viewTarget = viewTarget; +} + +void CDmeGameModel::SetFlags( int nFlags ) +{ + m_flags = nFlags; +} + +void CDmeGameModel::SetSkin( int nSkin ) +{ + m_skin = nSkin; +} + +void CDmeGameModel::SetBody( int nBody ) +{ + m_body = nBody; +} + +void CDmeGameModel::SetSequence( int nSequence ) +{ + m_sequence = nSequence; +} + +int CDmeGameModel::GetSkin() const +{ + return m_skin; +} + +int CDmeGameModel::GetBody() const +{ + return m_body; +} + +int CDmeGameModel::GetSequence() const +{ + return m_sequence; +} + +const char *CDmeGameModel::GetModelName() const +{ + return m_modelName.Get(); +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGameSprite, CDmeGameSprite ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGameSprite::OnConstruction() +{ + m_modelName .Init( this, "modelName" ); + m_frame .Init( this, "frame" ); + m_rendermode.Init( this, "rendermode" ); + m_renderfx .Init( this, "renderfx" ); + m_renderscale.Init( this, "renderscale" ); + m_color .Init( this, "color" ); + m_proxyRadius.Init( this, "proxyRadius" ); +} + +void CDmeGameSprite::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +const char *CDmeGameSprite::GetModelName() const +{ + return m_modelName.Get(); +} + +float CDmeGameSprite::GetScale() const +{ + return m_renderscale; +} + +float CDmeGameSprite::GetFrame() const +{ + return m_frame; +} + +int CDmeGameSprite::GetRenderMode() const +{ + return m_rendermode; +} + +int CDmeGameSprite::GetRenderFX() const +{ + return m_renderfx; +} + +const Color &CDmeGameSprite::GetColor() const +{ + return m_color; +} + +float CDmeGameSprite::GetProxyRadius() const +{ + return m_proxyRadius; +} + +void CDmeGameSprite::SetState( bool bVisible, float nFrame, int nRenderMode, int nRenderFX, float flRenderScale, float flProxyRadius, + const Vector &pos, const Quaternion &rot, const Color &color ) +{ + m_Visible = bVisible; + m_frame = nFrame; + m_rendermode = nRenderMode; + m_renderfx = nRenderFX; + m_renderscale = flRenderScale; + m_proxyRadius = flProxyRadius; + m_color = color; + + CDmeTransform *pTransform = GetTransform(); + pTransform->SetPosition( pos ); + pTransform->SetOrientation( rot ); +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGamePortal, CDmeGamePortal ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGamePortal::OnConstruction() +{ + m_flStaticAmount .Init( this, "staticAmount" ); + m_flSecondaryStaticAmount .Init( this, "secondaryStaticAmount" ); + m_flOpenAmount .Init( this, "openAmount" ); + m_nPortalId .Init( this, "portalId" ); + m_nLinkedPortalId .Init( this, "linkedPortalId" ); + m_bIsPortal2 .Init( this, "isPortal2" ); +} + +void CDmeGamePortal::OnDestruction() +{ +} diff --git a/movieobjects/dmegamemodelinput.cpp b/movieobjects/dmegamemodelinput.cpp new file mode 100644 index 0000000..903db2d --- /dev/null +++ b/movieobjects/dmegamemodelinput.cpp @@ -0,0 +1,253 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// game model input - gets its values from an MDL within the game +// +//============================================================================= + +#include "movieobjects/dmegamemodelinput.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGameModelInput, CDmeGameModelInput ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGameModelInput::OnConstruction() +{ + m_skin.Init( this, "skin" ); + m_body.Init( this, "body" ); + m_sequence.Init( this, "sequence" ); + m_visible.Init( this, "visible" ); + m_flags.Init( this, "flags" ); + m_flexWeights.Init( this, "flexWeights" ); + m_viewTarget.Init( this, "viewTarget" ); + m_bonePositions.Init( this, "bonePositions" ); + m_boneRotations.Init( this, "boneRotations" ); + m_position.Init( this, "position" ); + m_rotation.Init( this, "rotation" ); +} + +void CDmeGameModelInput::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// Operator methods +//----------------------------------------------------------------------------- +bool CDmeGameModelInput::IsDirty() +{ + return true; // TODO - keep some bit of state that remembers when its changed +} + +void CDmeGameModelInput::Operate() +{ +} + +void CDmeGameModelInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_skin.GetAttribute() ); + attrs.AddToTail( m_body.GetAttribute() ); + attrs.AddToTail( m_sequence.GetAttribute() ); + attrs.AddToTail( m_visible.GetAttribute() ); + attrs.AddToTail( m_flags.GetAttribute() ); + attrs.AddToTail( m_flexWeights.GetAttribute() ); + attrs.AddToTail( m_viewTarget.GetAttribute() ); + attrs.AddToTail( m_bonePositions.GetAttribute() ); + attrs.AddToTail( m_boneRotations.GetAttribute() ); + attrs.AddToTail( m_position.GetAttribute() ); + attrs.AddToTail( m_rotation.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +void CDmeGameModelInput::SetFlags( int nFlags ) +{ + m_flags = nFlags; +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +void CDmeGameModelInput::AddBone( const Vector& pos, const Quaternion& rot ) +{ + m_bonePositions.AddToTail( pos ); + m_boneRotations.AddToTail( rot ); +} + +void CDmeGameModelInput::SetBone( uint index, const Vector& pos, const Quaternion& rot ) +{ + m_bonePositions.Set( index, pos ); + m_boneRotations.Set( index, rot ); +} + +void CDmeGameModelInput::SetRootBone( const Vector& pos, const Quaternion& rot ) +{ + m_position.Set( pos ); + m_rotation.Set( rot ); +} + +uint CDmeGameModelInput::NumBones() const +{ + Assert( m_bonePositions.Count() == m_boneRotations.Count() ); + return m_bonePositions.Count(); +} + +void CDmeGameModelInput::SetFlexWeights( uint nFlexWeights, const float* flexWeights ) +{ + m_flexWeights.CopyArray( flexWeights, nFlexWeights ); +} + +uint CDmeGameModelInput::NumFlexWeights() const +{ + return m_flexWeights.Count(); +} + +const Vector& CDmeGameModelInput::GetViewTarget() const +{ + return m_viewTarget; +} + +void CDmeGameModelInput::SetViewTarget( const Vector &viewTarget ) +{ + m_viewTarget = viewTarget; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGameSpriteInput, CDmeGameSpriteInput ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGameSpriteInput::OnConstruction() +{ + m_visible .Init( this, "visible" ); + m_frame .Init( this, "frame" ); + m_rendermode .Init( this, "rendermode" ); + m_renderfx .Init( this, "renderfx" ); + m_renderscale.Init( this, "renderscale" ); + m_proxyRadius.Init( this, "proxyRadius" ); + m_position .Init( this, "position" ); + m_rotation .Init( this, "rotation" ); + m_color .Init( this, "color" ); +} + +void CDmeGameSpriteInput::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// Operator methods +//----------------------------------------------------------------------------- +bool CDmeGameSpriteInput::IsDirty() +{ + return true; // TODO - keep some bit of state that remembers when its changed +} + +void CDmeGameSpriteInput::Operate() +{ +} + +void CDmeGameSpriteInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_visible.GetAttribute() ); + attrs.AddToTail( m_frame.GetAttribute() ); + attrs.AddToTail( m_rendermode.GetAttribute() ); + attrs.AddToTail( m_renderfx.GetAttribute() ); + attrs.AddToTail( m_renderscale.GetAttribute() ); + attrs.AddToTail( m_proxyRadius.GetAttribute() ); + attrs.AddToTail( m_position.GetAttribute() ); + attrs.AddToTail( m_rotation.GetAttribute() ); + attrs.AddToTail( m_color.GetAttribute() ); +} + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- + +void CDmeGameSpriteInput::SetState( bool bVisible, float nFrame, int nRenderMode, int nRenderFX, float flRenderScale, float flProxyRadius, + const Vector &pos, const Quaternion &rot, const Color &color ) +{ + m_visible = bVisible; + m_frame = nFrame; + m_rendermode = nRenderMode; + m_renderfx = nRenderFX; + m_renderscale = flRenderScale; + m_proxyRadius = flProxyRadius; + m_position = pos; + m_rotation = rot; + m_color = color; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGameCameraInput, CDmeGameCameraInput ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeGameCameraInput::OnConstruction() +{ + m_position.Init( this, "position" ); + m_rotation.Init( this, "rotation" ); + m_fov.Init( this, "fov" ); +} + +void CDmeGameCameraInput::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// Operator methods +//----------------------------------------------------------------------------- +bool CDmeGameCameraInput::IsDirty() +{ + return true; // TODO - keep some bit of state that remembers when its changed +} + +void CDmeGameCameraInput::Operate() +{ +} + +void CDmeGameCameraInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_position.GetAttribute() ); + attrs.AddToTail( m_rotation.GetAttribute() ); + attrs.AddToTail( m_fov.GetAttribute() ); +} + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +void CDmeGameCameraInput::SetPosition( const Vector& pos ) +{ + m_position.Set( pos ); +} + +void CDmeGameCameraInput::SetOrientation( const Quaternion& rot ) +{ + m_rotation.Set( rot ); +} + +void CDmeGameCameraInput::SetFOV( float fov ) +{ + m_fov = fov; +} diff --git a/movieobjects/dmeimage.cpp b/movieobjects/dmeimage.cpp new file mode 100644 index 0000000..2b972dc --- /dev/null +++ b/movieobjects/dmeimage.cpp @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeimage.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "bitmap/imageformat.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeImage, CDmeImage ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeImage::OnConstruction() +{ + m_Width.Init( this, "width" ); + m_Height.Init( this, "height" ); + m_Format.Init( this, "format" ); + m_Bits.Init( this, "bits" ); +} + +void CDmeImage::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Image format +//----------------------------------------------------------------------------- +ImageFormat CDmeImage::Format() const +{ + return (ImageFormat)( m_Format.Get() ); +} + +const char *CDmeImage::FormatName() const +{ + return ImageLoader::GetName( Format() ); +} + diff --git a/movieobjects/dmeinput.cpp b/movieobjects/dmeinput.cpp new file mode 100644 index 0000000..838082b --- /dev/null +++ b/movieobjects/dmeinput.cpp @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeinput.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ABSTRACT_ELEMENT( DmeInput, CDmeInput ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeInput::OnConstruction() +{ +} + +void CDmeInput::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// IsDirty - ie needs to operate +//----------------------------------------------------------------------------- +bool CDmeInput::IsDirty() +{ + return true; +} diff --git a/movieobjects/dmejoint.cpp b/movieobjects/dmejoint.cpp new file mode 100644 index 0000000..feb3f16 --- /dev/null +++ b/movieobjects/dmejoint.cpp @@ -0,0 +1,151 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Dme version of a joint of a skeletal model (gets compiled into a MDL) +// +//============================================================================= +#include "movieobjects/dmejoint.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imesh.h" +#include "tier1/KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeJoint, CDmeJoint ); + + +//----------------------------------------------------------------------------- +// Should I draw joints? +//----------------------------------------------------------------------------- +bool CDmeJoint::s_bDrawJoints = false; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeJoint::OnConstruction() +{ + if ( !g_pMaterialSystem ) + return; + + KeyValues *pVMTKeyValues = new KeyValues( "wireframe" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + pVMTKeyValues->SetInt( "$ignorez", 1 ); + m_JointMaterial.Init( "__DmeJointMaterial", pVMTKeyValues ); +} + +void CDmeJoint::OnDestruction() +{ + if ( !g_pMaterialSystem ) + return; + + m_JointMaterial.Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Activate, deactivate joint drawing +//----------------------------------------------------------------------------- +void CDmeJoint::DrawJointHierarchy( bool bDrawJoints ) +{ + s_bDrawJoints = bDrawJoints; +} + + +//----------------------------------------------------------------------------- +// For rendering joints +//----------------------------------------------------------------------------- +#define AXIS_SIZE 3.0f + +void CDmeJoint::DrawJoints( ) +{ + if ( !g_pMaterialSystem ) + return; + + int cn = GetChildCount(); + + // Draw the joint hierarchy + PushDagTransform(); + matrix3x4_t shapeToWorld; + GetShapeToWorldTransform( shapeToWorld ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadMatrix( shapeToWorld ); + + pRenderContext->Bind( m_JointMaterial ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 3 + cn ); + + for ( int ci = 0; ci < cn; ++ci ) + { + CDmeJoint *pJoint = CastElement<CDmeJoint>( GetChild( ci ) ); + if ( !pJoint ) + continue; + + Vector vecChildPosition = pJoint->GetTransform()->GetPosition(); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 128, 128, 128, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( vecChildPosition.Base() ); + meshBuilder.Color4ub( 128, 128, 128, 255 ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( AXIS_SIZE, 0.0f, 0.0f ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, AXIS_SIZE, 0.0f ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( 0.0f, 0.0f, AXIS_SIZE ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadIdentity(); + + PopDagTransform(); +} + + +//----------------------------------------------------------------------------- +// Rendering method for the dag +//----------------------------------------------------------------------------- +void CDmeJoint::Draw( CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + if ( s_bDrawJoints && IsVisible() ) + { + DrawJoints(); + } + + BaseClass::Draw( pDrawSettings ); +} + + diff --git a/movieobjects/dmekeyboardinput.cpp b/movieobjects/dmekeyboardinput.cpp new file mode 100644 index 0000000..0fcea71 --- /dev/null +++ b/movieobjects/dmekeyboardinput.cpp @@ -0,0 +1,141 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmekeyboardinput.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" + +#include "vgui/iinput.h" +#include "vgui/keycode.h" +#include "tier3/tier3.h" + +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// global list of all keys supported +//----------------------------------------------------------------------------- +struct KeyInfo +{ + vgui::KeyCode code; + const char *str; +}; + +const uint g_nKeys = 48; +const KeyInfo g_keyInfo[ g_nKeys ] = +{ + { KEY_0, "0" }, + { KEY_1, "1" }, + { KEY_2, "2" }, + { KEY_3, "3" }, + { KEY_4, "4" }, + { KEY_5, "5" }, + { KEY_6, "6" }, + { KEY_7, "7" }, + { KEY_8, "8" }, + { KEY_9, "9" }, + { KEY_A, "A" }, + { KEY_B, "B" }, + { KEY_C, "C" }, + { KEY_D, "D" }, + { KEY_E, "E" }, + { KEY_F, "F" }, + { KEY_G, "G" }, + { KEY_H, "H" }, + { KEY_I, "I" }, + { KEY_J, "J" }, + { KEY_K, "K" }, + { KEY_L, "L" }, + { KEY_M, "M" }, + { KEY_N, "N" }, + { KEY_O, "O" }, + { KEY_P, "P" }, + { KEY_Q, "Q" }, + { KEY_R, "R" }, + { KEY_S, "S" }, + { KEY_T, "T" }, + { KEY_U, "U" }, + { KEY_V, "V" }, + { KEY_W, "W" }, + { KEY_X, "X" }, + { KEY_Y, "Y" }, + { KEY_Z, "Z" }, + { KEY_F1, "F1" }, + { KEY_F2, "F2" }, + { KEY_F3, "F3" }, + { KEY_F4, "F4" }, + { KEY_F5, "F5" }, + { KEY_F6, "F6" }, + { KEY_F7, "F7" }, + { KEY_F8, "F8" }, + { KEY_F9, "F9" }, + { KEY_F10, "F10" }, + { KEY_F11, "F11" }, + { KEY_F12, "F12" }, +}; + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeKeyboardInput, CDmeKeyboardInput ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeKeyboardInput::OnConstruction() +{ + m_keys = new CDmaVar< bool >[ g_nKeys ]; + + for ( uint ki = 0; ki < g_nKeys; ++ki ) + { + m_keys[ ki ].Init( this, g_keyInfo[ ki ].str ); + } +} + +void CDmeKeyboardInput::OnDestruction() +{ + delete[] m_keys; +} + +bool CDmeKeyboardInput::IsDirty() +{ + for ( uint ki = 0; ki < g_nKeys; ++ki ) + { + if ( m_keys[ ki ].Get() != GetKeyStatus( ki ) ) + return true; + } + return false; +} + +void CDmeKeyboardInput::Operate() +{ + for ( uint ki = 0; ki < g_nKeys; ++ki ) + { + m_keys[ ki ].Set( GetKeyStatus( ki ) ); + } +} + +void CDmeKeyboardInput::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ +} + +void CDmeKeyboardInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + for ( uint ki = 0; ki < g_nKeys; ++ki ) + { + attrs.AddToTail( m_keys[ ki ].GetAttribute() ); + } +} + +bool CDmeKeyboardInput::GetKeyStatus( uint ki ) +{ + return g_pVGuiInput->IsKeyDown( g_keyInfo[ ki ].code ); +} diff --git a/movieobjects/dmelight.cpp b/movieobjects/dmelight.cpp new file mode 100644 index 0000000..61de774 --- /dev/null +++ b/movieobjects/dmelight.cpp @@ -0,0 +1,331 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmelight.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "mathlib/vector.h" +#include "movieobjects/dmetransform.h" +#include "materialsystem/imaterialsystem.h" +#include "movieobjects_interfaces.h" +#include "tier2/tier2.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeLight, CDmeLight ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeLight::OnConstruction() +{ + m_Color.InitAndSet( this, "color", Color( 255, 255, 255, 255 ), FATTRIB_HAS_CALLBACK ); + m_flIntensity.InitAndSet( this, "intensity", 1.0f, FATTRIB_HAS_CALLBACK ); +} + +void CDmeLight::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the color and intensity +// NOTE: Color is specified 0-255 floating point. +//----------------------------------------------------------------------------- +void CDmeLight::SetColor( const Color &color ) +{ + m_Color.Set( color ); +} + +void CDmeLight::SetIntensity( float flIntensity ) +{ + m_flIntensity = flIntensity; +} + + +//----------------------------------------------------------------------------- +// Sets up render state in the material system for rendering +//----------------------------------------------------------------------------- +void CDmeLight::SetupRenderStateInternal( LightDesc_t &desc, float flAtten0, float flAtten1, float flAtten2 ) +{ + desc.m_Color[0] = m_Color.Get().r(); + desc.m_Color[1] = m_Color.Get().g(); + desc.m_Color[2] = m_Color.Get().b(); + desc.m_Color *= m_flIntensity / 255.0f; + + desc.m_Attenuation0 = flAtten0; + desc.m_Attenuation1 = flAtten1; + desc.m_Attenuation2 = flAtten2; + + desc.m_Flags = 0; + if ( desc.m_Attenuation0 != 0.0f ) + { + desc.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0; + } + if ( desc.m_Attenuation1 != 0.0f ) + { + desc.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1; + } + if ( desc.m_Attenuation2 != 0.0f ) + { + desc.m_Flags |= LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2; + } +} + + +//----------------------------------------------------------------------------- +// Sets lighting state +//----------------------------------------------------------------------------- +void CDmeLight::SetupRenderState( int nLightIndex ) +{ + LightDesc_t desc; + if ( GetLightDesc( &desc ) ) + { + // FIXME: Should we pass the light color in? + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->SetLight( nLightIndex, desc ); + } +} + + + +//----------------------------------------------------------------------------- +// +// A directional light +// +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeDirectionalLight, CDmeDirectionalLight ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeDirectionalLight::OnConstruction() +{ + m_Direction.InitAndSet( this, "direction", Vector( 0.0f, 0.0f, -1.0f ), FATTRIB_HAS_CALLBACK ); +} + +void CDmeDirectionalLight::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the light direction +//----------------------------------------------------------------------------- +void CDmeDirectionalLight::SetDirection( const Vector &direction ) +{ + m_Direction.Set( direction ); +} + + +//----------------------------------------------------------------------------- +// Gets a light desc for the light +//----------------------------------------------------------------------------- +bool CDmeDirectionalLight::GetLightDesc( LightDesc_t *pDesc ) +{ + memset( pDesc, 0, sizeof(LightDesc_t) ); + + pDesc->m_Type = MATERIAL_LIGHT_DIRECTIONAL; + SetupRenderStateInternal( *pDesc, 1.0f, 0.0f, 0.0f ); + + matrix3x4_t m; + GetTransform()->GetTransform( m ); + VectorRotate( m_Direction.Get(), m, pDesc->m_Direction ); + VectorNormalize( pDesc->m_Direction ); + + pDesc->m_Theta = 0.0f; + pDesc->m_Phi = 0.0f; + pDesc->m_Falloff = 1.0f; + + return true; +} + + +//----------------------------------------------------------------------------- +// +// A point light +// +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePointLight, CDmePointLight ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmePointLight::OnConstruction() +{ + m_Position.InitAndSet( this, "position", Vector( 0, 0, 0 ), FATTRIB_HAS_CALLBACK ); + m_flAttenuation0.InitAndSet( this, "constantAttenuation", 1.0f, FATTRIB_HAS_CALLBACK ); + m_flAttenuation1.InitAndSet( this, "linearAttenuation", 0.0f, FATTRIB_HAS_CALLBACK ); + m_flAttenuation2.InitAndSet( this, "quadraticAttenuation", 0.0f, FATTRIB_HAS_CALLBACK ); + m_flMaxDistance.InitAndSet( this, "maxDistance", 0.0f, FATTRIB_HAS_CALLBACK ); +} + +void CDmePointLight::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the attenuation factors +//----------------------------------------------------------------------------- +void CDmePointLight::SetAttenuation( float flConstant, float flLinear, float flQuadratic ) +{ + m_flAttenuation0 = flConstant; + m_flAttenuation1 = flLinear; + m_flAttenuation2 = flQuadratic; +} + + +//----------------------------------------------------------------------------- +// Sets the maximum range +//----------------------------------------------------------------------------- +void CDmePointLight::SetMaxDistance( float flMaxDistance ) +{ + m_flMaxDistance = flMaxDistance; +} + + +//----------------------------------------------------------------------------- +// Sets up render state in the material system for rendering +//----------------------------------------------------------------------------- +bool CDmePointLight::GetLightDesc( LightDesc_t *pDesc ) +{ + memset( pDesc, 0, sizeof(LightDesc_t) ); + + pDesc->m_Type = MATERIAL_LIGHT_POINT; + SetupRenderStateInternal( *pDesc, m_flAttenuation0, m_flAttenuation1, m_flAttenuation2 ); + + matrix3x4_t m; + GetTransform()->GetTransform( m ); + VectorTransform( m_Position, m, pDesc->m_Position ); + pDesc->m_Direction.Init( 0, 0, 1 ); + pDesc->m_Range = m_flMaxDistance; + + pDesc->m_Theta = 0.0f; + pDesc->m_Phi = 0.0f; + pDesc->m_Falloff = 1.0f; + + return true; +} + + +//----------------------------------------------------------------------------- +// +// A spot light +// +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSpotLight, CDmeSpotLight ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSpotLight::OnConstruction() +{ + m_Direction.InitAndSet( this, "direction", Vector( 0.0f, 0.0f, -1.0f ) ); + m_flSpotInnerAngle.InitAndSet( this, "spotInnerAngle", 60.0f ); + m_flSpotOuterAngle.InitAndSet( this, "spotOuterAngle", 90.0f ); + m_flSpotAngularFalloff.InitAndSet( this, "spotAngularFalloff", 1.0f ); +} + +void CDmeSpotLight::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the light direction +//----------------------------------------------------------------------------- +void CDmeSpotLight::SetDirection( const Vector &direction ) +{ + m_Direction = direction; +} + + +//----------------------------------------------------------------------------- +// Sets the spotlight angle factors +// Angles are specified in degrees, as full angles (as opposed to half-angles) +//----------------------------------------------------------------------------- +void CDmeSpotLight::SetAngles( float flInnerAngle, float flOuterAngle, float flAngularFalloff ) +{ + m_flSpotInnerAngle = flInnerAngle; + m_flSpotOuterAngle = flOuterAngle; + m_flSpotAngularFalloff = flAngularFalloff; +} + + +//----------------------------------------------------------------------------- +// Sets up render state in the material system for rendering +//----------------------------------------------------------------------------- +bool CDmeSpotLight::GetLightDesc( LightDesc_t *pDesc ) +{ + memset( pDesc, 0, sizeof(LightDesc_t) ); + + pDesc->m_Type = MATERIAL_LIGHT_SPOT; + SetupRenderStateInternal( *pDesc, m_flAttenuation0, m_flAttenuation1, m_flAttenuation2 ); + + matrix3x4_t m; + GetTransform()->GetTransform( m ); + VectorTransform( m_Position, m, pDesc->m_Position ); + VectorRotate( m_Direction.Get(), m, pDesc->m_Direction ); + VectorNormalize( pDesc->m_Direction ); + pDesc->m_Range = m_flMaxDistance; + + // Convert to radians + pDesc->m_Theta = m_flSpotInnerAngle * M_PI / 180.0f; + pDesc->m_Phi = m_flSpotOuterAngle * M_PI / 180.0f; + pDesc->m_Falloff = m_flSpotAngularFalloff; + + return true; +} + + +//----------------------------------------------------------------------------- +// +// An ambient light +// +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeAmbientLight, CDmeAmbientLight ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeAmbientLight::OnConstruction() +{ +} + +void CDmeAmbientLight::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets up render state in the material system for rendering +//----------------------------------------------------------------------------- +void CDmeAmbientLight::SetupRenderState( int nLightIndex ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + Vector4D cube[6]; + + Vector4D vec4color( m_Color.Get().r(), m_Color.Get().g(), m_Color.Get().b(), m_Color.Get().a() ); + Vector4DMultiply( vec4color, m_flIntensity / 255.0f, cube[0] ); + cube[1] = cube[0]; + cube[2] = cube[0]; + cube[3] = cube[0]; + cube[4] = cube[0]; + cube[5] = cube[0]; + + pRenderContext->SetAmbientLightCube( cube ); +} + diff --git a/movieobjects/dmelog.cpp b/movieobjects/dmelog.cpp new file mode 100644 index 0000000..6eb5e51 --- /dev/null +++ b/movieobjects/dmelog.cpp @@ -0,0 +1,6209 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmelog.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmehandle.h" +#include "vstdlib/random.h" + +#include "tier0/dbg.h" + +#include <limits.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LayerSelectionData_t::DataLayer_t::DataLayer_t( float frac, CDmeLogLayer *layer ) : + m_flStartFraction( frac ) +{ + m_hData = layer; +} + +LayerSelectionData_t::LayerSelectionData_t() : + m_DataType( AT_UNKNOWN ), + m_nDuration( 0 ), + m_tStartOffset( DMETIME_ZERO ) +{ + m_nHoldTimes[ 0 ] = m_nHoldTimes[ 1 ] = 0; +} + +void LayerSelectionData_t::Release() +{ + for ( int i = 0; i < m_vecData.Count(); ++i ) + { + DataLayer_t *dl = &m_vecData[ i ]; + if ( dl->m_hData.Get() ) + { + g_pDataModel->DestroyElement( dl->m_hData->GetHandle() ); + } + } + m_vecData.Purge(); +} + +//----------------------------------------------------------------------------- +// Interpolatable types +//----------------------------------------------------------------------------- +inline bool IsInterpolableType( DmAttributeType_t type ) +{ + return ( type == AT_FLOAT ) || + ( type == AT_COLOR ) || + ( type == AT_VECTOR2 ) || + ( type == AT_VECTOR3 ) || + ( type == AT_QANGLE ) || + ( type == AT_QUATERNION ); +} + +static Vector s_pInterolationPoints[ 4 ] = +{ + Vector( 0.0f, 0.0f, 0.0f ), + Vector( 0.0f, 0.0f, 0.0f ), + Vector( 1.0f, 1.0f, 0.0f ), + Vector( 1.0f, 1.0f, 0.0f ) +}; + +static inline float ComputeInterpolationFactor( float flFactor, int nInterpolatorType ) +{ + Vector out; + Interpolator_CurveInterpolate + ( + nInterpolatorType, + s_pInterolationPoints[ 0 ], // unused + s_pInterolationPoints[ 1 ], + s_pInterolationPoints[ 2 ], + s_pInterolationPoints[ 3 ], // unused + flFactor, + out + ); + return out.y; // clamp( out.y, 0.0f, 1.0f ); +} + +float DmeLog_TimeSelection_t::AdjustFactorForInterpolatorType( float flFactor, int nSide ) const +{ + return ComputeInterpolationFactor( flFactor, m_nFalloffInterpolatorTypes[ nSide ] ); +} + +//----------------------------------------------------------------------------- +// NOTE: See DmeTimeSelectionTimes_t for return values, -1 means before, TS_TIME_COUNT means after +//----------------------------------------------------------------------------- +static inline int ComputeRegionForTime( DmeTime_t t, const DmeTime_t *pRegionTimes ) +{ + if ( t >= pRegionTimes[TS_LEFT_HOLD] ) + { + if ( t <= pRegionTimes[TS_RIGHT_HOLD] ) + return 2; + return ( t <= pRegionTimes[TS_RIGHT_FALLOFF] ) ? 3 : 4; + } + return ( t >= pRegionTimes[TS_LEFT_FALLOFF] ) ? 1 : 0; +} + + +//----------------------------------------------------------------------------- +// NOTE: See DmeTimeSelectionTimes_t for return values, -1 means before, TS_TIME_COUNT means after +//----------------------------------------------------------------------------- +int DmeLog_TimeSelection_t::ComputeRegionForTime( DmeTime_t curtime ) const +{ + return ::ComputeRegionForTime( curtime, m_nTimes ); +} + + +//----------------------------------------------------------------------------- +// per-type averaging methods +//----------------------------------------------------------------------------- +float DmeLog_TimeSelection_t::GetAmountForTime( DmeTime_t dmetime ) const +{ + float minfrac = 0.0f; + + float t = dmetime.GetSeconds(); + + // FIXME, this is slow, we should cache this maybe? + COMPILE_TIME_ASSERT( TS_TIME_COUNT == 4 ); + float times[ TS_TIME_COUNT ]; + times[ 0 ] = m_nTimes[ 0 ].GetSeconds(); + times[ 1 ] = m_nTimes[ 1 ].GetSeconds(); + times[ 2 ] = m_nTimes[ 2 ].GetSeconds(); + times[ 3 ] = m_nTimes[ 3 ].GetSeconds(); + + float dt1, dt2; + dt1 = times[ 1 ] - times[ 0 ]; + dt2 = times[ 3 ] - times[ 2 ]; + + if ( dt1 > 0.0f && t >= times[ 0 ] && t < times[ 1 ] ) + { + float f = ( t - times[ 0 ] ) / dt1; + + Vector out; + + Interpolator_CurveInterpolate + ( + m_nFalloffInterpolatorTypes[ 0 ], + s_pInterolationPoints[ 0 ], // unused + s_pInterolationPoints[ 1 ], + s_pInterolationPoints[ 2 ], + s_pInterolationPoints[ 3 ], // unused + f, + out + ); + return clamp( out.y, minfrac, 1.0f ); + } + + if ( t >= times[ 1 ] && t <= times[ 2 ] ) + return 1.0f; + + if ( dt2 > 0.0f && t > times[ 2 ] && t <= times[ 3 ] ) + { + float f = ( times[ 3 ] - t ) / dt2; + + Vector out; + + Interpolator_CurveInterpolate + ( + m_nFalloffInterpolatorTypes[ 1 ], + s_pInterolationPoints[ 0 ], // unused + s_pInterolationPoints[ 1 ], + s_pInterolationPoints[ 2 ], + s_pInterolationPoints[ 3 ], // unused + f, + out + ); + return clamp( out.y, minfrac, 1.0f ); + } + + return minfrac; +} + +// catch-all for non-interpolable types - just holds first value +template < class T > +T Average( const T *pValues, int nValues) +{ + if ( IsInterpolableType( CDmAttributeInfo< T >::AttributeType() ) ) + { + static bool first = true; + if ( first ) + { + first = false; + Warning( "CDmeLog: interpolable type %s doesn't have an averaging function!", CDmAttributeInfo< T >::AttributeTypeName() ); + } + } + + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return T(); // uninitialized for most value classes!!! + + return pValues[ 0 ]; +} + +// float version +template <> +float Average( const float *pValues, int nValues ) +{ + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return 0.0f; + + float sum = 0.0f; + for ( int i = 0; i < nValues; ++i ) + { + sum += pValues[ i ]; + } + return sum / nValues; +} + +// Color version +template <> +Color Average( const Color *pValues, int nValues ) +{ + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return Color( 0, 0, 0, 0 ); + + float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f; + for ( int i = 0; i < nValues; ++i ) + { + r += pValues[ i ].r(); + g += pValues[ i ].g(); + b += pValues[ i ].b(); + a += pValues[ i ].a(); + } + float inv = nValues; + return Color( r * inv, g * inv, b * inv, a * inv ); +} + +// Vector2 version +template <> +Vector2D Average( const Vector2D *pValues, int nValues ) +{ + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return Vector2D( 0.0f, 0.0f ); + + Vector2D sum( 0.0f, 0.0f ); + for ( int i = 0; i < nValues; ++i ) + { + sum += pValues[ i ]; + } + return sum / nValues; +} + +// Vector3 version +template <> +Vector Average( const Vector *pValues, int nValues ) +{ + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return Vector( 0.0f, 0.0f, 0.0f ); + + Vector sum( 0.0f, 0.0f, 0.0f ); + for ( int i = 0; i < nValues; ++i ) + { + sum += pValues[ i ]; + } + return sum / nValues; +} + +// QAngle version +template <> +QAngle Average( const QAngle *pValues, int nValues ) +{ + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return QAngle( 0.0f, 0.0f, 0.0f ); + + Quaternion ave; + AngleQuaternion( pValues[ 0 ], ave ); + + // this is calculating the average by slerping with decreasing weights + // for example: ave = 1/3 * q2 + 2/3 ( 1/2 * q1 + 1/2 * q0 ) + for ( int i = 1; i < nValues; ++i ) + { + Quaternion quat; + AngleQuaternion( pValues[ i ], quat ); + QuaternionSlerp( ave, quat, 1 / float( i + 1 ), ave ); + } + + QAngle qangle; + QuaternionAngles( ave, qangle ); + return qangle; +} + +// Quaternion version +template <> +Quaternion Average( const Quaternion *pValues, int nValues ) +{ + Assert( nValues > 0 ); + if ( nValues <= 0 ) + return Quaternion( 0.0f, 0.0f, 0.0f, 1.0f ); + + Quaternion ave = pValues[ 0 ]; + + // this is calculating the average by slerping with decreasing weights + // for example: ave = 1/3 * q2 + 2/3 ( 1/2 * q1 + 1/2 * q0 ) + for ( int i = 1; i < nValues; ++i ) + { + QuaternionSlerp( ave, pValues[ i ], 1 / float( i + 1 ), ave ); + } + + return ave; +} + + + +//----------------------------------------------------------------------------- +// per-type interpolation methods +//----------------------------------------------------------------------------- + +// catch-all for non-interpolable types - just holds first value +template < class T > +T Interpolate( float t, const T& ti, const T& tj ) +{ + if ( IsInterpolableType( CDmAttributeInfo< T >::AttributeType() ) ) + { + static bool first = true; + if ( first ) + { + first = false; + Warning( "CDmeLog: interpolable type %s doesn't have an interpolation function!", CDmAttributeInfo< T >::AttributeTypeName() ); + } + } + + return ti; +} + +// float version +template <> +float Interpolate( float t, const float& ti, const float& tj ) +{ + return t * tj + (1.0f - t) * ti; +} + +// Color version +template <> +Color Interpolate( float t, const Color& ti, const Color& tj ) +{ + int ri, gi, bi, ai; + int rj, gj, bj, aj; + + ti.GetColor( ri, gi, bi, ai ); + tj.GetColor( rj, gj, bj, aj ); + + return Color( t * rj + (1.0f - t) * ri, + t * gj + (1.0f - t) * gi, + t * bj + (1.0f - t) * bi, + t * aj + (1.0f - t) * ai); +} + +// Vector2 version +template <> +Vector2D Interpolate( float t, const Vector2D& ti, const Vector2D& tj ) +{ + return t * tj + (1.0f - t) * ti; +} + +// Vector3 version +template <> +Vector Interpolate( float t, const Vector& ti, const Vector& tj ) +{ + return t * tj + (1.0f - t) * ti; +} + +// QAngle version +template <> +QAngle Interpolate( float t, const QAngle& ti, const QAngle& tj ) +{ + QAngle qaResult; + Quaternion q, qi, qj; // Some Quaternion temps for doing the slerp + + AngleQuaternion( ti, qi ); // Convert QAngles to Quaternions + AngleQuaternion( tj, qj ); + QuaternionSlerp( qi, qj, t, q ); // Do a slerp as Quaternions + QuaternionAngles( q, qaResult ); // Convert back to QAngles + return qaResult; +} + +// Quaternion version +template <> +Quaternion Interpolate( float t, const Quaternion& ti, const Quaternion& tj ) +{ + static Quaternion s_value; + QuaternionSlerp( ti, tj, t, s_value ); + return s_value; +} + +// catch-all for non-interpolable types - just holds first value +template < class T > +T Curve_Interpolate( float t, DmeTime_t times[ 4 ], const T values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) +{ + if ( IsInterpolableType( CDmAttributeInfo< T >::AttributeType() ) ) + { + static bool first = true; + if ( first ) + { + first = false; + Warning( "CDmeLog: interpolable type %s doesn't have an interpolation function!", CDmAttributeInfo< T >::AttributeTypeName() ); + } + } + + return t; +} + +// float version +template <> +float Curve_Interpolate( float t, DmeTime_t times[ 4 ], const float values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) +{ + Vector args[ 4 ]; + for ( int i = 0; i < 4; ++i ) + { + args[ i ].Init( times[ i ].GetSeconds(), values[ i ], 0.0f ); + } + + Vector vOut; + int dummy; + int earlypart, laterpart; + + // Not holding out value of previous curve... + Interpolator_CurveInterpolatorsForType( curveTypes[ 1 ], dummy, earlypart ); + Interpolator_CurveInterpolatorsForType( curveTypes[ 2 ], laterpart, dummy ); + + if ( earlypart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + VectorLerp( args[ 1 ], args[ 2 ], t, vOut ); + vOut.y = args[ 1 ].y; + } + else if ( laterpart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + VectorLerp( args[ 1 ], args[ 2 ], t, vOut ); + vOut.y = args[ 2 ].y; + } + else + { + bool sameCurveType = earlypart == laterpart ? true : false; + if ( sameCurveType ) + { + Interpolator_CurveInterpolate( laterpart, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], t, vOut ); + } + else // curves differ, sigh + { + Vector vOut1, vOut2; + + Interpolator_CurveInterpolate( earlypart, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], t, vOut1 ); + Interpolator_CurveInterpolate( laterpart, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], t, vOut2 ); + + VectorLerp( vOut1, vOut2, t, vOut ); + } + } + + // FIXME: This means we can only work with curves that range from 0.0 to 1.0f!!! + float retval = clamp( vOut.y, fmin, fmax ); + return retval; +} + +// Vector version +template <> +Vector Curve_Interpolate( float t, DmeTime_t times[ 4 ], const Vector values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) +{ + Vector vOut; + int dummy; + int earlypart, laterpart; + + // Not holding out value of previous curve... + Interpolator_CurveInterpolatorsForType( curveTypes[ 1 ], dummy, earlypart ); + Interpolator_CurveInterpolatorsForType( curveTypes[ 2 ], laterpart, dummy ); + + if ( earlypart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + vOut = values[ 1 ]; + } + else if ( laterpart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + vOut = values[ 2 ]; + } + else + { + bool sameCurveType = earlypart == laterpart; + if ( sameCurveType ) + { + Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut ); + } + else // curves differ, sigh + { + Vector vOut1, vOut2; + + Interpolator_CurveInterpolate_NonNormalized( earlypart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut1 ); + Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut2 ); + + VectorLerp( vOut1, vOut2, t, vOut ); + } + } + + return vOut; +} + +// Quaternion version +template <> +Quaternion Curve_Interpolate( float t, DmeTime_t times[ 4 ], const Quaternion values[ 4 ], int curveTypes[ 4 ], float fmin, float fmax ) +{ + Quaternion vOut; + int dummy; + int earlypart, laterpart; + + // Not holding out value of previous curve... + Interpolator_CurveInterpolatorsForType( curveTypes[ 1 ], dummy, earlypart ); + Interpolator_CurveInterpolatorsForType( curveTypes[ 2 ], laterpart, dummy ); + + if ( earlypart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + vOut = values[ 1 ]; + } + else if ( laterpart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + vOut = values[ 2 ]; + } + else + { + bool sameCurveType = ( earlypart == laterpart ) ? true : false; + if ( sameCurveType ) + { + Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut ); + } + else // curves differ, sigh + { + Quaternion vOut1, vOut2; + + Interpolator_CurveInterpolate_NonNormalized( earlypart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut1 ); + Interpolator_CurveInterpolate_NonNormalized( laterpart, values[ 0 ], values[ 1 ], values[ 2 ], values[ 3 ], t, vOut2 ); + + QuaternionSlerp( vOut1, vOut2, t, vOut ); + } + } + + return vOut; +} + + +template< class T > +T ScaleValue( const T& value, float scale ) +{ + return value * scale; +} +template<> +bool ScaleValue( const bool& value, float scale ) +{ + Assert( 0 ); + return value; +} +template<> +Color ScaleValue( const Color& value, float scale ) +{ + Assert( 0 ); + return value; +} +template<> +Vector4D ScaleValue( const Vector4D& value, float scale ) +{ + return Vector4D( value.x * scale, value.y * scale, value.z * scale, value.w * scale ); +} + +template<> +Quaternion ScaleValue( const Quaternion& value, float scale ) +{ + return Quaternion( value.x * scale, value.y * scale, value.z * scale, value.w * scale ); +} + +template<> +VMatrix ScaleValue( const VMatrix& value, float scale ) +{ + Assert( 0 ); + return value; +} + +template<> +CUtlString ScaleValue( const CUtlString& value, float scale ) +{ + Assert( 0 ); + return value; +} + +template< class T > +float LengthOf( const T& value ) +{ + return value; +} + +template<> +float LengthOf( const bool& value ) +{ + if ( value ) + return 1.0f; + return 0.0f; +} +template<> +float LengthOf( const Color& value ) +{ + return (float)sqrt( (float)( value.r() * value.r() + + value.g() * value.g() + + value.b() * value.b() + + value.a() * value.a()) ); +} +template<> +float LengthOf( const Vector4D& value ) +{ + return sqrt( value.x * value.x + + value.y * value.y + + value.z * value.z + + value.w * value.w ); +} + +template<> +float LengthOf( const Quaternion& value ) +{ + return sqrt( value.x * value.x + + value.y * value.y + + value.z * value.z + + value.w * value.w ); +} + +template<> +float LengthOf( const VMatrix& value ) +{ + return 0.0f; +} + +template<> +float LengthOf( const CUtlString& value ) +{ + return 0.0f; +} + +template<> +float LengthOf( const Vector2D& value ) +{ + return value.Length(); +} + +template<> +float LengthOf( const Vector& value ) +{ + return value.Length(); +} + +template<> +float LengthOf( const QAngle& value ) +{ + return value.Length(); +} + +template< class T > +T Subtract( const T& v1, const T& v2 ) +{ + return v1 - v2; +} + +template<> +bool Subtract( const bool& v1, const bool& v2 ) +{ + return v1; +} + +template<> +CUtlString Subtract( const CUtlString& v1, const CUtlString& v2 ) +{ + return v1; +} + +template<> +Color Subtract( const Color& v1, const Color& v2 ) +{ + Color ret; + for ( int i = 0; i < 4; ++i ) + { + ret[ i ] = clamp( v1[ i ] - v2[ i ], 0, 255 ); + } + return ret; +} + +template<> +Vector4D Subtract( const Vector4D& v1, const Vector4D& v2 ) +{ + Vector4D ret; + for ( int i = 0; i < 4; ++i ) + { + ret[ i ] = v1[ i ] - v2[ i ]; + } + return ret; +} + +template<> +Quaternion Subtract( const Quaternion& v1, const Quaternion& v2 ) +{ + Quaternion ret; + for ( int i = 0; i < 4; ++i ) + { + ret[ i ] = v1[ i ]; + } + return ret; +} + +template< class T > +T Add( const T& v1, const T& v2 ) +{ + return v1 + v2; +} + +template<> +bool Add( const bool& v1, const bool& v2 ) +{ + return v1; +} + +template<> +CUtlString Add( const CUtlString& v1, const CUtlString& v2 ) +{ + return v1; +} + + +template<> +Color Add( const Color& v1, const Color& v2 ) +{ + Color ret; + for ( int i = 0; i < 4; ++i ) + { + ret[ i ] = clamp( v1[ i ] + v2[ i ], 0, 255 ); + } + return ret; +} + +template<> +Vector4D Add( const Vector4D& v1, const Vector4D& v2 ) +{ + Vector4D ret; + for ( int i = 0; i < 4; ++i ) + { + ret[ i ] = v1[ i ] + v2[ i ]; + } + return ret; +} + +template<> +Quaternion Add( const Quaternion& v1, const Quaternion& v2 ) +{ + return v1; +} + +IMPLEMENT_ABSTRACT_ELEMENT( DmeLogLayer, CDmeLogLayer ); + +IMPLEMENT_ELEMENT_FACTORY( DmeIntLogLayer, CDmeIntLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeFloatLogLayer, CDmeFloatLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeBoolLogLayer, CDmeBoolLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeColorLogLayer, CDmeColorLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector2LogLayer, CDmeVector2LogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector3LogLayer, CDmeVector3LogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector4LogLayer, CDmeVector4LogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeQAngleLogLayer, CDmeQAngleLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeQuaternionLogLayer, CDmeQuaternionLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeVMatrixLogLayer, CDmeVMatrixLogLayer ); +IMPLEMENT_ELEMENT_FACTORY( DmeStringLogLayer, CDmeStringLogLayer ); + +//----------------------------------------------------------------------------- +// explicit template instantiation +//----------------------------------------------------------------------------- +template class CDmeTypedLogLayer<int>; +template class CDmeTypedLogLayer<float>; +template class CDmeTypedLogLayer<bool>; +template class CDmeTypedLogLayer<Color>; +template class CDmeTypedLogLayer<Vector2D>; +template class CDmeTypedLogLayer<Vector>; +template class CDmeTypedLogLayer<Vector4D>; +template class CDmeTypedLogLayer<QAngle>; +template class CDmeTypedLogLayer<Quaternion>; +template class CDmeTypedLogLayer<VMatrix>; +template class CDmeTypedLogLayer<CUtlString>; + + +IMPLEMENT_ABSTRACT_ELEMENT( DmeCurveInfo, CDmeCurveInfo ); + +IMPLEMENT_ELEMENT_FACTORY( DmeIntCurveInfo, CDmeIntCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeFloatCurveInfo, CDmeFloatCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeBoolCurveInfo, CDmeBoolCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeColorCurveInfo, CDmeColorCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector2CurveInfo, CDmeVector2CurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector3CurveInfo, CDmeVector3CurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector4CurveInfo, CDmeVector4CurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeQAngleCurveInfo, CDmeQAngleCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeQuaternionCurveInfo, CDmeQuaternionCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeVMatrixCurveInfo, CDmeVMatrixCurveInfo ); +IMPLEMENT_ELEMENT_FACTORY( DmeStringCurveInfo, CDmeStringCurveInfo ); + +//----------------------------------------------------------------------------- +// explicit template instantiation +//----------------------------------------------------------------------------- +template class CDmeTypedCurveInfo<int>; +template class CDmeTypedCurveInfo<float>; +template class CDmeTypedCurveInfo<bool>; +template class CDmeTypedCurveInfo<Color>; +template class CDmeTypedCurveInfo<Vector2D>; +template class CDmeTypedCurveInfo<Vector>; +template class CDmeTypedCurveInfo<Vector4D>; +template class CDmeTypedCurveInfo<QAngle>; +template class CDmeTypedCurveInfo<Quaternion>; +template class CDmeTypedCurveInfo<VMatrix>; +template class CDmeTypedCurveInfo<CUtlString>; + + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- +IMPLEMENT_ABSTRACT_ELEMENT( DmeLog, CDmeLog ); + +IMPLEMENT_ELEMENT_FACTORY( DmeIntLog, CDmeIntLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeFloatLog, CDmeFloatLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeBoolLog, CDmeBoolLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeColorLog, CDmeColorLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector2Log, CDmeVector2Log ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector3Log, CDmeVector3Log ); +IMPLEMENT_ELEMENT_FACTORY( DmeVector4Log, CDmeVector4Log ); +IMPLEMENT_ELEMENT_FACTORY( DmeQAngleLog, CDmeQAngleLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeQuaternionLog, CDmeQuaternionLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeVMatrixLog, CDmeVMatrixLog ); +IMPLEMENT_ELEMENT_FACTORY( DmeStringLog, CDmeStringLog ); + + +//----------------------------------------------------------------------------- +// explicit template instantiation +//----------------------------------------------------------------------------- +template class CDmeTypedLog<int>; +template class CDmeTypedLog<float>; +template class CDmeTypedLog<bool>; +template class CDmeTypedLog<Color>; +template class CDmeTypedLog<Vector2D>; +template class CDmeTypedLog<Vector>; +template class CDmeTypedLog<Vector4D>; +template class CDmeTypedLog<QAngle>; +template class CDmeTypedLog<Quaternion>; +template class CDmeTypedLog<VMatrix>; +template class CDmeTypedLog<CUtlString>; + + +//----------------------------------------------------------------------------- +// instantiate and initialize static vars +//----------------------------------------------------------------------------- +float CDmeIntLog::s_defaultThreshold = 0.0f; +float CDmeFloatLog::s_defaultThreshold = 0.0f; +float CDmeBoolLog::s_defaultThreshold = 0.0f; +float CDmeColorLog::s_defaultThreshold = 0.0f; +float CDmeVector2Log::s_defaultThreshold = 0.0f; +float CDmeVector3Log::s_defaultThreshold = 0.0f; +float CDmeVector4Log::s_defaultThreshold = 0.0f; +float CDmeQAngleLog::s_defaultThreshold = 0.0f; +float CDmeQuaternionLog::s_defaultThreshold = 0.0f; +float CDmeVMatrixLog::s_defaultThreshold = 0.0f; +float CDmeStringLog::s_defaultThreshold = 0.0f; + + +void CDmeLogLayer::OnConstruction() +{ + m_pOwnerLog = NULL; + m_lastKey = 0; + m_times.Init( this, "times" ); + m_CurveTypes.Init( this, "curvetypes" ); +} + +void CDmeLogLayer::OnDestruction() +{ +} + +CDmeLog *CDmeLogLayer::GetOwnerLog() +{ + return m_pOwnerLog; +} + +const CDmeLog *CDmeLogLayer::GetOwnerLog() const +{ + return m_pOwnerLog; +} + +DmeTime_t CDmeLogLayer::GetBeginTime() const +{ + if ( m_times.Count() == 0 ) + return DmeTime_t::MinTime(); + + return DmeTime_t( m_times[ 0 ] ); +} + +DmeTime_t CDmeLogLayer::GetEndTime() const +{ + uint tn = m_times.Count(); + if ( tn == 0 ) + return DmeTime_t::MaxTime(); + + return DmeTime_t( m_times[ tn - 1 ] ); +} + + +// Validates that all keys are correctly sorted in time +bool CDmeLogLayer::ValidateKeys() const +{ + int nCount = m_times.Count(); + for ( int i = 1; i < nCount; ++i ) + { + if ( m_times[i] <= m_times[i-1] ) + { + Warning( "Error in log %s! Key times are out of order [keys %d->%d: %d->%d]!\n", + GetName(), i-1, i, m_times[i-1], m_times[i] ); + return false; + } + } + return true; +} + +int CDmeLogLayer::FindKey( DmeTime_t time ) const +{ + int tn = m_times.Count(); + if ( m_lastKey >= 0 && m_lastKey < tn ) + { + if ( time >= DmeTime_t( m_times[ m_lastKey ] ) ) + { + // common case - playing forward + for ( ; m_lastKey < tn - 1; ++m_lastKey ) + { + if ( time < DmeTime_t( m_times[ m_lastKey + 1 ] ) ) + return m_lastKey; + } + + // if time past the end, return the last key + return m_lastKey; + } + else + { + tn = m_lastKey; + } + } + + for ( int ti = tn - 1; ti >= 0; --ti ) + { + if ( time >= DmeTime_t( m_times[ ti ] ) ) + { + m_lastKey = ti; + return ti; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Returns the number of keys +//----------------------------------------------------------------------------- +int CDmeLogLayer::GetKeyCount() const +{ + return m_times.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : nKeyIndex - +// keyTime - +//----------------------------------------------------------------------------- +void CDmeLogLayer::SetKeyTime( int nKeyIndex, DmeTime_t keyTime ) +{ + m_times.Set( nKeyIndex, keyTime.GetTenthsOfMS() ); +} + +//----------------------------------------------------------------------------- +// Returns a specific key's value +//----------------------------------------------------------------------------- +DmeTime_t CDmeLogLayer::GetKeyTime( int nKeyIndex ) const +{ + return DmeTime_t( m_times[ nKeyIndex ] ); +} + + +//----------------------------------------------------------------------------- +// Scale + bias key times +//----------------------------------------------------------------------------- +void CDmeLogLayer::ScaleBiasKeyTimes( double flScale, DmeTime_t nBias ) +{ + // Don't waste time on the identity transform + if ( ( nBias == DMETIME_ZERO ) && ( fabs( flScale - 1.0 ) < 1e-5 ) ) + return; + + int nCount = GetKeyCount(); + for ( int i = 0; i < nCount; ++i ) + { + DmeTime_t t = GetKeyTime( i ); + t.SetSeconds( t.GetSeconds() * flScale ); + t += nBias; + SetKeyTime( i, t ); + } +} + + +//----------------------------------------------------------------------------- +// Returns the index of a particular key +//----------------------------------------------------------------------------- +int CDmeLogLayer::FindKeyWithinTolerance( DmeTime_t nTime, DmeTime_t nTolerance ) +{ + int nClosest = -1; + DmeTime_t nClosestTolerance = DmeTime_t::MaxTime(); + DmeTime_t nCurrTolerance; + int start = 0, end = GetKeyCount() - 1; + while ( start <= end ) + { + int mid = (start + end) >> 1; + DmeTime_t nDelta = nTime - DmeTime_t( m_times[mid] ); + if ( nDelta > DmeTime_t( 0 ) ) + { + nCurrTolerance = nDelta; + start = mid + 1; + } + else if ( nDelta < DmeTime_t( 0 ) ) + { + nCurrTolerance = -nDelta; + end = mid - 1; + } + else + { + return mid; + } + + if ( nCurrTolerance < nClosestTolerance ) + { + nClosest = mid; + nClosestTolerance = nCurrTolerance; + } + } + if ( nClosestTolerance > nTolerance ) + return -1; + return nClosest; +} + +void CDmeLogLayer::OnUsingCurveTypesChanged() +{ + if ( g_pDataModel->IsUnserializing() ) + return; + + if ( !IsUsingCurveTypes() ) + { + m_CurveTypes.RemoveAll(); + } + else + { + m_CurveTypes.RemoveAll(); + // Fill in an array with the default curve type for + int c = m_times.Count(); + for ( int i = 0; i < c; ++i ) + { + m_CurveTypes.AddToTail( GetDefaultCurveType() ); + } + } +} + +bool CDmeLogLayer::IsUsingCurveTypes() const +{ + return GetOwnerLog() ? GetOwnerLog()->IsUsingCurveTypes() : false; +} + +int CDmeLogLayer::GetDefaultCurveType() const +{ + return GetOwnerLog()->GetDefaultCurveType(); +} + +void CDmeLogLayer::SetKeyCurveType( int nKeyIndex, int curveType ) +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + return; + + Assert( GetOwnerLog()->IsUsingCurveTypes() ); + Assert( m_CurveTypes.IsValidIndex( nKeyIndex ) ); + if ( !m_CurveTypes.IsValidIndex( nKeyIndex ) ) + return; + + m_CurveTypes.Set( nKeyIndex, curveType ); +} + +int CDmeLogLayer::GetKeyCurveType( int nKeyIndex ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + return CURVE_DEFAULT; + + Assert( GetOwnerLog()->IsUsingCurveTypes() ); + Assert( m_CurveTypes.IsValidIndex( nKeyIndex ) ); + if ( !m_CurveTypes.IsValidIndex( nKeyIndex ) ) + return GetOwnerLog()->GetDefaultCurveType(); + + return m_CurveTypes[ nKeyIndex ]; +} + + +//----------------------------------------------------------------------------- +// Removes all keys outside the specified time range +//----------------------------------------------------------------------------- +void CDmeLogLayer::RemoveKeysOutsideRange( DmeTime_t tStart, DmeTime_t tEnd ) +{ + int i; + int nKeysToRemove = 0; + int nKeyCount = m_times.Count(); + for ( i = 0; i < nKeyCount; ++i, ++nKeysToRemove ) + { + if ( m_times[i] >= tStart.GetTenthsOfMS() ) + break; + } + + if ( nKeysToRemove ) + { + RemoveKey( 0, nKeysToRemove ); + } + + nKeyCount = m_times.Count(); + for ( i = 0; i < nKeyCount; ++i ) + { + if ( m_times[i] > tEnd.GetTenthsOfMS() ) + break; + } + nKeysToRemove = nKeyCount - i; + if ( nKeysToRemove ) + { + RemoveKey( i, nKeysToRemove ); + } +} + + +template < class T > +class CUndoLayerAdded : public CUndoElement +{ + typedef CUndoElement BaseClass; + +public: + CUndoLayerAdded( const char *desc, CDmeLog *pLog ) : + BaseClass( desc ), + m_bNeedsCleanup( false ), + m_hLog( pLog ) + { + Assert( pLog && pLog->GetFileId() != DMFILEID_INVALID ); + } + + virtual ~CUndoLayerAdded() + { + if ( m_bNeedsCleanup ) + { + g_pDataModel->DestroyElement( m_hLayer ); + } + } + + virtual void Undo() + { + m_bNeedsCleanup = true; + m_hLayer = m_hLog->RemoveLayerFromTail()->GetHandle(); + g_pDataModel->MarkHandleInvalid( m_hLayer ); + } + + virtual void Redo() + { + m_bNeedsCleanup = false; + g_pDataModel->MarkHandleValid( m_hLayer ); + m_hLog->AddLayerToTail( GetElement< CDmeTypedLogLayer< T > >( m_hLayer ) ); + } + + virtual const char *GetDesc() + { + static char sz[ 512 ]; + int iLayer = m_hLog->GetTopmostLayer(); + if ( iLayer >= 0 ) + { + CDmeLogLayer *layer = m_hLog->GetLayer( iLayer ); + Q_snprintf( sz, sizeof( sz ), "addlayer: log %p lc[%d], layer %p", + m_hLog.Get(), m_hLog->GetNumLayers(), layer ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "addlayer: log %p lc[%d], layer NULL", + m_hLog.Get(), m_hLog->GetNumLayers() ); + } + return sz; + } + +private: + CDmeHandle< CDmeLog > m_hLog; + bool m_bNeedsCleanup; + CDmeCountedHandle m_hLayer; +}; + +template < class T > +class CUndoFlattenLayers : public CUndoElement +{ + typedef CUndoElement BaseClass; + +public: + + CUndoFlattenLayers( const char *desc, CDmeTypedLog< T > *pLog, float threshold, int flags ) : + BaseClass( desc ), + m_bNeedsCleanup( true ), + m_hLog( pLog ), + m_nFlags( flags ), + m_flThreshold( threshold ) + { + Assert( pLog && pLog->GetFileId() != DMFILEID_INVALID ); + LatchCurrentLayers(); + } + + virtual ~CUndoFlattenLayers() + { + if ( m_bNeedsCleanup ) + { + for ( int i = 0; i < m_hLayers.Count(); ++i ) + { + m_hLayers[ i ] = DMELEMENT_HANDLE_INVALID; +#ifdef _DEBUG + CDmElement *pElement = g_pDataModel->GetElement( m_hLayers[ i ] ); + Assert( !pElement || pElement->IsStronglyReferenced() ); +#endif + } + } + } + + virtual void Undo() + { + m_bNeedsCleanup = false; + + for ( int i = 0; i < m_hLayers.Count(); ++i ) + { + if ( i == 0 ) + { + // Copy base layer in place so handles to the base layer remain valid + CDmeTypedLogLayer< T > *base = m_hLog->GetLayer( i ); + base->CopyLayer( GetElement< CDmeTypedLogLayer< T > >( m_hLayers[ i ] ) ); + // Release it since we didn't txfer it over + g_pDataModel->DestroyElement( m_hLayers[ i ] ); + } + else + { + // This transfers ownership, so no Release needed + m_hLog->AddLayerToTail( GetElement< CDmeTypedLogLayer< T > >( m_hLayers[ i ] ) ); + } + } + + m_hLayers.RemoveAll(); + } + + virtual void Redo() + { + m_bNeedsCleanup = true; + Assert( m_hLayers.Count() == 0 ); + + LatchCurrentLayers(); + // Flatten them again (won't create undo records since we're in undo already) + + m_hLog->FlattenLayers( m_flThreshold, m_nFlags ); + } + + virtual const char *GetDesc() + { + static char sz[ 512 ]; + Q_snprintf( sz, sizeof( sz ), "flatten log %p lc[%d]", + m_hLog.Get(), m_hLayers.Count() ); + return sz; + } + +private: + + void LatchCurrentLayers() + { + CDisableUndoScopeGuard guard; + Assert( m_hLayers.Count() == 0 ); + Assert( m_hLog->GetNumLayers() >= 1 ); + + // Entry 0 is the original "base" layer + for ( int i = 0; i < m_hLog->GetNumLayers(); ++i ) + { + CDmeTypedLogLayer< T > *pLayer = CastElement< CDmeTypedLogLayer< T > >( CreateLayer< T >( m_hLog ) ); + pLayer->CopyLayer( m_hLog->GetLayer( i ) ); + m_hLayers.AddToTail( pLayer->GetHandle() ); + } + } + + CDmeHandle< CDmeTypedLog< T > > m_hLog; + bool m_bNeedsCleanup; + CUtlVector< CDmeCountedHandle > m_hLayers; + int m_nFlags; + float m_flThreshold; +}; + +//----------------------------------------------------------------------------- +// CDmeTypedLogLayer - a generic typed layer used by a log +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLogLayer< T >::OnConstruction() +{ +// m_times.Init( this, "times" ); +// m_CurveTypes.Init( this, "curvetypes" ); + m_values.Init( this, "values" ); +} + +template< class T > +void CDmeTypedLogLayer< T >::SetOwnerLog( CDmeLog *owner ) +{ + Assert( owner ); + Assert( assert_cast< CDmeTypedLog< T > * >( owner ) ); + m_pOwnerLog = owner; +} + +template< class T > +CDmeTypedLog< T > *CDmeTypedLogLayer< T >::GetTypedOwnerLog() +{ + return assert_cast< CDmeTypedLog< T > * >( m_pOwnerLog ); +} + +template< class T > +const CDmeTypedLog< T > *CDmeTypedLogLayer< T >::GetTypedOwnerLog() const +{ + return assert_cast< CDmeTypedLog< T > * >( m_pOwnerLog ); +} + +template< class T > +void CDmeTypedLogLayer< T >::OnDestruction() +{ +} + +template< class T > +void CDmeTypedLogLayer< T >::RemoveKeys( DmeTime_t starttime ) +{ + int ti = FindKey( starttime ); + if ( ti < 0 ) + return; + + if ( starttime > DmeTime_t( m_times[ ti ] ) ) + ++ti; + + int nKeys = m_times.Count() - ti; + if ( nKeys == 0 ) + return; + + m_times.RemoveMultiple( ti, nKeys ); + m_values.RemoveMultiple( ti, nKeys ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.RemoveMultiple( ti, nKeys ); + } + + if ( m_lastKey >= ti && m_lastKey < ti + nKeys ) + { + m_lastKey = ( ti > 0 ) ? ti - 1 : 0; + } +} + +template< class T > +void CDmeTypedLogLayer< T >::ClearKeys() +{ + m_times.RemoveAll(); + m_values.RemoveAll(); + m_CurveTypes.RemoveAll(); + m_lastKey = 0; +} + +template< class T > +void CDmeTypedLogLayer< T >::RemoveKey( int nKeyIndex, int nNumKeysToRemove /*= 1*/ ) +{ + m_times.RemoveMultiple( nKeyIndex, nNumKeysToRemove ); + m_values.RemoveMultiple( nKeyIndex, nNumKeysToRemove ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.RemoveMultiple( nKeyIndex, nNumKeysToRemove ); + } +} + +//----------------------------------------------------------------------------- +// Sets a key, removes all keys after this time +// FIXME: This needs to account for interpolation!!! +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLogLayer< T >::SetKey( DmeTime_t time, const T& value, int curveType /*=CURVE_DEFAULT*/) +{ + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + + // Remove all keys after this time + RemoveKeys( time ); + + // Add the key and then check to see if the penultimate key is still necessary + m_times.AddToTail( time.GetTenthsOfMS() ); + m_values.AddToTail( value ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.AddToTail( curveType ); + } + + int nKeys = m_values.Count(); + if ( ( nKeys < 3 ) || + ( IsUsingCurveTypes() && ( curveType != m_CurveTypes[ nKeys -1 ] || ( curveType != m_CurveTypes[ nKeys - 2 ] ) ) ) + ) + { + return; + } + + // If adding the new means that the penultimate key's value was unneeded, then we will remove the penultimate key value + T check = GetValueSkippingKey( nKeys - 2 ); + T oldPenultimateValue = m_values[ nKeys - 2 ]; + if ( GetTypedOwnerLog()->ValuesDiffer( oldPenultimateValue, check ) ) + { + return; + } + + // Remove penultimate, it's not needed + m_times.Remove( nKeys - 2 ); + m_values.Remove( nKeys - 2 ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.Remove( nKeys - 2 ); + } +} + + +//----------------------------------------------------------------------------- +// Finds a key within tolerance, or adds one +//----------------------------------------------------------------------------- +template< class T > +int CDmeTypedLogLayer< T >::FindOrAddKey( DmeTime_t nTime, DmeTime_t nTolerance, const T& value, int curveType /*=CURVE_DEFAULT*/ ) +{ + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + + // NOTE: This math must occur in 64bits because the max delta nDelta + // can be 33 bits large. Bleah. + int nClosest = -1; + int64 nClosestTolerance = DmeTime_t::MinTime().GetTenthsOfMS(); + int64 nCurrTolerance; + int start = 0, end = GetKeyCount() - 1; + while ( start <= end ) + { + int mid = (start + end) >> 1; + int64 nDelta = (int64)nTime.GetTenthsOfMS() - (int64)m_times[mid]; + if ( nDelta > 0 ) + { + nCurrTolerance = nDelta; + start = mid + 1; + } + else if ( nDelta < 0 ) + { + nCurrTolerance = -nDelta; + end = mid - 1; + } + else + { + nClosest = end = mid; + nClosestTolerance = 0; + break; + } + + if ( nCurrTolerance < nClosestTolerance ) + { + nClosest = mid; + nClosestTolerance = nCurrTolerance; + } + } + + // At this point, end is the entry less than or equal to the entry + if ( nClosest == -1 || nTolerance.GetTenthsOfMS() < nClosestTolerance ) + { + ++end; + nClosest = m_times.InsertBefore( end, nTime.GetTenthsOfMS() ); + m_values.InsertBefore( end, value ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.InsertBefore( end, curveType ); + } + } + return nClosest; +} + + +//----------------------------------------------------------------------------- +// This inserts a key. Unlike SetKey, this will *not* delete keys after the specified time +//----------------------------------------------------------------------------- +template < class T > +int CDmeTypedLogLayer< T >::InsertKey( DmeTime_t nTime, const T& value, int curveType /*=CURVE_DEFAULT*/ ) +{ + int idx = FindOrAddKey( nTime, DmeTime_t( 0 ), value ); + m_times .Set( idx, nTime.GetTenthsOfMS() ); + m_values.Set( idx, value ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.Set( idx, curveType ); + } + return idx; +} + +template< class T > +int CDmeTypedLogLayer< T >::InsertKeyAtTime( DmeTime_t nTime, int curveType /*=CURVE_DEFAULT*/ ) +{ + T curVal = GetValue( nTime ); + return InsertKey( nTime, curVal, curveType ); +} + +static bool CanInterpolateType( DmAttributeType_t attType ) +{ + switch ( attType ) + { + default: + return false; + case AT_FLOAT: + case AT_VECTOR3: + case AT_QUATERNION: + break; + } + return true; +} + +template< class T > +const T& CDmeTypedLogLayer< T >::GetValue( DmeTime_t time ) const +{ + // Curve Interpolation only for 1-D float data right now!!! + if ( IsUsingCurveTypes() && + CanInterpolateType( GetDataType() ) ) + { + static T out; + GetValueUsingCurveInfo( time, out ); + return out; + } + + int tc = m_times.Count(); + + Assert( m_values.Count() == tc ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == tc ) ); + + int ti = FindKey( time ); + if ( ti < 0 ) + { + if ( tc > 0 ) + return m_values[ 0 ]; + + const CDmeTypedLog< T > *pOwner = GetTypedOwnerLog(); + if ( pOwner->HasDefaultValue() ) + return pOwner->GetDefaultValue(); + + static T s_value; + CDmAttributeInfo< T >::SetDefaultValue( s_value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return s_value; + } + + // Early out if we're at the end + if ( ti >= tc - 1 ) + return m_values[ ti ]; + + if ( !IsInterpolableType( GetDataType() ) ) + return m_values[ ti ]; + + // Figure out the lerp factor + float t = GetFractionOfTimeBetween( time, DmeTime_t( m_times[ti] ), DmeTime_t( m_times[ti+1] ) ); + static T s_value; + s_value = Interpolate( t, m_values[ti], m_values[ti+1] ); // Compute the lerp between ti and ti+1 + return s_value; +} + +template< class T > +void CDmeTypedLogLayer< T >::SetKey( DmeTime_t time, const CDmAttribute *pAttr, uint index, int curveType /*= CURVE_DEFAULT*/ ) +{ + DmAttributeType_t type = pAttr->GetType(); + if ( IsValueType( type ) ) + { + Assert( pAttr->GetType() == GetDataType() ); + SetKey( time, pAttr->GetValue< T >(), curveType ); + } + else if ( IsArrayType( type ) ) + { + Assert( ArrayTypeToValueType( type ) == GetDataType() ); + CDmrArrayConst<T> array( pAttr ); + SetKey( time, array[ index ], curveType ); + } + else + { + Assert( 0 ); + } +} + +template< class T > +bool CDmeTypedLogLayer< T >::SetDuplicateKeyAtTime( DmeTime_t time ) +{ + int nKeys = m_times.Count(); + if ( nKeys == 0 || DmeTime_t( m_times[ nKeys - 1 ] ) == time ) + return false; + + T value = GetValue( time ); + // these two calls need to be separated (and we need to make an extra copy here) because + // CUtlVector has an assert to try to safeguard against inserting an existing value + // therefore, m_values.AddToTail( m_values[ i ] ) is illegal (or at least, triggers the assert) + SetKey( time, value ); + return true; +} + + +//----------------------------------------------------------------------------- +// Returns a specific key's value +//----------------------------------------------------------------------------- +template< class T > +const T& CDmeTypedLogLayer< T >::GetKeyValue( int nKeyIndex ) const +{ + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + + return m_values[ nKeyIndex ]; +} + + +template< class T > +void CDmeTypedLogLayer< T >::GetValue( DmeTime_t time, CDmAttribute *pAttr, uint index ) const +{ + DmAttributeType_t attrtype = pAttr->GetType(); + if ( IsValueType( attrtype ) ) + { + Assert( attrtype == GetDataType() ); + pAttr->SetValue( GetValue( time ) ); + } + else if ( IsArrayType( attrtype ) ) + { + Assert( ArrayTypeToValueType( attrtype ) == GetDataType() ); + CDmrArray<T> array( pAttr ); + array.Set( index, GetValue( time ) ); + } + else + { + Assert( 0 ); + } +} + +template< class T > +float CDmeTypedLogLayer< T >::GetComponent( DmeTime_t time, int componentIndex ) const +{ + return ::GetComponent( GetValue( time ), componentIndex ); +} + +template< class T > +void CDmeTypedLogLayer< T >::SetKeyValue( int nKey, const T& value ) +{ + Assert( nKey >= 0 ); + Assert( nKey < m_values.Count() ); + + m_values.Set( nKey, value ); +} + + +//----------------------------------------------------------------------------- +// resampling and filtering +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLogLayer< T >::Resample( DmeFramerate_t samplerate ) +{ + // FIXME: Might have to revisit how to determine "curve types" for "resampled points... + Assert( !IsUsingCurveTypes() ); + + // make sure we resample to include _at_least_ the existing time range + DmeTime_t begin = GetBeginTime(); + DmeTime_t end = GetEndTime(); + int nSamples = 2 + FrameForTime( end - begin, samplerate ); + + CUtlVector< int > resampledTimes; + CUtlVector< T > resampledValues; + CUtlVector< int > resampledCurveTypes; + + resampledValues.EnsureCapacity( nSamples ); + resampledTimes.EnsureCapacity( nSamples ); + + DmeTime_t time( begin ); + for ( int i = 0; i < nSamples; ++i ) + { + resampledTimes.AddToTail( time.GetTenthsOfMS() ); + resampledValues.AddToTail( GetValue( time ) ); + if ( IsUsingCurveTypes() ) + { + resampledCurveTypes.AddToTail( CURVE_DEFAULT ); + } + + time = time.TimeAtNextFrame( samplerate ); + } + + m_times.SwapArray( resampledTimes ); + m_values.SwapArray( resampledValues ); + if ( IsUsingCurveTypes() ) + { + m_CurveTypes.SwapArray( resampledCurveTypes ); + } +} + +template< class T > +void CDmeTypedLogLayer< T >::Filter( int nSampleRadius ) +{ + // Doesn't mess with curvetypes!!! + + const CUtlVector< T > &values = m_values.Get(); + CUtlVector< T > filteredValues; + + int nValues = values.Count(); + filteredValues.EnsureCapacity( nValues ); + + for ( int i = 0; i < nValues; ++i ) + { + int nSamples = min( nSampleRadius, min( i, nValues - i - 1 ) ); + filteredValues.AddToTail( Average( values.Base() + i - nSamples, 2 * nSamples + 1 ) ); + } + + m_values.SwapArray( filteredValues ); +} + +template< class T > +void CDmeTypedLogLayer< T >::Filter2( DmeTime_t sampleRadius ) +{ + // Doesn't mess with curvetypes!!! + + const CUtlVector< T > &values = m_values.Get(); + CUtlVector< T > filteredValues; + + int nValues = values.Count(); + filteredValues.EnsureCapacity( nValues ); + + DmeTime_t earliest = DMETIME_ZERO; + if ( nValues > 0 ) + { + earliest = DmeTime_t( m_times[ 0 ] ); + } + for ( int i = 0; i < nValues; ++i ) + { + T vals[ 3 ]; + DmeTime_t t = GetKeyTime( i ); + DmeTime_t t0 = t - sampleRadius; + DmeTime_t t1 = t + sampleRadius; + + if ( t0 >= earliest ) + { + vals[ 0 ] = GetValue( t0 ); + } + else + { + vals[ 0 ] = m_values[ 0 ]; + } + vals[ 1 ] = GetValue( t ); + vals[ 2 ] = GetValue( t1 ); + + if ( i == 0 || i == nValues - 1 ) + { + filteredValues.AddToTail( values[ i ] ); + } + else + { + filteredValues.AddToTail( Average( vals, 3 ) ); + } + } + + m_values.SwapArray( filteredValues ); +} + +template< class T > +const T& CDmeTypedLogLayer< T >::GetValueSkippingKey( int nKeyToSkip ) const +{ + // Curve Interpolation only for 1-D float data right now!!! + if ( IsUsingCurveTypes() && CanInterpolateType( GetDataType() ) ) + { + static T out; + GetValueUsingCurveInfoSkippingKey( nKeyToSkip, out ); + return out; + } + + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + + DmeTime_t time = GetKeyTime( nKeyToSkip ); + + int prevKey = nKeyToSkip - 1; + int nextKey = nKeyToSkip + 1; + + DmeTime_t prevTime; + T prevValue; + int prevCurveType; + DmeTime_t nextTime; + T nextValue; + int nextCurveType; + + GetBoundedSample( prevKey, prevTime, prevValue, prevCurveType ); + GetBoundedSample( nextKey, nextTime, nextValue, nextCurveType ); + + // Figure out the lerp factor + float t = GetFractionOfTimeBetween( time, prevTime, nextTime ); + + static T s_value; + s_value = Interpolate( t, prevValue, nextValue ); + return s_value; +} + +template< class T > +void CDmeTypedLog<T>::RemoveRedundantKeys( float threshold ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + + GetLayer( bestLayer )->RemoveRedundantKeys( threshold ); +} + +template< class T > +void CDmeTypedLogLayer<T>::RemoveRedundantKeys( float threshold ) +{ + Assert( GetTypedOwnerLog() ); + if ( !GetTypedOwnerLog() ) + return; + + float saveThreshold; + { + CDisableUndoScopeGuard sg; + saveThreshold = GetTypedOwnerLog()->GetValueThreshold(); + GetTypedOwnerLog()->SetValueThreshold( threshold ); + } + + RemoveRedundantKeys(); + + { + CDisableUndoScopeGuard sg; + GetTypedOwnerLog()->SetValueThreshold( saveThreshold ); + } + +} + +// Implementation of Douglas-Peucker curve simplification routine (hacked to only care about error against original curve (sort of 1D) + +template< class T > +void CDmeTypedLogLayer< T >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< T > *output ) +{ + if ( endPoint <= startPoint + 1 ) + { + return; + } + + int maxPoint = startPoint; + float maxDistanceSqr = 0.0f; + + for ( int i = startPoint + 1 ; i < endPoint; ++i ) + { + DmeTime_t keyTime = GetKeyTime( i ); + T check = GetKeyValue( i ); + T check2 = output->GetValue( keyTime ); + T dist = Subtract( check, check2 ); + float distSqr = LengthOf( dist ) * LengthOf( dist ); + + if ( distSqr < maxDistanceSqr ) + continue; + + maxPoint = i; + maxDistanceSqr = distSqr; + } + + if ( maxDistanceSqr > thresholdSqr ) + { + output->InsertKey( GetKeyTime( maxPoint ), GetKeyValue( maxPoint ) ); + CurveSimplify_R( thresholdSqr, startPoint, maxPoint, output ); + CurveSimplify_R( thresholdSqr, maxPoint, endPoint, output ); + } +} + +template<> void CDmeTypedLogLayer< bool >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< bool > *output ) {}; +template<> void CDmeTypedLogLayer< int >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< int > *output ) {}; +template<> void CDmeTypedLogLayer< Color >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< Color > *output ) {}; +template<> void CDmeTypedLogLayer< Quaternion >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< Quaternion > *output ) {}; +template<> void CDmeTypedLogLayer< VMatrix >::CurveSimplify_R( float thresholdSqr, int startPoint, int endPoint, CDmeTypedLogLayer< VMatrix > *output ) {}; + +// We can't just walk the keys linearly since it'll accumulate too much error and give us a bad curve after simplification. We do a recursive subdivide which has a worst case of O(n^2) but +// probably is better than that in most cases. +template< class T > +void CDmeTypedLogLayer<T>::RemoveRedundantKeys() +{ + CDmeTypedLog< T > *pOwner = GetTypedOwnerLog(); + if ( !pOwner ) + return; + + int nKeys = GetKeyCount(); + if ( nKeys <= 2 ) + return; + + float thresh = pOwner->GetValueThreshold(); + if ( thresh < 0.0f ) + return; + + CDmeTypedLogLayer< T > *save = 0; + { + CDisableUndoScopeGuard guard; + save = CastElement< CDmeTypedLogLayer< T > >( CreateLayer< T >( pOwner ) ); + Assert( save ); + + save->m_times.EnsureCapacity( nKeys ); + save->m_values.EnsureCapacity( nKeys ); + + // Insert start and end points as first "guess" at simplified curve + // Skip preceeding and ending keys that have the same value + int nFirstKey, nLastKey; + for ( nFirstKey = 1; nFirstKey < nKeys; ++nFirstKey ) + { + // FIXME: Should we use a tolerance check here? + if ( GetKeyValue( nFirstKey ) != GetKeyValue( nFirstKey - 1 ) ) + break; + } + --nFirstKey; + + for ( nLastKey = nKeys; --nLastKey >= 1; ) + { + // FIXME: Should we use a tolerance check here? + if ( GetKeyValue( nLastKey ) != GetKeyValue( nLastKey - 1 ) ) + break; + } + + if ( nLastKey <= nFirstKey ) + { + save->InsertKey( GetKeyTime( 0 ), GetKeyValue( 0 ) ); + } + else + { + if ( GetDataType() == AT_FLOAT ) + { + save->InsertKey( GetKeyTime( nFirstKey ), GetKeyValue( nFirstKey ) ); + save->InsertKey( GetKeyTime( nLastKey ), GetKeyValue( nLastKey ) ); + + // Recursively finds the point with the largest error from the "simplified curve" and subdivides the problem on both sides until the largest delta from the simplified + // curve is less than the tolerance (squared) + CurveSimplify_R( thresh * thresh, nFirstKey, nLastKey, save ); + } + else + { + save->InsertKey( GetKeyTime( nFirstKey ), GetKeyValue( nFirstKey ) ); + + // copy over keys that differ from their prior or next keys - this keeps the first and last key of a run of same-valued keys + for ( int i = nFirstKey + 1; i < nLastKey; ++i ) + { + // prev is from the saved log to allow deleting runs of same-valued keys + const T &prev = save->GetKeyValue( save->GetKeyCount() - 1 ); + const T &curr = GetKeyValue( i ); + const T &next = GetKeyValue( i + 1 ); + if ( pOwner->ValuesDiffer( prev, curr ) || pOwner->ValuesDiffer( curr, next ) ) + { + save->InsertKey( GetKeyTime( i ), curr ); + } + } + + save->InsertKey( GetKeyTime( nLastKey ), GetKeyValue( nLastKey ) ); + } + } + } + + // This operation is undoable + CopyLayer( save ); + + { + CDisableUndoScopeGuard guard; + g_pDataModel->DestroyElement( save->GetHandle() ); + } +} + +// curve info helpers +template< class T > +const CDmeTypedCurveInfo< T > *CDmeTypedLogLayer<T>::GetTypedCurveInfo() const +{ + Assert( GetTypedOwnerLog() ); + return GetTypedOwnerLog()->GetTypedCurveInfo(); +} + +template< class T > +CDmeTypedCurveInfo< T > *CDmeTypedLogLayer<T>::GetTypedCurveInfo() +{ + Assert( GetTypedOwnerLog() ); + return GetTypedOwnerLog()->GetTypedCurveInfo(); +} + +template< class T > +bool CDmeTypedLogLayer< T >::IsUsingEdgeInfo() const +{ + return GetTypedOwnerLog()->IsUsingEdgeInfo(); +} + +template< class T > +const T& CDmeTypedLogLayer< T >::GetDefaultEdgeZeroValue() const +{ + return GetTypedOwnerLog()->GetDefaultEdgeZeroValue(); +} + +template< class T > +DmeTime_t CDmeTypedLogLayer< T >::GetRightEdgeTime() const +{ + return GetTypedOwnerLog()->GetRightEdgeTime(); +} + + +template< class T > +void CDmeTypedLogLayer< T >::GetEdgeInfo( int edge, bool& active, T& val, int& curveType ) const +{ + GetTypedOwnerLog()->GetEdgeInfo( edge, active, val, curveType ); +} + +template< class T > +int CDmeTypedLogLayer< T >::GetEdgeCurveType( int edge ) const +{ + return GetTypedOwnerLog()->GetEdgeCurveType( edge ); +} + +template< class T > +void CDmeTypedLogLayer< T >::GetZeroValue( int side, T& val ) const +{ + return GetTypedOwnerLog()->GetZeroValue( side, val ); +} + +template< class T > +void CDmeTypedLogLayer< T >::GetBoundedSample( int keyindex, DmeTime_t& time, T& val, int& curveType ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + time = DmeTime_t( 0 ); + CDmAttributeInfo< T >::SetDefaultValue( val ); + curveType = CURVE_DEFAULT; + return; + } + + if ( keyindex < 0 ) + { + time = DmeTime_t( 0 ); + GetZeroValue( 0, val ); + curveType = GetEdgeCurveType( 0 ); + return; + } + else if ( keyindex >= m_times.Count() ) + { + time = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( time == DmeTime_t( 0 ) && m_times.Count() > 0 ) + { + // Push it one msec past the final end time + time = DmeTime_t( m_times[ m_times.Count() - 1 ] ) + DmeTime_t( 1 ); + } + GetTypedOwnerLog()->GetZeroValue( 1, val ); + curveType = GetTypedOwnerLog()->GetEdgeCurveType( 1 ); + return; + } + + time = DmeTime_t( m_times[ keyindex ] ); + val = m_values[ keyindex ]; + if ( IsUsingCurveTypes() ) + { + curveType = m_CurveTypes[ keyindex ]; + if ( curveType == CURVE_DEFAULT ) + { + curveType = GetTypedOwnerLog()->GetDefaultCurveType(); + } + } +} + +template<> +void CDmeTypedLogLayer< float >::GetValueUsingCurveInfoSkippingKey( int nKeyToSkip, float& out ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + out = 0.0f; + return; + } + + Assert( CanInterpolateType( GetDataType() ) ); + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + Assert( IsInterpolableType( GetDataType() ) ); + + float v[ 4 ]; + DmeTime_t t[ 4 ]; + int curvetypes[ 4 ]; + int ti = nKeyToSkip; + DmeTime_t time = GetKeyTime( nKeyToSkip ); + + if ( !IsUsingCurveTypes() ) + { + if ( ti < 0 ) + { + CDmAttributeInfo< float >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return; + } + else if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti + 1 ]; + return; + } + } + + DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( finalTime != DmeTime_t( 0 ) ) + { + if ( time > finalTime ) + { + GetZeroValue( 1, out ); + return; + } + } + else + { + if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti + 1 ]; + return; + } + } + + GetBoundedSample( ti - 2, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); + GetBoundedSample( ti - 1, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); + GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); + GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); + + float frac = 0.0f; + if ( t[2] > t[ 1 ] ) + { + frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); + } + + // Compute the lerp between ti and ti+1 + out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); +} + +template<> +void CDmeTypedLogLayer< Vector >::GetValueUsingCurveInfoSkippingKey( int nKeyToSkip, Vector& out ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + CDmAttributeInfo< Vector >::SetDefaultValue( out ); + return; + } + + Assert( CanInterpolateType( GetDataType() ) ); + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + Assert( IsInterpolableType( GetDataType() ) ); + + Vector v[ 4 ]; + DmeTime_t t[ 4 ]; + int curvetypes[ 4 ]; + int ti = nKeyToSkip; + DmeTime_t time = GetKeyTime( nKeyToSkip ); + + if ( !IsUsingCurveTypes() ) + { + if ( ti < 0 ) + { + CDmAttributeInfo< Vector >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return; + } + else if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti + 1 ]; + return; + } + } + + DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( finalTime != DmeTime_t( 0 ) ) + { + if ( time > finalTime ) + { + CDmAttributeInfo< Vector >::SetDefaultValue( out ); + return; + } + } + else + { + if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti + 1 ]; + return; + } + } + + GetBoundedSample( ti - 2, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); + GetBoundedSample( ti - 1, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); + GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); + GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); + + float frac = 0.0f; + if ( t[2] > t[ 1 ] ) + { + frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); + } + + // Compute the lerp between ti and ti+1 + out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); +} + +template<> +void CDmeTypedLogLayer< Quaternion >::GetValueUsingCurveInfoSkippingKey( int nKeyToSkip, Quaternion& out ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); + return; + } + + Assert( CanInterpolateType( GetDataType() ) ); + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + Assert( IsInterpolableType( GetDataType() ) ); + + Quaternion v[ 4 ]; + DmeTime_t t[ 4 ]; + int curvetypes[ 4 ]; + int ti = nKeyToSkip; + DmeTime_t time = GetKeyTime( nKeyToSkip ); + + if ( !IsUsingCurveTypes() ) + { + if ( ti < 0 ) + { + CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return; + } + else if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti + 1 ]; + return; + } + } + + DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( finalTime != DmeTime_t( 0 ) ) + { + if ( time > finalTime ) + { + CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); + return; + } + } + else + { + if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti + 1 ]; + return; + } + } + + GetBoundedSample( ti - 2, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); + GetBoundedSample( ti - 1, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); + GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); + GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); + + float frac = 0.0f; + if ( t[2] > t[ 1 ] ) + { + frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); + } + + // Compute the lerp between ti and ti+1 + out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); +} + + +template<> +void CDmeTypedLogLayer< float >::GetValueUsingCurveInfo( DmeTime_t time, float& out ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + out = 0.0f; + return; + } + + Assert( CanInterpolateType( GetDataType() ) ); + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + Assert( IsInterpolableType( GetDataType() ) ); + + float v[ 4 ]; + DmeTime_t t[ 4 ]; + int curvetypes[ 4 ]; + int ti = FindKey( time ); + if ( !IsUsingCurveTypes() ) + { + if ( ti < 0 ) + { + CDmAttributeInfo< float >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return; + } + else if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti ]; + return; + } + } + + DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( finalTime != DmeTime_t( 0 ) ) + { + if ( time > finalTime ) + { + GetZeroValue( 1, out ); + return; + } + } + else + { + if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti ]; + return; + } + } + + GetBoundedSample( ti - 1, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); + GetBoundedSample( ti + 0, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); + GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); + GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); + + float frac = 0.0f; + if ( t[2] > t[ 1 ] ) + { + frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); + } + + // Compute the lerp between ti and ti+1 + out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); +} + +template<> +void CDmeTypedLogLayer< Vector >::GetValueUsingCurveInfo( DmeTime_t time, Vector& out ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + CDmAttributeInfo< Vector >::SetDefaultValue( out ); + return; + } + + Assert( CanInterpolateType( GetDataType() ) ); + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + Assert( IsInterpolableType( GetDataType() ) ); + + Vector v[ 4 ]; + DmeTime_t t[ 4 ]; + int curvetypes[ 4 ]; + int ti = FindKey( time ); + if ( !IsUsingCurveTypes() ) + { + if ( ti < 0 ) + { + CDmAttributeInfo< Vector >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return; + } + else if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti ]; + return; + } + } + + DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( finalTime != DmeTime_t( 0 ) ) + { + if ( time > finalTime ) + { + CDmAttributeInfo< Vector >::SetDefaultValue( out ); + return; + } + } + else + { + if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti ]; + return; + } + } + + GetBoundedSample( ti - 1, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); + GetBoundedSample( ti + 0, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); + GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); + GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); + + float frac = 0.0f; + if ( t[2] > t[ 1 ] ) + { + frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); + } + + // Compute the lerp between ti and ti+1 + out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); +} + +template<> +void CDmeTypedLogLayer< Quaternion >::GetValueUsingCurveInfo( DmeTime_t time, Quaternion& out ) const +{ + Assert( GetOwnerLog() ); + if ( !GetOwnerLog() ) + { + CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); + return; + } + + Assert( CanInterpolateType( GetDataType() ) ); + Assert( m_values.Count() == m_times.Count() ); + Assert( !IsUsingCurveTypes() || ( m_CurveTypes.Count() == m_times.Count() ) ); + Assert( IsInterpolableType( GetDataType() ) ); + + Quaternion v[ 4 ]; + DmeTime_t t[ 4 ]; + int curvetypes[ 4 ]; + int ti = FindKey( time ); + if ( !IsUsingCurveTypes() ) + { + if ( ti < 0 ) + { + CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return; + } + else if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti ]; + return; + } + } + + DmeTime_t finalTime = GetTypedOwnerLog()->GetRightEdgeTime(); + if ( finalTime != DmeTime_t( 0 ) ) + { + if ( time > finalTime ) + { + CDmAttributeInfo< Quaternion >::SetDefaultValue( out ); + return; + } + } + else + { + if ( ti >= m_times.Count() - 1 ) + { + out = m_values[ ti ]; + return; + } + } + + GetBoundedSample( ti - 1, t[ 0 ], v[ 0 ], curvetypes[ 0 ] ); + GetBoundedSample( ti + 0, t[ 1 ], v[ 1 ], curvetypes[ 1 ] ); + GetBoundedSample( ti + 1, t[ 2 ], v[ 2 ], curvetypes[ 2 ] ); + GetBoundedSample( ti + 2, t[ 3 ], v[ 3 ], curvetypes[ 3 ] ); + + float frac = 0.0f; + if ( t[2] > t[ 1 ] ) + { + frac = (time.GetSeconds() - t[1].GetSeconds()) / (float) ( t[2].GetSeconds() - t[ 1 ].GetSeconds() ); + } + + // Compute the lerp between ti and ti+1 + out = Curve_Interpolate( frac, t, v, curvetypes, GetOwnerLog()->GetMinValue(), GetOwnerLog()->GetMaxValue() ); +} + +template< class T > +void CDmeTypedLogLayer< T >::CopyLayer( const CDmeLogLayer *src ) +{ + const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); + m_times = pSrc->m_times; + m_lastKey = pSrc->m_lastKey; + m_values = pSrc->m_values; + m_CurveTypes = pSrc->m_CurveTypes; +} + +template< class T > +void CDmeTypedLogLayer< T >::InsertKeyFromLayer( DmeTime_t keyTime, const CDmeLogLayer *src, DmeTime_t srcKeyTime ) +{ + const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); + Assert( pSrc ); + + // NOTE: This copy is necessary if src == this + T value = pSrc->GetValue( srcKeyTime ); + InsertKey( keyTime, value ); +} + +template< class T > +void CDmeTypedLogLayer< T >::ExplodeLayer( const CDmeLogLayer *src, DmeTime_t startTime, DmeTime_t endTime, bool bRebaseTimestamps, DmeTime_t tResampleInterval ) +{ + const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); + Assert( pSrc ); + + DmeTime_t tTimeOffset = DMETIME_ZERO; + if ( bRebaseTimestamps ) + { + tTimeOffset = -startTime; + } + + m_times.RemoveAll(); + m_values.RemoveAll(); + m_CurveTypes.RemoveAll(); + + bool usecurvetypes = pSrc->IsUsingCurveTypes(); + + // Now copy the data for the later + for ( DmeTime_t t = startTime ; t + tResampleInterval < endTime; t += tResampleInterval ) + { + DmeTime_t keyTime = DmeTime_t( t ); + if ( keyTime > endTime ) + { + keyTime = endTime; + } + + T val = pSrc->GetValue( keyTime ); + + keyTime += tTimeOffset; + + InsertKey( keyTime, val, usecurvetypes ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + + m_lastKey = m_times.Count() - 1; +} + +template< class T > +void CDmeTypedLogLayer< T >::CopyPartialLayer( const CDmeLogLayer *src, DmeTime_t startTime, DmeTime_t endTime, bool bRebaseTimestamps ) +{ + const CDmeTypedLogLayer< T > *pSrc = static_cast< const CDmeTypedLogLayer< T > * >( src ); + Assert( pSrc ); + + int nTimeOffset = 0; + if ( bRebaseTimestamps ) + { + nTimeOffset = -startTime.GetTenthsOfMS(); + } + + m_times.RemoveAll(); + m_values.RemoveAll(); + m_CurveTypes.RemoveAll(); + + bool usecurvetypes = pSrc->IsUsingCurveTypes(); + + // Now copy the data for the later + int c = pSrc->m_times.Count(); + for ( int i = 0; i < c; ++i ) + { + DmeTime_t keyTime = DmeTime_t( pSrc->m_times[ i ] ); + if ( keyTime < startTime || keyTime > endTime ) + continue; + + m_times.AddToTail( pSrc->m_times[ i ] + nTimeOffset ); + m_values.AddToTail( pSrc->m_values[ i ] ); + if ( usecurvetypes ) + { + m_CurveTypes.AddToTail( pSrc->m_CurveTypes[ i ] ); + } + } + + m_lastKey = m_times.Count() - 1; +} + +//----------------------------------------------------------------------------- +// Creates a log of a specific type +//----------------------------------------------------------------------------- +template< class T > +CDmeLogLayer *CreateLayer< T >( CDmeTypedLog< T > *pOwnerLog ) +{ + DmFileId_t fileid = pOwnerLog ? pOwnerLog->GetFileId() : DMFILEID_INVALID; + CDmeLogLayer *layer = NULL; + + switch ( CDmAttributeInfo<T>::AttributeType() ) + { + case AT_INT: + case AT_INT_ARRAY: + layer = CreateElement< CDmeIntLogLayer >( "int log", fileid ); + break; + case AT_FLOAT: + case AT_FLOAT_ARRAY: + layer = CreateElement< CDmeFloatLogLayer >( "float log", fileid ); + break; + case AT_BOOL: + case AT_BOOL_ARRAY: + layer = CreateElement< CDmeBoolLogLayer >( "bool log", fileid ); + break; + case AT_COLOR: + case AT_COLOR_ARRAY: + layer = CreateElement< CDmeColorLogLayer >( "color log", fileid ); + break; + case AT_VECTOR2: + case AT_VECTOR2_ARRAY: + layer = CreateElement< CDmeVector2LogLayer >( "vector2 log", fileid ); + break; + case AT_VECTOR3: + case AT_VECTOR3_ARRAY: + layer = CreateElement< CDmeVector3LogLayer >( "vector3 log", fileid ); + break; + case AT_VECTOR4: + case AT_VECTOR4_ARRAY: + layer = CreateElement< CDmeVector4LogLayer >( "vector4 log", fileid ); + break; + case AT_QANGLE: + case AT_QANGLE_ARRAY: + layer = CreateElement< CDmeQAngleLogLayer >( "qangle log", fileid ); + break; + case AT_QUATERNION: + case AT_QUATERNION_ARRAY: + layer = CreateElement< CDmeQuaternionLogLayer >( "quaternion log", fileid ); + break; + case AT_VMATRIX: + case AT_VMATRIX_ARRAY: + layer = CreateElement< CDmeVMatrixLogLayer >( "vmatrix log", fileid ); + break; + case AT_STRING: + case AT_STRING_ARRAY: + layer = CreateElement< CDmeStringLogLayer >( "string log", fileid ); + break; + } + + if ( layer ) + { + layer->SetOwnerLog( pOwnerLog ); + } + return layer; +} + + + +//----------------------------------------------------------------------------- +// +// CDmeCurveInfo - abstract base class +// +//----------------------------------------------------------------------------- +void CDmeCurveInfo::OnConstruction() +{ + m_DefaultCurveType.Init( this, "defaultCurveType" ); + + m_MinValue.InitAndSet( this, "minvalue", 0.0f ); + m_MaxValue.InitAndSet( this, "maxvalue", 1.0f ); +} + +void CDmeCurveInfo::OnDestruction() +{ +} + +// Global override for all keys unless overriden by specific key +void CDmeCurveInfo::SetDefaultCurveType( int curveType ) +{ + m_DefaultCurveType = curveType; +} + +int CDmeCurveInfo::GetDefaultCurveType() const +{ + return m_DefaultCurveType.Get(); +} + +void CDmeCurveInfo::SetMinValue( float val ) +{ + m_MinValue = val; +} + +float CDmeCurveInfo::GetMinValue() const +{ + return m_MinValue; +} + +void CDmeCurveInfo::SetMaxValue( float val ) +{ + m_MaxValue = val; +} + +float CDmeCurveInfo::GetMaxValue() const +{ + return m_MaxValue; +} + + +//----------------------------------------------------------------------------- +// +// CDmeTypedCurveInfo - implementation class for all logs +// +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedCurveInfo< T >::OnConstruction() +{ + m_bUseEdgeInfo.Init( this, "useEdgeInfo" ); + m_DefaultEdgeValue.Init( this, "defaultEdgeZeroValue" ); + m_RightEdgeTime.Init( this, "rightEdgeTime" ); + + for ( int i = 0; i < 2; ++i ) + { + char edgename[ 32 ]; + Q_snprintf( edgename, sizeof( edgename ), "%s", i == 0 ? "left" : "right" ); + char name[ 32 ]; + Q_snprintf( name, sizeof( name ), "%sEdgeActive", edgename ); + m_bEdgeActive[ i ].Init( this, name ); + Q_snprintf( name, sizeof( name ), "%sEdgeValue", edgename ); + m_EdgeValue[ i ].Init( this, name ); + Q_snprintf( name, sizeof( name ), "%sEdgeCurveType", edgename ); + m_EdgeCurveType[ i ].Init( this, name ); + } +} + +template< class T > +void CDmeTypedCurveInfo< T >::OnDestruction() +{ +} + +template< class T > +void CDmeTypedCurveInfo< T >::SetUseEdgeInfo( bool state ) +{ + m_bUseEdgeInfo = state; +} + +template< class T > +bool CDmeTypedCurveInfo< T >::IsUsingEdgeInfo() const +{ + return m_bUseEdgeInfo; +} + +template< class T > +void CDmeTypedCurveInfo< T >::SetEdgeInfo( int edge, bool active, const T& val, int curveType ) +{ + SetUseEdgeInfo( true ); + + Assert( edge == 0 || edge == 1 ); + + m_bEdgeActive[ edge ] = active; + m_EdgeValue[ edge ] = val; + m_EdgeCurveType[ edge ] = curveType; +} + +template< class T > +void CDmeTypedCurveInfo< T >::SetDefaultEdgeZeroValue( const T& val ) +{ + m_DefaultEdgeValue = val; +} + +template< class T > +const T& CDmeTypedCurveInfo< T >::GetDefaultEdgeZeroValue() const +{ + return m_DefaultEdgeValue; +} + +template< class T > +void CDmeTypedCurveInfo< T >::SetRightEdgeTime( DmeTime_t time ) +{ + m_RightEdgeTime = time.GetTenthsOfMS(); +} + +template< class T > +DmeTime_t CDmeTypedCurveInfo< T >::GetRightEdgeTime() const +{ + return DmeTime_t( m_RightEdgeTime ); +} + +template< class T > +void CDmeTypedCurveInfo< T >::GetEdgeInfo( int edge, bool& active, T& val, int& curveType ) const +{ + Assert( IsUsingEdgeInfo() ); + + Assert( edge == 0 || edge == 1 ); + + active = m_bEdgeActive[ edge ]; + val = m_EdgeValue[ edge ]; + curveType = m_EdgeCurveType[ edge ]; +} + +template< class T > +int CDmeTypedCurveInfo< T >::GetEdgeCurveType( int edge ) const +{ + Assert( edge == 0 || edge == 1 ); + + if ( !m_bEdgeActive[ edge ] ) + { + return m_DefaultCurveType; + } + + if ( m_EdgeCurveType[ edge ] == CURVE_DEFAULT ) + { + return m_DefaultCurveType; + } + + return m_EdgeCurveType[ edge ]; +} + +template<> +void CDmeTypedCurveInfo<float>::GetZeroValue( int side, float& val ) const +{ + if ( !m_bUseEdgeInfo ) + { + val = 0.0f; + return; + } + + if ( m_bEdgeActive[ side ] ) + { + val = m_EdgeValue[ side ]; + return; + } + + val = m_DefaultEdgeValue; +} + +template<> +bool CDmeTypedCurveInfo<float>::IsEdgeActive( int edge ) const +{ + return m_bEdgeActive[ edge ]; +} + +template<> +void CDmeTypedCurveInfo<float>::GetEdgeValue( int edge, float& value ) const +{ + value = m_EdgeValue[ edge ]; +} + +template<> +void CDmeTypedCurveInfo<Vector>::GetZeroValue( int side, Vector& val ) const +{ + if ( !m_bUseEdgeInfo ) + { + val = vec3_origin; + return; + } + + if ( m_bEdgeActive[ side ] ) + { + val = m_EdgeValue[ side ]; + return; + } + + val = m_DefaultEdgeValue; +} + +template<> +void CDmeTypedCurveInfo<Quaternion>::GetZeroValue( int side, Quaternion& val ) const +{ + if ( !m_bUseEdgeInfo ) + { + val.Init(); + return; + } + + if ( m_bEdgeActive[ side ] ) + { + val = m_EdgeValue[ side ]; + return; + } + + val = m_DefaultEdgeValue; +} + + +//----------------------------------------------------------------------------- +// +// CDmeLog - abstract base class +// +//----------------------------------------------------------------------------- +void CDmeLog::OnConstruction() +{ + m_Layers.Init( this, "layers", FATTRIB_MUSTCOPY | FATTRIB_HAS_ARRAY_CALLBACK ); + m_CurveInfo.Init( this, "curveinfo", FATTRIB_MUSTCOPY | FATTRIB_HAS_CALLBACK ); +} + +void CDmeLog::OnDestruction() +{ +} + +int CDmeLog::GetTopmostLayer() const +{ + return m_Layers.Count() - 1; +} + +int CDmeLog::GetNumLayers() const +{ + return m_Layers.Count(); +} + +CDmeLogLayer *CDmeLog::GetLayer( int index ) +{ + return m_Layers[ index ]; +} + +const CDmeLogLayer *CDmeLog::GetLayer( int index ) const +{ + return m_Layers[ index ]; +} + +bool CDmeLog::IsEmpty() const +{ + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + if ( layer->GetKeyCount() > 0 ) + return false; + } + return true; +} + +void CDmeLog::FindLayersForTime( DmeTime_t time, CUtlVector< int >& list ) const +{ + list.RemoveAll(); + int c = m_Layers.Count(); + // The base layer is always available!!! + if ( c > 0 ) + { + list.AddToTail( 0 ); + } + for ( int i = 1; i < c; ++i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + DmeTime_t layerStart = layer->GetBeginTime(); + if ( layerStart == DmeTime_t::MinTime() ) + continue; + DmeTime_t layerEnd = layer->GetEndTime(); + if ( layerEnd == DmeTime_t::MaxTime() ) + continue; + + if ( time >= layerStart && time <= layerEnd ) + { + list.AddToTail( i ); + } + } +} + +int CDmeLog::FindLayerForTimeSkippingTopmost( DmeTime_t time ) const +{ + int c = m_Layers.Count() - 1; // This makes it never consider the topmost layer!!! + for ( int i = c - 1; i >= 0; --i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + DmeTime_t layerStart = layer->GetBeginTime(); + if ( layerStart == DmeTime_t::MinTime() ) + continue; + DmeTime_t layerEnd = layer->GetEndTime(); + if ( layerEnd == DmeTime_t::MaxTime() ) + continue; + + if ( time >= layerStart && time <= layerEnd ) + return i; + } + return ( c > 0 ) ? 0 : -1; +} + +int CDmeLog::FindLayerForTime( DmeTime_t time ) const +{ + int c = m_Layers.Count(); + for ( int i = c - 1; i >= 0; --i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + DmeTime_t layerStart = layer->GetBeginTime(); + if ( layerStart == DmeTime_t::MinTime() ) + continue; + DmeTime_t layerEnd = layer->GetEndTime(); + if ( layerEnd == DmeTime_t::MaxTime() ) + continue; + + if ( time >= layerStart && time <= layerEnd ) + return i; + } + return ( c > 0 ) ? 0 : -1; +} + +DmeTime_t CDmeLog::GetBeginTime() const +{ + int c = m_Layers.Count(); + if ( c == 0 ) + return DmeTime_t::MinTime(); + + DmeTime_t bestMin = DmeTime_t::MinTime(); + for ( int i = 0; i < c; ++i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + DmeTime_t layerStart = layer->GetBeginTime(); + if ( layerStart == DmeTime_t::MinTime() ) + continue; + + if ( bestMin == DmeTime_t::MinTime() ) + { + bestMin = layerStart; + } + else if ( layerStart < bestMin ) + { + bestMin = layerStart; + } + } + + return bestMin; +} + +DmeTime_t CDmeLog::GetEndTime() const +{ + int c = m_Layers.Count(); + if ( c == 0 ) + return DmeTime_t::MaxTime(); + + DmeTime_t bestMax = DmeTime_t::MaxTime(); + for ( int i = 0; i < c; ++i ) + { + CDmeLogLayer *layer = m_Layers[ i ]; + DmeTime_t layerEnd = layer->GetEndTime(); + if ( layerEnd == DmeTime_t::MaxTime() ) + continue; + if ( bestMax == DmeTime_t::MaxTime() ) + { + bestMax = layerEnd; + } + else if ( layerEnd > bestMax ) + { + bestMax = layerEnd; + } + } + + return bestMax; +} + +//----------------------------------------------------------------------------- +// Returns the number of keys +//----------------------------------------------------------------------------- +int CDmeLog::GetKeyCount() const +{ + int count = 0; + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + int timecount = layer->GetKeyCount(); + count += timecount; + } + return count; +} + + +//----------------------------------------------------------------------------- +// Scale + bias key times +//----------------------------------------------------------------------------- +void CDmeLog::ScaleBiasKeyTimes( double flScale, DmeTime_t nBias ) +{ + // Don't waste time on the identity transform + if ( ( nBias == DMETIME_ZERO ) && ( fabs( flScale - 1.0 ) < 1e-5 ) ) + return; + + int nCount = GetNumLayers(); + for ( int i = 0; i < nCount; ++i ) + { + CDmeLogLayer *pLayer = GetLayer( i ); + pLayer->ScaleBiasKeyTimes( flScale, nBias ); + } +} + + +//----------------------------------------------------------------------------- +// Resolve - keeps non-attribute data in sync with attribute data +//----------------------------------------------------------------------------- +void CDmeLog::Resolve() +{ + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmeLogLayer* layer = m_Layers[ i ]; + layer->SetOwnerLog( this ); + } +} + +void CDmeLog::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + if ( pAttribute == m_CurveInfo.GetAttribute() ) + { + OnUsingCurveTypesChanged(); + } +} + +void CDmeLog::OnUsingCurveTypesChanged() +{ + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + GetLayer( i )->OnUsingCurveTypesChanged(); + } +} + +// curve info helpers +bool CDmeLog::IsUsingCurveTypes() const +{ + return m_CurveInfo.GetElement() != NULL; +} + +const CDmeCurveInfo *CDmeLog::GetCurveInfo() const +{ + return m_CurveInfo.GetElement(); +} + +CDmeCurveInfo *CDmeLog::GetCurveInfo() +{ + return m_CurveInfo.GetElement(); +} + +// accessors for CurveInfo data +int CDmeLog::GetDefaultCurveType() const +{ + Assert( IsUsingCurveTypes() ); + return m_CurveInfo->GetDefaultCurveType(); +} + +// min/max accessors +float CDmeLog::GetMinValue() const +{ + Assert( IsUsingCurveTypes() ); + return m_CurveInfo->GetMinValue(); +} + +void CDmeLog::SetMinValue( float val ) +{ + Assert( IsUsingCurveTypes() ); + m_CurveInfo->SetMinValue( val ); +} + +float CDmeLog::GetMaxValue() const +{ + Assert( IsUsingCurveTypes() ); + return m_CurveInfo->GetMaxValue(); +} + +void CDmeLog::SetMaxValue( float val ) +{ + Assert( IsUsingCurveTypes() ); + m_CurveInfo->SetMaxValue( val ); +} + +void CDmeLog::SetKeyCurveType( int nKeyIndex, int curveType ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + + GetLayer( bestLayer )->SetKeyCurveType( nKeyIndex, curveType ); +} + +int CDmeLog::GetKeyCurveType( int nKeyIndex ) const +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return CURVE_DEFAULT; + + return GetLayer( bestLayer )->GetKeyCurveType( nKeyIndex ); +} + + +//----------------------------------------------------------------------------- +// Removes all keys in a certain time interval +//----------------------------------------------------------------------------- +bool CDmeLog::RemoveKeys( DmeTime_t tStartTime, DmeTime_t tEndTime ) +{ + CDmeLogLayer *pLayer = GetLayer( GetTopmostLayer() ); + + int nKeyCount = pLayer->GetKeyCount(); + int nFirstRemove = -1; + int nLastRemove = -1; + for ( int nKey = 0; nKey < nKeyCount; ++nKey ) + { + DmeTime_t tKeyTime = pLayer->GetKeyTime( nKey ); + if ( tKeyTime < tStartTime ) + continue; + if ( tKeyTime > tEndTime ) + break; + if ( nFirstRemove == -1 ) + { + nFirstRemove = nKey; + } + nLastRemove = nKey; + } + + if ( nFirstRemove != -1 ) + { + int nRemoveCount = nLastRemove - nFirstRemove + 1; + pLayer->RemoveKey( nFirstRemove, nRemoveCount ); + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// CDmeTypedLog - implementation class for all logs +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLog< T >::OnConstruction() +{ + if ( !g_pDataModel->IsUnserializing() ) + { + // Add the default layer!!! + AddNewLayer(); + Assert( m_Layers.Count() == 1 ); + } + + m_threshold = s_defaultThreshold; + + m_UseDefaultValue.InitAndSet( this, "usedefaultvalue", false ); + m_DefaultValue.Init( this, "defaultvalue" ); +} + +template< class T > +void CDmeTypedLog< T >::OnDestruction() +{ +} + +template< class T > +void CDmeTypedLog< T >::SetDefaultValue( const T& value ) +{ + m_UseDefaultValue = true; + m_DefaultValue.Set( value ); +} + +template< class T > +const T& CDmeTypedLog< T >::GetDefaultValue() const +{ + Assert( (bool)m_UseDefaultValue ); + return m_DefaultValue; +} + +template< class T > +bool CDmeTypedLog< T >::HasDefaultValue() const +{ + return m_UseDefaultValue; +} + +template< class T > +void CDmeTypedLog< T >::ClearDefaultValue() +{ + m_UseDefaultValue = false; + T out; + CDmAttributeInfo< T >::SetDefaultValue( out ); + m_DefaultValue.Set( out ); +} + +// Only used by undo system!!! +template< class T > +void CDmeTypedLog< T >::AddLayerToTail( CDmeLogLayer *layer ) +{ + Assert( layer ); + Assert( (static_cast< CDmeTypedLogLayer< T > * >( layer ))->GetTypedOwnerLog() == this ); + m_Layers.AddToTail( layer ); +} + +template< class T > +CDmeLogLayer *CDmeTypedLog< T >::RemoveLayerFromTail() +{ + Assert( m_Layers.Count() >= 1 ); + CDmeLogLayer *layer = m_Layers[ m_Layers.Count() -1 ]; + m_Layers.Remove( m_Layers.Count() - 1 ); + return layer; +} + +template< class T > +CDmeLogLayer *CDmeTypedLog< T >::RemoveLayer( int iLayer ) +{ + Assert( m_Layers.IsValidIndex( iLayer ) ); + CDmeLogLayer *layer = m_Layers[ iLayer ]; + m_Layers.Remove( iLayer ); + return layer; +} + + +template< class T > +CDmeLogLayer *CDmeTypedLog< T >::AddNewLayer() +{ + if ( g_pDataModel->UndoEnabledForElement( this ) ) + { + CUndoLayerAdded<T> *pUndo = new CUndoLayerAdded<T>( "AddNewLayer", this ); + g_pDataModel->AddUndoElement( pUndo ); + } + + CDisableUndoScopeGuard guard; + + // Now add the layer to the stack!!! + CDmeTypedLogLayer< T > *layer = static_cast< CDmeTypedLogLayer< T > * >( CreateLayer<T>( this ) ); + if ( layer ) + { + layer->SetOwnerLog( this ); + m_Layers.AddToTail( layer ); + } + + return layer; +} + +// curve info helpers +template< class T > +const CDmeTypedCurveInfo< T > *CDmeTypedLog<T>::GetTypedCurveInfo() const +{ + Assert( !m_CurveInfo.GetElement() || dynamic_cast< const CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ) ); + return static_cast< const CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ); +} + +template< class T > +CDmeTypedCurveInfo< T > *CDmeTypedLog<T>::GetTypedCurveInfo() +{ + Assert( !m_CurveInfo.GetElement() || dynamic_cast< CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ) ); + return static_cast< CDmeTypedCurveInfo< T > * >( m_CurveInfo.GetElement() ); +} + +template< class T > +void CDmeTypedLog<T>::SetCurveInfo( CDmeCurveInfo *pCurveInfo ) +{ + Assert( !pCurveInfo || dynamic_cast< CDmeTypedCurveInfo< T > * >( pCurveInfo ) ); + m_CurveInfo = pCurveInfo; + OnUsingCurveTypesChanged(); // FIXME: Is this really necessary? OnAttributeChanged should have already called this! +} + +template< class T > +CDmeCurveInfo *CDmeTypedLog<T>::GetOrCreateCurveInfo() +{ + CDmeCurveInfo *pCurveInfo = m_CurveInfo.GetElement(); + if ( pCurveInfo ) + return pCurveInfo; + + SetCurveInfo( CreateElement< CDmeTypedCurveInfo< T > >( "curveinfo", GetFileId() ) ); + return m_CurveInfo.GetElement(); +} + + + +template < class T > +struct ActiveLayer_t +{ + ActiveLayer_t() : + bValid( false ), + priority( 0 ), + firstTime( 0 ), + lastTime( 0 ), + layer( NULL ) + { + } + + static bool PriorityLessFunc( ActiveLayer_t< T > * const & lhs, ActiveLayer_t< T > * const & rhs ) + { + return lhs->priority < rhs->priority; + } + + int priority; // higher wins + + bool bValid; + DmeTime_t firstTime; + DmeTime_t lastTime; + + CDmeTypedLogLayer< T > *layer; +}; + +template < class T > +struct LayerEvent_t +{ + enum EventType_t + { + LE_START = 0, + LE_END + }; + + LayerEvent_t() : m_pList( NULL ), m_Type( LE_START ), m_nLayer( 0 ), m_Time( 0 ) + { + } + + static bool LessFunc( const LayerEvent_t& lhs, const LayerEvent_t& rhs ) + { + return lhs.m_Time < rhs.m_Time; + } + + CUtlVector< ActiveLayer_t< T > > *m_pList; + EventType_t m_Type; + int m_nLayer; + DmeTime_t m_Time; + T m_NeighborValue; +}; + +template< class T > +static const T& GetActiveLayerValue( CUtlVector< ActiveLayer_t< T > > &layerlist, DmeTime_t t, int nTopmostLayer ) +{ + int nCount = layerlist.Count(); +#ifdef _DEBUG + Assert( nCount >= nTopmostLayer ); +#endif + + for ( int i = nTopmostLayer; i >= 0; --i ) + { + ActiveLayer_t< T > &layer = layerlist[i]; + if ( layer.firstTime > t || layer.lastTime < t ) + continue; + + return layer.layer->GetValue( t ); + } + + if ( nCount != 0 ) + { + const CDmeTypedLog< T > *pOwner = layerlist[0].layer->GetTypedOwnerLog(); + if ( pOwner->HasDefaultValue() ) + return pOwner->GetDefaultValue(); + } + + static T defaultVal; + CDmAttributeInfo<T>::SetDefaultValue( defaultVal ); + return defaultVal; +} + + +template< class T > +static void SpewEvents( CUtlRBTree< LayerEvent_t< T > > &events ) +{ + for ( unsigned short idx = events.FirstInorder(); idx != events.InvalidIndex(); idx = events.NextInorder( idx ) ) + { + LayerEvent_t< T > *pEvent = &events[ idx ]; + Msg( "Event %u layer %i at time %i type %s\n", + (unsigned)idx, pEvent->m_nLayer, pEvent->m_Time.GetTenthsOfMS(), pEvent->m_Type == LayerEvent_t< T >::LE_START ? "start" : "end" ); + } +} + +template< class T > +inline void SpewKey( const T& ) +{ + Msg( "GenericType" ); +} + +template<> +inline void SpewKey<float>( const float& val ) +{ + Msg( "%f", val ); +} + +template<> +inline void SpewKey<int>( const int& val ) +{ + Msg( "%d", val ); +} + +template<> +inline void SpewKey<Vector2D>( const Vector2D& val ) +{ + Msg( "%f,%f", val.x, val.y ); +} + +template<> +inline void SpewKey<Vector4D>( const Vector4D& val ) +{ + Msg( "%f,%f,%f,%f", val.x, val.y, val.z, val.w ); +} + +template<> +inline void SpewKey<DmeTime_t>( const DmeTime_t& val ) +{ + Msg( "%d", val.GetTenthsOfMS() ); +} + +template<> +inline void SpewKey<bool>( const bool& val ) +{ + Msg( "%s", val ? "true" : "false" ); +} + +template<> +inline void SpewKey<Color>( const Color& val ) +{ + Msg( "%08x", val.GetRawColor() ); +} + +template< > +inline void SpewKey( const Vector& val ) +{ + Msg( "[%f %f %f]", val.x, val.y, val.z ); +} + +template< > +inline void SpewKey( const Quaternion& val ) +{ + Msg( "[%f %f %f %f]", val.x, val.y, val.z, val.w ); +} + +template< class T > +static void SpewFlattenedKey( CDmeTypedLogLayer< T > *pLogLayer, ActiveLayer_t< T > *pActiveLayer, DmeTime_t t, const T& val ) +{ + Msg( "Layer %d: adding key at time %f [%d -> %d], value ", + pActiveLayer->priority, t.GetSeconds(), pActiveLayer->firstTime.GetTenthsOfMS(), pActiveLayer->lastTime.GetTenthsOfMS() ); + SpewKey( val ); + Msg( "\n" ); +} + +template< class T > +static void ComputeLayerEvents( CDmeTypedLog< T >* pLog, + CUtlVector< ActiveLayer_t< T > > &layerlist, + CUtlRBTree< LayerEvent_t< T > > &events ) +{ + // Build a list of all known layers and a sorted list of layer "transitions" + for ( int i = 0; i < pLog->GetNumLayers(); ++i ) + { + ActiveLayer_t< T > layer; + layer.priority = i; + layer.layer = static_cast< CDmeTypedLogLayer< T > * >( pLog->GetLayer( i ) ); + layer.firstTime = layer.layer->GetBeginTime(); + layer.lastTime = layer.layer->GetEndTime(); + layer.bValid = true; + + if ( ( layer.firstTime == DMETIME_MINTIME || layer.lastTime == DMETIME_MAXTIME ) && ( i > 0 ) ) // Base layer is always valid + { + layer.bValid = false; + } + + // Skip invalid layers + if ( !layer.bValid ) + continue; + + // Layer zero can capture everything from above... + if ( i == 0 ) + { + layer.firstTime = DmeTime_t::MinTime(); + layer.lastTime = DmeTime_t::MaxTime(); + } + + // Add layer to global list + int nIndex = layerlist.AddToTail( layer ); + + // Add layer start/end events + DmeTime_t tNeighbor = ( layer.firstTime != DMETIME_MINTIME ) ? ( layer.firstTime - DMETIME_MINDELTA ) : DMETIME_MINTIME; + LayerEvent_t< T > start; + start.m_pList = &layerlist; + start.m_nLayer = nIndex; + start.m_Type = LayerEvent_t< T >::LE_START; + start.m_Time = layer.firstTime; + start.m_NeighborValue = GetActiveLayerValue( layerlist, tNeighbor, nIndex - 1 ); + events.Insert( start ); + + tNeighbor = ( layer.lastTime != DMETIME_MAXTIME ) ? ( layer.lastTime + DMETIME_MINDELTA ) : DMETIME_MAXTIME; + LayerEvent_t< T > end; + end.m_pList = &layerlist; + end.m_nLayer = nIndex; + end.m_Type = LayerEvent_t< T >::LE_END; + end.m_Time = layer.lastTime; + end.m_NeighborValue = GetActiveLayerValue( layerlist, tNeighbor, nIndex - 1 ); + events.Insert( end ); + } +} + + +template< class T > +static void AddDiscontinitySample( CDmeTypedLogLayer< T > *pTargetLayer, CDmeTypedLog< T > *pLog, DmeTime_t tKeyTime, const T& val, const char *pSpewLabel ) +{ + // Finally, add a helper key + if ( pLog->IsUsingCurveTypes() ) + { + if ( pSpewLabel ) + { + Msg( "Adding %s helper key at %d value ", pSpewLabel, tKeyTime.GetTenthsOfMS() ); + SpewKey( val ); + Msg( " [curvetype %s]\n", Interpolator_NameForCurveType( pLog->GetDefaultCurveType(), false ) ); + } + pTargetLayer->SetKey( tKeyTime, val, pLog->GetDefaultCurveType() ); + } + else + { + if ( pSpewLabel ) + { + Msg( "Adding %s helper key at %d value ", pSpewLabel, tKeyTime.GetTenthsOfMS() ); + SpewKey( val ); + Msg( "\n" ); + } + pTargetLayer->SetKey( tKeyTime, val ); + } +} + + +template< class T > +static DmeTime_t ProcessStartLayerStartEvent( + bool bSpew, + bool bFixupDiscontinuities, + CDmeTypedLog< T > *pLog, + LayerEvent_t< T > *pEvent, + CUtlVector< ActiveLayer_t< T > > &layerlist, + CUtlRBTree< ActiveLayer_t< T > * > &active, + CDmeTypedLogLayer< T > *flattenedlayer ) +{ + Assert( pEvent->m_Type == LayerEvent_t< T >::LE_START ); + + // Push it onto the active stack if it's not already on the stack + if ( active.Find( &layerlist[ pEvent->m_nLayer ] ) != active.InvalidIndex() ) + return pEvent->m_Time; + + if ( bSpew ) + { + Msg( "adding layer %d to stack\n", layerlist[ pEvent->m_nLayer ].priority ); + } + active.Insert( &layerlist[ pEvent->m_nLayer ] ); + + if ( !bFixupDiscontinuities || ( pEvent->m_Time == DMETIME_MINTIME ) ) + return pEvent->m_Time; + + // We'll need to add 2 new "discontinuity" fixup samples. + // 1) A sample from the base layer @ start time - .1 msec + // 2) A sample from the new layer @ start time + int nActiveCount = active.Count(); + if ( nActiveCount >= 2 ) + { + DmeTime_t tKeyTime = pEvent->m_Time - DmeTime_t( 1 ); + AddDiscontinitySample( flattenedlayer, pLog, tKeyTime, pEvent->m_NeighborValue, bSpew ? "start" : NULL ); + } + AddDiscontinitySample( flattenedlayer, pLog, pEvent->m_Time, GetActiveLayerValue( layerlist, pEvent->m_Time, pEvent->m_nLayer ), bSpew ? "start" : NULL ); + return pEvent->m_Time; +} + +template< class T > +static DmeTime_t ProcessStartLayerEndEvent( + bool bSpew, + bool bFixupDiscontinuities, + CDmeTypedLog< T > *pLog, + LayerEvent_t< T > *pEvent, + CUtlVector< ActiveLayer_t< T > > &layerlist, + CUtlRBTree< ActiveLayer_t< T > * > &active, + CDmeTypedLogLayer< T > *pBaseLayer ) +{ + Assert( pEvent->m_Type == LayerEvent_t< T >::LE_END ); + + // Push it onto the active stack if it's not already on the stack + if ( bSpew ) + { + Msg( "removing layer %d from stack\n", layerlist[ pEvent->m_nLayer ].priority ); + } + + // We'll need to add a "discontinuity" fixup sample from the + // 1) A sample from the ending layer @ start time + // 2) A sample from the new layer @ start time + .1 msec + // NOTE: This will cause problems if there are non-default value keys at max time + Assert( active.Count() >= 1 ); + if ( bFixupDiscontinuities && ( pEvent->m_Time != DMETIME_MAXTIME ) ) + { + AddDiscontinitySample( pBaseLayer, pLog, pEvent->m_Time, GetActiveLayerValue( layerlist, pEvent->m_Time, pEvent->m_nLayer ), bSpew ? "end" : NULL ); + if ( active.Count() >= 2 ) + { + DmeTime_t keyTime = pEvent->m_Time + DmeTime_t( 1 ); + AddDiscontinitySample( pBaseLayer, pLog, keyTime, pEvent->m_NeighborValue, bSpew ? "end" : NULL ); + } + } + + active.Remove( &layerlist[ pEvent->m_nLayer ] ); + return ( active.Count() >= 2 ) ? pEvent->m_Time + DmeTime_t( 1 ) : pEvent->m_Time; +} + +template< class T > +void CDmeTypedLog< T >::FlattenLayers( float threshold, int flags ) +{ + // Already flattened + if ( m_Layers.Count() <= 1 ) + return; + + if ( g_pDataModel->UndoEnabledForElement( this ) ) + { + CUndoFlattenLayers<T> *pUndo = new CUndoFlattenLayers<T>( "FlattenLayers", this, threshold, flags ); + g_pDataModel->AddUndoElement( pUndo ); + } + + bool bSpew = ( flags & FLATTEN_SPEW ) != 0; + bool bFixupDiscontinuities = true; //( flags & FLATTEN_NODISCONTINUITY_FIXUP ) == 0; + + // NOTE: UNDO IS DISABLED FOR THE REST OF THIS OPERATION (the above function does what we need to preserve the layers) + CDisableUndoScopeGuard guard; + + CDmeTypedLogLayer< T > *flattenedlayer = static_cast< CDmeTypedLogLayer< T > * >( CreateLayer< T >( this ) ); + flattenedlayer->SetOwnerLog( this ); + + // Global list of layers + CUtlVector< ActiveLayer_t< T > > layerlist; + // List of all start/end layer events, sorted by the time at which the event occurs ( we walk this list in order ) + CUtlRBTree< LayerEvent_t< T > > events( 0, 0, LayerEvent_t< T >::LessFunc ); + // Stack of active events, sorted by event "priority", which means last item is the one writing data into the new base layer + CUtlRBTree< ActiveLayer_t< T > * > active( 0, 0, ActiveLayer_t< T >::PriorityLessFunc ); + + // Build layer list and list of start/end events and times + ComputeLayerEvents( this, layerlist, events ); + + // Debuggins + if ( bSpew ) + { + SpewEvents( events ); + } + + // Now walk from the earliest time in any layer until the latest time, going key by key and checking if the active layer should change as we go + + DmeTime_t iCurrentKeyTime = DmeTime_t::MinTime(); + unsigned short idx = events.FirstInorder(); + while ( 1 ) + { + if ( idx == events.InvalidIndex() ) + break; + + LayerEvent_t< T > *pEvent = &events[ idx ]; + + switch ( pEvent->m_Type ) + { + default: + iCurrentKeyTime = pEvent->m_Time; + Assert( 0 ); + break; + case LayerEvent_t< T >::LE_START: + iCurrentKeyTime = ProcessStartLayerStartEvent( bSpew, bFixupDiscontinuities, this, pEvent, layerlist, active, flattenedlayer ); + break; + + case LayerEvent_t< T >::LE_END: + iCurrentKeyTime = ProcessStartLayerEndEvent( bSpew, bFixupDiscontinuities, this, pEvent, layerlist, active, flattenedlayer ); + break; + } + + int nNextIndex = events.NextInorder( idx ); + + // We popped the last item off the stack + if ( nNextIndex == events.InvalidIndex() ) + { + Assert( active.Count() == 0 ); + break; + } + + // Walk from current time up to the time of the next relevant event + LayerEvent_t< T > *nextevent = &events[ nNextIndex ]; + DmeTime_t layerFinishTime = nextevent->m_Time; + + // The topmost layer is the active layer + int layernum = active.LastInorder(); + if ( layernum == active.InvalidIndex() ) + break; + + ActiveLayer_t< T > *activeLayer = active[ layernum ]; + CDmeTypedLogLayer< T > *loglayer = activeLayer->layer; + + // Splat all keys betweeen the current head position and the next event time (layerFinishTime) into the flattened layer + int keyCount = loglayer->GetKeyCount(); + for ( int j = 0; j < keyCount; ++j ) + { + DmeTime_t keyTime = loglayer->GetKeyTime( j ); + // Key is too early, skip + if ( keyTime < iCurrentKeyTime ) + continue; + + // Done with this layer, set time exactly equal to end time so next layer can take over + // at the correct spot + if ( keyTime >= layerFinishTime ) + { + iCurrentKeyTime = layerFinishTime; + break; + } + + // Advance the head position + iCurrentKeyTime = keyTime; + + // Because it's a key, the interpolated value should == the actual value (not true for certain 4 point curve types, but we shouldn't support them + // for this type of operation anyway) + const T& val = loglayer->GetKeyValue( j ); + + // Debugging spew + if ( bSpew ) + { + SpewFlattenedKey( loglayer, activeLayer, iCurrentKeyTime, val ); + } + + // Now set the key into the flattened layer + flattenedlayer->SetKey( iCurrentKeyTime, val, loglayer->IsUsingCurveTypes() ? loglayer->GetKeyCurveType( j ) : CURVE_DEFAULT ); + } + idx = nNextIndex; + } + + // Blow away all of the existing layers except the original base layer + while ( GetNumLayers() > 1 ) + { + CDmeTypedLogLayer< T > *layer = static_cast< CDmeTypedLogLayer< T > * >( RemoveLayerFromTail() ); + g_pDataModel->DestroyElement( layer->GetHandle() ); + } + + // Compress the flattened layer + flattenedlayer->RemoveRedundantKeys( threshold ); + + // Copy the flattened layer over the existing base layer + GetLayer( 0 )->CopyLayer( flattenedlayer ); + + g_pDataModel->DestroyElement( flattenedlayer->GetHandle() ); +} + +template< class T > +void CDmeTypedLog< T >::StampKeyAtHead( DmeTime_t tHeadPosition, DmeTime_t tPreviousHeadPosition, const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) +{ + DmAttributeType_t type = pAttr->GetType(); + if ( IsValueType( type ) ) + { + Assert( pAttr->GetType() == GetDataType() ); + StampKeyAtHead( tHeadPosition, tPreviousHeadPosition, params, pAttr->GetValue< T >() ); + } + else if ( IsArrayType( type ) ) + { + Assert( ArrayTypeToValueType( type ) == GetDataType() ); + CDmrArrayConst<T> array( pAttr ); + StampKeyAtHead( tHeadPosition, tPreviousHeadPosition, params, array[ index ] ); + } + else + { + Assert( 0 ); + } +} + +template< class T > +void CDmeTypedLog< T >::FinishTimeSelection( DmeTime_t tHeadPosition, DmeLog_TimeSelection_t& params ) +{ + bool bWasAdvancing = params.IsTimeAdvancing(); + params.ResetTimeAdvancing(); + + if ( !params.m_bAttachedMode ) + return; + + if ( !bWasAdvancing ) + return; + + // Should be in "layer recording" mode!!! + Assert( GetNumLayers() >= 2 ); + int nBestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) + if ( nBestLayer < 1 ) + return; + + CDmeTypedLogLayer< T > *pWriteLayer = GetLayer( nBestLayer ); + Assert( pWriteLayer ); + if ( !pWriteLayer ) + return; + + int nKeyCount = pWriteLayer->GetKeyCount(); + if ( nKeyCount <= 0 ) + return; + + // The head is considered to be at the "last" value + T headValue = pWriteLayer->GetKeyValue( nKeyCount - 1 ); + _StampKeyAtHeadResample( tHeadPosition, params, headValue, true, false ); +} + +template< > +float CDmeTypedLog< float >::ClampValue( const float& value ) +{ + float retval; + if ( !IsUsingCurveTypes() ) + { + retval = clamp( value, 0.0f, 1.0f ); + } + else + { + retval = clamp( value, GetMinValue(), GetMaxValue() ); + } + return retval; +} + +template< class T > +void CDmeTypedLog< T >::StampKeyAtHead( DmeTime_t tHeadPosition, DmeTime_t tPreviousHeadPosition, const DmeLog_TimeSelection_t& params, const T& value ) +{ + //T useValue = ClampValue( value ); + + // This gets set if time ever starts moving (even if the user pauses time while still holding a slider) + if ( params.IsTimeAdvancing() ) + { + // This uses the time selection as a "filter" to decide whether to stamp a new key at the current position + _StampKeyAtHeadFilteredByTimeSelection( tHeadPosition, tPreviousHeadPosition, params, value ); + } + else + { + Assert( params.m_bResampleMode ); + _StampKeyAtHeadResample( tHeadPosition, params, value, false, true ); + } +} + +/* +template<> +void CDmeTypedLog< float >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const float& value ); +template<> +void CDmeTypedLog< bool >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const bool& value ); +template<> +void CDmeTypedLog< Color >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Color& value ); +template<> +void CDmeTypedLog< Vector4D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector4D& value ); +template<> +void CDmeTypedLog< Vector2D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector2D& value ); +template<> +void CDmeTypedLog< VMatrix >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const VMatrix& value ); +template<> +void CDmeTypedLog< Quaternion >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Quaternion& value ); +template<> +void CDmeTypedLog< QAngle >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const QAngle& value ); +*/ + + +//----------------------------------------------------------------------------- +// Helper class used to compute falloff blend factors +//----------------------------------------------------------------------------- +template< class T > +struct LogClampHelper_t +{ +public: + LogClampHelper_t() : m_tLastTime( DMETIME_MINTIME ) {} + + DmeTime_t m_tLastTime; + T m_LastUnclampedValue; +}; + + +template< class T > +class CLogFalloffBlend +{ +public: + void Init( CDmeTypedLog<T> *pLog, DmeTime_t tFalloff, DmeTime_t tHold, bool bLeftFalloff, int nInterpolatorType, const T& delta ); + void Init( CDmeTypedLog<T> *pLog, const T& delta ); + float ComputeBlendFactor( DmeTime_t tTime, const T& oldVal, bool bUsingInterpolation ) const; + const T& GetDelta() const; + void StampKey( CDmeTypedLogLayer<T>* pWriteLayer, DmeTime_t t, const CDmeTypedLogLayer<T>* pReadLayer, float flIntensity, LogClampHelper_t<T> &helper, bool bSpew, const T* pInterpTarget ); + void UpdateClampHelper( DmeTime_t t, const CDmeTypedLogLayer<T>* pReadLayer, float flIntensity, LogClampHelper_t<T> &helper, const T* pInterpTarget ); + +private: + void ComputeDelta( CDmeTypedLog<T> *pLog, const T& delta, const T& holdValue ); + void InsertClampTransitionPoints( CDmeTypedLogLayer<T>* pWriteLayer, DmeTime_t t, LogClampHelper_t<T> &clampHelper, const T& val, bool bSpew ); + void ComputeBounds( CDmeTypedLog<T> *pLog ); + + T m_BaseValue; + T m_Delta; + DmeTime_t m_tBaseTime; + DmeTime_t m_tHoldTime; + float m_flOOTime; + int m_nTestSign; + int m_nInterpolatorType; + int m_nCurveType; + T m_MinValue; + T m_MaxValue; + bool m_bHold; +}; + +template< class T > +void CLogFalloffBlend< T >::Init( CDmeTypedLog<T> *pLog, DmeTime_t tFalloffTime, DmeTime_t tHoldTime, bool bLeftFalloff, int nInterpolatorType, const T& delta ) +{ + m_tBaseTime = tFalloffTime; + m_tHoldTime = tHoldTime; + m_BaseValue = pLog->GetValueSkippingTopmostLayer( tFalloffTime ); + T holdValue = pLog->GetValueSkippingTopmostLayer( tHoldTime ); + m_nTestSign = bLeftFalloff ? 1 : -1; + m_nInterpolatorType = nInterpolatorType; + m_bHold = false; + m_nCurveType = pLog->IsUsingCurveTypes() ? pLog->GetDefaultCurveType() : CURVE_DEFAULT; + + float flDuration = tHoldTime.GetSeconds() - tFalloffTime.GetSeconds(); + m_flOOTime = ( flDuration != 0.0f ) ? 1.0f / flDuration : 0.0f; + ComputeBounds( pLog ); + ComputeDelta( pLog, delta, holdValue ); +} + +template< class T > +void CLogFalloffBlend< T >::Init( CDmeTypedLog<T> *pLog, const T& delta ) +{ + m_nTestSign = 0; + m_nInterpolatorType = INTERPOLATE_DEFAULT; + m_bHold = true; + m_nCurveType = pLog->IsUsingCurveTypes() ? pLog->GetDefaultCurveType() : CURVE_DEFAULT; + m_Delta = delta; + ComputeBounds( pLog ); +} + +template< class T > +void CLogFalloffBlend< T >::ComputeBounds( CDmeTypedLog<T> *pLog ) +{ +} + +template<> +void CLogFalloffBlend< float >::ComputeBounds( CDmeTypedLog<float> *pLog ) +{ + m_MinValue = pLog->IsUsingCurveTypes() ? pLog->GetMinValue() : 0.0f; + m_MaxValue = pLog->IsUsingCurveTypes() ? pLog->GetMaxValue() : 1.0f; +} + +template< class T > +void CLogFalloffBlend< T >::ComputeDelta( CDmeTypedLog<T> *pLog, const T& delta, const T& holdValue ) +{ + // By default, no clamping + m_Delta = delta; +} + +template<> +void CLogFalloffBlend< float >::ComputeDelta( CDmeTypedLog<float> *pLog, const float& delta, const float& holdValue ) +{ + if ( LengthOf( delta ) > 0.0f ) + { + m_Delta = min( delta, m_MaxValue - holdValue ); // Max amount we can move up... + } + else + { + m_Delta = max( delta, m_MinValue - holdValue ); // Amount we can move down... + } +} + + +template< class T > +float CLogFalloffBlend< T >::ComputeBlendFactor( DmeTime_t tTime, const T& oldVal, bool bUsingInterpolation ) const +{ + if ( m_bHold ) + return 1.0f; + + // Clamp inside region; hold time beats base time (for zero width regions) + if ( ( tTime - m_tHoldTime ) * m_nTestSign >= DMETIME_ZERO ) + return 1.0f; + + if ( ( tTime - m_tBaseTime ) * m_nTestSign <= DMETIME_ZERO ) + return 0.0f; + + float flFactor = ( tTime.GetSeconds() - m_tBaseTime.GetSeconds() ) * m_flOOTime; + return ComputeInterpolationFactor( flFactor, m_nInterpolatorType ); +} + +template< class T > +const T& CLogFalloffBlend< T >::GetDelta( ) const +{ + return m_Delta; +} + + +//----------------------------------------------------------------------------- +// Insert points where clamping begins or ends +//----------------------------------------------------------------------------- +template< class T > +void CLogFalloffBlend< T >::InsertClampTransitionPoints( CDmeTypedLogLayer<T>* pWriteLayer, + DmeTime_t t, LogClampHelper_t<T> &clampHelper, const T& val, bool bSpew ) +{ + // NOTE: By default, nothing clamps, so no transition points are needed +} + +template<> +void CLogFalloffBlend< float >::InsertClampTransitionPoints( CDmeTypedLogLayer<float>* pWriteLayer, + DmeTime_t t, LogClampHelper_t<float> &clampHelper, const float& val, bool bSpew ) +{ + bool bLastLess, bLastGreater, bCurrLess, bCurrGreater; + DmeTime_t tCrossing, tDuration; + double flOODv; + + // First time through? cache last values. + if ( clampHelper.m_tLastTime == DMETIME_MINTIME ) + goto cacheLastValues; + + bLastLess = clampHelper.m_LastUnclampedValue < m_MinValue; + bLastGreater = clampHelper.m_LastUnclampedValue > m_MaxValue; + bCurrLess = val < m_MinValue; + bCurrGreater = val > m_MaxValue; + if ( bLastLess == bCurrLess && bLastGreater == bCurrGreater ) + goto cacheLastValues; + + // NOTE: The check above means val != m_LastUnclampedValue + flOODv = 1.0 / ( val - clampHelper.m_LastUnclampedValue ); + tDuration = t - clampHelper.m_tLastTime; + + // NOTE: Clamp semantics here favor keeping the non-clamped value + // That's why when we start outside + end inside, we never overwrite the dest + // and why when we start inside + end outside, we never overwrite the start + // These two checks deal with starting outside + heading inside + if ( bLastLess && !bCurrLess ) + { + // Insert at min crossing + double flFactor = ( m_MinValue - clampHelper.m_LastUnclampedValue ) * flOODv; + tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; + tCrossing.Clamp( clampHelper.m_tLastTime, t - DMETIME_MINDELTA ); + pWriteLayer->InsertKey( tCrossing, m_MinValue, m_nCurveType ); + if ( bSpew ) + { + Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MinValue ); + } + } + else if ( bLastGreater && !bCurrGreater ) + { + // Insert at max crossing + double flFactor = ( m_MaxValue - clampHelper.m_LastUnclampedValue ) * flOODv; + tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; + tCrossing.Clamp( clampHelper.m_tLastTime, t - DMETIME_MINDELTA ); + pWriteLayer->InsertKey( tCrossing, m_MaxValue, m_nCurveType ); + if ( bSpew ) + { + Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MaxValue ); + } + } + + // These two checks deal with starting inside + heading outside + if ( !bLastLess && bCurrLess ) + { + // Insert at min crossing + // NOTE: Clamp semantics here favor keeping the non-clamped value + double flFactor = ( m_MinValue - clampHelper.m_LastUnclampedValue ) * flOODv; + tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; + tCrossing.Clamp( clampHelper.m_tLastTime + DMETIME_MINDELTA, t ); + pWriteLayer->InsertKey( tCrossing, m_MinValue, m_nCurveType ); + if ( bSpew ) + { + Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MinValue ); + } + } + else if ( !bLastGreater && bCurrGreater ) + { + // Insert at max crossing + double flFactor = ( m_MaxValue - clampHelper.m_LastUnclampedValue ) * flOODv; + tCrossing = clampHelper.m_tLastTime + tDuration * flFactor; + tCrossing.Clamp( clampHelper.m_tLastTime + DMETIME_MINDELTA, t ); + pWriteLayer->InsertKey( tCrossing, m_MaxValue, m_nCurveType ); + if ( bSpew ) + { + Msg(" Clamp Crossing Key: %d %f\n", tCrossing.GetTenthsOfMS(), m_MaxValue ); + } + } + + // Cache off the last values +cacheLastValues: + clampHelper.m_tLastTime = t; + clampHelper.m_LastUnclampedValue = val; +} + + + +//----------------------------------------------------------------------------- +// Stamp the key at the specified time +//----------------------------------------------------------------------------- +template< class T > +void CLogFalloffBlend< T >::StampKey( CDmeTypedLogLayer<T>* pWriteLayer, DmeTime_t t, + const CDmeTypedLogLayer<T>* pReadLayer, float flIntensity, LogClampHelper_t<T> &clampHelper, bool bSpew, const T* pInterpTarget ) +{ + // Stamp the key at the current time + T oldVal = pReadLayer->GetValue( t ); + + // In the falloff area + float flFactor = ComputeBlendFactor( t, oldVal, ( pInterpTarget != NULL ) ); + flFactor *= flIntensity; + + T newVal; + if ( !pInterpTarget ) + { + newVal = ScaleValue( m_Delta, flFactor ); + newVal = Add( oldVal, newVal ); + } + else + { + newVal = Interpolate( flFactor, oldVal, *pInterpTarget ); + } + + InsertClampTransitionPoints( pWriteLayer, t, clampHelper, newVal, bSpew ); + + T clampedVal = pWriteLayer->GetTypedOwnerLog()->ClampValue( newVal ); + + // Add a key to the new "layer" at this time with this value + pWriteLayer->InsertKey( t, clampedVal, m_nCurveType ); + + if ( bSpew ) + { + Msg(" Key: %d ", t.GetTenthsOfMS() ); + SpewKey( clampedVal ); + Msg(" [" ); + SpewKey( newVal ); + Msg( "]\n" ); + } +} + + +//----------------------------------------------------------------------------- +// Stamp the key at the specified time +//----------------------------------------------------------------------------- +template< class T > +void CLogFalloffBlend< T >::UpdateClampHelper( DmeTime_t t, const CDmeTypedLogLayer<T>* pReadLayer, + float flIntensity, LogClampHelper_t<T> &clampHelper, const T* pInterpTarget ) +{ + // Stamp the key at the current time + T oldVal = pReadLayer->GetValue( t ); + + // In the falloff area + float flFactor = ComputeBlendFactor( t, oldVal, ( pInterpTarget != NULL ) ); + flFactor *= flIntensity; + + T val; + if ( !pInterpTarget ) + { + val = ScaleValue( m_Delta, flFactor ); + val = Add( oldVal, val ); + } + else + { + val = Interpolate( flFactor, oldVal, *pInterpTarget ); + } + + clampHelper.m_tLastTime = t; + clampHelper.m_LastUnclampedValue = val; +} + + +//----------------------------------------------------------------------------- +// This is used to modify the entire portion of the curve under the time selection +//----------------------------------------------------------------------------- +static inline DmeTime_t ComputeResampleStartTime( const DmeLog_TimeSelection_t ¶ms, int nSide ) +{ + // NOTE: This logic will place the resampled points centered in the falloff regions + DmeTimeSelectionTimes_t start = ( nSide == 0 ) ? TS_LEFT_FALLOFF : TS_RIGHT_HOLD; + DmeTimeSelectionTimes_t end = ( nSide == 0 ) ? TS_LEFT_HOLD : TS_RIGHT_FALLOFF; + + if ( params.m_nFalloffInterpolatorTypes[nSide] != INTERPOLATE_LINEAR_INTERP ) + { + DmeTime_t tDuration = params.m_nTimes[end] - params.m_nTimes[start]; + if ( tDuration > params.m_nResampleInterval ) + { + int nFactor = tDuration.GetTenthsOfMS() / params.m_nResampleInterval.GetTenthsOfMS(); + tDuration -= params.m_nResampleInterval * nFactor; + tDuration /= 2; + return params.m_nTimes[start] + tDuration; + } + } + return DMETIME_MAXTIME; +} + + +//----------------------------------------------------------------------------- +// This is used to modify the entire portion of the curve under the time selection +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLog< T >::_StampKeyAtHeadResample( DmeTime_t tHeadPosition, const DmeLog_TimeSelection_t& params, const T& value, bool bSkipToHead, bool bClearPreviousKeys ) +{ + Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); + if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) + return; + + // Should be in "layer recording" mode!!! + Assert( GetNumLayers() >= 2 ); + int nBestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) + if ( nBestLayer < 1 ) + return; + CDmeTypedLogLayer< T > *pWriteLayer = GetLayer( nBestLayer ); + Assert( pWriteLayer ); + if ( !pWriteLayer ) + return; + + if ( bClearPreviousKeys ) + { + pWriteLayer->ClearKeys(); + } + + bool bSpew = false; + + // NOTE: The headDelta is only used when not blending toward a preset + // When not blending toward a preset, just add the head delta onto everything. + // When blending toward a preset, lerp towards the preset. + T oldHeadValue = GetValueSkippingTopmostLayer( tHeadPosition ); + T headDelta = Subtract( value, oldHeadValue ); + + // When dragging preset fader, eveything get's blended in by the amount of the preset being applied + bool bUsePresetRules = ( RECORD_PRESET == params.GetRecordingMode() ); + bool bIsStampingQuaternions = ( CDmAttributeInfo<T>::ATTRIBUTE_TYPE == AT_QUATERNION ); + bool bPerformInterpolation = bUsePresetRules || bIsStampingQuaternions; + + // FIXME: Preset value should never be NULL. We need to grab it from the attribute + bool bUsePresetValue = bUsePresetRules && params.m_pPresetValue && params.m_pPresetValue->GetType() == CDmAttributeInfo<T>::ATTRIBUTE_TYPE; + const T& interpTarget = bUsePresetValue ? params.m_pPresetValue->GetValue<T>() : value; + + // Compute falloff region blend factors + CLogFalloffBlend< T > blend[ 3 ]; + blend[0].Init( this, params.m_nTimes[ TS_FALLOFF(0) ], params.m_nTimes[ TS_HOLD(0) ], true, params.m_nFalloffInterpolatorTypes[0], headDelta ); + blend[1].Init( this, headDelta ); + blend[2].Init( this, params.m_nTimes[ TS_FALLOFF(1) ], params.m_nTimes[ TS_HOLD(1) ], false, params.m_nFalloffInterpolatorTypes[1], headDelta ); + + // The algorithm we're going to use is to add samples in the following places: + // 1) At each time selection transition point (start, end of falloff regions) + // NOTE: If a falloff region has 0 size, we'll add points right outside the transition + // 2) At the resample point (we're going to base this so the resamples always occur at the same spots) + // 3) At any existing sample position + // 4) Any time we switch from clamped to not clamped + // By doing this, we will guarantee no bogus slope changes + + // First, compute times for transition regions + DmeTime_t tTransitionTimes[TS_TIME_COUNT]; + memcpy( &tTransitionTimes, ¶ms.m_nTimes, sizeof(params.m_nTimes) ); + if ( tTransitionTimes[TS_LEFT_FALLOFF] == tTransitionTimes[TS_LEFT_HOLD] ) + { + tTransitionTimes[TS_LEFT_FALLOFF] -= DMETIME_MINDELTA; + } + if ( tTransitionTimes[TS_RIGHT_FALLOFF] == tTransitionTimes[TS_RIGHT_HOLD] ) + { + tTransitionTimes[TS_RIGHT_FALLOFF] += DMETIME_MINDELTA; + } + + DmeTime_t tStartTime = params.m_nTimes[ TS_LEFT_FALLOFF ]; + + // Next, compute the first resample time for each region + DmeTime_t tResampleStartTime[TS_TIME_COUNT]; + tResampleStartTime[TS_LEFT_FALLOFF] = DMETIME_MAXTIME; + tResampleStartTime[TS_LEFT_HOLD] = ComputeResampleStartTime( params, 0 ); + tResampleStartTime[TS_RIGHT_HOLD] = DMETIME_MAXTIME; + tResampleStartTime[TS_RIGHT_FALLOFF] = ComputeResampleStartTime( params, 1 ); + + // Finally, figure out which layer we're reading from, + // where the next key is, and when we must stop reading from it + int nReadLayer = FindLayerForTimeSkippingTopmost( tStartTime ); + CDmeTypedLogLayer< T > *pReadLayer = GetLayer( nReadLayer ); + int nLayerSampleIndex = pReadLayer->FindKey( tStartTime ) + 1; + DmeTime_t tLayerEndTime = pReadLayer->GetEndTime(); + // NOTE: This can happen after reading off the end of layer 0 + if ( tLayerEndTime <= tStartTime ) + { + tLayerEndTime = DMETIME_MAXTIME; + } + DmeTime_t tNextSampleTime = nLayerSampleIndex >= pReadLayer->GetKeyCount() ? tLayerEndTime : pReadLayer->GetKeyTime( nLayerSampleIndex ); + if ( tNextSampleTime > tLayerEndTime ) + { + tNextSampleTime = tLayerEndTime; + } + + // Now keep going until we've hit the end point + // NOTE: We use tTransitionTimes, *not* params.m_nTimes, so that we can get a single + // sample before zero-width left falloff regions + DmeTime_t tCurrent = tTransitionTimes[TS_LEFT_FALLOFF]; + int nNextTransition = TS_LEFT_HOLD; + DmeTime_t tResampleTime = tResampleStartTime[nNextTransition]; + + const T* pInterpTarget = bPerformInterpolation ? &interpTarget : NULL; + + if ( bSpew ) + { + Msg( "Stamp key at head resample: %s\n", GetName() ); + } + + LogClampHelper_t<T> clampHelper; + while( nNextTransition < TS_TIME_COUNT ) + { + // Stamp the key at the current time + if ( !bSkipToHead || ( tCurrent >= tHeadPosition ) ) + { + blend[nNextTransition-1].StampKey( pWriteLayer, tCurrent, pReadLayer, params.m_flIntensity, clampHelper, bSpew, pInterpTarget ); + } + + // Update the read layer sample + if ( tCurrent == tNextSampleTime ) + { + ++nLayerSampleIndex; + tNextSampleTime = nLayerSampleIndex >= pReadLayer->GetKeyCount() ? tLayerEndTime : pReadLayer->GetKeyTime( nLayerSampleIndex ); + } + + // Update the read layer + if ( tCurrent == tLayerEndTime ) + { + nReadLayer = FindLayerForTimeSkippingTopmost( tCurrent + DMETIME_MINDELTA ); + pReadLayer = GetLayer( nReadLayer ); + nLayerSampleIndex = pReadLayer->FindKey( tCurrent ) + 1; + tLayerEndTime = pReadLayer->GetEndTime(); + + // NOTE: This can happen after reading off the end of layer 0 + if ( tLayerEndTime <= tCurrent ) + { + tLayerEndTime = DMETIME_MAXTIME; + } + + tNextSampleTime = nLayerSampleIndex >= pReadLayer->GetKeyCount() ? tLayerEndTime : pReadLayer->GetKeyTime( nLayerSampleIndex ); + if ( tNextSampleTime > tLayerEndTime ) + { + tNextSampleTime = tLayerEndTime; + } + } + + // Update the transition time + if ( tCurrent == tTransitionTimes[nNextTransition] ) + { + // NOTE: This is necessary because each blend region has different 'deltas' + // to avoid overdriving in the falloff regions. Therefore, the 'previous value' + // used in the clamping operation will be different + if ( nNextTransition < ARRAYSIZE(blend) ) + { + blend[nNextTransition].UpdateClampHelper( tCurrent, pReadLayer, params.m_flIntensity, clampHelper, pInterpTarget ); + } + + // Also need to update the 'previous' value stored in the + ++nNextTransition; + if ( nNextTransition >= ARRAYSIZE(tResampleStartTime) ) + break; + + // Update the first resample time + tResampleTime = tResampleStartTime[nNextTransition]; + + if ( bSpew ) + { + Msg( " Entering region %d\n", nNextTransition-1 ); + } + } + + // Update the resample time + if ( tCurrent == tResampleTime ) + { + tResampleTime += params.m_nResampleInterval; + } + + // Now that the key is stamped, update current time. + tCurrent = tTransitionTimes[nNextTransition]; + if ( tResampleTime < tCurrent ) + { + tCurrent = tResampleTime; + } + if ( tNextSampleTime < tCurrent ) + { + tCurrent = tNextSampleTime; + } + } +} + + +//----------------------------------------------------------------------------- +// In this case, we actually stamp a key right at the head position unlike the above method +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLog< T >::_StampKeyFilteredByTimeSelection( CDmeTypedLogLayer< T > *pWriteLayer, DmeTime_t t, const DmeLog_TimeSelection_t ¶ms, const T& value, bool bForce ) +{ + // Found a key which needs to be modulated upward + float flFraction = params.GetAmountForTime( t ) * params.m_flIntensity; + if ( flFraction <= 0.0f && !bForce ) + return; + + // When dragging preset fader, eveything get's blended in by the amount of the preset being applied + bool bUsePresetRules = ( RECORD_PRESET == params.GetRecordingMode() ); + + // FIXME: Preset value should never be NULL. We need to grab it from the attribute + const T& interpTarget = ( bUsePresetRules && params.m_pPresetValue ) ? params.m_pPresetValue->GetValue<T>() : value; + T oldVal = GetValueSkippingTopmostLayer( t ); + T newVal = Interpolate( flFraction, oldVal, interpTarget ); + T writeVal = ClampValue( newVal ); + pWriteLayer->InsertKey( t, writeVal, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); +} + + +//----------------------------------------------------------------------------- +// In this case, we actually stamp a key right at the head position unlike the above method +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLog< T >::_StampKeyAtHeadFilteredByTimeSelection( DmeTime_t tHeadPosition, DmeTime_t tPreviousHeadPosition, const DmeLog_TimeSelection_t ¶ms, const T& value ) +{ + // Should be in "layer recording" mode!!! + Assert( GetNumLayers() >= 2 ); + int nBestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) + if ( nBestLayer < 1 ) + return; + + CDmeTypedLogLayer< T > *pWriteLayer = GetLayer( nBestLayer ); + Assert( pWriteLayer ); + if ( !pWriteLayer ) + return; + + // NOTE: This little trickery is necessary to generate samples right outside the + // transition region in the case of zero length falloff regions + DmeLog_TimeSelection_t tempParams = params; + if ( tempParams.m_nTimes[TS_LEFT_FALLOFF] == tempParams.m_nTimes[TS_LEFT_HOLD] ) + { + tempParams.m_nTimes[TS_LEFT_FALLOFF] -= DMETIME_MINDELTA; + } + if ( tempParams.m_nTimes[TS_RIGHT_FALLOFF] == tempParams.m_nTimes[TS_RIGHT_HOLD] ) + { + tempParams.m_nTimes[TS_RIGHT_FALLOFF] += DMETIME_MINDELTA; + } + + int nPrevRegion = tempParams.ComputeRegionForTime( tPreviousHeadPosition ); + int nCurrRegion = tempParams.ComputeRegionForTime( tHeadPosition ); + + // Test for backward performance! + if ( nCurrRegion < nPrevRegion ) + { + V_swap( nCurrRegion, nPrevRegion ); + } + + // Insert samples at each transition point we skipped over + for ( int i = nPrevRegion; i < nCurrRegion; ++i ) + { + _StampKeyFilteredByTimeSelection( pWriteLayer, tempParams.m_nTimes[i], params, value, true ); + } + + _StampKeyFilteredByTimeSelection( pWriteLayer, tHeadPosition, params, value ); +} + +template< class T > +void CDmeTypedLog< T >::RemoveKeys( DmeTime_t starttime ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + + GetLayer( bestLayer )->RemoveKeys( starttime ); +} + +template< class T > +void CDmeTypedLog< T >::RemoveKey( int nKeyIndex, int nNumKeysToRemove /*= 1*/ ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + + GetLayer( bestLayer )->RemoveKey( nKeyIndex, nNumKeysToRemove ); +} + +template< class T > +void CDmeTypedLog< T >::ClearKeys() +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + + GetLayer( bestLayer )->ClearKeys(); +} + +//----------------------------------------------------------------------------- +// Returns a specific key's value +//----------------------------------------------------------------------------- +template< class T > +DmeTime_t CDmeTypedLog< T >::GetKeyTime( int nKeyIndex ) const +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return DmeTime_t::MinTime(); + return GetLayer( bestLayer )->GetKeyTime( nKeyIndex ); +} + +template< class T > +void CDmeTypedLog< T >::SetKeyTime( int nKeyIndex, DmeTime_t keyTime ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + return GetLayer( bestLayer )->SetKeyTime( nKeyIndex, keyTime ); +} + +//----------------------------------------------------------------------------- +// Returns the index of a particular key +//----------------------------------------------------------------------------- +template< class T > +int CDmeTypedLog< T >::FindKeyWithinTolerance( DmeTime_t nTime, DmeTime_t nTolerance ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return -1; + + return GetLayer( bestLayer )->FindKeyWithinTolerance( nTime, nTolerance ); +} + +//----------------------------------------------------------------------------- +// tests whether two values differ by more than the threshold +//----------------------------------------------------------------------------- +template<> +bool CDmeTypedLog< Vector >::ValuesDiffer( const Vector& a, const Vector& b ) const +{ + return a.DistToSqr( b ) > m_threshold * m_threshold; +} + +template<> +bool CDmeTypedLog< QAngle >::ValuesDiffer( const QAngle& a, const QAngle& b ) const +{ + return ( a - b ).LengthSqr() > m_threshold * m_threshold; +} + +template<> +bool CDmeTypedLog< Quaternion >::ValuesDiffer( const Quaternion& a, const Quaternion& b ) const +{ + return QuaternionAngleDiff( a, b ) > m_threshold; +} + +template<> +bool CDmeTypedLog< float >::ValuesDiffer( const float& a, const float& b ) const +{ + return fabs( a - b ) > m_threshold; +} + +template< class T > +bool CDmeTypedLog< T >::ValuesDiffer( const T& a, const T& b ) const +{ + return a != b; +} + +//----------------------------------------------------------------------------- +// Sets a key, removes all keys after this time +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLog< T >::SetKey( DmeTime_t time, const T& value, int curveType /*=CURVE_DEFAULT*/) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer < 0 ) + return; + + GetLayer( bestLayer )->SetKey( time, value, curveType ); +} + +template< class T > +CDmeTypedLogLayer< T > *CDmeTypedLog< T >::GetLayer( int index ) +{ + if ( index < 0 ) + return NULL; + + return static_cast< CDmeTypedLogLayer< T > * >( m_Layers[ index ] ); +} + +template< class T > +const CDmeTypedLogLayer< T > *CDmeTypedLog< T >::GetLayer( int index ) const +{ + if ( index < 0 ) + return NULL; + + return static_cast< CDmeTypedLogLayer< T > * >( m_Layers[ index ] ); +} + + +//----------------------------------------------------------------------------- +// Finds a key within tolerance, or adds one +//----------------------------------------------------------------------------- +template< class T > +int CDmeTypedLog< T >::FindOrAddKey( DmeTime_t nTime, DmeTime_t nTolerance, const T& value, int curveType /*=CURVE_DEFAULT*/ ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer == -1 ) + return -1; + + return GetLayer( bestLayer )->FindOrAddKey( nTime, nTolerance, value, curveType ); +} + + +//----------------------------------------------------------------------------- +// This inserts a key. Unlike SetKey, this will *not* delete keys after the specified time +//----------------------------------------------------------------------------- +template < class T > +int CDmeTypedLog< T >::InsertKey( DmeTime_t nTime, const T& value, int curveType /*=CURVE_DEFAULT*/ ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer == -1 ) + return -1; + + return GetLayer( bestLayer )->InsertKey( nTime, value, curveType ); +} + +template < class T > +int CDmeTypedLog< T >::InsertKeyAtTime( DmeTime_t nTime, int curveType /*=CURVE_DEFAULT*/ ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer == -1 ) + return -1; + + return GetLayer( bestLayer )->InsertKeyAtTime( nTime, curveType ); +} + +template< class T > +const T& CDmeTypedLog< T >::GetValue( DmeTime_t time ) const +{ + int bestLayer = FindLayerForTime( time ); + if ( bestLayer < 0 ) + { + static T s_value; + CDmAttributeInfo< T >::SetDefaultValue( s_value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return s_value; + } + + return GetLayer( bestLayer )->GetValue( time ); +} + +template< class T > +const T& CDmeTypedLog< T >::GetValueSkippingTopmostLayer( DmeTime_t time ) const +{ + int nLayer = FindLayerForTimeSkippingTopmost( time ); + if ( nLayer < 0 ) + return GetValue( time ); + return GetLayer( nLayer )->GetValue( time ); +} + +template< class T > +void CDmeTypedLog< T >::SetKey( DmeTime_t time, const CDmAttribute *pAttr, uint index, int curveType /*= CURVE_DEFAULT*/ ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer == -1 ) + return; + + GetLayer( bestLayer )->SetKey( time, pAttr, index, curveType ); +} + +template< class T > +bool CDmeTypedLog< T >::SetDuplicateKeyAtTime( DmeTime_t time ) +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer == -1 ) + return false; + + return GetLayer( bestLayer )->SetDuplicateKeyAtTime( time ); +} + + +//----------------------------------------------------------------------------- +// Returns a specific key's value +//----------------------------------------------------------------------------- +template< class T > +const T& CDmeTypedLog< T >::GetKeyValue( int nKeyIndex ) const +{ + int bestLayer = GetTopmostLayer(); + if ( bestLayer == -1 ) + { + static T s_value; + CDmAttributeInfo< T >::SetDefaultValue( s_value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + return s_value; + } + + return GetLayer( bestLayer )->GetKeyValue( nKeyIndex ); +} + +template< class T > +void CDmeTypedLog< T >::GetValue( DmeTime_t time, CDmAttribute *pAttr, uint index ) const +{ + int bestLayer = FindLayerForTime( time ); + if ( bestLayer < 0 ) + { + T value; + CDmAttributeInfo< T >::SetDefaultValue( value ); // TODO - create GetDefaultValue that returns a default T, to avoid rebuilding every time + pAttr->SetValue( CDmAttributeInfo< T >::AttributeType(), &value ); + } + + return GetLayer( bestLayer )->GetValue( time, pAttr, index ); +} + +template< class T > +void CDmeTypedLog< T >::GetValueSkippingTopmostLayer( DmeTime_t time, CDmAttribute *pAttr, uint index = 0 ) const +{ + CUtlVector< int > layers; + FindLayersForTime( time, layers ); + int layerCount = layers.Count(); + if ( layerCount <= 1 ) + { + return GetValue( time, pAttr, index ); + } + + int topMostLayer = GetTopmostLayer(); + int useLayer = layers[ layerCount - 1 ]; + if ( topMostLayer == useLayer ) + { + useLayer = layers[ layerCount - 2 ]; + } + Assert( useLayer >= 0 ); + return GetLayer( useLayer )->GetValue( time, pAttr, index ); +} + +template< class T > +float CDmeTypedLog< T >::GetComponent( DmeTime_t time, int componentIndex ) const +{ + return ::GetComponent( GetValue( time ), componentIndex ); +} + +//----------------------------------------------------------------------------- +// resampling and filtering +//----------------------------------------------------------------------------- +template< class T > +void CDmeTypedLog< T >::Resample( DmeFramerate_t samplerate ) +{ + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + GetLayer( i )->Resample( samplerate ); + } +} + +template< class T > +void CDmeTypedLog< T >::Filter( int nSampleRadius ) +{ + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + GetLayer( i )->Filter( nSampleRadius ); + } +} + +template< class T > +void CDmeTypedLog< T >::Filter2( DmeTime_t sampleRadius ) +{ + int c = m_Layers.Count(); + for ( int i = 0; i < c; ++i ) + { + GetLayer( i )->Filter2( sampleRadius ); + } +} + +template< class T > +void CDmeTypedLog< T >::OnAttributeArrayElementAdded( CDmAttribute *pAttribute, int nFirstElem, int nLastElem ) +{ + BaseClass::OnAttributeArrayElementAdded( pAttribute, nFirstElem, nLastElem ); + if ( pAttribute == m_Layers.GetAttribute() ) + { + for ( int i = nFirstElem; i <= nLastElem; ++i ) + { + m_Layers[i]->SetOwnerLog( this ); + } + return; + } +} + +template< class T > +void CDmeTypedLog< T >::SetUseEdgeInfo( bool state ) +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->SetUseEdgeInfo( state ); +} + +template< class T > +bool CDmeTypedLog< T >::IsUsingEdgeInfo() const +{ + Assert( IsUsingCurveTypes() ); + return GetTypedCurveInfo()->IsUsingEdgeInfo(); +} + +template< class T > +void CDmeTypedLog< T >::SetEdgeInfo( int edge, bool active, const T& val, int curveType ) +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->SetEdgeInfo( edge, active, val, curveType ); +} + +template< class T > +void CDmeTypedLog< T >::SetDefaultEdgeZeroValue( const T& val ) +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->SetDefaultEdgeZeroValue( val ); +} + +template< class T > +const T& CDmeTypedLog< T >::GetDefaultEdgeZeroValue() const +{ + Assert( IsUsingCurveTypes() ); + return GetTypedCurveInfo()->GetDefaultEdgeZeroValue(); +} + +template< class T > +void CDmeTypedLog< T >::SetRightEdgeTime( DmeTime_t time ) +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->SetRightEdgeTime( time ); +} + +template< class T > +DmeTime_t CDmeTypedLog< T >::GetRightEdgeTime() const +{ + Assert( IsUsingCurveTypes() ); + return GetTypedCurveInfo()->GetRightEdgeTime(); +} + +template< class T > +void CDmeTypedLog< T >::GetEdgeInfo( int edge, bool& active, T& val, int& curveType ) const +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->GetEdgeInfo( edge, active, val, curveType ); +} + +template< class T > +int CDmeTypedLog< T >::GetEdgeCurveType( int edge ) const +{ + Assert( IsUsingCurveTypes() ); + return GetTypedCurveInfo()->GetEdgeCurveType( edge ); +} + +template< class T > +void CDmeTypedLog< T >::GetZeroValue( int side, T& val ) const +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->GetZeroValue( side, val ); +} + +template< class T > +bool CDmeTypedLog< T >::IsEdgeActive( int edge ) const +{ + Assert( IsUsingCurveTypes() ); + return GetTypedCurveInfo()->IsEdgeActive( edge ); +} + +template< class T > +void CDmeTypedLog< T >::GetEdgeValue( int edge, T& val ) const +{ + Assert( IsUsingCurveTypes() ); + GetTypedCurveInfo()->GetEdgeValue( edge, val ); +} + +template< class T > +void CDmeTypedLog< T >::BlendTimesUsingTimeSelection( const CDmeLogLayer *firstLayer, const CDmeLogLayer *secondLayer, CDmeLogLayer *outputLayer, const DmeLog_TimeSelection_t ¶ms, DmeTime_t tStartOffset ) +{ + const CDmeTypedLogLayer< T > *topLayer = static_cast< const CDmeTypedLogLayer< T > * >( secondLayer ); + if ( !topLayer ) + return; + + const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( firstLayer ); + if ( !baseLayer ) + return; + + CDmeTypedLogLayer< T > *newLayer = static_cast< CDmeTypedLogLayer< T > * >( outputLayer ); + if ( !newLayer ) + return; + + Assert( topLayer->GetKeyCount() == baseLayer->GetKeyCount() ); + + int i; + // Resample everything in the base layer first + int kc = baseLayer->GetKeyCount(); + + newLayer->ClearKeys(); + + for ( i = 0; i < kc; ++i ) + { + DmeTime_t baseKeyTime = baseLayer->GetKeyTime( i ); + DmeTime_t checkTime = baseKeyTime + tStartOffset; + if ( checkTime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + if ( checkTime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + break; + float frac = params.GetAmountForTime( checkTime ); + float frac2 = params.m_flIntensity; + float flInterp = frac2 * frac; + + DmeTime_t targetKeyTime = topLayer->GetKeyTime( i ); + + DmeTime_t blendedKeyTime = Lerp( flInterp, baseKeyTime, targetKeyTime ) + tStartOffset; + + T baseVal = baseLayer->GetKeyValue( i ); + + newLayer->InsertKey( blendedKeyTime, baseVal ); + } +} + +template< class T > +void CDmeTypedLog< T >::BlendLayersUsingTimeSelection( const CDmeLogLayer *firstLayer, const CDmeLogLayer *secondLayer, CDmeLogLayer *outputLayer, const DmeLog_TimeSelection_t ¶ms, bool bUseBaseLayerSamples, DmeTime_t tStartOffset ) +{ + const CDmeTypedLogLayer< T > *topLayer = static_cast< const CDmeTypedLogLayer< T > * >( secondLayer ); + if ( !topLayer ) + return; + + const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( firstLayer ); + if ( !baseLayer ) + return; + + CDmeTypedLogLayer< T > *newLayer = static_cast< CDmeTypedLogLayer< T > * >( outputLayer ); + if ( !newLayer ) + return; + + int i; + // Resample everything in the base layer first + int kc = baseLayer->GetKeyCount(); + if ( bUseBaseLayerSamples ) + { + for ( i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = baseLayer->GetKeyTime( i ); + if ( keyTime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + if ( keyTime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + break; + + float frac = params.GetAmountForTime( keyTime ); + float frac2 = params.m_flIntensity; + + T baseVal = baseLayer->GetKeyValue( i ); + T newVal = topLayer->GetValue( keyTime ); + T blended = Interpolate( frac2 * frac, baseVal, newVal ); + + newLayer->SetKey( keyTime + tStartOffset, blended ); + } + } + + kc = topLayer->GetKeyCount(); + for ( i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = topLayer->GetKeyTime( i ); + DmeTime_t finalKeyTime = keyTime + tStartOffset; + if ( finalKeyTime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + if ( finalKeyTime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + break; + float frac = params.GetAmountForTime( finalKeyTime ); + float frac2 = params.m_flIntensity; + + T baseVal = baseLayer->GetValue( keyTime ); + T newVal = topLayer->GetKeyValue( i ); + T blended = Interpolate( frac2 *frac, baseVal, newVal ); + + newLayer->InsertKey( finalKeyTime, blended ); + } + + if ( g_pDmElementFramework->GetPhase() == PH_EDIT ) + { + newLayer->RemoveRedundantKeys( params.m_flThreshold ); + } +} + +template< class T > +void CDmeTypedLog< T >::BlendLayersUsingTimeSelection( const DmeLog_TimeSelection_t ¶ms ) +{ + Assert( GetNumLayers() >= 2 ); + int bestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) + if ( bestLayer <= 0 ) + return; + + Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); + if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) + return; + + CDmeTypedLogLayer< T > *topLayer = GetLayer( bestLayer ); + Assert( topLayer ); + if ( !topLayer ) + return; + + CDmeTypedLogLayer< T > *baseLayer = GetLayer( 0 ); + if ( !baseLayer ) + return; + + CDmeTypedLogLayer< T > *newLayer = static_cast< CDmeTypedLogLayer< T > * >( CreateLayer< T >( this ) ); + if ( !newLayer ) + return; + + BlendLayersUsingTimeSelection( baseLayer, topLayer, newLayer, params, true, DMETIME_ZERO ); + + // Store it back into the new topmost layer + topLayer->CopyLayer( newLayer ); + + g_pDataModel->DestroyElement( newLayer->GetHandle() ); +} + +template< class T > +void CDmeTypedLog< T >::RevealUsingTimeSelection( const DmeLog_TimeSelection_t ¶ms, CDmeLogLayer *savedLayer ) +{ + CDmeTypedLogLayer< T > *saved = static_cast< CDmeTypedLogLayer< T > * >( savedLayer ); + if ( !saved ) + return; + + Assert( GetNumLayers() >= 2 ); + int bestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) + if ( bestLayer <= 0 ) + return; + + Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); + if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) + return; + + CDmeTypedLogLayer< T > *writeLayer = static_cast< CDmeTypedLogLayer< T > * >( GetLayer( bestLayer ) ); + Assert( writeLayer ); + if ( !writeLayer ) + return; + + CDmeLogLayer *baseLayer = GetLayer( 0 ); + if ( !baseLayer ) + return; + + DmeTime_t resample = 0.5f * params.m_nResampleInterval; + + // Do a second pass where we bis the keys in the falloff area back toward the original value + for ( int t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) + { + DmeTime_t curtime = DmeTime_t( t ); + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; + + float frac = params.GetAmountForTime( curtime ); + frac *= params.m_flIntensity; + + if ( frac <= 0.0f ) + continue; + + // Get current value in layer + T curValue = GetValueSkippingTopmostLayer( curtime ); + T revealValue = saved->GetValue( curtime ); + + T newValue = Interpolate( frac, curValue, revealValue ); + + // Overwrite key + writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + + if ( g_pDmElementFramework->GetPhase() == PH_EDIT ) + { + writeLayer->RemoveRedundantKeys( params.m_flThreshold ); + } +} + +template< class T > +void RandomValue( const T& average, const T& oldValue, T& newValue ) +{ + newValue = oldValue; +} + +template<> void RandomValue( const Vector& average, const Vector& oldValue, Vector& newValue ) +{ + newValue = oldValue; + + for ( int i = 0; i < 3; ++i ) + { + newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); + } +} + +template<> void RandomValue( const Quaternion& average, const Quaternion& oldValue, Quaternion& newValue ) +{ + newValue = oldValue; + + for ( int i = 0; i < 4; ++i ) + { + newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); + } +} + +template<> void RandomValue( const Vector4D& average, const Vector4D& oldValue, Vector4D& newValue ) +{ + newValue = oldValue; + + for ( int i = 0; i < 4; ++i ) + { + newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); + } +} + +template<> void RandomValue( const Vector2D& average, const Vector2D& oldValue, Vector2D& newValue ) +{ + newValue = oldValue; + + for ( int i = 0; i < 2; ++i ) + { + newValue[ i ] += RandomFloat( -fabs( average[ i ] ), fabs( average[ i ] ) ); + } +} + +template<> void RandomValue( const float& average, const float& oldValue, float& newValue ) +{ + newValue = oldValue + RandomFloat( -average, average ); +} + +template<> void RandomValue( const int& average, const int& oldValue, int& newValue ) +{ + newValue = oldValue + RandomInt( -average, average ); +} + +// Builds a layer with samples matching the times in reference layer, from the data in pDataLayer, putting the resulting keys into pOutputLayer +template< class T > +void CDmeTypedLog< T >::BuildCorrespondingLayer( const CDmeLogLayer *pReferenceLayer, const CDmeLogLayer *pDataLayer, CDmeLogLayer *pOutputLayer ) +{ + const CDmeTypedLogLayer< T > *ref = static_cast< const CDmeTypedLogLayer< T > * >( pReferenceLayer ); + const CDmeTypedLogLayer< T > *data = static_cast< const CDmeTypedLogLayer< T > * >( pDataLayer ); + CDmeTypedLogLayer< T > *out = static_cast< CDmeTypedLogLayer< T > * >( pOutputLayer ); + + if ( !ref || !data || !out ) + { + Assert( 0 ); + return; + } + + bool usecurvetypes = ref->IsUsingCurveTypes(); + + out->ClearKeys(); + int kc = ref->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = ref->GetKeyTime( i ); + T value = data->GetValue( keyTime ); + + out->InsertKey( keyTime, value, usecurvetypes ? GetDefaultCurveType() : CURVE_DEFAULT ); + } +} + +template< class T > +void CDmeTypedLog< T >::StaggerUsingTimeSelection( const DmeLog_TimeSelection_t& params, DmeTime_t tStaggerAmount, const CDmeLogLayer *pBaseLayer, CDmeLogLayer *pWriteLayer ) +{ + CDmeTypedLogLayer< T > *writeLayer = static_cast< CDmeTypedLogLayer< T > * >( pWriteLayer ); + Assert( writeLayer ); + if ( !writeLayer ) + return; + + const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( pBaseLayer ); + if ( !baseLayer ) + return; + + writeLayer->ClearKeys(); + + DmeLog_TimeSelection_t newParams; + newParams = params; + + // Move the hold area by the stagger amount + float flScaleFactor[ 2 ] = { 1.0f, 1.0f }; + + newParams.m_nTimes[ TS_LEFT_HOLD ] += tStaggerAmount; + newParams.m_nTimes[ TS_RIGHT_HOLD ] += tStaggerAmount; + + for ( int i = 0; i < 2 ; ++i ) + { + DmeTime_t dt = params.m_nTimes[ 2 * i + 1 ] - params.m_nTimes[ 2 * i ]; + if ( dt > DMETIME_ZERO ) + { + DmeTime_t newDt = newParams.m_nTimes[ 2 * i + 1 ] - newParams.m_nTimes[ 2 * i ]; + flScaleFactor[ i ] = newDt / dt; + } + } + + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t curtime = baseLayer->GetKeyTime( i ); + T oldValue = baseLayer->GetKeyValue( i ); + + // Classify time + if ( curtime <= params.m_nTimes[ TS_LEFT_HOLD ] ) + { + curtime = curtime * flScaleFactor[ 0 ]; + } + else if ( curtime >= params.m_nTimes[ TS_RIGHT_HOLD ] ) + { + curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ] - ( params.m_nTimes[ TS_RIGHT_FALLOFF ] - curtime ) * flScaleFactor[ 1 ]; + } + else + { + curtime += tStaggerAmount; + } + + writeLayer->InsertKey( curtime, oldValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } +} + +template< class T > +void CDmeTypedLog< T >::FilterUsingTimeSelection( IUniformRandomStream *random, const DmeLog_TimeSelection_t& params, int filterType, bool bResample, bool bApplyFalloff ) +{ + Assert( GetNumLayers() >= 2 ); + int bestLayer = GetTopmostLayer(); // Topmost should be at least layer # 1 (0 is the base layer) + if ( bestLayer <= 0 ) + return; + + CDmeTypedLogLayer< T > *writeLayer = GetLayer( bestLayer ); + Assert( writeLayer ); + if ( !writeLayer ) + return; + + CDmeTypedLogLayer< T > *baseLayer = GetLayer( 0 ); + if ( !baseLayer ) + return; + + FilterUsingTimeSelection( random, 1.0f, params, filterType, bResample, bApplyFalloff, baseLayer, writeLayer ); +} + +template< class T > +void CDmeTypedLog< T >::FilterUsingTimeSelection( IUniformRandomStream *random, float flScale, const DmeLog_TimeSelection_t& params, int filterType, bool bResample, bool bApplyFalloff, const CDmeLogLayer *pBaseLayer, CDmeLogLayer *pWriteLayer ) +{ + Assert( params.m_nResampleInterval > DmeTime_t( 0 ) ); + if ( params.m_nResampleInterval < DmeTime_t( 0 ) ) + return; + + CDmeTypedLogLayer< T > *writeLayer = static_cast< CDmeTypedLogLayer< T > * >( pWriteLayer ); + Assert( writeLayer ); + if ( !writeLayer ) + return; + + const CDmeTypedLogLayer< T > *baseLayer = static_cast< const CDmeTypedLogLayer< T > * >( pBaseLayer ); + if ( !baseLayer ) + return; + + writeLayer->ClearKeys(); + + DmeTime_t resample = 0.5f * params.m_nResampleInterval; + + switch ( filterType ) + { + default: + case FILTER_SMOOTH: + { + int t; + if ( bResample ) + { + for ( t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) + { + DmeTime_t curtime = DmeTime_t( t ); + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; + + T curValue = baseLayer->GetValue( curtime ); + writeLayer->SetKey( curtime, curValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + } + else + { + // Do a second pass where we bias the keys in the falloff area back toward the original value + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t curtime = baseLayer->GetKeyTime( i ); + if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + continue; + + T oldValue = baseLayer->GetKeyValue( i ); + writeLayer->InsertKey( curtime, oldValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + } + + writeLayer->Filter2( params.m_nResampleInterval * 0.95f * flScale ); + + if ( bApplyFalloff ) + { + if ( bResample ) + { + // Do a second pass where we bias the keys in the falloff area back toward the original value + for ( t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) + { + DmeTime_t curtime = DmeTime_t( t ); + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; + + T oldValue = baseLayer->GetValue( curtime ); + + if ( curtime >= params.m_nTimes[ TS_LEFT_HOLD ] && curtime <= params.m_nTimes[ TS_RIGHT_HOLD ] ) + continue; + + // Modulate these keys back down toward the original value + T newValue = writeLayer->GetValue( curtime ); + + float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; + + newValue = Interpolate( frac, oldValue, newValue ); + + // Overwrite key + writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + } + else + { + // Do a second pass where we bias the keys in the falloff area back toward the original value + int kc = writeLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t curtime = writeLayer->GetKeyTime( i ); + if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + continue; + + if ( curtime >= params.m_nTimes[ TS_LEFT_HOLD ] && curtime <= params.m_nTimes[ TS_RIGHT_HOLD ] ) + continue; + + T oldValue = baseLayer->GetValue( curtime ); + + // Modulate these keys back down toward the original value + T newValue = writeLayer->GetValue( curtime ); + + float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; + + newValue = Interpolate( frac, oldValue, newValue ); + + // Overwrite key + writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + } + } + + if ( bResample ) + { + writeLayer->RemoveRedundantKeys( params.m_flThreshold ); + } + } + break; + case FILTER_JITTER: + { + // Compute average value in entire log + + T average = Average( baseLayer->m_values.Base(), baseLayer->m_values.Count() ); + average = ScaleValue( average, 0.05f * flScale ); + + if ( bResample ) + { + int t; + for ( t = params.m_nTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); t < params.m_nTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS() + resample.GetTenthsOfMS(); t += resample.GetTenthsOfMS() ) + { + DmeTime_t curtime = DmeTime_t( t ); + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + curtime = params.m_nTimes[ TS_RIGHT_FALLOFF ]; + + float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; + + T oldValue = baseLayer->GetValue( curtime ); + + T newValue; + RandomValue( average, oldValue, newValue ); + + newValue = Interpolate( frac, oldValue, newValue ); + + writeLayer->SetKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + + } + else + { + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t curtime = baseLayer->GetKeyTime( i ); + if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + continue; + + float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; + + T oldValue = baseLayer->GetValue( curtime ); + + T newValue; + RandomValue( average, oldValue, newValue ); + + newValue = Interpolate( frac, oldValue, newValue ); + + writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + } + } + break; + case FILTER_SHARPEN: + case FILTER_SOFTEN: + { + writeLayer->ClearKeys(); + + bool bSharpen = filterType == FILTER_SHARPEN; + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t curtime = baseLayer->GetKeyTime( i ); + if ( curtime < params.m_nTimes[ TS_LEFT_FALLOFF ] ) + continue; + + if ( curtime > params.m_nTimes[ TS_RIGHT_FALLOFF ] ) + continue; + + float frac = bApplyFalloff ? params.GetAmountForTime( curtime ) : 1.0f; + + T oldValue = baseLayer->GetValue( curtime ); + + T newValue = oldValue; + if ( frac != 1.0f ) + { + T crossingValue[ 2 ] = { oldValue, oldValue }; + if ( curtime <= params.m_nTimes[ TS_LEFT_HOLD ] ) + { + // Get the value at the crossing point (either green edge for sharpen, or left edge for soften...) + crossingValue[ 0 ] = baseLayer->GetValue( params.m_nTimes[ TS_LEFT_FALLOFF ] ); + crossingValue[ 1 ] = baseLayer->GetValue( params.m_nTimes[ TS_LEFT_HOLD ] ); + } + else if ( curtime >= params.m_nTimes[ TS_RIGHT_HOLD ] ) + { + crossingValue[ 0 ] = baseLayer->GetValue( params.m_nTimes[ TS_RIGHT_FALLOFF ] ); + crossingValue[ 1 ] = baseLayer->GetValue( params.m_nTimes[ TS_RIGHT_HOLD ] ); + } + else + { + Assert( 0 ); + } + + T dynamicRange = Subtract( crossingValue[ 1 ], crossingValue[ 0 ] ); + + int iType = bSharpen ? INTERPOLATE_EASE_IN : INTERPOLATE_EASE_OUT; + + Vector points[ 4 ]; + points[ 0 ].Init(); + points[ 1 ].Init( 0.0, 0.0, 0.0f ); + points[ 2 ].Init( 1.0f, 1.0f, 0.0f ); + points[ 3 ].Init(); + + Vector out; + + Interpolator_CurveInterpolate + ( + iType, + points[ 0 ], // unused + points[ 1 ], + points[ 2 ], + points[ 3 ], // unused + frac, + out + ); + + float flBias = clamp( out.y, 0.0f, 1.0f ); + float dFrac = flScale * ( frac - flBias ); + + newValue = Add( oldValue, ScaleValue( dynamicRange, dFrac ) ); + } + + writeLayer->InsertKey( curtime, newValue, IsUsingCurveTypes() ? GetDefaultCurveType() : CURVE_DEFAULT ); + } + } + break; + } +} + +enum PasteState_t +{ + PASTE_STATE_BEFORE = -1, + + PASTE_STATE_RAMP_IN = 0, + PASTE_STATE_HOLD, + PASTE_STATE_RAMP_OUT, + + PASTE_STATE_COUNT, + PASTE_STATE_AFTER = PASTE_STATE_COUNT, +}; + +template<class T > +static void CountClipboardSamples( int *pCount, CDmeTypedLogLayer< T > *pClipboard, const DmeLog_TimeSelection_t ¶ms ) +{ + pCount[0] = pCount[1] = pCount[2] = 0; + + int nKeyCount = pClipboard->GetKeyCount(); + for ( int i = 0; i < nKeyCount; ++i ) + { + DmeTime_t tKeyTime = pClipboard->GetKeyTime( i ); + int nIndex = params.ComputeRegionForTime( tKeyTime ) - 1; + if ( nIndex < 0 || nIndex > 2 ) + continue; + + // Only count interstitial samples.. don't count ones that land exactly on boundaries + if ( tKeyTime != params.m_nTimes[nIndex] && tKeyTime != params.m_nTimes[nIndex+1] ) + { + pCount[nIndex]++; + } + } +} + + +//----------------------------------------------------------------------------- +// Used by PasteAndRescaleSamples to determine if it should skip a transition or not +//----------------------------------------------------------------------------- +static inline bool ShouldSkipTransition( int nTransition, int nZeroField ) +{ + // NOTE: This is pretty tricky. The bits of the 'zero field' are set to true + // for each region whose source + dest region size is exactly 0 seconds. + // Here's the table this logic is reproducing: + // 0,1,2,3 are the time selection m_nTimes, and A,B,C are the regions + // 0 1 2 3 + // | A | B | C | + // + // nZeroField bits + // C B A Skip transitions + // 0 0 0 none + // 0 0 1 2 + // 0 1 0 2 + // 0 1 1 1, 2 + // 1 0 0 1 + // 1 0 1 1, 2 + // 1 1 0 1, 2 + // 1 1 1 1, 2, 3 + switch( nTransition ) + { + default: case 0: return false; + case 1: return ( (( nZeroField & 0x1 ) != 0 ) || nZeroField >= 5 ); + case 2: return ( nZeroField >= 2 ); + case 3: return ( nZeroField == 7 ); + } +} + +template< class T > +void CDmeTypedLog< T >::PasteAndRescaleSamples( + const CDmeLogLayer *pBase, + const CDmeLogLayer *pDataLayer, + CDmeLogLayer *pOutputLayer, + const DmeLog_TimeSelection_t& srcParams, + const DmeLog_TimeSelection_t& destParams, + bool bBlendAreaInFalloffRegion ) +{ + Assert( GetNumLayers() >= 2 ); + if ( GetNumLayers() < 2 ) + return; + + CDmeTypedLogLayer< T > *pClipboard = CastElement< CDmeTypedLogLayer< T > >( const_cast< CDmeLogLayer * >( pDataLayer ) ); + + // Could have passed in layer with wrong attribute type?! + Assert( pClipboard ); + if ( !pClipboard ) + return; + + CDmeTypedLogLayer< T > *pBaseLayer = CastElement< CDmeTypedLogLayer< T > >( const_cast< CDmeLogLayer * >( pBase ) ); + CDmeTypedLogLayer< T > *pWriteLayer = CastElement< CDmeTypedLogLayer< T > >( pOutputLayer ); + Assert( pBaseLayer ); + Assert( pWriteLayer ); + + // NOTE: Array index 0 is src (pClipboard), index 1 is dest (pWriteLayer) + DmeTime_t tStartTime[ PASTE_STATE_COUNT+1 ][ 2 ] = + { + { DmeTime_t( srcParams.m_nTimes[ 0 ] ), DmeTime_t( destParams.m_nTimes[ 0 ] ) }, + { DmeTime_t( srcParams.m_nTimes[ 1 ] ), DmeTime_t( destParams.m_nTimes[ 1 ] ) }, + { DmeTime_t( srcParams.m_nTimes[ 2 ] ), DmeTime_t( destParams.m_nTimes[ 2 ] ) }, + { DmeTime_t( srcParams.m_nTimes[ 3 ] ), DmeTime_t( destParams.m_nTimes[ 3 ] ) }, + }; + + // compute rescaling factors + int pDuration[ PASTE_STATE_COUNT ][ 2 ]; + double pScaleFactor[ PASTE_STATE_COUNT ]; + int nZeroField = 0; + for ( int i = 0; i < PASTE_STATE_COUNT; ++i ) + { + for ( int s = 0; s < 2; ++s ) + { + pDuration[ i ][ s ] = tStartTime[ i+1 ][ s ].GetTenthsOfMS() - tStartTime[ i ][ s ].GetTenthsOfMS(); + } + + // We're building up a bitfield to find which regions have src + dest durations of 0 + // for use in determining which regions to completely skip processing + if ( pDuration[i][0] == 0 && pDuration[i][1] == 0 ) + { + nZeroField |= ( 1 << i ); + } + + pScaleFactor[i] = 1.0; + if ( pDuration[ i ][ 0 ] > 0 ) + { + pScaleFactor[i] = 1.0 / ( double )pDuration[ i ][ 0 ]; + } + } + + // Compute values used to paste into selection state transitions + T pStartValue[ PASTE_STATE_COUNT + 1 ] = + { + bBlendAreaInFalloffRegion ? pBaseLayer->GetValue( tStartTime[ PASTE_STATE_RAMP_IN ][ 1 ] ) : pClipboard->GetValue( tStartTime[ PASTE_STATE_RAMP_IN ][ 0 ] ), + pClipboard->GetValue( tStartTime[ PASTE_STATE_HOLD ][ 0 ] ), + pClipboard->GetValue( tStartTime[ PASTE_STATE_RAMP_OUT ][ 0 ] ), + bBlendAreaInFalloffRegion ? pBaseLayer->GetValue( tStartTime[ PASTE_STATE_AFTER ][ 1 ] ) : pClipboard->GetValue( tStartTime[ PASTE_STATE_AFTER ][ 0 ] ) + }; + + // Compute state necessary to blend in the ramp in + ramp out regions + // NOTE: These computations are only used if bBlendAreaInFalloffRegion is true + T pBlendBase[ 2 ]; + float pOOBlendLength[ 2 ]; + DmeTime_t pBlendTime[ 2 ]; + for ( int s = 0; s < 2; ++s ) + { + pBlendTime[ s ] = destParams.m_nTimes[ TS_FALLOFF(s) ]; + pBlendBase[ s ] = pBaseLayer->GetValue( pBlendTime[ s ] ); + T holdValue = pBaseLayer->GetValue( destParams.m_nTimes[ TS_HOLD(s) ] ); + + Vector2D vec; + vec.x = destParams.m_nTimes[ TS_HOLD(s) ].GetSeconds() - pBlendTime[ s ].GetSeconds(); + vec.y = LengthOf( Subtract( holdValue, pBlendBase[ s ] ) ); + pOOBlendLength[ s ] = vec.Length(); + if ( pOOBlendLength[ s ] != 0.0f ) + { + pOOBlendLength[ s ] = 1.0f / pOOBlendLength[ s ]; + } + } + + // Count the number of samples on the clipboard in the various regions + int pKeyCount[PASTE_STATE_COUNT]; + CountClipboardSamples( pKeyCount, pClipboard, srcParams ); + + // Walk the samples in the clipboard + int nKeyCount = pClipboard->GetKeyCount(); + int nPrevState = PASTE_STATE_BEFORE; + DmeTime_t tLastWrittenTime = DMETIME_MINTIME; + DmeTime_t tMaxKeyTime = DMETIME_MAXTIME; + bool bCollapseSamples = false; + for ( int j = 0 ; j < nKeyCount; ++j ) + { + DmeTime_t tKeyTime = pClipboard->GetKeyTime( j ); + T val = pClipboard->GetKeyValue( j ); + + // Determine which state we're in + // NOTE: Don't use ComputeRegionForTime here because it includes + // the endpoint of the hold region into the hold region. + int nState; + for ( nState = nPrevState; nState < PASTE_STATE_COUNT; ++nState ) + { + if ( tKeyTime < tStartTime[ nState + 1 ][ 0 ] ) + break; + } + + // This logic inserts a key if there is no sample in the clipboard at the transition time + bool bForceKey = false; + if ( nPrevState < nState ) + { + nState = ++nPrevState; + + // This logic will prevent samples at the hold start + end if + // the source + dest regions are 0 width and will only do the first and last + // if we're squeezing the entire time selection down to a single point. + bForceKey = true; + + if ( nState != PASTE_STATE_AFTER ) + { + bCollapseSamples = ( pKeyCount[nState] >= pDuration[nState][1] ); + tMaxKeyTime = bCollapseSamples ? tStartTime[ nState ][ 1 ] : ( tStartTime[ nState+1 ][ 1 ] - DmeTime_t( pKeyCount[nState] + 1 ) ); + } + else + { + bCollapseSamples = false; + tMaxKeyTime = DMETIME_MAXTIME; + } + + // NOTE: This has to occur after collapse samples + max key time has been set + if ( ShouldSkipTransition( nState, nZeroField ) ) + { + --j; + continue; + } + + // Don't insert an extra key if the current one we're looking at is right at that point + if ( tKeyTime != tStartTime[ nPrevState ][ 0 ] ) + { + tKeyTime = tStartTime[ nPrevState ][ 0 ]; + val = pStartValue[nPrevState]; + + // We want to re-do this key, since we inserted a key beforehand + --j; + } + } + + if ( nState == PASTE_STATE_BEFORE ) + continue; + + if ( nState == PASTE_STATE_AFTER && !bForceKey ) + return; + + // Compute destination time based on scale + offset + double flFactor = ( tKeyTime - tStartTime[ nState ][ 0 ] ).GetTenthsOfMS() * pScaleFactor[ nState ]; + + // FIXME: Fix the algorithm, then uncomment to get time-scaled falloff regions + // if ( nState == PASTE_STATE_RAMP_IN || nState == PASTE_STATE_RAMP_OUT ) + // { + // int s = ( nState == PASTE_STATE_RAMP_IN ) ? 0 : 1; + // flFactor = ComputeInterpolationFactor( flFactor, destParams.m_nFalloffInterpolatorTypes[s] ); + // } + double flTempTime = flFactor * pDuration[ nState ][ 1 ]; + DmeTime_t tDestTime( (int)( flTempTime + 0.5 ) ); + tDestTime += tStartTime[ nState ][ 1 ]; + + // Clamp necessary to not lose samples + // NOTE: The !bForceKey check here makes it so we don't clamp points + // in time corresponding to transitions of the time selection + if ( !bForceKey && ( tDestTime > tMaxKeyTime ) ) + { + tDestTime = tMaxKeyTime; + } + if ( tMaxKeyTime != DMETIME_MAXTIME ) + { + tMaxKeyTime += DMETIME_MINDELTA; + } + + // This logic will cause *all* samples to appear if we have enough room for them + if ( !bCollapseSamples ) + { + bForceKey = true; + } + + // If we'd go outside our region and we're not forcing the key, then skip + if ( !bForceKey && tDestTime >= tStartTime[ nState+1 ][ 1 ] ) + continue; + + // Perform blending on ramp in + ramp out regions + if ( bBlendAreaInFalloffRegion && ( nState != PASTE_STATE_HOLD ) ) + { + int nBlendIndex = ( nState < PASTE_STATE_HOLD ) ? 0 : 1; + T baseValue = pBaseLayer->GetValue( tDestTime ); + + Vector2D oldDist; + oldDist.x = tDestTime.GetSeconds() - pBlendTime[ nBlendIndex ].GetSeconds(); + oldDist.y = LengthOf( Subtract( baseValue, pBlendBase[ nBlendIndex ] ) ); + + float flDistance = oldDist.Length(); + float flFactorBlend = flDistance * pOOBlendLength[ nBlendIndex ]; + flFactorBlend = destParams.AdjustFactorForInterpolatorType( flFactorBlend, nBlendIndex ); + val = Interpolate( flFactorBlend, baseValue, val ); + } + + // Force key insertion when we transition between states + if ( bForceKey && ( tLastWrittenTime >= tDestTime ) ) + { + tDestTime = tLastWrittenTime + DMETIME_MINDELTA; + } + + // Insert the key into the log + if ( tLastWrittenTime < tDestTime ) + { + pWriteLayer->InsertKey( tDestTime, val ); + tLastWrittenTime = tDestTime; + } + } +} + +template< class T > +void CDmeTypedLog< T >::PasteAndRescaleSamples( + const CDmeLogLayer *src, // clipboard data + const DmeLog_TimeSelection_t& srcParams, // clipboard time selection + const DmeLog_TimeSelection_t& destParams, // current time selection + bool bBlendAreaInFalloffRegion ) // blending behavior in falloff area of current time selection +{ + CDmeLogLayer *pBaseLayer = GetLayer( 0 ); + CDmeLogLayer *pWriteLayer = GetLayer( GetTopmostLayer() ); + PasteAndRescaleSamples( pBaseLayer, src, pWriteLayer, srcParams, destParams, bBlendAreaInFalloffRegion ); +} + +template<> +void CDmeTypedLog< Vector >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) +{ + Assert( target ); + Assert( GetDataType() != AT_FLOAT ); + + CDmeTypedLogLayer< Vector > *baseLayer = static_cast< CDmeTypedLogLayer< Vector > * >( GetLayer( 0 ) ); + if ( !baseLayer ) + return; + + float flMin = FLT_MAX; + float flMax = FLT_MIN; + + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = baseLayer->GetKeyTime( i ); + Vector keyValue = baseLayer->GetKeyValue( i ); + + float len = keyValue.Length(); + if ( len < flMin ) + { + flMin = len; + } + if ( len > flMax ) + { + flMax = len; + } + + target->InsertKey( keyTime, len ); + } + + for ( int i = 0; i < kc; ++i ) + { + float keyValue = target->GetKeyValue( i ); + float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); + target->SetKeyValue( i, normalized ); + } + + if ( HasDefaultValue() ) + { + target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue().Length(), flMin, flMax, 0.0f, 1.0f ) ); + } +} + +template<> +void CDmeTypedLog< Vector2D >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) +{ + Assert( target ); + Assert( GetDataType() != AT_FLOAT ); + + CDmeTypedLogLayer< Vector2D > *baseLayer = static_cast< CDmeTypedLogLayer< Vector2D > * >( GetLayer( 0 ) ); + if ( !baseLayer ) + return; + + float flMin = FLT_MAX; + float flMax = FLT_MIN; + + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = baseLayer->GetKeyTime( i ); + Vector2D keyValue = baseLayer->GetKeyValue( i ); + + float len = keyValue.Length(); + + if ( len < flMin ) + { + flMin = len; + } + if ( len > flMax ) + { + flMax = len; + } + + target->InsertKey( keyTime, len ); + } + + for ( int i = 0; i < kc; ++i ) + { + float keyValue = target->GetKeyValue( i ); + float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); + target->SetKeyValue( i, normalized ); + } + + if ( HasDefaultValue() ) + { + target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue().Length(), flMin, flMax, 0.0f, 1.0f ) ); + } +} + +template<> +void CDmeTypedLog< Vector4D >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) +{ + Assert( target ); + Assert( GetDataType() != AT_FLOAT ); + + CDmeTypedLogLayer< Vector4D > *baseLayer = static_cast< CDmeTypedLogLayer< Vector4D > * >( GetLayer( 0 ) ); + if ( !baseLayer ) + return; + + float flMin = FLT_MAX; + float flMax = FLT_MIN; + + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = baseLayer->GetKeyTime( i ); + Vector4D keyValue = baseLayer->GetKeyValue( i ); + + float len = keyValue.Length(); + + if ( len < flMin ) + { + flMin = len; + } + if ( len > flMax ) + { + flMax = len; + } + + target->InsertKey( keyTime, len ); + } + + for ( int i = 0; i < kc; ++i ) + { + float keyValue = target->GetKeyValue( i ); + float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); + target->SetKeyValue( i, normalized ); + } + + if ( HasDefaultValue() ) + { + target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue().Length(), flMin, flMax, 0.0f, 1.0f ) ); + } +} + +template<> +void CDmeTypedLog< int >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) +{ + Assert( target ); + Assert( GetDataType() != AT_FLOAT ); + + CDmeTypedLogLayer< int > *baseLayer = static_cast< CDmeTypedLogLayer< int > * >( GetLayer( 0 ) ); + if ( !baseLayer ) + return; + + float flMin = FLT_MAX; + float flMax = FLT_MIN; + + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = baseLayer->GetKeyTime( i ); + int keyValue = baseLayer->GetKeyValue( i ); + + float len = (float)keyValue; + + if ( len < flMin ) + { + flMin = len; + } + if ( len > flMax ) + { + flMax = len; + } + + target->InsertKey( keyTime, len ); + } + + for ( int i = 0; i < kc; ++i ) + { + float keyValue = target->GetKeyValue( i ); + float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); + target->SetKeyValue( i, normalized ); + } + + if ( HasDefaultValue() ) + { + target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue(), flMin, flMax, 0.0f, 1.0f ) ); + } +} + +template<> +void CDmeTypedLog< float >::BuildNormalizedLayer( CDmeTypedLogLayer< float > *target ) +{ + Assert( target ); + Assert( GetDataType() != AT_FLOAT ); + + CDmeTypedLogLayer< float > *baseLayer = static_cast< CDmeTypedLogLayer< float > * >( GetLayer( 0 ) ); + if ( !baseLayer ) + return; + + float flMin = FLT_MAX; + float flMax = FLT_MIN; + + int kc = baseLayer->GetKeyCount(); + for ( int i = 0; i < kc; ++i ) + { + DmeTime_t keyTime = baseLayer->GetKeyTime( i ); + int keyValue = baseLayer->GetKeyValue( i ); + + float len = (float)keyValue; + + if ( len < flMin ) + { + flMin = len; + } + if ( len > flMax ) + { + flMax = len; + } + + target->InsertKey( keyTime, len ); + } + + for ( int i = 0; i < kc; ++i ) + { + float keyValue = target->GetKeyValue( i ); + float normalized = RemapVal( keyValue, flMin, flMax, 0.0f, 1.0f ); + target->SetKeyValue( i, normalized ); + } + + if ( HasDefaultValue() ) + { + target->GetTypedOwnerLog()->SetDefaultValue( RemapVal( GetDefaultValue(), flMin, flMax, 0.0f, 1.0f ) ); + } +} + +//----------------------------------------------------------------------------- +// Creates a log of a specific type +//----------------------------------------------------------------------------- +CDmeLog *CDmeLog::CreateLog( DmAttributeType_t type, DmFileId_t fileid ) +{ + switch ( type ) + { + case AT_INT: + case AT_INT_ARRAY: + return CreateElement< CDmeIntLog >( "int log", fileid ); + case AT_FLOAT: + case AT_FLOAT_ARRAY: + return CreateElement< CDmeFloatLog >( "float log", fileid ); + case AT_BOOL: + case AT_BOOL_ARRAY: + return CreateElement< CDmeBoolLog >( "bool log", fileid ); + case AT_COLOR: + case AT_COLOR_ARRAY: + return CreateElement< CDmeColorLog >( "color log", fileid ); + case AT_VECTOR2: + case AT_VECTOR2_ARRAY: + return CreateElement< CDmeVector2Log >( "vector2 log", fileid ); + case AT_VECTOR3: + case AT_VECTOR3_ARRAY: + return CreateElement< CDmeVector3Log >( "vector3 log", fileid ); + case AT_VECTOR4: + case AT_VECTOR4_ARRAY: + return CreateElement< CDmeVector4Log >( "vector4 log", fileid ); + case AT_QANGLE: + case AT_QANGLE_ARRAY: + return CreateElement< CDmeQAngleLog >( "qangle log", fileid ); + case AT_QUATERNION: + case AT_QUATERNION_ARRAY: + return CreateElement< CDmeQuaternionLog >( "quaternion log", fileid ); + case AT_VMATRIX: + case AT_VMATRIX_ARRAY: + return CreateElement< CDmeVMatrixLog >( "vmatrix log", fileid ); + case AT_STRING: + case AT_STRING_ARRAY: + return CreateElement< CDmeStringLog >( "string log", fileid ); + } + + return NULL; +} + +// Disallowed methods for types +//template<> void CDmeTypedLog< bool >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const bool& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< bool >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const bool& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< bool >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< bool >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } +// +//template<> void CDmeTypedLog< Color >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Color& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Color >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Color& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Color >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Color >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } +// +//template<> void CDmeTypedLog< Vector4D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Vector4D& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector4D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector4D& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector4D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector4D >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } +// +//template<> void CDmeTypedLog< Vector2D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Vector2D& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector2D >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector2D& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector2D >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector2D >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } + +//template<> void CDmeTypedLog< Vector >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Vector& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Vector& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Vector >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } + +//template<> void CDmeTypedLog< VMatrix >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const VMatrix& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< VMatrix >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const VMatrix& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< VMatrix >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< VMatrix >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } +// +//template<> void CDmeTypedLog< Quaternion >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const Quaternion& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Quaternion >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const Quaternion& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Quaternion >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< Quaternion >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } +// +//template<> void CDmeTypedLog< QAngle >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const QAngle& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< QAngle >::_StampKeyAtHeadResample( const DmeLog_TimeSelection_t& params, const QAngle& value ) { Assert( 0 ); } +//template<> void CDmeTypedLog< QAngle >::StampKeyAtHead( const DmeLog_TimeSelection_t& params, const CDmAttribute *pAttr, uint index /*= 0*/ ) { Assert( 0 ); } +//template<> void CDmeTypedLog< QAngle >::FinishTimeSelection( DmeLog_TimeSelection_t& params ) { Assert( 0 ); } + + +//----------------------------------------------------------------------------- +// Helpers for particular types of log layers +//----------------------------------------------------------------------------- +void GenerateRotationLog( CDmeQuaternionLogLayer *pLayer, const Vector &vecAxis, DmeTime_t pTime[4], float pRevolutionsPerSec[4] ) +{ + for ( int i = 1; i < 4; ++i ) + { + if ( pTime[i] < pTime[i-1] ) + { + Warning( "Bogus times passed into GenerateRotationLog\n" ); + return; + } + } + + // Gets the initial value + matrix3x4_t initial; + Quaternion q = pLayer->GetValue( pTime[0] ); + QuaternionMatrix( q, initial ); + + // Find the max rps, and compute the total rotation in degrees + // by the time we reach the transition points. The total rotation = + // integral from 0 to t of 360 * ( rate[i] - rate[i-1] ) t / tl + rate[i-1] ) + // == 360 * ( ( rate[i] - rate[i-1] ) t^2 / 2 + rate[i-1] t ) + float pTotalRotation[4]; + float flMaxRPS = pRevolutionsPerSec[0]; + pTotalRotation[0] = 0.0f; + for ( int i = 1; i < 4; ++i ) + { + if ( pRevolutionsPerSec[i] > flMaxRPS ) + { + flMaxRPS = pRevolutionsPerSec[i]; + } + float dt = pTime[i].GetSeconds() - pTime[i-1].GetSeconds(); + float dRot = pRevolutionsPerSec[i] - pRevolutionsPerSec[i-1]; + pTotalRotation[i] = 360.0f * ( dRot * dt * 0.5 + pRevolutionsPerSec[i-1] * dt ) + pTotalRotation[i-1]; + } + + // We need to compute how long a single rotation takes, then create samples + // at 1/4 the frequency of that amount of time + VMatrix rot; + matrix3x4_t total; + QAngle angles; + float flMaxRotationTime = (flMaxRPS != 0.0f) ? ( 0.125f / flMaxRPS ) : ( pTime[3].GetSeconds() - pTime[0].GetSeconds() ); + DmeTime_t dt( flMaxRotationTime ); + for ( DmeTime_t t = pTime[0]; t <= pTime[3]; t += dt ) + { + int i = ( t < pTime[1] ) ? 1 : ( ( t < pTime[2] ) ? 2 : 3 ); + float flInterval = t.GetSeconds() - pTime[i-1].GetSeconds(); + float flOOSegmentDur = pTime[i].GetSeconds() - pTime[i-1].GetSeconds(); + if ( flOOSegmentDur == 0.0f ) + { + Assert( flInterval == 0.0f ); + flOOSegmentDur = 1.0f; + } + else + { + flOOSegmentDur = 1.0f / flOOSegmentDur; + } + float dRot = pRevolutionsPerSec[i] - pRevolutionsPerSec[i-1]; + float flRotation = 360.0f * ( dRot * flInterval * flInterval * 0.5f * flOOSegmentDur + pRevolutionsPerSec[i-1] * flInterval ) + pTotalRotation[i-1]; + + MatrixBuildRotationAboutAxis( rot, vecAxis, flRotation ); + ConcatTransforms( initial, rot.As3x4(), total ); + MatrixToAngles( total, angles ); + AngleQuaternion( angles, q ); + pLayer->SetKey( t, q ); + } +} + + +//----------------------------------------------------------------------------- +// Transforms a position log +//----------------------------------------------------------------------------- +void RotatePositionLog( CDmeVector3LogLayer *pPositionLog, const matrix3x4_t& matrix ) +{ + Assert( fabs( matrix[0][3] ) < 1e-3 && fabs( matrix[1][3] ) < 1e-3 && fabs( matrix[2][3] ) < 1e-3 ); + Vector position; + int nCount = pPositionLog->GetKeyCount(); + for ( int i = 0; i < nCount; ++i ) + { + const Vector &srcPosition = pPositionLog->GetKeyValue( i ); + VectorTransform( srcPosition, matrix, position ); + pPositionLog->SetKeyValue( i, position ); + } +} + + +//----------------------------------------------------------------------------- +// Transforms a orientation log +//----------------------------------------------------------------------------- +void RotateOrientationLog( CDmeQuaternionLogLayer *pOrientationLog, const matrix3x4_t& matrix, bool bPreMultiply = false ) +{ + Assert( fabs( matrix[0][3] ) < 1e-3 && fabs( matrix[1][3] ) < 1e-3 && fabs( matrix[2][3] ) < 1e-3 ); + matrix3x4_t orientation, newOrientation; + Quaternion q; + int nCount = pOrientationLog->GetKeyCount(); + for ( int i = 0; i < nCount; ++i ) + { + const Quaternion &srcQuat = pOrientationLog->GetKeyValue( i ); + QuaternionMatrix( srcQuat, orientation ); + if ( bPreMultiply ) + { + ConcatTransforms( matrix, orientation, newOrientation ); + } + else + { + ConcatTransforms( orientation, matrix, newOrientation ); + } + MatrixQuaternion( newOrientation, q ); + pOrientationLog->SetKeyValue( i, q ); + } +} diff --git a/movieobjects/dmemakefile.cpp b/movieobjects/dmemakefile.cpp new file mode 100644 index 0000000..3fc41ba --- /dev/null +++ b/movieobjects/dmemakefile.cpp @@ -0,0 +1,628 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Describes an asset: something that is compiled from sources, +// in potentially multiple steps, to a compiled resource +// +//============================================================================= + + +#include "movieobjects/dmemdlmakefile.h" +#include "movieobjects/idmemakefileutils.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "tier2/fileutils.h" +#include "tier3/tier3.h" +#include "filesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Hook into element factories +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSource, CDmeSource ); + + +//----------------------------------------------------------------------------- +// Construction/destruction +//----------------------------------------------------------------------------- +void CDmeSource::OnConstruction() +{ + m_DependentMakefile = NULL; +} + +void CDmeSource::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets/gets the makefile that was used to build this source +//----------------------------------------------------------------------------- +void CDmeSource::SetDependentMakefile( CDmeMakefile *pMakeFile ) +{ + m_DependentMakefile = pMakeFile; +} + +CDmeMakefile *CDmeSource::GetDependentMakefile() +{ + return m_DependentMakefile.Get(); +} + + +//----------------------------------------------------------------------------- +// Call this to open the source file in an editor +//----------------------------------------------------------------------------- +void CDmeSource::OpenEditor() +{ + if ( g_pDmeMakefileUtils ) + { + g_pDmeMakefileUtils->PerformOpenEditor( this ); + } +} + + +//----------------------------------------------------------------------------- +// Hook into element factories +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMakefile, CDmeMakefile ); + + +//----------------------------------------------------------------------------- +// Construction/destruction +//----------------------------------------------------------------------------- +void CDmeMakefile::OnConstruction() +{ + m_Sources.Init( this, "sources" ); + m_hOutput = NULL; + m_hCompileProcess = PROCESS_HANDLE_INVALID; + m_bIsDirty = false; +} + +void CDmeMakefile::OnDestruction() +{ + DestroyOutputElement( m_hOutput.Get() ); + m_hOutput = NULL; +} + + +//----------------------------------------------------------------------------- +// Performs pre-compilation step +//----------------------------------------------------------------------------- +void CDmeMakefile::PreCompile( ) +{ + // Make all outputs writeable + MakeOutputsWriteable(); + + // Destroy the current output object; we'll need to reload it + // NOTE: Don't check for m_hOutput == 0; we always need to call DestroyOutputElement + // Sometimes makefiles have to do stuff even if m_hOutput == NULL + DestroyOutputElement( m_hOutput ); + m_hOutput = NULL; +} + +void CDmeMakefile::PostCompile( ) +{ +} + + +//----------------------------------------------------------------------------- +// Gets the output element created by compilation of this makefile +//----------------------------------------------------------------------------- +CDmElement *CDmeMakefile::GetOutputElement( bool bCreateIfNecessary ) +{ + if ( m_hOutput.Get() ) + return m_hOutput.Get(); + + if ( !bCreateIfNecessary ) + return NULL; + + if ( !g_pDmeMakefileUtils || !g_pDmeMakefileUtils->IsCurrentlyCompiling() ) + { + m_hOutput = CreateOutputElement(); + } + + return m_hOutput.Get(); +} + + +//----------------------------------------------------------------------------- +// Gets the path of the makefile +//----------------------------------------------------------------------------- +void CDmeMakefile::GetMakefilePath( char *pFullPath, int nBufLen ) +{ + DmFileId_t fileId = GetFileId(); + const char *pFileName = ( fileId != DMFILEID_INVALID ) ? g_pDataModel->GetFileName( fileId ) : ""; + Assert( !pFileName[0] || Q_IsAbsolutePath( pFileName ) ); + + Q_ExtractFilePath( pFileName, pFullPath, nBufLen ); +} + + +//----------------------------------------------------------------------------- +// Returns the output directory we expect to compile files into +//----------------------------------------------------------------------------- +bool CDmeMakefile::GetOutputDirectory( char *pFullPath, int nBufLen ) +{ + return GetDefaultDirectory( GetOutputDirectoryID(), pFullPath, nBufLen ); +} + + +//----------------------------------------------------------------------------- +// Returns the output name (output directory + filename, no extension) +//----------------------------------------------------------------------------- +bool CDmeMakefile::GetOutputName( char *pFullPath, int nBufLen ) +{ + pFullPath[0] = 0; + + char pOutputPath[MAX_PATH]; + if ( !GetDefaultDirectory( GetOutputDirectoryID(), pOutputPath, sizeof(pOutputPath) ) ) + return false; + + DmFileId_t fileId = GetFileId(); + const char *pFileName = ( fileId != DMFILEID_INVALID ) ? g_pDataModel->GetFileName( fileId ) : ""; + if ( !pFileName || !pFileName[0] ) + return false; + + Q_ComposeFileName( pOutputPath, Q_UnqualifiedFileName(pFileName), pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); + return true; +} + + +//----------------------------------------------------------------------------- +// Converts the m_pDefaultDirectoryID field of the DmeMakefileType_t to a full path +//----------------------------------------------------------------------------- +bool CDmeMakefile::GetDefaultDirectory( const char *pDefaultDirectoryID, char *pFullPath, int nBufLen ) +{ + if ( StringHasPrefix( pDefaultDirectoryID, "contentdir:" ) ) + { + pDefaultDirectoryID += 11; + GetModContentSubdirectory( pDefaultDirectoryID, pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); + return true; + } + + if ( StringHasPrefix( pDefaultDirectoryID, "gamedir:" ) ) + { + pDefaultDirectoryID += 8; + GetModSubdirectory( pDefaultDirectoryID, pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); + return true; + } + + if ( StringHasPrefix( pDefaultDirectoryID, "makefiledir:" ) ) + { + char pMakefilePath[MAX_PATH]; + GetMakefilePath( pMakefilePath, sizeof(pMakefilePath) ); + pDefaultDirectoryID += 12; + Q_ComposeFileName( pMakefilePath, pDefaultDirectoryID, pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); + return true; + } + + if ( StringHasPrefix( pDefaultDirectoryID, "makefilegamedir:" ) ) + { + char pMakefilePath[MAX_PATH]; + GetMakefilePath( pMakefilePath, sizeof(pMakefilePath) ); + + char pModContentDirectory[MAX_PATH]; + GetModContentSubdirectory( NULL, pModContentDirectory, sizeof(pModContentDirectory) ); + + char pRelativePath[MAX_PATH]; + if ( !Q_MakeRelativePath( pMakefilePath, pModContentDirectory, pRelativePath, sizeof(pRelativePath) ) ) + { + pFullPath[0] = 0; + return false; + } + + char pModDirectory[MAX_PATH]; + GetModSubdirectory( NULL, pModDirectory, sizeof(pModDirectory) ); + + char pMakefileGamePath[MAX_PATH]; + Q_ComposeFileName( pModDirectory, pRelativePath, pMakefileGamePath, sizeof(pMakefileGamePath) ); + + pDefaultDirectoryID += 16; + Q_ComposeFileName( pMakefileGamePath, pDefaultDirectoryID, pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); + return true; + } + + // Assume it's a content subdir + GetModContentSubdirectory( pDefaultDirectoryID, pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); + return true; +} + + +//----------------------------------------------------------------------------- +// Relative path to full path +//----------------------------------------------------------------------------- +void CDmeMakefile::RelativePathToFullPath( const char *pRelativePath, char *pFullPath, int nBufLen ) +{ + if ( !pRelativePath[0] ) + { + pFullPath[0] = 0; + return; + } + char pRootDir[ MAX_PATH ]; + GetMakefilePath( pRootDir, sizeof(pRootDir) ); + Q_ComposeFileName( pRootDir, pRelativePath, pFullPath, nBufLen ); + Q_RemoveDotSlashes( pFullPath ); +} + + +//----------------------------------------------------------------------------- +// Fullpath to relative path +//----------------------------------------------------------------------------- +void CDmeMakefile::FullPathToRelativePath( const char *pFullPath, char *pRelativePath, int nBufLen ) +{ + if ( !pFullPath[0] ) + { + pRelativePath[0] = 0; + return; + } + char pRootDir[ MAX_PATH ]; + GetMakefilePath( pRootDir, sizeof(pRootDir) ); + if ( pRootDir[0] ) + { + Q_MakeRelativePath( pFullPath, pRootDir, pRelativePath, nBufLen ); + } + else + { + Q_strncpy( pRelativePath, pFullPath, nBufLen ); + Q_FixSlashes( pRelativePath ); + } +} + + +//----------------------------------------------------------------------------- +// Adds a single source +//----------------------------------------------------------------------------- +CDmeSource *CDmeMakefile::AddSource( const char *pSourceType, const char *pFullPath ) +{ + if ( pFullPath[0] && FindSource( pSourceType, pFullPath ) ) + { + Warning( "Attempted to add the same source twice %s!\n", pFullPath ); + return NULL; + } + + CDmElement *pElement = GetElement< CDmElement >( g_pDataModel->CreateElement( pSourceType, "", GetFileId() ) ); + CDmeSource *pSource = CastElement< CDmeSource >( pElement ); + Assert( pSource ); + if ( !pSource ) + { + Warning( "Invalid source type name %s!\n", pSourceType ); + if ( pElement ) + { + DestroyElement( pElement ); + } + return NULL; + } + + char pRelativePath[MAX_PATH]; + FullPathToRelativePath( pFullPath, pRelativePath, sizeof( pRelativePath ) ); + pSource->SetRelativeFileName( pRelativePath ); + m_Sources.AddToTail( pSource ); + return pSource; +} + + +//----------------------------------------------------------------------------- +// Removes a single source +//----------------------------------------------------------------------------- +CDmeSource *CDmeMakefile::FindSource( const char *pSourceType, const char *pFullPath ) +{ + char pRelativePath[MAX_PATH]; + FullPathToRelativePath( pFullPath, pRelativePath, sizeof( pRelativePath ) ); + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( Q_stricmp( pSourceType, m_Sources[i]->GetTypeString() ) ) + continue; + + if ( !Q_stricmp( pRelativePath, m_Sources[i]->GetRelativeFileName() ) ) + return m_Sources[i]; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Sets a source to be a single source +//----------------------------------------------------------------------------- +CDmeSource *CDmeMakefile::SetSingleSource( const char *pSourceType, const char *pFullPath ) +{ + // FIXME: we maybe shouldn't remove everything if the source can't be created for some reason? + RemoveAllSources( pSourceType ); + return AddSource( pSourceType, pFullPath ); +} + + +//----------------------------------------------------------------------------- +// Changes a source +//----------------------------------------------------------------------------- +void CDmeMakefile::SetSourceFullPath( CDmeSource *pSource, const char *pFullPath ) +{ + char pRelativePath[MAX_PATH]; + FullPathToRelativePath( pFullPath, pRelativePath, sizeof( pRelativePath ) ); + + if ( Q_stricmp( pRelativePath, pSource->GetRelativeFileName() ) ) + { + pSource->SetRelativeFileName( pRelativePath ); + + // FIXME: Should we delete the dependent makefile? + pSource->SetDependentMakefile( NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Returns the full path of a source +//----------------------------------------------------------------------------- +void CDmeMakefile::GetSourceFullPath( CDmeSource *pSource, char *pFullPath, int nBufLen ) +{ + const char *pRelativePath = pSource->GetRelativeFileName( ); + RelativePathToFullPath( pRelativePath, pFullPath, nBufLen ); +} + + +//----------------------------------------------------------------------------- +// Returns a list of sources +//----------------------------------------------------------------------------- +void CDmeMakefile::GetSources( const char *pSourceType, CUtlVector< CDmeHandle< CDmeSource > > &sources ) +{ + int nCount = m_Sources.Count(); + sources.EnsureCapacity( nCount ); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Sources[i]->IsA( pSourceType ) ) + { + int j = sources.AddToTail(); + sources[j] = m_Sources[i]; + } + } +} + + +//----------------------------------------------------------------------------- +// Gets a list of all sources, regardless of type +//----------------------------------------------------------------------------- +int CDmeMakefile::GetSourceCount() +{ + return m_Sources.Count(); +} + +CDmeSource *CDmeMakefile::GetSource( int nIndex ) +{ + return m_Sources[nIndex]; +} + + +//----------------------------------------------------------------------------- +// Removes a single source +//----------------------------------------------------------------------------- +void CDmeMakefile::RemoveSource( CDmeSource *pSource ) +{ + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Sources[i] == pSource ) + { + m_Sources.Remove( i ); + break; + } + } +} + +void CDmeMakefile::RemoveSource( const char *pSourceType, const char *pFullPath ) +{ + char pRelativePath[MAX_PATH]; + FullPathToRelativePath( pFullPath, pRelativePath, sizeof( pRelativePath ) ); + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( Q_stricmp( pSourceType, m_Sources[i]->GetTypeString() ) ) + continue; + + if ( !Q_stricmp( pRelativePath, m_Sources[i]->GetRelativeFileName() ) ) + { + m_Sources.Remove( i ); + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Removes all sources of a particular type +//----------------------------------------------------------------------------- +void CDmeMakefile::RemoveAllSources( const char *pSourceType ) +{ + int nCount = m_Sources.Count(); + for ( int i = nCount; --i >= 0; ) + { + if ( !Q_stricmp( pSourceType, m_Sources[i]->GetTypeString() ) ) + { + // NOTE: This works because we're iterating backward + m_Sources.Remove( i ); + } + } +} + + +//----------------------------------------------------------------------------- +// Source iteration +//----------------------------------------------------------------------------- +bool CDmeMakefile::HasSourceOfType( const char *pSourceType ) +{ + int nCount = m_Sources.Count(); + for ( int i = nCount; --i >= 0; ) + { + if ( !Q_stricmp( pSourceType, m_Sources[i]->GetTypeString() ) ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Updates the source names to be relative to a particular path +//----------------------------------------------------------------------------- +bool CDmeMakefile::UpdateSourceNames( const char *pOldRootDir, const char *pNewRootDir, bool bApplyChanges ) +{ + char pOldSourcePath[ MAX_PATH ]; + char pNewSourcePath[ MAX_PATH ]; + + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + const char *pOldRelativePath = m_Sources[i]->GetRelativeFileName(); + if ( pOldRelativePath[0] ) + { + Q_ComposeFileName( pOldRootDir, pOldRelativePath, pOldSourcePath, sizeof(pOldSourcePath) ); + Q_RemoveDotSlashes( pOldSourcePath ); + if ( !Q_MakeRelativePath( pOldSourcePath, pNewRootDir, pNewSourcePath, sizeof(pNewSourcePath) ) ) + { + Assert( !bApplyChanges ); + return false; + } + } + else + { + pNewSourcePath[0] = 0; + } + + if ( !bApplyChanges ) + continue; + + m_Sources[i]->SetRelativeFileName( pNewSourcePath ); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the filename +//----------------------------------------------------------------------------- +const char *CDmeMakefile::GetFileName() const +{ + DmFileId_t fileId = GetFileId(); + return g_pDataModel->GetFileName( fileId ); +} + + +//----------------------------------------------------------------------------- +// Call this to change the file the makefile is stored in +// Will make all sources be relative to this path +//----------------------------------------------------------------------------- +bool CDmeMakefile::SetFileName( const char *pFileName ) +{ + if ( !Q_IsAbsolutePath( pFileName ) ) + return false; + + char pOldRootDir[ MAX_PATH ]; + char pNewRootDir[ MAX_PATH ]; + GetMakefilePath( pOldRootDir, sizeof(pOldRootDir) ); + Q_ExtractFilePath( pFileName, pNewRootDir, sizeof(pNewRootDir) ); + + // Gotta do this twice; once to check for validity, once to actually do it + if ( !UpdateSourceNames( pOldRootDir, pNewRootDir, false ) ) + return false; + + UpdateSourceNames( pOldRootDir, pNewRootDir, true ); + + DmFileId_t fileId = GetFileId(); + if ( fileId == DMFILEID_INVALID ) + { + fileId = g_pDataModel->FindOrCreateFileId( pFileName ); + SetFileId( fileId, TD_DEEP ); + } + else + { + g_pDataModel->SetFileName( fileId, pFileName ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Make all outputs writeable +//----------------------------------------------------------------------------- +void CDmeMakefile::MakeOutputsWriteable( ) +{ + // When we publish, we'll check them out. + CUtlVector<CUtlString> outputs; + GetOutputs( outputs ); + int nCount = outputs.Count(); + for ( int i = 0; i < nCount; ++i ) + { + g_pFullFileSystem->SetFileWritable( outputs[i], true ); + } +} + + + +//----------------------------------------------------------------------------- +// Sets a makefile/source association +//----------------------------------------------------------------------------- +void CDmeMakefile::SetAssociation( CDmeSource *pSource, CDmeMakefile *pSourceMakefile ) +{ + if ( !pSource ) + return; + + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Sources[i] != pSource ) + continue; + + CDmeMakefile *pDependentMakeFile = m_Sources[i]->GetDependentMakefile(); + if ( pSourceMakefile != pDependentMakeFile ) + { + // FIXME: Should I recursively delete pDependentMakeFile ? + m_Sources[i]->SetDependentMakefile( pSourceMakefile ); + } + return; + } +} + + +//----------------------------------------------------------------------------- +// Finds a dependent makefile +//----------------------------------------------------------------------------- +CDmeMakefile *CDmeMakefile::FindDependentMakefile( CDmeSource *pSource ) +{ + if ( !pSource ) + return NULL; + + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Sources[i] == pSource ) + return m_Sources[i]->GetDependentMakefile(); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Finds the associated source +//----------------------------------------------------------------------------- +CDmeSource *CDmeMakefile::FindAssociatedSource( CDmeMakefile *pChildMakefile ) +{ + if ( !pChildMakefile ) + return NULL; + + int nCount = m_Sources.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_Sources[i]->GetDependentMakefile() == pChildMakefile ) + return m_Sources[i]; + } + + return NULL; +} + diff --git a/movieobjects/dmemakefileutils.cpp b/movieobjects/dmemakefileutils.cpp new file mode 100644 index 0000000..5f20661 --- /dev/null +++ b/movieobjects/dmemakefileutils.cpp @@ -0,0 +1,613 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Interface for makefiles to build differently depending on where they are run from +// +//===========================================================================// + +#include "movieobjects/dmemakefileutils.h" +#include "movieobjects/dmemdlmakefile.h" +#include "movieobjects/dmedccmakefile.h" +#include "tier2/fileutils.h" +#include "filesystem.h" + + +//----------------------------------------------------------------------------- +// Statics +//----------------------------------------------------------------------------- +IMPLEMENT_DMEMAKEFILE_UTIL_CLASS( CDmeMakefileUtils ); + + +//----------------------------------------------------------------------------- +// Default implementation +//----------------------------------------------------------------------------- +static CDmeMakefileUtils s_MakefileUtils; +IDmeMakefileUtils *GetDefaultDmeMakefileUtils() +{ + return &s_MakefileUtils; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CDmeMakefileUtils::CDmeMakefileUtils() : BaseClass( false ) +{ + m_CompilationStep = NOT_COMPILING; + m_hCompileProcess = PROCESS_HANDLE_INVALID; + m_nCurrentCompileTask = -1; + m_nExitCode = 0; +} + +CDmeMakefileUtils::~CDmeMakefileUtils() +{ + +} + + +//----------------------------------------------------------------------------- +// Here's where systems can access other interfaces implemented by this object +//----------------------------------------------------------------------------- +void *CDmeMakefileUtils::QueryInterface( const char *pInterfaceName ) +{ + if ( !V_strcmp( pInterfaceName, DMEMAKEFILE_UTILS_INTERFACE_VERSION ) ) + return (IDmeMakefileUtils*)this; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Initialization.. set up messagemaps +//----------------------------------------------------------------------------- +InitReturnVal_t CDmeMakefileUtils::Init() +{ + InitializeFuncMaps(); + return INIT_OK; +} + + +//----------------------------------------------------------------------------- +// Looks for an appropriate method to compile this element with +//----------------------------------------------------------------------------- +CCompileFuncAdapterBase *CDmeMakefileUtils::DetermineCompileAdapter( CDmElement *pElement ) +{ + int nBestInheritanceDepth = -1; + CCompileFuncAdapterBase *pBestAdapter = NULL; + + CompileFuncTree_t *pTree = GetCompileTree(); + while ( pTree ) + { + CCompileFuncAdapterBase *pCurr = pTree->m_pFirstAdapter; + for ( ; pCurr; pCurr = pCurr->m_pNext ) + { + // Choose this factory if it's more derived than the previous best + int nInheritanceDepth = pElement->GetInheritanceDepth( pCurr->m_ElementType ); + if ( nInheritanceDepth < 0 ) + continue; + + if ( nInheritanceDepth == 0 ) + { + // Found exact match.. do it! + return pCurr; + } + + // Don't look for the best thingy if we're not the root + if ( nBestInheritanceDepth >= 0 && ( nInheritanceDepth >= nBestInheritanceDepth ) ) + continue; + + nBestInheritanceDepth = nInheritanceDepth; + pBestAdapter = pCurr; + } + + pTree = pTree->m_pBaseAdapterTree; + } + + // Return the closest match we could find + return pBestAdapter; +} + + +//----------------------------------------------------------------------------- +// Looks for an appropriate method to open this element with +//----------------------------------------------------------------------------- +COpenEditorFuncAdapterBase *CDmeMakefileUtils::DetermineOpenEditorAdapter( CDmElement *pElement ) +{ + int nBestInheritanceDepth = -1; + COpenEditorFuncAdapterBase *pBestAdapter = NULL; + OpenEditorFuncTree_t *pTree = GetOpenEditorTree(); + while ( pTree ) + { + COpenEditorFuncAdapterBase *pCurr = pTree->m_pFirstAdapter; + for ( ; pCurr; pCurr = pCurr->m_pNext ) + { + // Choose this factory if it's more derived than the previous best + int nInheritanceDepth = pElement->GetInheritanceDepth( pCurr->m_ElementType ); + if ( nInheritanceDepth < 0 ) + continue; + + // Found exact match.. do it! + if ( nInheritanceDepth == 0 ) + return pCurr; + + if ( nBestInheritanceDepth >= 0 && ( nInheritanceDepth >= nBestInheritanceDepth ) ) + continue; + + nBestInheritanceDepth = nInheritanceDepth; + pBestAdapter = pCurr; + } + + pTree = pTree->m_pBaseAdapterTree; + } + return pBestAdapter; +} + + +//----------------------------------------------------------------------------- +// Opens a element in an external editor +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::PerformOpenEditor( CDmElement *pElement ) +{ + COpenEditorFuncAdapterBase *pAdapter = DetermineOpenEditorAdapter( pElement ); + if ( pAdapter ) + { + pAdapter->OpenEditor( pElement ); + } +} + + +//----------------------------------------------------------------------------- +// Queues up a compilation task +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::AddCompilationTask( CDmElement* pElement, CCompileFuncAdapterBase *pAdapter ) +{ + Assert( m_CompilationStep == BUILDING_STANDARD_DEPENDENCIES || m_CompilationStep == BUILDING_ALL_DEPENDENCIES ); + + // Queue up the compilation task + int j = m_CompileTasks.AddToTail(); + m_CompileTasks[j].m_hElement = pElement; + m_CompileTasks[j].m_pAdapter = pAdapter; +} + +void CDmeMakefileUtils::AddCompilationTask( CDmElement* pElement ) +{ + CCompileFuncAdapterBase *pAdapter = DetermineCompileAdapter( pElement ); + if ( pAdapter ) + { + // Queue up the compilation task + AddCompilationTask( pElement, pAdapter ); + } +} + + +//----------------------------------------------------------------------------- +// Sets the compile process +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::SetCompileProcess( ProcessHandle_t hProcess ) +{ + Assert( m_CompilationStep == PERFORMING_COMPILATION ); + m_hCompileProcess = hProcess; + if ( m_hCompileProcess == PROCESS_HANDLE_INVALID ) + { + m_CompilationStep = AFTER_COMPILATION_FAILED; + } +} + + +//----------------------------------------------------------------------------- +// Default implementatations for compile dependencies +//----------------------------------------------------------------------------- +bool CDmeMakefileUtils::AddCompileDependencies( CDmeMakefile *pMakefile, bool bBuildAllDependencies ) +{ + if ( !pMakefile ) + return true; + + CUtlVector< CUtlString > outputs; + int nCount = pMakefile->GetSourceCount(); + for ( int i = 0; i < nCount; ++i ) + { + CDmeSource *pSource = pMakefile->GetSource( i ); + if ( !pSource ) + continue; + + CDmeMakefile *pDependentMakefile = pSource->GetDependentMakefile(); + if ( !pDependentMakefile ) + continue; + + bool bShouldBuildFile = bBuildAllDependencies; + + // Does the output files exist? + int j = 0; + if ( !bBuildAllDependencies ) + { + pDependentMakefile->GetOutputs( outputs ); + int nOutputCount = outputs.Count(); + for ( j = 0; j < nOutputCount; ++j ) + { + // If the file doesn't exist, we have to build it + if ( !g_pFullFileSystem->FileExists( outputs[j] ) ) + break; + + bShouldBuildFile = true; + break; + } + } + + if ( !bShouldBuildFile ) + continue; + + CCompileFuncAdapterBase *pAdapter = DetermineCompileAdapter( pDependentMakefile ); + if ( pAdapter ) + { + // Add dependent makefiles first + if ( !pAdapter->PerformCompilationStep( pDependentMakefile, bBuildAllDependencies ? BUILDING_ALL_DEPENDENCIES : BUILDING_STANDARD_DEPENDENCIES ) ) + return false; + } + + // Queue up the compilation task + AddCompilationTask( pDependentMakefile, pAdapter ); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Default implementatations for precompilation step +//----------------------------------------------------------------------------- +bool CDmeMakefileUtils::PerformCompilationStep( CDmElement *pElement, CompilationStep_t step ) +{ + // Do nothing + return true; +} + +bool CDmeMakefileUtils::PerformCompilationStep( CDmeMakefile *pMakefile, CompilationStep_t step ) +{ + switch( step ) + { + case BUILDING_ALL_DEPENDENCIES: + return AddCompileDependencies( pMakefile, true ); + + case BUILDING_STANDARD_DEPENDENCIES: + return AddCompileDependencies( pMakefile, false ); + + case BEFORE_COMPILATION: + pMakefile->PreCompile(); + break; + + case AFTER_COMPILATION_SUCCEEDED: + pMakefile->PostCompile(); + break; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Starts the next compile task +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::StartNextCompileTask( ) +{ + Assert( m_hCompileProcess == PROCESS_HANDLE_INVALID ); + ++m_nCurrentCompileTask; + if ( m_nCurrentCompileTask == m_CompileTasks.Count() ) + { + PerformCompilationStep( AFTER_COMPILATION_SUCCEEDED ); + m_nCurrentCompileTask = -1; + m_CompileTasks.RemoveAll(); + return; + } + + m_hCompileProcess = PROCESS_HANDLE_INVALID; + + // NOTE: PerformCompilationStep is expected to call SetCompileProcess to set m_hCompileProcess + CompileInfo_t &info = m_CompileTasks[m_nCurrentCompileTask]; + bool bOk = info.m_pAdapter->PerformCompilationStep( info.m_hElement, PERFORMING_COMPILATION ); + + if ( !bOk || ( m_hCompileProcess == PROCESS_HANDLE_INVALID ) ) + { + AbortCurrentCompilation(); + return; + } +} + + +//----------------------------------------------------------------------------- +// Performs the compilation step on all elements +//----------------------------------------------------------------------------- +bool CDmeMakefileUtils::PerformCompilationStep( CompilationStep_t step ) +{ + // Iterate through all elements and run a compilation step + m_CompilationStep = step; + int nCount = m_CompileTasks.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CompileInfo_t &info = m_CompileTasks[i]; + if ( info.m_hElement.Get() ) + { + if ( !info.m_pAdapter->PerformCompilationStep( info.m_hElement, step ) ) + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Main entry point for compilation +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::PerformCompile( CDmElement *pElement, bool bBuildAllDependencies ) +{ + if ( IsCurrentlyCompiling() ) + { + AbortCurrentCompilation(); + } + + CCompileFuncAdapterBase *pAdapter = DetermineCompileAdapter( pElement ); + if ( !pAdapter ) + { + m_CompilationStep = AFTER_COMPILATION_FAILED; + return; + } + + // Add dependent makefiles first + m_CompilationStep = bBuildAllDependencies ? BUILDING_ALL_DEPENDENCIES : BUILDING_STANDARD_DEPENDENCIES; + if ( !pAdapter->PerformCompilationStep( pElement, m_CompilationStep ) ) + { + AbortCurrentCompilation(); + return; + } + + // Queue up the compilation task + AddCompilationTask( pElement, pAdapter ); + + // Iterate through all elements and run a precompilation step + // NOTE: This is where perforce integration should go + if ( !PerformCompilationStep( BEFORE_COMPILATION ) ) + { + AbortCurrentCompilation(); + return; + } + + // Dequeue the first compile task and start it up + m_CompilationStep = PERFORMING_COMPILATION; + StartNextCompileTask(); +} + + +//----------------------------------------------------------------------------- +// Are we in the middle of compiling this makefile? +//----------------------------------------------------------------------------- +bool CDmeMakefileUtils::IsCurrentlyCompiling() +{ + return ( m_CompilationStep != NOT_COMPILING ); +} + + +//----------------------------------------------------------------------------- +// Aborts any current compilation +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::AbortCurrentCompilation() +{ + if ( m_hCompileProcess != PROCESS_HANDLE_INVALID ) + { + g_pProcessUtils->AbortProcess( m_hCompileProcess ); + m_hCompileProcess = PROCESS_HANDLE_INVALID; + } + + if ( IsCurrentlyCompiling() ) + { + PerformCompilationStep( AFTER_COMPILATION_FAILED ); + m_nCurrentCompileTask = -1; + m_CompileTasks.RemoveAll(); + } +} + + +//----------------------------------------------------------------------------- +// Returns the exit code of the failed compilation (if COMPILATION_FAILED occurred) +//----------------------------------------------------------------------------- +int CDmeMakefileUtils::GetExitCode() +{ + return m_nExitCode; +} + + +//----------------------------------------------------------------------------- +// Returns output from the compilation +//----------------------------------------------------------------------------- +int CDmeMakefileUtils::GetCompileOutputSize() +{ + if ( m_hCompileProcess == PROCESS_HANDLE_INVALID ) + return 0; + return g_pProcessUtils->GetProcessOutputSize( m_hCompileProcess ); +} + +CompilationState_t CDmeMakefileUtils::UpdateCompilation( char *pOutputBuf, int nBufLen ) +{ + switch( m_CompilationStep ) + { + case BUILDING_STANDARD_DEPENDENCIES: + case BUILDING_ALL_DEPENDENCIES: + case BEFORE_COMPILATION: + return COMPILATION_NOT_COMPLETE; + + case AFTER_COMPILATION_FAILED: + m_CompilationStep = NOT_COMPILING; + return COMPILATION_FAILED; + + case AFTER_COMPILATION_SUCCEEDED: + m_CompilationStep = NOT_COMPILING; + return COMPILATION_SUCCESSFUL; + } + + // This is the PERFORMING_COMPILATION case: + + // FIXME: Check return codes from compile process.. + // fail if compilation process had a problem + if ( m_hCompileProcess == PROCESS_HANDLE_INVALID ) + { + if ( nBufLen > 0 ) + { + pOutputBuf[0] = 0; + } + return COMPILATION_SUCCESSFUL; + } + + if ( nBufLen > 0 ) + { + g_pProcessUtils->GetProcessOutput( m_hCompileProcess, pOutputBuf, nBufLen ); + } + + if ( !g_pProcessUtils->IsProcessComplete( m_hCompileProcess ) ) + return COMPILATION_NOT_COMPLETE; + + m_nExitCode = g_pProcessUtils->GetProcessExitCode( m_hCompileProcess ); + bool bCompileSucceeded = ( m_nExitCode == 0 ); + g_pProcessUtils->CloseProcess( m_hCompileProcess ); + m_hCompileProcess = PROCESS_HANDLE_INVALID; + + if ( !bCompileSucceeded ) + { + AbortCurrentCompilation(); + return COMPILATION_NOT_COMPLETE; + } + + StartNextCompileTask(); + if ( m_CompilationStep == PERFORMING_COMPILATION ) + return COMPILATION_NOT_COMPLETE; + + CompilationState_t retVal = ( m_CompilationStep == AFTER_COMPILATION_SUCCEEDED ) ? COMPILATION_SUCCESSFUL : COMPILATION_FAILED; + m_CompilationStep = NOT_COMPILING; + return retVal; +} + + +//----------------------------------------------------------------------------- +// Type-specific compilation functions +//----------------------------------------------------------------------------- +bool CDmeMakefileUtils::PerformCompilationStep( CDmeMDLMakefile *pMakeFile, CompilationStep_t step ) +{ + if ( step != PERFORMING_COMPILATION ) + return PerformCompilationStep( static_cast<CDmeMakefile*>( pMakeFile ), step ); + + char pBinDirectory[MAX_PATH]; + GetModSubdirectory( "..\\bin", pBinDirectory, sizeof(pBinDirectory) ); + Q_RemoveDotSlashes( pBinDirectory ); + + char pStudioMDLCmd[MAX_PATH]; +#ifdef _DEBUG + Q_snprintf( pStudioMDLCmd, sizeof(pStudioMDLCmd), "%s\\studiomdl.exe -allowdebug %s", pBinDirectory, pMakeFile->GetFileName() ); +#else + Q_snprintf( pStudioMDLCmd, sizeof(pStudioMDLCmd), "%s\\studiomdl.exe %s", pBinDirectory, pMakeFile->GetFileName() ); +#endif + + ProcessHandle_t hProcess = g_pProcessUtils->StartProcess( pStudioMDLCmd, true ); + SetCompileProcess( hProcess ); + return true; +} + + +//----------------------------------------------------------------------------- +// Exports a Maya file to a DMX file +//----------------------------------------------------------------------------- +bool CDmeMakefileUtils::PerformCompilationStep( CDmeMayaMakefile *pMakeFile, CompilationStep_t step ) +{ + if ( step != PERFORMING_COMPILATION ) + return PerformCompilationStep( static_cast<CDmeMakefile*>( pMakeFile ), step ); + + // FIXME: Create batch export command here + CUtlString mayaCommand; + mayaCommand = "vsDmxIO -export"; + + CUtlVector< CDmeHandle< CDmeSourceMayaFile > > sources; + pMakeFile->GetSources( sources ); + + if ( !sources.Count() ) + return false; + + CDmeSourceMayaFile *pDmeSourceDCCFile( sources[ 0 ].Get() ); + + mayaCommand += " -selection"; + + char pObjectId[128]; + UniqueIdToString( pMakeFile->GetId(), pObjectId, sizeof(pObjectId) ); + mayaCommand += " -makefileObjectId \\\""; + mayaCommand += pObjectId; + mayaCommand += "\\\""; + + mayaCommand += " -"; + mayaCommand += pDmeSourceDCCFile->m_ExportType.GetAttribute()->GetName(); + + switch ( pDmeSourceDCCFile->m_ExportType.Get() ) + { + case 1: // skeletal animation + mayaCommand += " skeletalAnimation"; + + mayaCommand += " -"; + mayaCommand += pDmeSourceDCCFile->m_FrameStart.GetAttribute()->GetName(); + mayaCommand += " "; + mayaCommand += pDmeSourceDCCFile->m_FrameStart.Get(); + + mayaCommand += " -"; + mayaCommand += pDmeSourceDCCFile->m_FrameEnd.GetAttribute()->GetName(); + mayaCommand += " "; + mayaCommand += pDmeSourceDCCFile->m_FrameEnd.Get(); + + mayaCommand += " -"; + mayaCommand += pDmeSourceDCCFile->m_FrameIncrement.GetAttribute()->GetName(); + mayaCommand += " "; + mayaCommand += pDmeSourceDCCFile->m_FrameIncrement.Get(); + break; + default: // Model + mayaCommand += " model"; + break; + } + + char pFileName[MAX_PATH]; + Q_strncpy( pFileName, pMakeFile->GetFileName(), sizeof( pFileName ) ); + Q_FixSlashes( pFileName, '/' ); + mayaCommand += " -filename \\\""; + mayaCommand += pFileName; + mayaCommand += "\\\""; + + const int rootObjectCount( pDmeSourceDCCFile->m_RootDCCObjects.Count() ); + for ( int rootObjectIndex( 0 ); rootObjectIndex < rootObjectCount; ++rootObjectIndex ) + { + mayaCommand += " "; + mayaCommand += pDmeSourceDCCFile->m_RootDCCObjects[ rootObjectIndex ]; + } + + char pSourcePath[MAX_PATH]; + pMakeFile->GetSourceFullPath( pDmeSourceDCCFile, pSourcePath, sizeof(pSourcePath) ); + + // Maya wants forward slashes + Q_FixSlashes( pSourcePath, '/' ); + + char pMayaCommand[1024]; + Q_snprintf( pMayaCommand, sizeof(pMayaCommand), "mayabatch.exe -batch -file \"%s\" -command \"%s\"", pSourcePath, mayaCommand.Get() ); + ProcessHandle_t hProcess = g_pProcessUtils->StartProcess( pMayaCommand, true ); + SetCompileProcess( hProcess ); + return true; +} + + +//----------------------------------------------------------------------------- +// Opens Maya with a particular file +//----------------------------------------------------------------------------- +void CDmeMakefileUtils::OpenEditor( CDmeSourceMayaFile *pDmeSourceDCCFile ) +{ + CDmeMayaMakefile *pMakefile = FindReferringElement< CDmeMayaMakefile >( pDmeSourceDCCFile, "sources" ); + if ( !pMakefile ) + return; + + char pSourcePath[MAX_PATH]; + pMakefile->GetSourceFullPath( pDmeSourceDCCFile, pSourcePath, sizeof(pSourcePath) ); + + // Maya wants forward slashes + Q_FixSlashes( pSourcePath, '/' ); + + char pMayaCommand[1024]; + Q_snprintf( pMayaCommand, sizeof(pMayaCommand), "maya.exe -file \"%s\"", pSourcePath ); + g_pProcessUtils->StartProcess( pMayaCommand, true ); +} diff --git a/movieobjects/dmematerial.cpp b/movieobjects/dmematerial.cpp new file mode 100644 index 0000000..3bc984a --- /dev/null +++ b/movieobjects/dmematerial.cpp @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmematerial.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects_interfaces.h" + +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialsystem.h" +#include "tier2/tier2.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMaterial, CDmeMaterial ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeMaterial::OnConstruction() +{ + m_pMTL = NULL; + m_mtlName.Init( this, "mtlName" ); +} + +void CDmeMaterial::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// resolve +//----------------------------------------------------------------------------- +void CDmeMaterial::Resolve() +{ + BaseClass::Resolve(); + if ( m_mtlName.IsDirty() ) + { + m_pMTL = NULL; // no cleanup necessary + } +} + + +//----------------------------------------------------------------------------- +// Sets the material +//----------------------------------------------------------------------------- +void CDmeMaterial::SetMaterial( const char *pMaterialName ) +{ + m_mtlName = pMaterialName; +} + + +//----------------------------------------------------------------------------- +// Returns the material name +//----------------------------------------------------------------------------- +const char *CDmeMaterial::GetMaterialName() const +{ + return m_mtlName; +} + + +//----------------------------------------------------------------------------- +// accessor for cached IMaterial +//----------------------------------------------------------------------------- +IMaterial *CDmeMaterial::GetCachedMTL() +{ + if ( m_pMTL == NULL ) + { + const char *mtlName = m_mtlName.Get(); + if ( mtlName == NULL ) + return NULL; + m_pMTL = g_pMaterialSystem->FindMaterial( mtlName, NULL, false ); + } + return m_pMTL; +} diff --git a/movieobjects/dmematerialoverlayfxclip.cpp b/movieobjects/dmematerialoverlayfxclip.cpp new file mode 100644 index 0000000..3fb62b1 --- /dev/null +++ b/movieobjects/dmematerialoverlayfxclip.cpp @@ -0,0 +1,270 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmematerialoverlayfxclip.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" +#include "tier1/KeyValues.h" +#include "tier1/convar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// CDmeClip - common base class for filmclips, soundclips, and channelclips +//----------------------------------------------------------------------------- +IMPLEMENT_FX_CLIP_ELEMENT_FACTORY( DmeMaterialOverlayFXClip, CDmeMaterialOverlayFXClip, "Material Overlay Effect" ); + +void CDmeMaterialOverlayFXClip::OnConstruction() +{ + m_Material.Init( this, "material" ); + m_Color.Init( this, "overlaycolor" ); + m_nLeft.Init( this, "left" ); + m_nTop.Init( this, "top" ); + m_nWidth.Init( this, "width" ); + m_nHeight.Init( this, "height" ); + m_bFullScreen.Init( this, "fullscreen" ); + m_bUseSubRect.Init( this, "useSubRect" ); + m_flMovementAngle.Init( this, "movementAngle" ); + m_flMovementSpeed.Init( this, "movementSpeed" ); + m_nSubRectLeft.Init( this, "subRectLeft" ); + m_nSubRectTop.Init( this, "subRectTop" ); + m_nSubRectWidth.Init( this, "subRectWidth" ); + m_nSubRectHeight.Init( this, "subRectHeight" ); + + m_Color.SetColor( 255, 255, 255, 255 ); + m_bFullScreen = true; + m_nLeft = m_nTop = 0; + m_nWidth = m_nHeight = 1; +} + +void CDmeMaterialOverlayFXClip::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Resolve +//----------------------------------------------------------------------------- +void CDmeMaterialOverlayFXClip::Resolve() +{ + if ( m_Material.IsDirty() ) + { + m_OverlayMaterial.Shutdown(); + const char *pName = m_Material.Get(); + if ( pName && pName[0] ) + { + m_OverlayMaterial.Init( pName, NULL, false ); + } + m_Material.GetAttribute()->RemoveFlag( FATTRIB_DIRTY ); + } +} + + +//----------------------------------------------------------------------------- +// Helper for overlays +//----------------------------------------------------------------------------- +void CDmeMaterialOverlayFXClip::SetOverlayEffect( const char *pMaterialName ) +{ + m_Material = pMaterialName; +} + +void CDmeMaterialOverlayFXClip::SetAlpha( float flAlpha ) +{ + m_Color.SetAlpha( flAlpha * 255 ); +} + +float CDmeMaterialOverlayFXClip::GetAlpha( void ) +{ + return ( (float)m_Color.a() ) / 255.0f; +} + +bool CDmeMaterialOverlayFXClip::HasOpaqueOverlay( void ) +{ + if ( m_OverlayMaterial ) + return ( !m_OverlayMaterial->IsTranslucent() && ( m_Color.a() == 255 ) && m_bFullScreen ); + + // no material overlay set + return false; +} + +IMaterial *CDmeMaterialOverlayFXClip::GetMaterial() +{ + return m_OverlayMaterial; +} + + +//----------------------------------------------------------------------------- +// All effects must be able to apply their effect +//----------------------------------------------------------------------------- +void CDmeMaterialOverlayFXClip::ApplyEffect( DmeTime_t time, Rect_t ¤tRect, Rect_t &totalRect, ITexture *pTextures[MAX_FX_INPUT_TEXTURES] ) +{ + if ( !m_OverlayMaterial || m_Color.a() == 0 ) + return; + + time = ToChildMediaTime( time, false ); + + // Clip the overlay rectangle to the currently drawn one + int x, y, w, h; + int tx, ty, tw, th; + if ( m_bFullScreen ) + { + x = currentRect.x; + y = currentRect.y; + w = currentRect.width; + h = currentRect.height; + tx = ty = 0; + tw = totalRect.width; + th = totalRect.height; + } + else + { + x = clamp( m_nLeft, currentRect.x, currentRect.x + currentRect.width ); + y = clamp( m_nTop, currentRect.y, currentRect.y + currentRect.height ); + int x1 = clamp( m_nLeft + m_nWidth, currentRect.x, currentRect.x + currentRect.width ); + int y1 = clamp( m_nTop + m_nHeight, currentRect.y, currentRect.y + currentRect.height ); + w = x1 - x; + h = y1 - y; + + tx = m_nLeft; + ty = m_nTop; + tw = m_nWidth; + th = m_nHeight; + + // Clipped... + if ( w <= 0 || h <= 0 ) + return; + } + + if ( tw == 0 || th == 0 ) + return; + + // Compute texture coordinate range of the entire texture + int mw = m_OverlayMaterial->GetMappingWidth(); + int mh = m_OverlayMaterial->GetMappingHeight(); + + // Compute the texture coords in texels we want over the entire image + float uMin = 0; + float uMax = mw; + float vMin = 0; + float vMax = mh; + + if ( m_bUseSubRect ) + { + uMin = m_nSubRectLeft; + vMin = m_nSubRectTop; + uMax = uMin + m_nSubRectWidth; + vMax = vMin + m_nSubRectHeight; + } + + if ( m_flMovementSpeed ) + { + float flRadians = M_PI * m_flMovementAngle / 180.0f; + float dUdT = -m_flMovementSpeed * cos( flRadians ); + float dVdT = m_flMovementSpeed * sin( flRadians ); + float dU = time.GetSeconds() * dUdT; + float dV = time.GetSeconds() * dVdT; + uMin += dU; uMax += dU; + vMin += dV; vMax += dV; + } + + // This is the range of normalizes (u,v) coordinates over the *total* image + uMin = ( uMin + 0.5f ) / mw; + vMin = ( vMin + 0.5f ) / mh; + uMax = ( uMax - 0.5f ) / mw; + vMax = ( vMax - 0.5f ) / mh; + + // Now determine the subrange we should use given we're rendering a portion of the image + float u0, v0, u1, v1, f; + + f = ( x - tx ) / tw; + u0 = Lerp( f, uMin, uMax ); + + f = ( x + w - tx ) / tw; + u1 = Lerp( f, uMin, uMax ); + + f = ( y - ty ) / th; + v0 = Lerp( f, vMin, vMax ); + + f = ( y + h - ty ) / th; + v1 = Lerp( f, vMin, vMax ); + + x -= currentRect.x; + y -= currentRect.y; + + if ( m_OverlayMaterial->NeedsPowerOfTwoFrameBufferTexture() ) + { + CMatRenderContextPtr pRenderContext( materials ); + ITexture *pTexture = materials->FindTexture( "_rt_PowerOfTwoFB", TEXTURE_GROUP_RENDER_TARGET ); + + // forced or only once per frame + Rect_t rect; + rect.x = 0; + rect.y = 0; + rect.width = currentRect.width; + rect.height = currentRect.height; + pRenderContext->CopyRenderTargetToTextureEx( pTexture, 0, &rect, NULL ); + pRenderContext->SetFrameBufferCopyTexture( pTexture ); + } + + float r, g, b, a; + m_OverlayMaterial->GetColorModulation( &r, &g, &b ); + a = m_OverlayMaterial->GetAlphaModulation(); + + m_OverlayMaterial->ColorModulate( m_Color.r() / 255.0f, m_Color.g() / 255.0f, m_Color.b() / 255.0f ); + m_OverlayMaterial->AlphaModulate( m_Color.a() / 255.0f ); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_OverlayMaterial ); + + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, 2 ); + + meshBuilder.Position3f( x, y, 0.0f ); + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.TexCoord2f( 0, u0, v0 ); + meshBuilder.TexCoord2f( 1, 0.0f, 0.0f ); + meshBuilder.TexCoord2f( 2, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( x, y+h, 0.0f ); + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.TexCoord2f( 0, u0, v1 ); + meshBuilder.TexCoord2f( 1, 0.0f, 1.0f ); + meshBuilder.TexCoord2f( 2, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( x+w, y, 0.0f ); + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.TexCoord2f( 0, u1, v0 ); + meshBuilder.TexCoord2f( 1, 1.0f, 0.0f ); + meshBuilder.TexCoord2f( 2, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( x+w, y+h, 0.0f ); + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.TexCoord2f( 0, u1, v1 ); + meshBuilder.TexCoord2f( 1, 1.0f, 1.0f ); + meshBuilder.TexCoord2f( 2, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + + m_OverlayMaterial->ColorModulate( r, g, b ); + m_OverlayMaterial->AlphaModulate( a ); +} + diff --git a/movieobjects/dmemdl.cpp b/movieobjects/dmemdl.cpp new file mode 100644 index 0000000..3b720f0 --- /dev/null +++ b/movieobjects/dmemdl.cpp @@ -0,0 +1,207 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmemdl.h" +#include "movieobjects/dmetransform.h" +#include "movieobjects/dmedag.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datacache/imdlcache.h" +#include "istudiorender.h" +#include "bone_setup.h" +#include "tier3/tier3.h" +#include "tier3/mdlutils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMDL, CDmeMDL ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMDL::OnConstruction() +{ + m_bDrawInEngine = false; +// SetAttributeValueElement( "transform", CreateElement< CDmeTransform >() ); +// SetAttributeValue( "mdlfilename", "models/alyx.mdl" ); + m_Color.InitAndSet( this, "color", Color( 255, 255, 255, 255 ) ); + m_nSkin.InitAndSet( this, "skin", 0 ); + m_nBody.InitAndSet( this, "body", 0 ); + m_nSequence.InitAndSet( this, "sequence", 0 ); + m_nLOD.InitAndSet( this, "lod", 0 ); + m_flPlaybackRate.InitAndSet( this, "playbackrate", 30.0f ); + m_flTime.InitAndSet( this, "time", 0.0f ); + m_vecViewTarget.Init( this, "viewTarget" ); + m_bWorldSpaceViewTarget.Init( this, "worldSpaceViewTarget" ); +} + +void CDmeMDL::OnDestruction() +{ + m_MDL.SetMDL( MDLHANDLE_INVALID ); +} + + +void CDmeMDL::SetMDL( MDLHandle_t handle ) +{ + m_MDL.SetMDL( handle ); + + Vector vecMins, vecMaxs; + GetMDLBoundingBox( &vecMins, &vecMaxs, m_MDL.GetMDL(), m_nSequence ); + Vector vecLookAt( 100.0f, 0.0f, vecMaxs.z ); + + m_vecViewTarget.Set( vecLookAt ); + m_bWorldSpaceViewTarget = false; +} + +MDLHandle_t CDmeMDL::GetMDL( ) const +{ + return m_MDL.GetMDL(); +} + + +//----------------------------------------------------------------------------- +// Loads the model matrix based on the transform +//----------------------------------------------------------------------------- +void CDmeMDL::DrawInEngine( bool bDrawInEngine ) +{ + m_bDrawInEngine = bDrawInEngine; +} + +bool CDmeMDL::IsDrawingInEngine() const +{ + return m_bDrawInEngine; +} + + +//----------------------------------------------------------------------------- +// Returns the bounding box for the model +//----------------------------------------------------------------------------- +void CDmeMDL::GetBoundingBox( Vector *pMins, Vector *pMaxs ) const +{ + GetMDLBoundingBox( pMins, pMaxs, m_MDL.GetMDL(), m_nSequence ); + + // Rotate the root transform to make it align with DMEs + // DMEs up vector is the y axis + if ( !m_bDrawInEngine ) + { + Vector vecMins, vecMaxs; + matrix3x4_t engineToDme; + CDmeDag::EngineToDmeMatrix( engineToDme ); + TransformAABB( engineToDme, *pMins, *pMaxs, vecMins, vecMaxs ); + *pMins = vecMins; + *pMaxs = vecMaxs; + } +} + + +//----------------------------------------------------------------------------- +// Returns the radius of the model as measured from the origin +//----------------------------------------------------------------------------- +float CDmeMDL::GetRadius() const +{ + return GetMDLRadius( m_MDL.GetMDL(), m_nSequence ); +} + + +//----------------------------------------------------------------------------- +// Returns a more accurate bounding sphere +//----------------------------------------------------------------------------- +void CDmeMDL::GetBoundingSphere( Vector &vecCenter, float &flRadius ) +{ + Vector vecEngineCenter; + GetMDLBoundingSphere( &vecEngineCenter, &flRadius, m_MDL.GetMDL(), m_nSequence ); + + // Rotate the root transform to make it align with DMEs + // DMEs up vector is the y axis + if ( !m_bDrawInEngine ) + { + matrix3x4_t engineToDme; + CDmeDag::EngineToDmeMatrix( engineToDme ); + VectorTransform( vecEngineCenter, engineToDme, vecCenter ); + } + else + { + vecCenter = vecEngineCenter; + } +} + + +//----------------------------------------------------------------------------- +// Updates the MDL rendering helper +//----------------------------------------------------------------------------- +void CDmeMDL::UpdateMDL() +{ + m_MDL.m_Color = m_Color; + m_MDL.m_nSkin = m_nSkin; + m_MDL.m_nBody = m_nBody; + m_MDL.m_nSequence = m_nSequence; + m_MDL.m_nLOD = m_nLOD; + m_MDL.m_flPlaybackRate = m_flPlaybackRate; + m_MDL.m_flTime = m_flTime; + m_MDL.m_vecViewTarget = m_vecViewTarget; + m_MDL.m_Color = m_Color; + m_MDL.m_bWorldSpaceViewTarget = m_bWorldSpaceViewTarget; +} + + +//----------------------------------------------------------------------------- +// Draws the mesh +//----------------------------------------------------------------------------- +void CDmeMDL::Draw( const matrix3x4_t &shapeToWorld, CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + UpdateMDL(); + studiohdr_t *pStudioHdr = m_MDL.GetStudioHdr(); + if ( !pStudioHdr ) + return; + + // FIXME: Why is this necessary!?!?!? + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + if ( !m_bDrawInEngine ) + { + pRenderContext->CullMode( MATERIAL_CULLMODE_CCW ); + } + + matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( pStudioHdr->numbones ); + SetUpBones( shapeToWorld, pStudioHdr->numbones, pBoneToWorld ); + g_pStudioRender->UnlockBoneMatrices(); + + m_MDL.Draw( shapeToWorld, pBoneToWorld ); + + // FIXME: Why is this necessary!?!?!? + if ( !m_bDrawInEngine ) + { + pRenderContext->CullMode( MATERIAL_CULLMODE_CW ); + } +} + + +void CDmeMDL::SetUpBones( const matrix3x4_t& shapeToWorld, int nMaxBoneCount, matrix3x4_t *pOutputMatrices ) +{ + UpdateMDL(); + + // Root transform + matrix3x4_t rootToWorld; + + // Rotate the root transform to make it align with DMEs + // DMEs up vector is the y axis + if ( !m_bDrawInEngine ) + { + matrix3x4_t engineToDme; + CDmeDag::EngineToDmeMatrix( engineToDme ); + ConcatTransforms( engineToDme, shapeToWorld, rootToWorld ); + } + else + { + MatrixCopy( shapeToWorld, rootToWorld ); + } + + m_MDL.SetUpBones( rootToWorld, nMaxBoneCount, pOutputMatrices ); +} diff --git a/movieobjects/dmemdlmakefile.cpp b/movieobjects/dmemdlmakefile.cpp new file mode 100644 index 0000000..f79e2fb --- /dev/null +++ b/movieobjects/dmemdlmakefile.cpp @@ -0,0 +1,287 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Describes an asset: something that is compiled from sources, +// in potentially multiple steps, to a compiled resource +// +//============================================================================= + + +#include "movieobjects/dmemdlmakefile.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmemdl.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datacache/imdlcache.h" +#include "filesystem.h" +#include "tier3/tier3.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSourceSkin, CDmeSourceSkin ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSourceSkin::OnConstruction() +{ + m_SkinName.Init( this, "skinName" ); + m_bFlipTriangles.Init( this, "flipTriangles" ); + m_flScale.InitAndSet( this, "scale", 1.0f ); +} + +void CDmeSourceSkin::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// These can be built from DCC makefiles +//----------------------------------------------------------------------------- +static const char *s_pSkinMakeFiles[] = +{ + "DmeMayaModelMakefile", + "DmeXSIModelMakefile", + NULL +}; + +const char **CDmeSourceSkin::GetSourceMakefileTypes() +{ + return s_pSkinMakeFiles; +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSourceCollisionModel, CDmeSourceCollisionModel ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSourceCollisionModel::OnConstruction() +{ +} + +void CDmeSourceCollisionModel::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// These can be built from DCC makefiles +//----------------------------------------------------------------------------- +const char **CDmeSourceCollisionModel::GetSourceMakefileTypes() +{ + return s_pSkinMakeFiles; +} + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSourceAnimation, CDmeSourceAnimation ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeSourceAnimation::OnConstruction() +{ + m_AnimationName.Init( this, "animationName" ); + m_SourceAnimationName.Init( this, "sourceAnimationName" ); +} + +void CDmeSourceAnimation::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// These can be built from DCC makefiles +//----------------------------------------------------------------------------- +static const char *s_pAnimationMakeFiles[] = +{ + "DmeMayaAnimationMakefile", + "DmeXSIAnimationMakefile", + NULL +}; + +const char **CDmeSourceAnimation::GetSourceMakefileTypes() +{ + return s_pAnimationMakeFiles; +} + + + +//----------------------------------------------------------------------------- +// Hook into datamodel +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMDLMakefile, CDmeMDLMakefile ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMDLMakefile::OnConstruction() +{ + m_hMDL = CreateElement< CDmeMDL >( "MDLMakefile Preview" ); + m_bFlushMDL = false; +} + +void CDmeMDLMakefile::OnDestruction() +{ + DestroyElement( m_hMDL.Get() ); +} + + +//----------------------------------------------------------------------------- +// Returns source types +//----------------------------------------------------------------------------- +static DmeMakefileType_t s_pSourceTypes[] = +{ + { "DmeSourceSkin", "Skin", true, "makefiledir:models\\dmx", "*.dmx", "Valve DMX File (*.dmx)" }, + { "DmeSourceAnimation", "Animation", false, "makefiledir:animations\\dmx", "*.dmx", "Valve DMX File (*.dmx)" }, + { "DmeSourceCollisionModel", "Collision Model", true, "makefiledir:models\\dmx", "*.dmx", "Valve DMX File (*.dmx)" }, + { NULL, NULL, false, NULL, NULL, NULL }, +}; + +DmeMakefileType_t* CDmeMDLMakefile::GetSourceTypes() +{ + return s_pSourceTypes; +} + + +//----------------------------------------------------------------------------- +// Makefile type +//----------------------------------------------------------------------------- +static DmeMakefileType_t s_MakefileType = +{ + "DmeMDLMakefile", "Model", true, "contentdir:models", "*.dmx", "Valve Model MakeFile (*.dmx)" +}; + + +DmeMakefileType_t *CDmeMDLMakefile::GetMakefileType() +{ + return &s_MakefileType; +} + + +//----------------------------------------------------------------------------- +// Add, remove sources +//----------------------------------------------------------------------------- +void CDmeMDLMakefile::SetSkin( const char *pFullPath ) +{ + RemoveAllSources( "DmeSourceSkin" ); + AddSource( "DmeSourceSkin", pFullPath ); +} + +void CDmeMDLMakefile::AddAnimation( const char *pFullPath ) +{ + AddSource( "animation", pFullPath ); +} + +void CDmeMDLMakefile::RemoveAnimation( const char *pFullPath ) +{ + RemoveSource( "animation", pFullPath ); +} + +void CDmeMDLMakefile::RemoveAllAnimations( ) +{ + RemoveAllSources( "animation" ); +} + + +//----------------------------------------------------------------------------- +// Inherited classes should re-implement these methods +//----------------------------------------------------------------------------- +CDmElement *CDmeMDLMakefile::CreateOutputElement( ) +{ + if ( m_bFlushMDL ) + { + // Flush the model out of the cache; detach it from the MDL + MDLHandle_t h = m_hMDL->GetMDL(); + if ( h != MDLHANDLE_INVALID ) + { + g_pMDLCache->Flush( h ); + } + m_bFlushMDL = false; + } + m_hMDL->SetMDL( MDLHANDLE_INVALID ); + + // FIXME: Should we ask the tool (studiomdl) for this? + // Should we have output type names? Not sure yet.. + // Doing the simplest thing first. + char pOutputName[MAX_PATH]; + Q_FileBase( GetFileName(), pOutputName, sizeof(pOutputName) ); + if ( !pOutputName[0] ) + return m_hMDL.Get(); + + char pOutputDir[MAX_PATH]; + GetOutputDirectory( pOutputDir, sizeof(pOutputDir) ); + if ( !pOutputDir[0] ) + return m_hMDL.Get(); + + Q_StripTrailingSlash( pOutputDir ); + char pFullPath[MAX_PATH]; + Q_snprintf( pFullPath, sizeof(pFullPath), "%s\\%s.mdl", pOutputDir, pOutputName ); + + char pRelativePath[MAX_PATH]; + g_pFullFileSystem->FullPathToRelativePathEx( pFullPath, "GAME", pRelativePath, sizeof( pRelativePath ) ); + + MDLHandle_t h = g_pMDLCache->FindMDL( pRelativePath ); + m_hMDL->SetMDL( h ); + return m_hMDL.Get(); +} + +void CDmeMDLMakefile::DestroyOutputElement( CDmElement *pOutput ) +{ + m_bFlushMDL = true; +} + + +//----------------------------------------------------------------------------- +// Compile assets +//----------------------------------------------------------------------------- +static const char *s_pOutputExtensions[] = +{ + "dx80.vtx", + "dx90.vtx", + "sw.vtx", + "mdl", + "vvd", + "phy", + NULL +}; + +void CDmeMDLMakefile::GetOutputs( CUtlVector<CUtlString> &fullPaths ) +{ + fullPaths.RemoveAll(); + + // FIXME: Should we ask the tool (studiomdl) for this? + // Should we have output type names? Not sure yet.. + // Doing the simplest thing first. + char pOutputName[MAX_PATH]; + Q_FileBase( GetFileName(), pOutputName, sizeof(pOutputName) ); + if ( !pOutputName[0] ) + return; + + char pOutputDir[MAX_PATH]; + GetOutputDirectory( pOutputDir, sizeof(pOutputDir) ); + if ( !pOutputDir[0] ) + return; + + Q_StripTrailingSlash( pOutputDir ); + char pFullPath[MAX_PATH]; + for ( int i = 0; s_pOutputExtensions[i]; ++i ) + { + Q_snprintf( pFullPath, sizeof(pFullPath), "%s\\%s.%s", pOutputDir, pOutputName, s_pOutputExtensions[i] ); + fullPaths.AddToTail( pFullPath ); + } +} + diff --git a/movieobjects/dmemesh.cpp b/movieobjects/dmemesh.cpp new file mode 100644 index 0000000..aac63a9 --- /dev/null +++ b/movieobjects/dmemesh.cpp @@ -0,0 +1,5017 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +// Standard includes +#include <limits.h> + +// Valve includes +#include "movieobjects/dmemesh.h" +#include "movieobjects/dmevertexdata.h" +#include "movieobjects/dmefaceset.h" +#include "movieobjects/dmematerial.h" +#include "movieobjects/dmetransform.h" +#include "movieobjects/dmemodel.h" +#include "movieobjects_interfaces.h" +#include "movieobjects/dmecombinationoperator.h" +#include "movieobjects/dmeselection.h" +#include "movieobjects/dmedrawsettings.h" +#include "movieobjects/dmmeshcomp.h" +#include "tier3/tier3.h" +#include "tier1/KeyValues.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imorph.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterialvar.h" +#include "istudiorender.h" +#include "studio.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Normal rendering materials +//----------------------------------------------------------------------------- +bool CDmeMesh::s_bNormalMaterialInitialized; +CMaterialReference CDmeMesh::s_NormalMaterial; +CMaterialReference CDmeMesh::s_NormalErrorMaterial; + + + +//----------------------------------------------------------------------------- +// Computes a skin matrix +//----------------------------------------------------------------------------- +static const matrix3x4_t *ComputeSkinMatrix( int nBoneCount, const float *pJointWeight, const int *pJointIndices, const matrix3x4_t *pPoseToWorld, matrix3x4_t &result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + + switch( nBoneCount ) + { + default: + case 1: + return &pPoseToWorld[pJointIndices[0]]; + + case 2: + { + const matrix3x4_t &boneMat0 = pPoseToWorld[pJointIndices[0]]; + const matrix3x4_t &boneMat1 = pPoseToWorld[pJointIndices[1]]; + flWeight0 = pJointWeight[0]; + flWeight1 = pJointWeight[1]; + + // NOTE: Inlining here seems to make a fair amount of difference + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1; + } + return &result; + + case 3: + { + const matrix3x4_t &boneMat0 = pPoseToWorld[pJointIndices[0]]; + const matrix3x4_t &boneMat1 = pPoseToWorld[pJointIndices[1]]; + const matrix3x4_t &boneMat2 = pPoseToWorld[pJointIndices[2]]; + flWeight0 = pJointWeight[0]; + flWeight1 = pJointWeight[1]; + flWeight2 = pJointWeight[2]; + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2; + } + return &result; + + case 4: + { + const matrix3x4_t &boneMat0 = pPoseToWorld[pJointIndices[0]]; + const matrix3x4_t &boneMat1 = pPoseToWorld[pJointIndices[1]]; + const matrix3x4_t &boneMat2 = pPoseToWorld[pJointIndices[2]]; + const matrix3x4_t &boneMat3 = pPoseToWorld[pJointIndices[3]]; + flWeight0 = pJointWeight[0]; + flWeight1 = pJointWeight[1]; + flWeight2 = pJointWeight[2]; + flWeight3 = pJointWeight[3]; + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2 + boneMat3[0][3] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2 + boneMat3[1][3] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2 + boneMat3[2][3] * flWeight3; + } + return &result; + } + + Assert(0); + return NULL; +} + + +//----------------------------------------------------------------------------- +// Helper class to deal with software skinning +//----------------------------------------------------------------------------- +class CRenderInfo +{ +public: + CRenderInfo( const CDmeVertexData *pBaseState ); + + void ComputeVertex( int vi, const matrix3x4_t *pPoseToWorld, CDmeMesh::RenderVertexDelta_t *pDelta, Vector *pPosition, Vector *pNormal, Vector4D *pTangent ); + void ComputeVertex( int vi, const matrix3x4_t *pPoseToWorld, Vector *pDeltaPosition, int nStride, Vector *pPosition ); + void ComputePosition( int posIndex, const matrix3x4_t *pPoseToWorld, Vector *pDeltaPosition, Vector *pPosition ); + + inline bool HasPositionData() const { return m_bHasPositionData; } + inline bool HasNormalData() const { return m_bHasNormalData; } + inline bool HasTangentData() const { return m_bHasTangentData; } +private: + const CUtlVector<int>& m_PositionIndices; + const CUtlVector<Vector>& m_PositionData; + const CUtlVector<int>& m_NormalIndices; + const CUtlVector<Vector>& m_NormalData; + const CUtlVector<int>& m_TangentIndices; + const CUtlVector<Vector4D>& m_TangentData; + const CDmeVertexData *m_pBaseState; + int m_nJointCount; + bool m_bHasPositionData; + bool m_bHasNormalData; + bool m_bHasTangentData; + bool m_bHasSkinningData; +}; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CRenderInfo::CRenderInfo( const CDmeVertexData *pBaseState ) : + m_PositionIndices( pBaseState->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ) ), + m_PositionData( pBaseState->GetPositionData() ), + m_NormalIndices( pBaseState->GetVertexIndexData( CDmeVertexData::FIELD_NORMAL ) ), + m_NormalData( pBaseState->GetNormalData() ), + m_TangentIndices( pBaseState->GetVertexIndexData( CDmeVertexData::FIELD_TANGENT ) ), + m_TangentData( pBaseState->GetTangentData() ) +{ + m_pBaseState = pBaseState; + m_bHasPositionData = m_PositionIndices.Count() > 0; + m_bHasNormalData = m_NormalIndices.Count() > 0; + m_bHasTangentData = m_TangentIndices.Count() > 0; + m_nJointCount = pBaseState->JointCount(); + m_bHasSkinningData = pBaseState->HasSkinningData() && m_nJointCount > 0; +} + + +//----------------------------------------------------------------------------- +// Computes where a vertex is +//----------------------------------------------------------------------------- +void CRenderInfo::ComputeVertex( int vi, const matrix3x4_t *pPoseToWorld, Vector *pDeltaPosition, int nDeltaStride, Vector *pPosition ) +{ + matrix3x4_t result; + Vector vecMorphPosition, vecMorphNormal; + const matrix3x4_t *pSkinMatrix = pPoseToWorld; + if ( m_bHasSkinningData ) + { + const float *pJointWeight = m_pBaseState->GetJointWeights( vi ); + const int *pJointIndices = m_pBaseState->GetJointIndices( vi ); + pSkinMatrix = ComputeSkinMatrix( m_nJointCount, pJointWeight, pJointIndices, pPoseToWorld, result ); + } + + int pi = m_PositionIndices[ vi ]; + const Vector *pPositionData = &m_PositionData[ pi ]; + if ( pDeltaPosition ) + { + Vector *pDelta = (Vector*)( (unsigned char *)pDeltaPosition + nDeltaStride * pi ); + VectorAdd( *pPositionData, *pDelta, vecMorphPosition ); + pPositionData = &vecMorphPosition; + } + VectorTransform( *pPositionData, *pSkinMatrix, *pPosition ); +} + + +//----------------------------------------------------------------------------- +// Computes where a vertex is +//----------------------------------------------------------------------------- +void CRenderInfo::ComputePosition( int posIndex, const matrix3x4_t *pPoseToWorld, Vector *pDeltaPosition, Vector *pPosition ) +{ + matrix3x4_t result; + Vector vecMorphPosition; + const matrix3x4_t *pSkinMatrix = pPoseToWorld; + + if ( m_bHasSkinningData ) + { + const float *pJointWeight = m_pBaseState->GetJointPositionWeights( posIndex ); + const int *pJointIndices = m_pBaseState->GetJointPositionIndices( posIndex ); + pSkinMatrix = ComputeSkinMatrix( m_nJointCount, pJointWeight, pJointIndices, pPoseToWorld, result ); + } + + const Vector *pPositionData = &m_PositionData[ posIndex ]; + + if ( pDeltaPosition ) + { + VectorAdd( *pPositionData, *( pDeltaPosition + posIndex ), vecMorphPosition ); + pPositionData = &vecMorphPosition; + } + + VectorTransform( *pPositionData, *pSkinMatrix, *( pPosition + posIndex ) ); +} + + +//----------------------------------------------------------------------------- +// Computes where a vertex is +//----------------------------------------------------------------------------- +void CRenderInfo::ComputeVertex( int vi, const matrix3x4_t *pPoseToWorld, CDmeMesh::RenderVertexDelta_t *pDelta, Vector *pPosition, Vector *pNormal, Vector4D *pTangent ) +{ + matrix3x4_t result; + Vector vecMorphPosition, vecMorphNormal; + const matrix3x4_t *pSkinMatrix = pPoseToWorld; + + if ( m_bHasSkinningData ) + { + const float *pJointWeight = m_pBaseState->GetJointWeights( vi ); + const int *pJointIndices = m_pBaseState->GetJointIndices( vi ); + pSkinMatrix = ComputeSkinMatrix( m_nJointCount, pJointWeight, pJointIndices, pPoseToWorld, result ); + } + + int pi = m_PositionIndices[ vi ]; + const Vector *pPositionData = &m_PositionData[ pi ]; + if ( pDelta ) + { + VectorAdd( *pPositionData, pDelta[ pi ].m_vecDeltaPosition, vecMorphPosition ); + pPositionData = &vecMorphPosition; + } + VectorTransform( *pPositionData, *pSkinMatrix, *pPosition ); + + if ( m_bHasNormalData ) + { + int ni = m_NormalIndices[ vi ]; + const Vector *pNormalData = &m_NormalData[ ni ]; + if ( pDelta ) + { + VectorAdd( *pNormalData, pDelta[ni].m_vecDeltaNormal, vecMorphNormal ); + pNormalData = &vecMorphNormal; + } + VectorRotate( *pNormalData, *pSkinMatrix, *pNormal ); + VectorNormalize( *pNormal ); + } + else + { + pNormal->Init( 0.0f, 0.0f, 1.0f ); + } + + if ( m_bHasTangentData ) + { + const Vector4D &tangentData = m_TangentData[ m_TangentIndices[ vi ] ]; + VectorRotate( tangentData.AsVector3D(), *pSkinMatrix, pTangent->AsVector3D() ); + VectorNormalize( pTangent->AsVector3D() ); + pTangent->w = tangentData.w; + } + else + { + pTangent->Init( 1.0f, 0.0f, 0.0f, 1.0f ); + } +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMesh, CDmeMesh ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMesh::OnConstruction() +{ + m_BindBaseState.Init( this, "bindState" ); + m_CurrentBaseState.Init( this, "currentState" ); + m_BaseStates.Init( this, "baseStates", FATTRIB_MUSTCOPY ); + m_DeltaStates.Init( this, "deltaStates", FATTRIB_MUSTCOPY | FATTRIB_HAS_CALLBACK ); + m_FaceSets.Init( this, "faceSets", FATTRIB_MUSTCOPY ); + m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL].Init( this, "deltaStateWeights" ); + m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED].Init( this, "deltaStateWeightsLagged" ); +} + +void CDmeMesh::OnDestruction() +{ + if ( g_pMaterialSystem ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + int nCount = m_hwFaceSets.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !m_hwFaceSets[i].m_bBuilt ) + continue; + + if ( m_hwFaceSets[i].m_pMesh ) + { + pRenderContext->DestroyStaticMesh( m_hwFaceSets[i].m_pMesh ); + } + } + m_hwFaceSets.RemoveAll(); + } + + DeleteAttributeVarElementArray( m_BaseStates ); + DeleteAttributeVarElementArray( m_DeltaStates ); + DeleteAttributeVarElementArray( m_FaceSets ); +} + + +//----------------------------------------------------------------------------- +// Initializes the normal material +//----------------------------------------------------------------------------- +void CDmeMesh::InitializeNormalMaterial() +{ + if ( !s_bNormalMaterialInitialized ) + { + s_bNormalMaterialInitialized = true; + + KeyValues *pVMTKeyValues = new KeyValues( "wireframe" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + pVMTKeyValues->SetInt( "$decal", 1 ); +// pVMTKeyValues->SetInt( "$ignorez", 0 ); + s_NormalMaterial.Init( "__DmeMeshNormalMaterial", pVMTKeyValues ); + + pVMTKeyValues = new KeyValues( "unlitgeneric" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + s_NormalErrorMaterial.Init( "__DmeMeshNormalErrorMaterial", pVMTKeyValues ); + } +} + +//----------------------------------------------------------------------------- +// resolve internal data from changed attributes +//----------------------------------------------------------------------------- +void CDmeMesh::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + BaseClass::OnAttributeChanged( pAttribute ); + + if ( pAttribute == m_DeltaStates.GetAttribute() ) + { + int nDeltaStateCount = m_DeltaStates.Count(); + for ( int i = 0; i < MESH_DELTA_WEIGHT_TYPE_COUNT; ++i ) + { + // Make sure we have the correct number of weights + int nWeightCount = m_DeltaStateWeights[i].Count(); + if ( nWeightCount < nDeltaStateCount ) + { + for ( int j = nWeightCount; j < nDeltaStateCount; ++j ) + { + m_DeltaStateWeights[i].AddToTail( Vector2D( 0.0f, 0.0f ) ); + } + } + else if ( nDeltaStateCount > nWeightCount ) + { + m_DeltaStateWeights[i].RemoveMultiple( nWeightCount, nWeightCount - nDeltaStateCount ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Adds deltas into a delta mesh +//----------------------------------------------------------------------------- +template< class T > bool CDmeMesh::AddVertexDelta( + CDmeVertexData *pBaseState, + void *pVertexData, int nStride, CDmeVertexDataBase::StandardFields_t fieldId, int nIndex, bool bDoLag ) +{ + CDmeVertexDeltaData *pDeltaState = GetDeltaState( nIndex ); + + if ( !pBaseState || !pDeltaState ) + return false; + + const FieldIndex_t nBaseFieldIndex = pBaseState->FindFieldIndex( fieldId == CDmeVertexData::FIELD_WRINKLE ? CDmeVertexData::FIELD_TEXCOORD : fieldId ); + const FieldIndex_t nDeltaFieldIndex = pDeltaState->FindFieldIndex( fieldId ); + if ( nBaseFieldIndex < 0 || nDeltaFieldIndex < 0 ) + return false; + + const CDmrArray<int> indices = pDeltaState->GetIndexData( nDeltaFieldIndex ); + const CDmrArray<T> delta = pDeltaState->GetVertexData( nDeltaFieldIndex ); + const int nDeltaCount = indices.Count(); + + const float flWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][nIndex].x; + + const FieldIndex_t nSpeedFieldIndex = pBaseState->FindFieldIndex( CDmeVertexData::FIELD_MORPH_SPEED ); + + if ( !bDoLag || nSpeedFieldIndex < 0 ) + { + for ( int j = 0; j < nDeltaCount; ++j ) + { + int nDataIndex = indices.Get( j ); + T* pDeltaData = (T*)( (char*)pVertexData + nStride * nDataIndex ); + *pDeltaData += delta.Get( j ) * flWeight; + } + + return true; + } + + const float flLaggedWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][nIndex].x; + + const CDmrArrayConst<int> speedIndices = pBaseState->GetIndexData( nSpeedFieldIndex ); + const CDmrArrayConst<float> speedDelta = pBaseState->GetVertexData( nSpeedFieldIndex ); + for ( int j = 0; j < nDeltaCount; ++j ) + { + int nDataIndex = indices.Get( j ); + const CUtlVector<int> &list = pBaseState->FindVertexIndicesFromDataIndex( nBaseFieldIndex, nDataIndex ); + Assert( list.Count() > 0 ); + // FIXME: Average everything in the list.. shouldn't be necessary though + float flSpeed = speedDelta.Get( speedIndices.Get( list[0] ) ); + float flActualWeight = Lerp( flSpeed, flLaggedWeight, flWeight ); + + T* pDeltaData = (T*)( (char*)pVertexData + nStride * nDataIndex ); + *pDeltaData += delta.Get( j ) * flActualWeight; + } + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::AddTexCoordDelta( RenderVertexDelta_t *pRenderDelta, float flWeight, CDmeVertexDeltaData *pDeltaState ) +{ + FieldIndex_t nFieldIndex = pDeltaState->FindFieldIndex( CDmeVertexDeltaData::FIELD_TEXCOORD ); + if ( nFieldIndex < 0 ) + return; + + bool bIsVCoordinateFlipped = pDeltaState->IsVCoordinateFlipped(); + const CDmrArray<int> indices = pDeltaState->GetIndexData( nFieldIndex ); + const CDmrArray<Vector2D> delta = pDeltaState->GetVertexData( nFieldIndex ); + int nDeltaCount = indices.Count(); + for ( int j = 0; j < nDeltaCount; ++j ) + { + Vector2D uvDelta = delta.Get( j ); + if ( bIsVCoordinateFlipped ) + { + uvDelta.y = -uvDelta.y; + } + Vector2D &vec2D = pRenderDelta[ indices.Get( j ) ].m_vecDeltaUV; + Vector2DMA( vec2D, flWeight, uvDelta, vec2D ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::AddColorDelta( RenderVertexDelta_t *pRenderDelta, float flWeight, CDmeVertexDeltaData *pDeltaState ) +{ + FieldIndex_t nFieldIndex = pDeltaState->FindFieldIndex( CDmeVertexDeltaData::FIELD_COLOR ); + if ( nFieldIndex < 0 ) + return; + + const CDmrArray<int> indices = pDeltaState->GetIndexData( nFieldIndex ); + const CDmrArray<Color> delta = pDeltaState->GetVertexData( nFieldIndex ); + int nDeltaCount = indices.Count(); + for ( int j = 0; j < nDeltaCount; ++j ) + { + const Color &srcDeltaColor = delta[ j ]; + Vector4D &vecDelta = pRenderDelta[ indices[ j ] ].m_vecDeltaColor; + vecDelta[0] += flWeight * srcDeltaColor.r(); + vecDelta[1] += flWeight * srcDeltaColor.g(); + vecDelta[2] += flWeight * srcDeltaColor.b(); + vecDelta[3] += flWeight * srcDeltaColor.a(); + } +} + +template< class T > bool CDmeMesh::AddStereoVertexDelta( + CDmeVertexData *pBaseState, + void *pVertexData, int nStride, CDmeVertexDataBase::StandardFields_t fieldId, int nIndex, bool bDoLag ) +{ + CDmeVertexDeltaData *pDeltaState = GetDeltaState( nIndex ); + if ( !pBaseState || !pDeltaState ) + return false; + + const FieldIndex_t nBaseFieldIndex = pBaseState->FindFieldIndex( fieldId == CDmeVertexData::FIELD_WRINKLE ? CDmeVertexData::FIELD_TEXCOORD : fieldId ); + const FieldIndex_t nDeltaFieldIndex = pDeltaState->FindFieldIndex( fieldId ); + if ( nBaseFieldIndex < 0 || nDeltaFieldIndex < 0 ) + return false; + + float flLeftWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][nIndex].x; + float flRightWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][nIndex].y; + + const CDmrArray<int> indices = pDeltaState->GetIndexData( nDeltaFieldIndex ); + const CDmrArray<T> delta = pDeltaState->GetVertexData( nDeltaFieldIndex ); + const CUtlVector<int>& balanceIndices = pBaseState->GetVertexIndexData( CDmeVertexData::FIELD_BALANCE ); + const CUtlVector<float> &balanceDelta = pBaseState->GetBalanceData(); + const int nDeltaCount = indices.Count(); + + const FieldIndex_t nSpeedFieldIndex = pBaseState->FindFieldIndex( CDmeVertexData::FIELD_MORPH_SPEED ); + + if ( !bDoLag || nSpeedFieldIndex < 0 ) + { + for ( int j = 0; j < nDeltaCount; ++j ) + { + int nDataIndex = indices.Get( j ); + const CUtlVector<int> &list = pBaseState->FindVertexIndicesFromDataIndex( nBaseFieldIndex, nDataIndex ); + Assert( list.Count() > 0 ); + // FIXME: Average everything in the list.. shouldn't be necessary though + float flRightAmount = balanceDelta[ balanceIndices[ list[0] ] ]; + float flWeight = Lerp( flRightAmount, flLeftWeight, flRightWeight ); + + T* pDeltaData = (T*)( (char*)pVertexData + nStride * nDataIndex ); + *pDeltaData += delta.Get( j ) * flWeight; + } + + return true; + } + + float flLeftWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][nIndex].x; + float flRightWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][nIndex].y; + + const CDmrArray<int> pSpeedIndices = pBaseState->GetIndexData( nSpeedFieldIndex ); + const CDmrArray<float> pSpeedDelta = pBaseState->GetVertexData( nSpeedFieldIndex ); + for ( int j = 0; j < nDeltaCount; ++j ) + { + int nDataIndex = indices.Get( j ); + const CUtlVector<int> &list = pBaseState->FindVertexIndicesFromDataIndex( nBaseFieldIndex, nDataIndex ); + Assert( list.Count() > 0 ); + // FIXME: Average everything in the list.. shouldn't be necessary though + float flRightAmount = balanceDelta[ balanceIndices[ list[0] ] ]; + float flWeight = Lerp( flRightAmount, flLeftWeight, flRightWeight ); + float flLaggedWeight = Lerp( flRightAmount, flLeftWeightLagged, flRightWeightLagged ); + float flSpeed = pSpeedDelta.Get( pSpeedIndices.Get( list[0] ) ); + float flActualWeight = Lerp( flSpeed, flLaggedWeight, flWeight ); + + T* pDeltaData = (T*)( (char*)pVertexData + nStride * nDataIndex ); + *pDeltaData += delta.Get( j ) * flActualWeight; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Draws the mesh when it uses too many bones +//----------------------------------------------------------------------------- +bool CDmeMesh::BuildDeltaMesh( int nVertices, RenderVertexDelta_t *pRenderDelta ) +{ + bool bHasWrinkleDelta = false; + + memset( pRenderDelta, 0, nVertices * sizeof( RenderVertexDelta_t ) ); + int nCount = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL].Count(); + Assert( m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL].Count() == m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED].Count() ); + + CDmeVertexData *pBindState = GetBindBaseState(); + + const FieldIndex_t nBalanceFieldIndex = pBindState->FindFieldIndex( CDmeVertexDeltaData::FIELD_BALANCE ); + const FieldIndex_t nSpeedFieldIndex = pBindState->FindFieldIndex( CDmeVertexDeltaData::FIELD_MORPH_SPEED ); + const bool bDoLag = nSpeedFieldIndex >= 0; + if ( nBalanceFieldIndex < 0 ) + { + for ( int i = 0; i < nCount; ++i ) + { + float flWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].x; + float flLaggedWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].x; + if ( flWeight <= 0.0f && flLaggedWeight <= 0.0f ) + continue; + + // prepare vertices + CDmeVertexDeltaData *pDeltaState = GetDeltaState(i); + AddVertexDelta<Vector>( pBindState, &pRenderDelta->m_vecDeltaPosition, sizeof(RenderVertexDelta_t), CDmeVertexDeltaData::FIELD_POSITION, i, bDoLag ); + AddVertexDelta<Vector>( pBindState, &pRenderDelta->m_vecDeltaNormal, sizeof(RenderVertexDelta_t), CDmeVertexDeltaData::FIELD_NORMAL, i, bDoLag ); + AddTexCoordDelta( pRenderDelta, flWeight, pDeltaState ); + AddColorDelta( pRenderDelta, flWeight, pDeltaState ); + bool bWrinkle = AddVertexDelta<float>( pBindState, &pRenderDelta->m_flDeltaWrinkle, sizeof(RenderVertexDelta_t), CDmeVertexDeltaData::FIELD_WRINKLE, i, bDoLag ); + bHasWrinkleDelta = bHasWrinkleDelta || bWrinkle; + } + + return bHasWrinkleDelta; + } + + for ( int i = 0; i < nCount; ++i ) + { + float flLeftWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].x; + float flRightWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].y; + float flLeftWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].x; + float flRightWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].y; + if ( flLeftWeight <= 0.0f && flRightWeight <= 0.0f && flLeftWeightLagged <= 0.0f && flRightWeightLagged <= 0.0f ) + continue; + + // FIXME: Need to make balanced versions of texcoord + color + bool bWrinkle; + CDmeVertexDeltaData *pDeltaState = GetDeltaState(i); + AddStereoVertexDelta<Vector>( pBindState, &pRenderDelta->m_vecDeltaPosition, sizeof(RenderVertexDelta_t), CDmeVertexDeltaData::FIELD_POSITION, i, bDoLag ); + AddStereoVertexDelta<Vector>( pBindState, &pRenderDelta->m_vecDeltaNormal, sizeof(RenderVertexDelta_t), CDmeVertexDeltaData::FIELD_NORMAL, i, bDoLag ); + bWrinkle = AddStereoVertexDelta<float>( pBindState, &pRenderDelta->m_flDeltaWrinkle, sizeof(RenderVertexDelta_t), CDmeVertexDeltaData::FIELD_WRINKLE, i, bDoLag); + bHasWrinkleDelta = bHasWrinkleDelta || bWrinkle; + AddTexCoordDelta( pRenderDelta, flLeftWeight, pDeltaState ); + AddColorDelta( pRenderDelta, flLeftWeight, pDeltaState ); + } + + return bHasWrinkleDelta; +} + + +//----------------------------------------------------------------------------- +// Writes triangulated indices for a face set into a meshbuilder +//----------------------------------------------------------------------------- +void CDmeMesh::WriteTriangluatedIndices( const CDmeVertexData *pBaseState, CDmeFaceSet *pFaceSet, CMeshBuilder &meshBuilder ) +{ + // prepare indices + int nFirstIndex = 0; + int nIndexCount = pFaceSet->NumIndices(); + while ( nFirstIndex < nIndexCount ) + { + int nVertexCount = pFaceSet->GetNextPolygonVertexCount( nFirstIndex ); + if ( nVertexCount >= 3 ) + { + int nOutCount = ( nVertexCount-2 ) * 3; + int *pIndices = (int*)_alloca( nOutCount * sizeof(int) ); + ComputeTriangulatedIndices( pBaseState, pFaceSet, nFirstIndex, pIndices, nOutCount ); + for ( int ii = 0; ii < nOutCount; ++ii ) + { + meshBuilder.FastIndex( pIndices[ii] ); + } + } + nFirstIndex += nVertexCount + 1; + } +} + + +//----------------------------------------------------------------------------- +// Draws the mesh when it uses too many bones +//----------------------------------------------------------------------------- +void CDmeMesh::DrawDynamicMesh( CDmeFaceSet *pFaceSet, matrix3x4_t *pPoseToWorld, bool bHasActiveDeltaStates, CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + CDmeVertexData *pBindBase = GetCurrentBaseState(); + if ( !pBindBase ) + return; + + // NOTE: This is inherently inefficient; we re-skin the *entire* mesh, + // even if it's not being used by the entire model. This is because we can't + // guarantee the various materials from the various face sets use the + // same vertex format (even though they should), and we don't want to + // spend the work to detemine the sub-part of the mesh used by this face set. + + // Compute vertex deltas for rendering + const int nVertices = pBindBase->VertexCount(); + + // NOTE: The Delta Data is actually indexed by the pPositionIndices, pNormalIndices, etc. + // The fact that we're storing one delta per final vertex nVertices + // is a waste of memory and simply implementational convenience. + bool bHasActiveWrinkle = false; + RenderVertexDelta_t *pVertexDelta = (RenderVertexDelta_t*)_alloca( nVertices * sizeof(RenderVertexDelta_t) ); + if ( bHasActiveDeltaStates ) + { + bHasActiveWrinkle = BuildDeltaMesh( nVertices, pVertexDelta ); + } + else + { + pVertexDelta = NULL; + } + + CRenderInfo renderInfo( pBindBase ); + Assert( renderInfo.HasPositionData() ); + + // prepare vertices + FieldIndex_t uvField = pBindBase->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + FieldIndex_t colorField = pBindBase->FindFieldIndex( CDmeVertexData::FIELD_COLOR ); + + bool bHasTexCoords = ( uvField >= 0 ); + bool bHasColors = ( colorField >= 0 ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + + const CDmrArrayConst<int> pUVIndices = bHasTexCoords ? pBindBase->GetIndexData( uvField ) : NULL; + + if ( bHasActiveWrinkle && bHasTexCoords ) + { + // Create the wrinkle flex mesh + IMesh *pFlexDelta = pRenderContext->GetFlexMesh(); + int nFlexVertexOffset = 0; + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pFlexDelta, MATERIAL_HETEROGENOUS, nVertices, 0, &nFlexVertexOffset ); + for ( int j=0; j < nVertices; j++ ) + { + // NOTE: The UV indices are also used to index into wrinkle data + int nUVIndex = pUVIndices.Get( j ); + meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.NormalDelta3f( 0.0f, 0.0f, 0.0f ); + meshBuilder.Wrinkle1f( pVertexDelta[nUVIndex].m_flDeltaWrinkle ); + meshBuilder.AdvanceVertex(); + } + meshBuilder.End( false, false ); + pMesh->SetFlexMesh( pFlexDelta, nFlexVertexOffset ); + } + + // build the mesh + int nIndices = pFaceSet->GetTriangulatedIndexCount(); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertices, nIndices ); + + const CDmrArrayConst<Vector2D> pUVData = bHasTexCoords ? pBindBase->GetVertexData( uvField ) : NULL; + const CDmrArrayConst<int> pColorIndices = bHasColors ? pBindBase->GetIndexData( colorField ) : NULL; + const CDmrArrayConst<Color> pColorData = bHasColors ? pBindBase->GetVertexData( colorField ) : NULL; + + Vector vecPosition, vecNormal; + Vector4D vecTangent; + for ( int vi = 0; vi < nVertices; ++vi ) + { + renderInfo.ComputeVertex( vi, pPoseToWorld, pVertexDelta, &vecPosition, &vecNormal, &vecTangent ); + meshBuilder.Position3fv( vecPosition.Base() ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.UserData( vecTangent.Base() ); + + if ( pUVData.IsValid() ) + { + int uvi = pUVIndices.Get( vi ); + Vector2D uv = pUVData.Get( uvi ); + if ( pBindBase->IsVCoordinateFlipped() ) + { + uv.y = 1.0f - uv.y; + } + + if ( bHasActiveDeltaStates ) + { + uv += pVertexDelta[uvi].m_vecDeltaUV; + } + meshBuilder.TexCoord2fv( 0, uv.Base() ); + } + else + { + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + } + + if ( pColorIndices.IsValid() ) + { + int ci = pColorIndices.Get( vi ); + int color = pColorData.Get( ci ).GetRawColor(); + meshBuilder.Color4ubv( (unsigned char*)&color ); + } + else + { + meshBuilder.Color4ub( 255, 255, 255, 255 ); + } + + meshBuilder.AdvanceVertex(); + } + + WriteTriangluatedIndices( pBindBase, pFaceSet, meshBuilder ); + + meshBuilder.End(); + + pMesh->Draw(); + + if ( pDrawSettings && pDrawSettings->GetNormals() ) + { + RenderNormals( pPoseToWorld, bHasActiveDeltaStates ? pVertexDelta : NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Renders normals +//----------------------------------------------------------------------------- +#define NORMAL_LINE_SIZE 0.25f + +void CDmeMesh::RenderNormals( matrix3x4_t *pPoseToWorld, RenderVertexDelta_t *pDelta ) +{ + CDmeVertexData *pBind = GetBindBaseState(); + if ( !pBind ) + return; + + CRenderInfo renderInfo( pBind ); + + Assert( renderInfo.HasPositionData() ); + if ( !renderInfo.HasNormalData() ) + return; + bool bHasTangents = renderInfo.HasTangentData(); + + // build the mesh + InitializeNormalMaterial(); + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( s_NormalMaterial ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + + int nMaxIndices, nMaxVertices; + pRenderContext->GetMaxToRender( pMesh, false, &nMaxVertices, &nMaxIndices ); + int nFirstVertex = 0; + int nVerticesRemaining = pBind->VertexCount();; + int nFactor = bHasTangents ? 6 : 2; + + while ( nVerticesRemaining > 0 ) + { + int nVertices = nVerticesRemaining; + if ( nVertices > nMaxVertices / nFactor ) + { + nVertices = nMaxVertices / nFactor; + } + if ( nVertices > nMaxIndices / nFactor ) + { + nVertices = nMaxIndices / nFactor; + } + nVerticesRemaining -= nVertices; + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, bHasTangents ? nVertices * 3 : nVertices ); + + Vector vecPosition, vecNormal, vecEndPoint, vecTangentS, vecTangentT; + Vector4D vecTangent; + for ( int vi = nFirstVertex; vi < nVertices; ++vi ) + { + renderInfo.ComputeVertex( vi, pPoseToWorld, pDelta, &vecPosition, &vecNormal, &vecTangent ); + + meshBuilder.Position3fv( vecPosition.Base() ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecPosition, NORMAL_LINE_SIZE, vecNormal, vecEndPoint ); + meshBuilder.Position3fv( vecEndPoint.Base() ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + continue; + + if ( !bHasTangents ) + continue; + + CrossProduct( vecNormal, vecTangent.AsVector3D(), vecTangentT ); + VectorNormalize( vecTangentT ); + // NOTE: This is the new, desired tangentS morphing behavior + // CrossProduct( vecTangentT, vecNormal, vecTangentS ); + VectorCopy( vecTangent.AsVector3D(), vecTangentS ); + vecTangentT *= vecTangent.w; + + meshBuilder.Position3fv( vecPosition.Base() ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecPosition, NORMAL_LINE_SIZE, vecTangentS, vecEndPoint ); + meshBuilder.Position3fv( vecEndPoint.Base() ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( vecPosition.Base() ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( vecPosition, NORMAL_LINE_SIZE, vecTangentT, vecEndPoint ); + meshBuilder.Position3fv( vecEndPoint.Base() ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(); + pMesh->Draw(); + + nFirstVertex += nVertices; + } +} + + +//----------------------------------------------------------------------------- +// Draws the passed DmeFaceSet in wireframe mode +//----------------------------------------------------------------------------- +void CDmeMesh::DrawWireframeFaceSet( CDmeFaceSet *pFaceSet, matrix3x4_t *pPoseToWorld, bool bHasActiveDeltaStates, CDmeDrawSettings *pDrawSettings ) +{ + CDmeVertexData *pBind = GetBindBaseState(); + if ( !pBind ) + return; + + const FieldIndex_t posField = pBind->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + if ( posField < 0 ) + return; + + const CUtlVector< Vector > &posData( CDmrArrayConst< Vector >( pBind->GetVertexData( posField ) ).Get() ); + const int nVertices = posData.Count(); + + const CUtlVector< int > &posIndices( CDmrArrayConst< int >( pBind->GetIndexData( posField ) ).Get() ); + + Vector *pDeltaVertices = bHasActiveDeltaStates ? pDeltaVertices = reinterpret_cast< Vector * >( alloca( nVertices * sizeof( Vector ) ) ) : NULL; + + if ( bHasActiveDeltaStates ) + { + memset( pDeltaVertices, 0, sizeof( Vector ) * nVertices ); + const int nCount = m_DeltaStateWeights[ MESH_DELTA_WEIGHT_NORMAL ].Count(); + + const FieldIndex_t nBalanceFieldIndex = pBind->FindFieldIndex( CDmeVertexDeltaData::FIELD_BALANCE ); + const FieldIndex_t nSpeedFieldIndex = pBind->FindFieldIndex( CDmeVertexDeltaData::FIELD_MORPH_SPEED ); + const bool bDoLag = ( nSpeedFieldIndex >= 0 ); + + if ( nBalanceFieldIndex < 0 ) + { + for ( int i = 0; i < nCount; ++i ) + { + float flWeight = m_DeltaStateWeights[ MESH_DELTA_WEIGHT_NORMAL ][ i ].x; + float flLaggedWeight = m_DeltaStateWeights[ MESH_DELTA_WEIGHT_LAGGED ][ i ].x; + if ( flWeight <= 0.0f && ( !bDoLag || flLaggedWeight <= 0.0f ) ) + continue; + + AddVertexDelta< Vector >( pBind, pDeltaVertices, sizeof( Vector ), CDmeVertexDeltaData::FIELD_POSITION, i, bDoLag ); + } + } + else + { + for ( int i = 0; i < nCount; ++i ) + { + float flLeftWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].x; + float flRightWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].y; + float flLeftWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].x; + float flRightWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].y; + if ( flLeftWeight <= 0.0f && flRightWeight <= 0.0f && ( !bDoLag || ( flLeftWeightLagged <= 0.0f && flRightWeightLagged <= 0.0f ) ) ) + continue; + + AddStereoVertexDelta< Vector >( pBind, pDeltaVertices, sizeof( Vector ), CDmeVertexDeltaData::FIELD_POSITION, i, bDoLag ); + } + } + } + + Vector *pVertices = reinterpret_cast< Vector * >( alloca( nVertices * sizeof( Vector ) ) ); + + CRenderInfo renderInfo( pBind ); + Assert( renderInfo.HasPositionData() ); + + for ( int pi = 0; pi < nVertices; ++pi ) + { + renderInfo.ComputePosition( pi, pPoseToWorld, pDeltaVertices, pVertices ); + } + + InitializeNormalMaterial(); + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( s_NormalMaterial ); + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + + // build the mesh + CMeshBuilder meshBuilder; + + // Draw the polygons in the face set + const int nFaceSetIndices = pFaceSet->NumIndices(); + const int *pFaceSetIndices = pFaceSet->GetIndices(); + + int vR = 0; + int vG = 0; + int vB = 0; + + if ( pDrawSettings ) + { + const Color &vColor = pDrawSettings->GetColor(); + vR = vColor.r(); + vG = vColor.g(); + vB = vColor.b(); + } + + int nFaceIndices; + for ( int i = 0; i < nFaceSetIndices; ) + { + nFaceIndices = pFaceSet->GetNextPolygonVertexCount( i ); + meshBuilder.Begin( pMesh, MATERIAL_LINES, nFaceIndices ); + + for ( int j = 0; j < nFaceIndices; ++j ) + { + Assert( i < nFaceSetIndices ); + + int vIndex0 = posIndices[ pFaceSetIndices[ i + j ] ]; + Assert( vIndex0 < nVertices ); + meshBuilder.Position3fv( reinterpret_cast< float * >( pVertices + vIndex0 ) ); + meshBuilder.Color3ub( vR, vG, vB ); + meshBuilder.AdvanceVertex(); + + int vIndex1 = posIndices[ pFaceSetIndices[ i + ( ( j + 1 ) % nFaceIndices ) ] ]; + Assert( vIndex1 < nVertices ); + meshBuilder.Position3fv( reinterpret_cast< float * >( pVertices + vIndex1) ); + meshBuilder.Color3ub( vR, vG, vB ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(); + + i += nFaceIndices + 1; + } + + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Do we have active delta state data? +//----------------------------------------------------------------------------- +bool CDmeMesh::HasActiveDeltaStates() const +{ + for ( int t = 0; t < MESH_DELTA_WEIGHT_TYPE_COUNT; ++t ) + { + int nCount = m_DeltaStateWeights[t].Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_DeltaStateWeights[t][i].x != 0.0f || m_DeltaStateWeights[t][i].y != 0.0f ) + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Draws the mesh +//----------------------------------------------------------------------------- +void CDmeMesh::Draw( const matrix3x4_t &shapeToWorld, CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + const CDmeVertexData *pBind = GetBindBaseState(); + + if ( !pBind || !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + const bool bHasActiveDeltaStates = HasActiveDeltaStates(); + const bool bDrawNormals = pDrawSettings ? pDrawSettings->GetNormals() : false; + const CDmeDrawSettings::DrawType_t drawType = pDrawSettings ? pDrawSettings->GetDrawType() : CDmeDrawSettings::DRAW_SMOOTH; + + const bool bShaded = ( drawType == CDmeDrawSettings::DRAW_SMOOTH || drawType == CDmeDrawSettings::DRAW_FLAT ); + const bool bWireframe = ( drawType == CDmeDrawSettings::DRAW_WIREFRAME ); +// const bool bBoundingBox = ( drawType == CDmeDrawSettings::DRAW_BOUNDINGBOX ); + + const bool bSoftwareSkinning = bHasActiveDeltaStates | bDrawNormals | bWireframe; + + matrix3x4_t *pPoseToWorld = CDmeModel::SetupModelRenderState( shapeToWorld, pBind->HasSkinningData(), bSoftwareSkinning ); + + pRenderContext->SetNumBoneWeights( pPoseToWorld ? 0 : pBind->JointCount() ); + + int nFaceSets = FaceSetCount(); + m_hwFaceSets.EnsureCount( nFaceSets ); + + const bool bindMaterial = pDrawSettings ? !pDrawSettings->IsAMaterialBound() : true; + + for ( int fi = 0; fi < nFaceSets; ++fi ) + { + CDmeFaceSet *pFaceSet = GetFaceSet( fi ); + + if ( bWireframe ) + { + DrawWireframeFaceSet( pFaceSet, pPoseToWorld, bHasActiveDeltaStates, pDrawSettings ); + continue; + } + + if ( bindMaterial ) + { + pRenderContext->Bind( pFaceSet->GetMaterial()->GetCachedMTL() ); + } + + if ( pPoseToWorld || ( bShaded && bSoftwareSkinning ) ) + { + DrawDynamicMesh( pFaceSet, pPoseToWorld, bHasActiveDeltaStates, pDrawSettings ); + continue; + } + + // TODO: figure out how to tell the mesh when the faceset's indices change + if ( !m_hwFaceSets[fi].m_bBuilt ) + { + m_hwFaceSets[fi].m_pMesh = CreateHwMesh( pFaceSet ); + m_hwFaceSets[fi].m_bBuilt = true; + } + + if ( m_hwFaceSets[fi].m_pMesh ) + { + m_hwFaceSets[fi].m_pMesh->Draw(); + } + } + + pRenderContext->SetNumBoneWeights( 0 ); + CDmeModel::CleanupModelRenderState(); +} + + +//----------------------------------------------------------------------------- +// Face sets +//----------------------------------------------------------------------------- +int CDmeMesh::FaceSetCount() const +{ + return m_FaceSets.Count(); +} + +CDmeFaceSet *CDmeMesh::GetFaceSet( int faceSetIndex ) +{ + return m_FaceSets[ faceSetIndex ]; +} + +const CDmeFaceSet *CDmeMesh::GetFaceSet( int faceSetIndex ) const +{ + return m_FaceSets[ faceSetIndex ]; +} + +void CDmeMesh::AddFaceSet( CDmeFaceSet *faceSet ) +{ + m_FaceSets.AddToTail( faceSet ); +} + +void CDmeMesh::RemoveFaceSet( int faceSetIndex ) +{ + m_FaceSets.Remove( faceSetIndex ); +} + + +//----------------------------------------------------------------------------- +// Find a base state by name +//----------------------------------------------------------------------------- +CDmeVertexData *CDmeMesh::FindBaseState( const char *pStateName ) const +{ + const int nBaseStateCount = BaseStateCount(); + for ( int i = 0; i < nBaseStateCount; ++i ) + { + CDmeVertexData *pBaseState = GetBaseState( i ); + if ( !Q_stricmp( pStateName, pBaseState->GetName() ) ) + return pBaseState; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Find a base state by name, add a new one if not found +//----------------------------------------------------------------------------- +CDmeVertexData *CDmeMesh::FindOrCreateBaseState( const char *pStateName ) +{ + CDmeVertexData *pBaseState = FindBaseState( pStateName ); + if ( pBaseState ) + return pBaseState; + + pBaseState = CreateElement< CDmeVertexData >( pStateName, GetFileId() ); + m_BaseStates.AddToTail( pBaseState ); + + return pBaseState; +} + + +//----------------------------------------------------------------------------- +// Remove a base state by name +//----------------------------------------------------------------------------- +bool CDmeMesh::DeleteBaseState( const char *pStateName ) +{ + const int nBaseStateCount = BaseStateCount(); + for ( int i = 0; i < nBaseStateCount; ++i ) + { + const CDmeVertexData *pBaseState = GetBaseState( i ); + if ( !Q_stricmp( pStateName, pBaseState->GetName() ) ) + { + m_BaseStates.Remove( i ); + g_pDataModel->DestroyElement( pBaseState->GetHandle() ); + + // TODO: Fix up all dependent states + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Selects a particular base state to be current state +//----------------------------------------------------------------------------- +void CDmeMesh::SetCurrentBaseState( const char *pStateName ) +{ + m_CurrentBaseState = FindBaseState( pStateName ); +} + + +//----------------------------------------------------------------------------- +// Selects a particular base state to be current state +//----------------------------------------------------------------------------- +CDmeVertexData *CDmeMesh::GetCurrentBaseState() +{ + return m_CurrentBaseState; +} + + +//----------------------------------------------------------------------------- +// Selects a particular base state to be current state +//----------------------------------------------------------------------------- +const CDmeVertexData *CDmeMesh::GetCurrentBaseState() const +{ + return m_CurrentBaseState; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeMesh::SetBindBaseState( CDmeVertexData *pBaseState ) +{ + if ( !pBaseState ) + return false; + + CDmeVertexData *pCheckState = FindBaseState( pBaseState->GetName() ); + if ( pCheckState != pBaseState ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeVertexData *CDmeMesh::GetBindBaseState() +{ + if ( m_BindBaseState.GetElement() ) + return m_BindBaseState; + + // Backwards compatibility + return FindBaseState( "bind" ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const CDmeVertexData *CDmeMesh::GetBindBaseState() const +{ + if ( m_BindBaseState.GetElement() ) + return m_BindBaseState; + + // Backwards compatibility + return FindBaseState( "bind" ); +} + + +//----------------------------------------------------------------------------- +// Delta states +//----------------------------------------------------------------------------- +int CDmeMesh::DeltaStateCount() const +{ + return m_DeltaStates.Count(); +} + + +//----------------------------------------------------------------------------- +// Returns the delta +//----------------------------------------------------------------------------- +CDmeVertexDeltaData *CDmeMesh::GetDeltaState( int nDeltaIndex ) const +{ + if ( nDeltaIndex < 0 || nDeltaIndex >= m_DeltaStates.Count() ) + return NULL; + + return m_DeltaStates[ nDeltaIndex ]; +} + + +//----------------------------------------------------------------------------- +// Finds a delta state by name. If it isn't found, return NULL +//----------------------------------------------------------------------------- +CDmeVertexDeltaData *CDmeMesh::FindDeltaState( const char *pDeltaName ) const +{ + return GetDeltaState( FindDeltaStateIndex( pDeltaName ) ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int SortDeltaNameFunc( const void *a, const void *b ) +{ + return Q_strcmp( *( const char ** )( a ), *( const char ** )( b ) ); +} + + +//----------------------------------------------------------------------------- +// If the name doe +//----------------------------------------------------------------------------- +const char *SortDeltaName( const char *pInDeltaName, char *pOutDeltaName, int nOutDeltaNameBufLen ) +{ + if ( !pInDeltaName || !strchr( pInDeltaName, '_' ) ) + return pInDeltaName; + + char **ppDeltaNames = reinterpret_cast< char ** >( stackalloc( nOutDeltaNameBufLen * sizeof( char * ) ) ); + memset( ppDeltaNames, 0, nOutDeltaNameBufLen * sizeof( char * ) ); + + const char *pStart = pInDeltaName; + int nDimensionCount = 0; + while ( pStart ) + { + const char *pUnderBar = strchr( pStart, '_' ); + const int nControlNameBufLen = ( pUnderBar ? pUnderBar - pStart : Q_strlen( pStart ) ) + 1; + + if ( nControlNameBufLen ) + { + ppDeltaNames[ nDimensionCount ] = reinterpret_cast< char * >( stackalloc( nControlNameBufLen * sizeof( char ) ) ); + Q_strncpy( ppDeltaNames[ nDimensionCount ], pStart, nControlNameBufLen ); + ++nDimensionCount; + } + + pStart = pUnderBar; + if ( pStart ) + { + ++pStart; + } + } + + // This should only happen if the input name is all _'s + if ( nDimensionCount <= 0 ) + return pInDeltaName; + + qsort( ppDeltaNames, nDimensionCount, sizeof( char * ), SortDeltaNameFunc ); + + char *pDst = pOutDeltaName; + for ( int i = 0; i < nDimensionCount; ++i ) + { + if ( i != 0 ) + { + Q_strncpy( pDst, "_", nOutDeltaNameBufLen ); + ++pDst; + --nOutDeltaNameBufLen; + } + + const int nControlNameLen = Q_strlen( ppDeltaNames[ i ] ); + Q_strncpy( pDst, ppDeltaNames[ i ], nOutDeltaNameBufLen ); + pDst += nControlNameLen; + nOutDeltaNameBufLen -= nControlNameLen; + } + + return pOutDeltaName; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeVertexDeltaData *CDmeMesh::FindOrCreateDeltaState( const char *pInDeltaName ) +{ + CDmeVertexDeltaData *pDeltaState = FindDeltaState( pInDeltaName ); + if ( pDeltaState ) + return pDeltaState; + + const int nDeltaNameBufLen = Q_strlen( pInDeltaName ) + 1; + char *pDeltaNameBuf = reinterpret_cast< char * >( stackalloc( nDeltaNameBufLen * sizeof( char ) ) ); + const char *pDeltaName = SortDeltaName( pInDeltaName, pDeltaNameBuf, nDeltaNameBufLen ); + + pDeltaState = CreateElement< CDmeVertexDeltaData >( pDeltaName, GetFileId() ); + if ( pDeltaState ) + { + m_DeltaStates.AddToTail( pDeltaState ); + } + + return pDeltaState; +} + + +//----------------------------------------------------------------------------- +// Finds a delta state index by comparing names, if it can't be found +// searches for all permutations of the delta name +//----------------------------------------------------------------------------- +int CDmeMesh::FindDeltaStateIndex( const char *pInDeltaName ) const +{ + const char *pDeltaName = pInDeltaName; + + if ( strchr( pInDeltaName, '_' ) ) + { + const int nDeltaNameBufLen = Q_strlen( pInDeltaName ) + 1; + char *pDeltaNameBuf = reinterpret_cast< char * >( stackalloc( nDeltaNameBufLen * sizeof( char ) ) ); + pDeltaName = SortDeltaName( pInDeltaName, pDeltaNameBuf, nDeltaNameBufLen ); + } + + int dn = DeltaStateCount(); + for ( int di = 0; di < dn; ++di ) + { + CDmeVertexDeltaData *pDeltaState = GetDeltaState( di ); + if ( !Q_stricmp( pDeltaName, pDeltaState->GetName() ) ) + return di; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::SetDeltaStateWeight( int nDeltaIndex, MeshDeltaWeightType_t type, float flMorphWeight ) +{ + if ( nDeltaIndex < m_DeltaStateWeights[type].Count() ) + { + m_DeltaStateWeights[type].Set( nDeltaIndex, Vector2D( flMorphWeight, flMorphWeight ) ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::SetDeltaStateWeight( int nDeltaIndex, MeshDeltaWeightType_t type, float flLeftWeight, float flRightWeight ) +{ + if ( nDeltaIndex < m_DeltaStateWeights[type].Count() ) + { + m_DeltaStateWeights[type].Set( nDeltaIndex, Vector2D( flLeftWeight, flRightWeight ) ); + } +} + +//----------------------------------------------------------------------------- +// Determines the appropriate vertex format for hardware meshes +//----------------------------------------------------------------------------- +VertexFormat_t CDmeMesh::ComputeHwMeshVertexFormat( void ) +{ + bool bIsDX7 = !g_pMaterialSystemHardwareConfig->SupportsVertexAndPixelShaders(); + VertexFormat_t vertexFormat = VERTEX_POSITION | VERTEX_COLOR | VERTEX_NORMAL | VERTEX_TEXCOORD_SIZE(0,2) | VERTEX_BONEWEIGHT(2) | VERTEX_BONE_INDEX + | ( bIsDX7 ? 0 : VERTEX_USERDATA_SIZE(4) ); + + // FIXME: set VERTEX_FORMAT_COMPRESSED if there are no artifacts and if it saves enough memory (use 'mem_dumpvballocs') + // vertexFormat |= VERTEX_FORMAT_COMPRESSED; + // FIXME: check for and strip unused vertex elements (see 'bHasNormals', etc, in CreateHwMesh below) + + return vertexFormat; +} + +//----------------------------------------------------------------------------- +// Builds a hardware mesh +//----------------------------------------------------------------------------- +IMesh *CDmeMesh::CreateHwMesh( CDmeFaceSet *pFaceSet ) +{ + const CDmeVertexData *pBind = GetBindBaseState(); + if ( !pBind ) + return NULL; + + // NOTE: This is memory inefficient. We create a copy of all vertices + // for each face set, even if those vertices aren't used by the face set + // Mostly chose to do this for code simplicity, although it also is faster to generate meshes + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + VertexFormat_t vertexFormat = ComputeHwMeshVertexFormat( ); + IMesh *pMesh = pRenderContext->CreateStaticMesh( vertexFormat, "dmemesh" ); + + CMeshBuilder meshBuilder; + + // prepare vertices + FieldIndex_t posField = pBind->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + FieldIndex_t normalField = pBind->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + FieldIndex_t tangentField = pBind->FindFieldIndex( CDmeVertexData::FIELD_TANGENT ); + FieldIndex_t uvField = pBind->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + FieldIndex_t colorField = pBind->FindFieldIndex( CDmeVertexData::FIELD_COLOR ); + + Assert( posField >= 0 ); + bool bHasNormals = ( normalField >= 0 ); + bool bHasTangent = ( tangentField >= 0 ); + bool bHasTexCoords = ( uvField >= 0 ); + bool bHasColors = ( colorField >= 0 ); + + // build the mesh + int nIndices = pFaceSet->GetTriangulatedIndexCount(); + int nVertices = pBind->VertexCount(); + int nJointCount = pBind->JointCount(); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertices, nIndices ); + + const CDmrArrayConst<int> pPositionIndices = pBind->GetIndexData( posField ); + const CDmrArrayConst<Vector> pPositionData = pBind->GetVertexData( posField ); + const CDmrArrayConst<int> pNormalIndices = bHasNormals ? pBind->GetIndexData( normalField ) : NULL; + const CDmrArrayConst<Vector> pNormalData = bHasNormals ? pBind->GetVertexData( normalField ) : NULL; + const CDmrArrayConst<int> pTangentIndices = bHasTangent ? pBind->GetIndexData( tangentField ) : NULL; + const CDmrArrayConst<Vector4D> pTangentData = bHasTangent ? pBind->GetVertexData( tangentField ) : NULL; + const CDmrArrayConst<int> pUVIndices = bHasTexCoords ? pBind->GetIndexData( uvField ) : NULL; + const CDmrArrayConst<Vector2D> pUVData = bHasTexCoords ? pBind->GetVertexData( uvField ) : NULL; + const CDmrArrayConst<int> pColorIndices = bHasColors ? pBind->GetIndexData( colorField ) : NULL; + const CDmrArrayConst<Color> pColorData = bHasColors ? pBind->GetVertexData( colorField ) : NULL; + + Vector4D defaultTangentS( 1.0f, 0.0f, 0.0f, 1.0f ); + for ( int vi = 0; vi < nVertices; ++vi ) + { + meshBuilder.Position3fv( pPositionData.Get( pPositionIndices.Get( vi ) ).Base() ); + if ( pNormalData.IsValid() ) + { + meshBuilder.Normal3fv( pNormalData.Get( pNormalIndices.Get( vi ) ).Base() ); + } + else + { + meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); + } + if ( pTangentData.IsValid() ) + { + meshBuilder.UserData( pTangentData.Get( pTangentIndices.Get( vi ) ).Base() ); + } + else + { + meshBuilder.UserData( defaultTangentS.Base() ); + } + if ( pUVData.IsValid() ) + { + const Vector2D &uv = pUVData.Get( pUVIndices.Get( vi ) ); + if ( !pBind->IsVCoordinateFlipped() ) + { + meshBuilder.TexCoord2fv( 0, uv.Base() ); + } + else + { + meshBuilder.TexCoord2f( 0, uv.x, 1.0f - uv.y ); + } + } + else + { + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + } + if ( pColorIndices.IsValid() ) + { + int color = pColorData.Get( pColorIndices.Get( vi ) ).GetRawColor(); + meshBuilder.Color4ubv( (unsigned char*)&color ); + } + else + { + meshBuilder.Color4ub( 255, 255, 255, 255 ); + } + + // FIXME: Note that this will break once we exceeed the max joint count + // that the hardware can handle + const float *pJointWeight = pBind->GetJointWeights( vi ); + const int *pJointIndices = pBind->GetJointIndices( vi ); + for ( int i = 0; i < nJointCount; ++i ) + { + meshBuilder.BoneWeight( i, pJointWeight[i] ); + meshBuilder.BoneMatrix( i, pJointIndices[i] ); + } + + for ( int i = nJointCount; i < 4; ++i ) + { + meshBuilder.BoneWeight( i, ( i == 0 ) ? 1.0f : 0.0f ); + meshBuilder.BoneMatrix( i, 0 ); + } + + meshBuilder.AdvanceVertex(); + } + + // prepare indices + int nFirstIndex = 0; + int nIndexCount = pFaceSet->NumIndices(); + while ( nFirstIndex < nIndexCount ) + { + int nVertexCount = pFaceSet->GetNextPolygonVertexCount( nFirstIndex ); + if ( nVertexCount >= 3 ) + { + int nOutCount = ( nVertexCount-2 ) * 3; + int *pIndices = (int*)_alloca( nOutCount * sizeof(int) ); + ComputeTriangulatedIndices( pBind, pFaceSet, nFirstIndex, pIndices, nOutCount ); + for ( int ii = 0; ii < nOutCount; ++ii ) + { + meshBuilder.FastIndex( pIndices[ii] ); + } + } + nFirstIndex += nVertexCount + 1; + } + + meshBuilder.End(); + + return pMesh; +} + + +//----------------------------------------------------------------------------- +// Compute triangulated indices +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeTriangulatedIndices( const CDmeVertexData *pBaseState, CDmeFaceSet *pFaceSet, int nFirstIndex, int *pIndices, int nOutCount ) +{ + // FIXME: Come up with a more efficient way of computing this + // This involves a bunch of recomputation of distances + float flMinDistance = FLT_MAX; + int nMinIndex = 0; + int nVertexCount = pFaceSet->GetNextPolygonVertexCount( nFirstIndex ); + + // Optimization for quads + triangles.. it's totally symmetric + int nLoopCount = nVertexCount; + if ( nVertexCount <= 3 ) + { + nLoopCount = 0; + } + else if ( nVertexCount == 4 ) + { + nLoopCount = 2; + } + + for ( int i = 0; i < nLoopCount; ++i ) + { + float flDistance = 0.0f; + const Vector &vecCenter = pBaseState->GetPosition( pFaceSet->GetIndex( nFirstIndex+i ) ); + for ( int j = 2; j < nVertexCount-1; ++j ) + { + int vi = ( i + j ) % nVertexCount; + const Vector &vecEdge = pBaseState->GetPosition( pFaceSet->GetIndex( nFirstIndex+vi ) ); + flDistance += vecEdge.DistTo( vecCenter ); + } + + if ( flDistance < flMinDistance ) + { + nMinIndex = i; + flMinDistance = flDistance; + } + } + + // Compute the triangulation indices + Assert( nOutCount == ( nVertexCount - 2 ) * 3 ); + int nOutIndex = 0; + for ( int i = 1; i < nVertexCount - 1; ++i ) + { + pIndices[nOutIndex++] = pFaceSet->GetIndex( nFirstIndex + nMinIndex ); + pIndices[nOutIndex++] = pFaceSet->GetIndex( nFirstIndex + ((nMinIndex + i) % nVertexCount) ); + pIndices[nOutIndex++] = pFaceSet->GetIndex( nFirstIndex + ((nMinIndex + i + 1) % nVertexCount) ); + } +} + + +//----------------------------------------------------------------------------- +// Build a map from vertex index to a list of triangles that share the vert. +//----------------------------------------------------------------------------- +void CDmeMesh::BuildTriangleMap( const CDmeVertexData *pBaseState, CDmeFaceSet* pFaceSet, CUtlVector<Triangle_t>& triangles, CUtlVector< CUtlVector<int> >* pVertToTriMap ) +{ + // prepare indices + int nFirstIndex = 0; + int nIndexCount = pFaceSet->NumIndices(); + while ( nFirstIndex < nIndexCount ) + { + int nVertexCount = pFaceSet->GetNextPolygonVertexCount( nFirstIndex ); + if ( nVertexCount >= 3 ) + { + int nOutCount = ( nVertexCount-2 ) * 3; + int *pIndices = (int*)_alloca( nOutCount * sizeof(int) ); + ComputeTriangulatedIndices( pBaseState, pFaceSet, nFirstIndex, pIndices, nOutCount ); + for ( int ii = 0; ii < nOutCount; ii += 3 ) + { + int t = triangles.AddToTail(); + Triangle_t& triangle = triangles[t]; + + triangle.m_nIndex[0] = pIndices[ii]; + triangle.m_nIndex[1] = pIndices[ii+1]; + triangle.m_nIndex[2] = pIndices[ii+2]; + + if ( pVertToTriMap ) + { + (*pVertToTriMap)[ pIndices[ii] ].AddToTail( t ); + (*pVertToTriMap)[ pIndices[ii+1] ].AddToTail( t ); + (*pVertToTriMap)[ pIndices[ii+2] ].AddToTail( t ); + } + } + } + nFirstIndex += nVertexCount + 1; + } +} + + +//----------------------------------------------------------------------------- +// Computes tangent space data for triangles +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeTriangleTangets( const CDmeVertexData *pVertexData, CUtlVector<Triangle_t>& triangles ) +{ + // Calculate the tangent space for each triangle. + int nTriangleCount = triangles.Count(); + for ( int triID = 0; triID < nTriangleCount; triID++ ) + { + Triangle_t &triangle = triangles[triID]; + + const Vector &p0 = pVertexData->GetPosition( triangle.m_nIndex[0] ); + const Vector &p1 = pVertexData->GetPosition( triangle.m_nIndex[1] ); + const Vector &p2 = pVertexData->GetPosition( triangle.m_nIndex[2] ); + const Vector2D &t0 = pVertexData->GetTexCoord( triangle.m_nIndex[0] ); + const Vector2D &t1 = pVertexData->GetTexCoord( triangle.m_nIndex[1] ); + const Vector2D &t2 = pVertexData->GetTexCoord( triangle.m_nIndex[2] ); + CalcTriangleTangentSpace( p0, p1, p2, t0, t1, t2, triangle.m_vecTangentS, triangle.m_vecTangentT ); + } +} + + +//----------------------------------------------------------------------------- +// Build a map from vertex index to a list of triangles that share the vert. +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeAverageTangent( CDmeVertexData *pVertexData, bool bSmoothTangents, CUtlVector< CUtlVector<int> >& vertToTriMap, CUtlVector<Triangle_t>& triangles ) +{ + FieldIndex_t posField = pVertexData->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + FieldIndex_t normalField = pVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + FieldIndex_t tangentField = pVertexData->FindFieldIndex( CDmeVertexData::FIELD_TANGENT ); + + const CDmrArray<int> pPositionIndices = pVertexData->GetIndexData( posField ); + const CDmrArray<Vector> pPositionData = pVertexData->GetVertexData( posField ); + const CDmrArray<int> pNormalIndices = pVertexData->GetIndexData( normalField ); + const CDmrArray<Vector> pNormalData = pVertexData->GetVertexData( normalField ); + + // calculate an average tangent space for each vertex. + int nVertexCount = pVertexData->VertexCount(); + Vector4D finalSVect; + for( int vertID = 0; vertID < nVertexCount; vertID++ ) + { + CUtlVector<int> &triangleList = vertToTriMap[vertID]; + + Vector sVect, tVect; + sVect.Init( 0.0f, 0.0f, 0.0f ); + tVect.Init( 0.0f, 0.0f, 0.0f ); + int nTriangleCount = triangleList.Count(); + for ( int triID = 0; triID < nTriangleCount; triID++ ) + { + Triangle_t &tri = triangles[ triangleList[triID] ]; + sVect += tri.m_vecTangentS; + tVect += tri.m_vecTangentT; + } + + // In the case of zbrush, everything needs to be treated as smooth. + if ( bSmoothTangents ) + { + const Vector &vertPos1 = pPositionData.Get( pPositionIndices.Get( vertID ) ); + for( int vertID2 = 0; vertID2 < nVertexCount; vertID2++ ) + { + if ( vertID2 == vertID ) + continue; + + const Vector &vertPos2 = pPositionData.Get( pPositionIndices.Get( vertID2 ) ); + if ( vertPos1 != vertPos2 ) + continue; + + CUtlVector<int> &triangleList2 = vertToTriMap[vertID2]; + int nTriangleCount2 = triangleList2.Count(); + for ( int triID2 = 0; triID2 < nTriangleCount2; triID2++ ) + { + Triangle_t &tri2 = triangles[ triangleList2[triID2] ]; + sVect += tri2.m_vecTangentS; + tVect += tri2.m_vecTangentT; + } + } + } + + // make an orthonormal system. + // need to check if we are left or right handed. + Vector tmpVect; + CrossProduct( sVect, tVect, tmpVect ); + const Vector &normal = pNormalData.Get( pNormalIndices.Get( vertID ) ); + bool bLeftHanded = DotProduct( tmpVect, normal ) < 0.0f; + if ( !bLeftHanded ) + { + CrossProduct( normal, sVect, tVect ); + CrossProduct( tVect, normal, sVect ); + VectorNormalize( sVect ); + VectorNormalize( tVect ); + finalSVect[0] = sVect[0]; + finalSVect[1] = sVect[1]; + finalSVect[2] = sVect[2]; + finalSVect[3] = 1.0f; + } + else + { + CrossProduct( sVect, normal, tVect ); + CrossProduct( normal, tVect, sVect ); + VectorNormalize( sVect ); + VectorNormalize( tVect ); + finalSVect[0] = sVect[0]; + finalSVect[1] = sVect[1]; + finalSVect[2] = sVect[2]; + finalSVect[3] = -1.0f; + } + + pVertexData->SetVertexData( tangentField, vertID, 1, AT_VECTOR4, &finalSVect ); + pVertexData->SetVertexIndices( tangentField, vertID, 1, &vertID ); + } +} + + +//----------------------------------------------------------------------------- +// Builds a map from vertex index to all triangles that use it +//----------------------------------------------------------------------------- +void CDmeMesh::BuildVertToTriMap( const CDmeVertexData *pVertexData, CUtlVector<Triangle_t> &triangles, CUtlVector< CUtlVector<int> > &vertToTriMap ) +{ + vertToTriMap.AddMultipleToTail( pVertexData->VertexCount() ); + + int nCount = FaceSetCount(); + for ( int i = 0; i < nCount; ++i ) + { + CDmeFaceSet *pFaceSet = GetFaceSet( i ); + BuildTriangleMap( pVertexData, pFaceSet, triangles, &vertToTriMap ); + } +} + + +//----------------------------------------------------------------------------- +// Compute a default per-vertex tangent given normal data + uv data +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeDefaultTangentData( CDmeVertexData *pVertexData, bool bSmoothTangents ) +{ + if ( !pVertexData ) + return; + + // Need to have valid pos, uv, and normal to perform this operation + FieldIndex_t posField = pVertexData->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + FieldIndex_t normalField = pVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + FieldIndex_t uvField = pVertexData->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + if ( posField < 0 || uvField < 0 || normalField < 0 ) + return; + + // FIXME: Need to do a pass to make sure no vertex is referenced by + // multiple facesets that have different materials in them. + // In that case, we need to add extra copies of that vertex and modify + // the face set data to refer to the new vertices + + // Build a map from vertex to a list of triangles that share the vert. + CUtlVector<Triangle_t> triangles( 0, 1024 ); + CUtlVector< CUtlVector<int> > vertToTriMap; + vertToTriMap.AddMultipleToTail( pVertexData->VertexCount() ); + + int nCount = FaceSetCount(); + for ( int i = 0; i < nCount; ++i ) + { + CDmeFaceSet *pFaceSet = GetFaceSet( i ); + BuildTriangleMap( pVertexData, pFaceSet, triangles, &vertToTriMap ); + } + + ComputeTriangleTangets( pVertexData, triangles ); + + // FIXME: We could do a pass to determine the unique combinations of + // position + tangent indices in the vertex data. We only need to have + // a unique tangent for each of these unique vertices. For simplicity + // (and speed), I'll assume all tangents are unique per vertex. + FieldIndex_t tangent = pVertexData->CreateField<Vector4D>( "tangents" ); + pVertexData->RemoveAllVertexData( tangent ); + pVertexData->AddVertexData( tangent, pVertexData->VertexCount() ); + + ComputeAverageTangent( pVertexData, bSmoothTangents, vertToTriMap, triangles ); +} + + +//----------------------------------------------------------------------------- +// Compute a default per-vertex tangent given normal data + uv data for all vertex data referenced by this mesh +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeDefaultTangentData( bool bSmoothTangents ) +{ + const int nBaseStateCount = m_BaseStates.Count(); + for ( int i = 0; i < nBaseStateCount; ++i ) + { + if ( m_BaseStates[i] && m_BaseStates[i]->NeedsTangentData() ) + { + ComputeDefaultTangentData( m_BaseStates[i], bSmoothTangents ); + } + } +} + + +//----------------------------------------------------------------------------- +// Utility method to compute default tangent data on all meshes in the sub-dag hierarchy +//----------------------------------------------------------------------------- +void ComputeDefaultTangentData( CDmeDag *pDag, bool bSmoothTangents ) +{ + if ( !pDag ) + return; + + CDmeMesh *pMesh = CastElement< CDmeMesh >( pDag->GetShape() ); + if ( pMesh ) + { + pMesh->ComputeDefaultTangentData( bSmoothTangents ); + } + + int nChildCount = pDag->GetChildCount(); + for ( int i = 0; i < nChildCount; ++i ) + { + ComputeDefaultTangentData( pDag->GetChild( i ), bSmoothTangents ); + } +} + + +//----------------------------------------------------------------------------- +// Compute the dimensionality of the delta state (how many inputs affect it) +//----------------------------------------------------------------------------- +int CDmeMesh::ComputeDeltaStateDimensionality( int nDeltaIndex ) +{ + CDmeVertexDeltaData *pDeltaState = GetDeltaState( nDeltaIndex ); + const char *pDeltaStateName = pDeltaState->GetName(); + + const char *pUnderBar = pDeltaStateName; + int nDimensions = 0; + while ( pUnderBar ) + { + ++nDimensions; + pUnderBar = strchr( pUnderBar, '_' ); + if ( pUnderBar ) + { + ++pUnderBar; + } + } + + return nDimensions; +} + + +//----------------------------------------------------------------------------- +// Computes the aggregate position for all vertices after applying a set of delta states +//----------------------------------------------------------------------------- +void CDmeMesh::AddDelta( CDmeVertexData *pBaseState, Vector *pDeltaPosition, int nDeltaStateIndex, CDmeVertexData::StandardFields_t fieldId ) +{ + CDmeVertexDeltaData *pDeltaState = GetDeltaState( nDeltaStateIndex ); + FieldIndex_t nFieldIndex = pDeltaState->FindFieldIndex( fieldId ); + if ( nFieldIndex < 0 ) + return; + + if ( pBaseState->FindFieldIndex( CDmeVertexData::FIELD_BALANCE ) != -1 ) + { + AddStereoVertexDelta<Vector>( pBaseState, pDeltaPosition, sizeof(Vector), fieldId, nDeltaStateIndex, true ); + } + else + { + AddVertexDelta<Vector>( pBaseState, pDeltaPosition, sizeof(Vector), fieldId, nDeltaStateIndex, true ); + } +} + + +//----------------------------------------------------------------------------- +// Computes correctly averaged vertex normals from position data +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeNormalsFromPositions( + CDmeVertexData *pBase, + const Vector *pPosition, + const CUtlVector<Triangle_t> &triangles, + int nNormalCount, + Vector *pNormals ) +{ + Assert( nNormalCount == pBase->GetNormalData().Count() ); + int *pNormalsAdded = (int*)_alloca( nNormalCount * sizeof(int) ); + memset( pNormalsAdded, 0, nNormalCount * sizeof(int) ); + memset( pNormals, 0, nNormalCount * sizeof(Vector) ); + + const CUtlVector<int> &positionIndices = pBase->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ); + const CUtlVector<int> &normalIndices = pBase->GetVertexIndexData( CDmeVertexData::FIELD_NORMAL ); + + int nTriangleCount = triangles.Count(); + for ( int i = 0; i < nTriangleCount; ++i ) + { + const Triangle_t &tri = triangles[i]; + int p1 = positionIndices[ tri.m_nIndex[0] ]; + int p2 = positionIndices[ tri.m_nIndex[1] ]; + int p3 = positionIndices[ tri.m_nIndex[2] ]; + + int n1 = normalIndices[ tri.m_nIndex[0] ]; + int n2 = normalIndices[ tri.m_nIndex[1] ]; + int n3 = normalIndices[ tri.m_nIndex[2] ]; + + Vector vecDelta, vecDelta2, vecNormal; + VectorSubtract( pPosition[p2], pPosition[p1], vecDelta ); + VectorSubtract( pPosition[p3], pPosition[p1], vecDelta2 ); + CrossProduct( vecDelta, vecDelta2, vecNormal ); + VectorNormalize( vecNormal ); + + pNormals[n1] += vecNormal; + pNormals[n2] += vecNormal; + pNormals[n3] += vecNormal; + + ++pNormalsAdded[n1]; ++pNormalsAdded[n2]; ++pNormalsAdded[n3]; + } + + for ( int i = 0; i < nNormalCount; ++i ) + { + if ( pNormalsAdded[i] > 0 ) + { + pNormals[i] /= pNormalsAdded[i]; + VectorNormalize( pNormals[i] ); + } + else + { + pNormals[i].Init( 0, 1, 0 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Converts pose-space normals into deltas appropriate for correction delta states +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeCorrectedNormalsFromActualNormals( const CUtlVector<int> &deltaStateList, int nNormalCount, Vector *pNormals ) +{ + CDmeVertexData *pBind = GetBindBaseState(); + if ( !pBind ) + return; + + Assert( nNormalCount == pBind->GetNormalData().Count() ); + + // Subtract out all other normal contributions + Vector *pUncorrectedNormals = (Vector*)_alloca( nNormalCount * sizeof(Vector) ); + memcpy( pUncorrectedNormals, pBind->GetNormalData().Base(), nNormalCount * sizeof( Vector ) ); + int nDeltaStateCount = deltaStateList.Count(); + for ( int i = 0; i < nDeltaStateCount; ++i ) + { + AddDelta( pBind, pUncorrectedNormals, deltaStateList[i], CDmeVertexData::FIELD_NORMAL ); + } + + for ( int i = 0; i < nNormalCount; ++i ) + { + pNormals[i] -= pUncorrectedNormals[i]; + } +} + + +//----------------------------------------------------------------------------- +// Copies the corrected normal data into a delta state +//----------------------------------------------------------------------------- +void CDmeMesh::SetDeltaNormalData( int nDeltaIndex, int nNormalCount, Vector *pNormals ) +{ + // pNormals represents the correct normal delta state for this combination + // Copy it into the delta state for this combination. + // Use tolerance to deal with precision errors introduced by the various computations + CDmeVertexDeltaData *pDeltaState = GetDeltaState( nDeltaIndex ); + FieldIndex_t nNormalField = pDeltaState->FindFieldIndex( CDmeVertexDeltaData::FIELD_NORMAL ); + if ( nNormalField >= 0 ) + { + pDeltaState->RemoveAllVertexData( nNormalField ); + } + else + { + nNormalField = pDeltaState->CreateField( CDmeVertexDeltaData::FIELD_NORMAL ); + } + + for ( int i = 0; i < nNormalCount; ++i ) + { + if ( pNormals[i].LengthSqr() < 1e-4 ) + continue; + + int nNormalIndex = pDeltaState->AddVertexData( nNormalField, 1 ); + pDeltaState->SetVertexData( nNormalField, nNormalIndex, 1, AT_VECTOR3, &pNormals[i] ); + pDeltaState->SetVertexIndices( nNormalField, nNormalIndex, 1, &i ); + } +} + + +//----------------------------------------------------------------------------- +// Discovers the atomic controls used by the various delta states +//----------------------------------------------------------------------------- +static int DeltaStateUsageLessFunc( const int * lhs, const int * rhs ) +{ + return *lhs - *rhs; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::BuildAtomicControlLists( int nCount, DeltaComputation_t *pInfo, CUtlVector< CUtlVector< int > > &deltaStateUsage ) +{ + CUtlVector< CUtlString > atomicControls; + deltaStateUsage.SetCount( nCount ); + + // Build a list of atomic controls + int nCurrentDelta; + for ( nCurrentDelta = 0; nCurrentDelta < nCount; ++nCurrentDelta ) + { + if ( pInfo[nCurrentDelta].m_nDimensionality != 1 ) + break; + int j = atomicControls.AddToTail( GetDeltaState( pInfo[nCurrentDelta].m_nDeltaIndex )->GetName() ); + deltaStateUsage[ nCurrentDelta ].AddToTail( j ); + } + + for ( ; nCurrentDelta < nCount; ++nCurrentDelta ) + { + CDmeVertexDeltaData *pDeltaState = GetDeltaState( pInfo[nCurrentDelta].m_nDeltaIndex ); + int nLen = Q_strlen( pDeltaState->GetName() ); + char *pTempBuf = (char*)_alloca( nLen + 1 ); + memcpy( pTempBuf, pDeltaState->GetName(), nLen+1 ); + char *pNext; + for ( char *pUnderBar = pTempBuf; pUnderBar; pUnderBar = pNext ) + { + pNext = strchr( pUnderBar, '_' ); + if ( pNext ) + { + *pNext = 0; + ++pNext; + } + + // Find this name in the list of strings + int j; + int nControlCount = atomicControls.Count(); + for ( j = 0; j < nControlCount; ++j ) + { + if ( !Q_stricmp( pUnderBar, atomicControls[j] ) ) + break; + } + if ( j == nControlCount ) + { + j = atomicControls.AddToTail( pUnderBar ); + } + deltaStateUsage[ nCurrentDelta ].AddToTail( j ); + } + deltaStateUsage[ nCurrentDelta ].Sort( DeltaStateUsageLessFunc ); + } +} + + +//----------------------------------------------------------------------------- +// Construct list of all n-1 -> 1 dimensional delta states +// that will be active when this delta state is active +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeDependentDeltaStateList( CUtlVector< DeltaComputation_t > &compList ) +{ + if ( compList.Count() == 0 ) + { + ComputeDeltaStateComputationList( compList ); + } + + CUtlVector< CUtlVector< int > > deltaStateUsage; + const int nCount( compList.Count() ); + BuildAtomicControlLists( nCount, compList.Base(), deltaStateUsage ); + + // Now build up a list of dependent delta states based on usage + // NOTE: Usage is sorted in ascending order. + for ( int i = 1; i < nCount; ++i ) + { + int nUsageCount1 = deltaStateUsage[i].Count(); + for ( int j = 0; j < i; ++j ) + { + // At the point they have the same dimensionality, no more need to check + if ( compList[j].m_nDimensionality == compList[i].m_nDimensionality ) + break; + + int ii = 0; + bool bSubsetFound = true; + int nUsageCount2 = deltaStateUsage[j].Count(); + for ( int ji = 0; ji < nUsageCount2; ++ji ) + { + for ( bSubsetFound = false; ii < nUsageCount1; ++ii ) + { + if ( deltaStateUsage[j][ji] == deltaStateUsage[i][ii] ) + { + ++ii; + bSubsetFound = true; + break; + } + + if ( deltaStateUsage[j][ji] < deltaStateUsage[i][ii] ) + break; + } + + if ( !bSubsetFound ) + break; + } + + if ( bSubsetFound ) + { + compList[i].m_DependentDeltas.AddToTail( compList[j].m_nDeltaIndex ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Sorts DeltaComputation_t's by dimensionality +//----------------------------------------------------------------------------- +int CDmeMesh::DeltaStateLessFunc( const void * lhs, const void * rhs ) +{ + DeltaComputation_t &info1 = *(DeltaComputation_t*)lhs; + DeltaComputation_t &info2 = *(DeltaComputation_t*)rhs; + return info1.m_nDimensionality - info2.m_nDimensionality; +} + + +//----------------------------------------------------------------------------- +// Generates a sorted list in order of dimensionality of the delta states +// NOTE: This assumes a naming scheme where delta state names have _ that separate control names +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeDeltaStateComputationList( CUtlVector< DeltaComputation_t > &compList ) +{ + // Do all combinations in order of dimensionality, lowest dimension first + const int nCount = DeltaStateCount(); + compList.EnsureCount( nCount ); // Resets the CUtlVector + for ( int i = 0; i < nCount; ++i ) + { + compList[i].m_nDeltaIndex = i; + compList[i].m_nDimensionality = ComputeDeltaStateDimensionality( i ); + } + qsort( compList.Base(), nCount, sizeof(DeltaComputation_t), DeltaStateLessFunc ); +} + + +//----------------------------------------------------------------------------- +// Computes normal deltas for all delta states based on position deltas +// NOTE: This assumes a naming scheme where delta state names have _ that separate control names +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeDeltaStateNormals() +{ + CDmeVertexData *pBind = GetBindBaseState(); + if ( !pBind ) + return; + + const FieldIndex_t nBindNormalIndex = pBind->CreateField( CDmeVertexData::FIELD_NORMAL ); + + const CUtlVector< Vector > &basePosData = pBind->GetPositionData(); + const int nPosCount = basePosData.Count(); + + // Build a map from vertex to a list of triangles that share the vert. + CUtlVector< Triangle_t > triangles( 0, 1024 ); + CUtlVector< CUtlVector<int> > vertToTriMap; + vertToTriMap.AddMultipleToTail( pBind->VertexCount() ); + + const int nFaceSetCount = FaceSetCount(); + for ( int i = 0; i < nFaceSetCount; ++i ) + { + CDmeFaceSet *pFaceSet = GetFaceSet( i ); + BuildTriangleMap( pBind, pFaceSet, triangles, &vertToTriMap ); + } + + // Temporary storage for normals + Vector *pNormals = reinterpret_cast< Vector * >( alloca( nPosCount * sizeof( Vector ) ) ); + + // Make all of the normals in the bind pose smooth + { + const CUtlVector< int > &basePosIndices = pBind->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ); + + pBind->SetVertexIndices( nBindNormalIndex, 0, basePosIndices.Count(), basePosIndices.Base() ); + pBind->RemoveAllVertexData( nBindNormalIndex ); + pBind->AddVertexData( nBindNormalIndex, nPosCount ); + + ComputeNormalsFromPositions( pBind, basePosData.Base(), triangles, nPosCount, pNormals ); + pBind->SetVertexData( nBindNormalIndex, 0, nPosCount, AT_VECTOR3, pNormals ); + + // Fix up the current state to have smooth normals if current is not bind + CDmeVertexData *pCurrent = GetCurrentBaseState(); + if ( pCurrent != pBind ) + { + const FieldIndex_t nCurrentNormalIndex = pCurrent->CreateField( CDmeVertexData::FIELD_NORMAL ); + pCurrent->SetVertexIndices( nCurrentNormalIndex, 0, basePosIndices.Count(), basePosIndices.Base() ); + pCurrent->RemoveAllVertexData( nCurrentNormalIndex ); + pCurrent->AddVertexData( nCurrentNormalIndex, nPosCount ); + + const CUtlVector< Vector > &currPosData = pCurrent->GetPositionData(); + ComputeNormalsFromPositions( pCurrent, currPosData.Base(), triangles, nPosCount, pNormals ); + pCurrent->SetVertexData( nCurrentNormalIndex, 0, nPosCount, AT_VECTOR3, pNormals ); + } + } + + // Temporary storage for the positions + Vector *pPosData = reinterpret_cast< Vector * >( alloca( nPosCount * sizeof( Vector ) ) ); + + // Compute the dependent delta state list like thing + CUtlVector< DeltaComputation_t > computationOrder; + ComputeDependentDeltaStateList( computationOrder ); + + const int nDeltaStateCount = computationOrder.Count(); + for ( int i = 0; i < nDeltaStateCount; ++i ) + { + const DeltaComputation_t &deltaComputation = computationOrder[ i ]; + + memcpy( pPosData, basePosData.Base(), nPosCount * sizeof( Vector ) ); + + const CUtlVector< int > &depDeltas = deltaComputation.m_DependentDeltas; + const int nDepStateCount = depDeltas.Count(); + for ( int j = 0; j < nDepStateCount; ++j ) + { + AddDelta( GetDeltaState( depDeltas[ j ] ), pPosData, nPosCount, CDmeVertexData::FIELD_POSITION ); + } + + AddDelta( GetDeltaState( deltaComputation.m_nDeltaIndex ), pPosData, nPosCount, CDmeVertexData::FIELD_POSITION ); + + ComputeNormalsFromPositions( pBind, pPosData, triangles, nPosCount, pNormals ); + + SetDeltaNormalDataFromActualNormals( computationOrder[ i ].m_nDeltaIndex, depDeltas, nPosCount, pNormals ); + } +} + + +//----------------------------------------------------------------------------- +// Computes normal deltas for all delta states based on position deltas +// NOTE: This assumes a naming scheme where delta state names have _ that separate control names +//----------------------------------------------------------------------------- +void CDmeMesh::SetDeltaNormalDataFromActualNormals( int nDeltaIndex, const CUtlVector<int> &deltaStateList, int nNormalCount, Vector *pNormals ) +{ + // Store off the current state values + CUtlVector< Vector2D > deltaStateWeights[MESH_DELTA_WEIGHT_TYPE_COUNT]; + for ( int i = 0; i < MESH_DELTA_WEIGHT_TYPE_COUNT; ++i ) + { + deltaStateWeights[i] = m_DeltaStateWeights[i].Get(); + + // Turn on the current weights to all be 1 to get max effect of morphs + int nCount = m_DeltaStateWeights[i].Count(); + for ( int j = 0; j < nCount; ++j ) + { + m_DeltaStateWeights[i].Set( j, Vector2D( 1.0f, 1.0f ) ); + } + } + + ComputeCorrectedNormalsFromActualNormals( deltaStateList, nNormalCount, pNormals ); + + // Finally, store the corrected normals into the delta state + SetDeltaNormalData( nDeltaIndex, nNormalCount, pNormals ); + + // Restore weights to their current value + for ( int i = 0; i < MESH_DELTA_WEIGHT_TYPE_COUNT; ++i ) + { + m_DeltaStateWeights[i] = deltaStateWeights[i]; + } +} + + +//----------------------------------------------------------------------------- +// A recursive algorithm to compute nCk, i.e. the number of order independent +// Combinations without any repeats of k items taking n at a time +// The size of the returned array is: +// +// n! +// ------------- +// k! ( n - r )! +// +// e.g. 4C4 = { 0 1 2 3 } +// e.g. 3C4 = { 0 1 2 }, { 0 1 3 }, { 0 2 3 }, { 1 2 3 } +// e.g. 2C4 = { 0 1 }, { 0 2 }, { 0 3 }, { 1 2 }, { 1 3 }, { 2 3 } +// e.g. 1C4 = { 0 }, { 1 }, { 2 }, { 3 } +// +// It's recursive and meant to be called by the user with just n, k and combos +// the other default arguments are for the recursive steps +//----------------------------------------------------------------------------- +void CDmeMesh::Combinations( + int n, + int k, + CUtlVector< CUtlVector< int > > &combos, + int *pTmpArray, + int start, + int currentK ) +{ + if ( !pTmpArray ) + { + pTmpArray = reinterpret_cast< int * >( alloca( k * sizeof( int ) ) ); + memset( pTmpArray, 0, k * sizeof( int ) ); + } + + if ( currentK >= k ) + { + combos[ combos.AddToTail() ].CopyArray( pTmpArray, k ); + return; + } + + for ( int i( start ); i < n; ++i ) + { + pTmpArray[ currentK ] = i; + + Combinations( n, k, combos, pTmpArray, i + 1, currentK + 1 ); + } +} + + +//----------------------------------------------------------------------------- +// Takes an incoming Delta state, splits it's name '_' and then finds the +// control delta (a state without a '_' in its name) and adds the index +// of that control delta to the referenced array +// +// Returns true if all of the control states exist, false otherwise +//----------------------------------------------------------------------------- +bool CDmeMesh::GetControlDeltaIndices( + CDmeVertexDeltaData *pDeltaState, + CUtlVector< int > &controlDeltaIndices ) const +{ + Assert( pDeltaState ); + return GetControlDeltaIndices( pDeltaState->GetName(), controlDeltaIndices ); +} + + +//----------------------------------------------------------------------------- +// Same as above but just uses the name of a delta +//----------------------------------------------------------------------------- +bool CDmeMesh::GetControlDeltaIndices( + const char *pDeltaStateName, + CUtlVector< int > &controlDeltaIndices ) const +{ + Assert( pDeltaStateName ); + controlDeltaIndices.RemoveAll(); + + const int nDeltaStateName( Q_strlen( pDeltaStateName ) ); + char *pTmpBuf( reinterpret_cast< char * >( alloca( nDeltaStateName + 1 ) ) ); + Q_strncpy( pTmpBuf, pDeltaStateName, nDeltaStateName + 1 ); + char *pNext; + for ( char *pCurr = pTmpBuf; pCurr; pCurr = pNext ) + { + pNext = strchr( pCurr, '_' ); + if ( pNext ) + { + *pNext = '\0'; + ++pNext; + } + + if ( Q_strlen( pCurr ) ) + { + const int controlDeltaIndex( FindDeltaStateIndex( pCurr ) ); + if ( controlDeltaIndex >= 0 ) + { + controlDeltaIndices.AddToTail( controlDeltaIndex ); + } + else + { + controlDeltaIndices.RemoveAll(); + return false; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Builds a list of all of the underlying control delta indices for each +// delta state in the mesh +// +// e.g. Say the delta states are (in this order): A, B, C, A_C, A_B_C +// +// Will build: { +// { 0 }, +// { 1 }, +// { 2 }, +// { 0, 2 }, +// { 0, 1, 2 } +// } +// +// Returns true if all of the control states exist, false otherwise +//----------------------------------------------------------------------------- +bool CDmeMesh::BuildCompleteDeltaStateControlList( + CUtlVector< CUtlVector< int > > &deltaStateControlList ) const +{ + deltaStateControlList.RemoveAll(); + + CUtlVector< int > tmpControlDeltaIndices; + + const int nDeltas( m_DeltaStates.Count() ); + for ( int i = 0; i < nDeltas; ++i ) + { + if ( !GetControlDeltaIndices( m_DeltaStates[ i ], tmpControlDeltaIndices ) ) + return false; + + deltaStateControlList[ deltaStateControlList.AddToTail() ].CopyArray( tmpControlDeltaIndices.Base(), tmpControlDeltaIndices.Count() ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Searches controlList for a sub array that has exactly the same indices as +// controlIndices. The order of the indices do not have to match but all of +// them must be present and no extras can be present. +// It assumes that the controlList is in the same order as m_deltaStates +//----------------------------------------------------------------------------- +int CDmeMesh::FindDeltaIndexFromControlIndices( + const CUtlVector< int > &controlIndices, + const CUtlVector< CUtlVector< int > > &controlList ) const +{ + const int nControlIndices( controlIndices.Count() ); + const int nControlList( controlList.Count() ); + + int nControlListIndices; + int foundCount; + + for ( int i = 0; i < nControlList; ++i ) + { + const CUtlVector< int > &controlListIndices( controlList[ i ] ); + nControlListIndices = controlListIndices.Count(); + if ( nControlListIndices == nControlIndices ) + { + foundCount = 0; + + for ( int j( 0 ); j < nControlListIndices; ++j ) + { + for ( int k( 0 ); k < nControlIndices; ++k ) + { + if ( controlListIndices[ j ] == controlIndices[ k ] ) + { + ++foundCount; + break; + } + } + } + + if ( foundCount == nControlIndices ) + return i; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Builds a list of all of the required underlying deltas that make up this +// state whether that do not exist. All of the control deltas must exist +// though (Deltas without '_' in their name). +// +// e.g. Say only Delta states A, B, C, D, A_B_C_D exist and A_B_C_D is +// passed in. This function will return: +// +// A_B_C, A_B_D, A_C_D, B_C_D, A_B, A_C, A_D, B_C, B_D, C_D +// +// Returns true if all of the control states exist, false otherwise +//----------------------------------------------------------------------------- +bool CDmeMesh::BuildMissingDependentDeltaList( + CDmeVertexDeltaData *pDeltaState, + CUtlVector< int > &controlIndices, + CUtlVector< CUtlVector< int > > &dependentStates ) const +{ + dependentStates.RemoveAll(); + + CUtlVector< CUtlVector< int > > deltaStateControlList; + BuildCompleteDeltaStateControlList( deltaStateControlList ); + + if ( !GetControlDeltaIndices( pDeltaState, controlIndices ) ) + return false; + + const int nControlIndices( controlIndices.Count() ); + + CUtlVector< int > comboControls; + + for ( int i( nControlIndices - 1 ); i > 0; --i ) + { + CUtlVector< CUtlVector< int > > combos; + Combinations( nControlIndices, i, combos ); + const int nCombos( combos.Count() ); + for ( int j( 0 ); j < nCombos; ++j ) + { + const CUtlVector< int > &comboIndices( combos[ j ] ); + const int nComboIndices( comboIndices.Count() ); + if ( comboIndices.Count() ) + { + comboControls.RemoveAll(); + comboControls.EnsureCapacity( nComboIndices ); + + for ( int k( 0 ); k < nComboIndices; ++k ) + { + comboControls.AddToTail( controlIndices[ comboIndices[ k ] ] ); + } + + if ( FindDeltaIndexFromControlIndices( comboControls, deltaStateControlList) < 0 ) + { + dependentStates[ dependentStates.AddToTail() ].CopyArray( comboControls.Base(), comboControls.Count() ); + } + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +int CDmeMesh::GenerateCompleteDataForDelta( + const CDmeVertexDeltaData *pDelta, + T_t *pFullData, + int nFullData, + CDmeVertexData::StandardFields_t standardField ) +{ + memset( pFullData, 0, nFullData * sizeof( T_t ) ); + + const FieldIndex_t fIndex( pDelta->FindFieldIndex( standardField ) ); + if ( fIndex >= 0 ) + { + CDmrArrayConst< T_t > fDataArray( pDelta->GetVertexData( fIndex ) ); + const CUtlVector< T_t > &fData( fDataArray.Get() ); + const CUtlVector< int > &fIndexData( pDelta->GetVertexIndexData( fIndex ) ); + const int nIndexData( fIndexData.Count() ); + + Assert( nIndexData <= nFullData ); + + int index; + + int i( 0 ); + + for ( int j( 0 ); j < nIndexData; ++j ) + { + index = fIndexData[ j ]; + while ( index > i ) + { + ++i; + } + + Assert( i < nFullData ); + pFullData[ i ] = fData[ j ]; + } + + return nIndexData; + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void CDmeMesh::AddDelta( + const CDmeVertexDeltaData *pDelta, + T_t *pFullData, + int nFullData, + FieldIndex_t fieldIndex, + float weight, + const CDmeSingleIndexedComponent *pMask ) +{ + if ( fieldIndex >= 0 ) + { + CDmrArrayConst< T_t > fDataArray( pDelta->GetVertexData( fieldIndex ) ); + const CUtlVector< T_t > &fData( fDataArray.Get() ); + const CUtlVector< int > &fIndexData( pDelta->GetVertexIndexData( fieldIndex ) ); + const int nIndexData( fIndexData.Count() ); + + T_t t; + + Assert( nIndexData <= nFullData ); + + int index; + + int i( 0 ); + + if ( pMask ) + { + float cWeight; + + for ( int j( 0 ); j < nIndexData; ++j ) + { + index = fIndexData[ j ]; + + if ( !pMask->GetWeight( index, cWeight ) ) + continue; + + while ( index > i ) + { + ++i; + } + + Assert( i < nFullData ); + + t = fData[ j ]; + t *= ( weight * cWeight ); + pFullData[ i ] += t; + } + } + else + { + for ( int j( 0 ); j < nIndexData; ++j ) + { + index = fIndexData[ j ]; + while ( index > i ) + { + ++i; + } + + Assert( i < nFullData ); + t = fData[ j ]; + t *= weight; + pFullData[ i ] += t; + } + } + } +} + +template void CDmeMesh::AddDelta< float >( const CDmeVertexDeltaData *, float *, int, FieldIndex_t, float, const CDmeSingleIndexedComponent * ); +template void CDmeMesh::AddDelta< Vector2D >( const CDmeVertexDeltaData *, Vector2D *, int, FieldIndex_t, float, const CDmeSingleIndexedComponent * ); +template void CDmeMesh::AddDelta< Vector >( const CDmeVertexDeltaData *, Vector *, int, FieldIndex_t, float, const CDmeSingleIndexedComponent * ); + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void CDmeMesh::AddDelta( + const CDmeVertexDeltaData *pDelta, + T_t *pFullData, + int nFullData, + CDmeVertexData::StandardFields_t standardField, + float weight, + const CDmeSingleIndexedComponent *pMask ) +{ + const FieldIndex_t fIndex( pDelta->FindFieldIndex( standardField ) ); + AddDelta( pDelta, pFullData, nFullData, fIndex, weight, pMask ); +} + +template void CDmeMesh::AddDelta< float >( const CDmeVertexDeltaData *, float *, int, CDmeVertexData::StandardFields_t, float, const CDmeSingleIndexedComponent * ); +template void CDmeMesh::AddDelta< Vector2D >( const CDmeVertexDeltaData *, Vector2D *, int, CDmeVertexData::StandardFields_t, float, const CDmeSingleIndexedComponent * ); +template void CDmeMesh::AddDelta< Vector >( const CDmeVertexDeltaData *, Vector *, int, CDmeVertexData::StandardFields_t, float, const CDmeSingleIndexedComponent * ); + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::ComputeAllCorrectedPositionsFromActualPositions() +{ + const CDmeVertexData *pBase = GetBindBaseState(); + if ( !pBase ) + return; + + CUtlVector< DeltaComputation_t > deltaList; + ComputeDependentDeltaStateList( deltaList ); + + const int nDeltas( deltaList.Count() ); + + const int nPositions( pBase->GetPositionData().Count() ); + + Vector *pPositions( reinterpret_cast< Vector * >( alloca( nPositions * sizeof( Vector ) ) ) ); + int *pIndices( reinterpret_cast< int * >( alloca( nPositions * sizeof( int ) ) ) ); + + int pCount; + + for ( int i = 0; i < nDeltas; ++i ) + { + const DeltaComputation_t &deltaComputation( deltaList[ i ] ); + CDmeVertexDeltaData *pDelta( m_DeltaStates[ deltaComputation.m_nDeltaIndex ] ); + if ( !pDelta->GetValue< bool >( "corrected" ) ) + { + const FieldIndex_t pIndex( pDelta->FindFieldIndex( CDmeVertexDeltaData::FIELD_POSITION ) ); + if ( pIndex < 0 ) + continue; + + GenerateCompleteDataForDelta( pDelta, pPositions, nPositions, CDmeVertexData::FIELD_POSITION ); + + const CUtlVector< int > &dependentDeltas( deltaComputation.m_DependentDeltas ); + const int nDependentDeltas( dependentDeltas.Count() ); + for ( int j( 0 ); j < nDependentDeltas; ++j ) + { + const CDmeVertexDeltaData *pDependentDelta( m_DeltaStates[ dependentDeltas[ j ] ] ); + const CUtlVector< Vector > &dPositions( pDependentDelta->GetPositionData() ); + const CUtlVector<int> &dIndices( pDependentDelta->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ) ); + Assert( dPositions.Count() == dIndices.Count() ); + const int nIndices( dIndices.Count() ); + + int index; + + int k( 0 ); + for ( int l( 0 ); l < nIndices; ++l ) + { + index = dIndices[ l ]; + while ( index > k ) + { + ++k; + } + + Assert( k < nPositions ); + pPositions[ k ] -= dPositions[ l ]; + } + } + + pCount = 0; + for ( int j( 0 ); j < nPositions; ++j ) + { + const Vector &v( pPositions[ j ] ); + // Kind of a magic number but it's because of 16 bit compression of the delta values + if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) + { + pPositions[ pCount ] = v; + pIndices[ pCount ] = j; + ++pCount; + } + } + + pDelta->RemoveAllVertexData( pIndex ); + + if ( pCount ) + { + pDelta->AddVertexData( pIndex, pCount ); + pDelta->SetVertexData( pIndex, 0, pCount, AT_VECTOR3, pPositions ); + pDelta->SetVertexIndices( pIndex, 0, pCount, pIndices ); + } + pDelta->SetValue( "corrected", true ); + } + } +} + + +//----------------------------------------------------------------------------- +// There's no guarantee that fields are added in any order, nor that only +// standard fields exist... +//----------------------------------------------------------------------------- +template < class T_t > +void CDmeMesh::AddCorrectedDelta( + CDmrArray< T_t > &baseDataArray, + const CUtlVector< int > &baseIndices, + const DeltaComputation_t &deltaComputation, + const char *pFieldName, + float weight, + const CDmeSingleIndexedComponent *pMask ) +{ + const CUtlVector< T_t > &baseData( baseDataArray.Get() ); + const int nData( baseData.Count() ); + T_t *pData( reinterpret_cast< T_t * >( alloca( nData * sizeof( T_t ) ) ) ); + Q_memcpy( pData, baseData.Base(), nData * sizeof( T_t ) ); + + CDmeVertexDeltaData *pDelta( GetDeltaState( deltaComputation.m_nDeltaIndex ) ); + + const int deltaFieldIndex( pDelta->FindFieldIndex( pFieldName ) ); + if ( deltaFieldIndex < 0 ) + return; + + AddDelta( pDelta, pData, nData, deltaFieldIndex, weight, pMask ); + + const CUtlVector< int > &depDeltas( deltaComputation.m_DependentDeltas ); + const int nDepDeltas( depDeltas.Count() ); + for ( int j( 0 ); j < nDepDeltas; ++j ) + { + pDelta = GetDeltaState( depDeltas[ j ] ); + + int depFieldIndex = pDelta->FindFieldIndex( pFieldName ); + if ( depFieldIndex < 0 ) + continue; + + AddDelta( pDelta, pData, nData, depFieldIndex, weight, pMask ); + } + + baseDataArray.CopyArray( pData, nData ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void CDmeMesh::AddCorrectedDelta( + CUtlVector< T_t > &baseData, + const CUtlVector< int > &baseIndices, + const DeltaComputation_t &deltaComputation, + const char *pFieldName, + float weight, + const CDmeSingleIndexedComponent *pMask ) +{ + const int nData( baseData.Count() ); + + CDmeVertexDeltaData *pDelta( GetDeltaState( deltaComputation.m_nDeltaIndex ) ); + + const int deltaFieldIndex( pDelta->FindFieldIndex( pFieldName ) ); + if ( deltaFieldIndex < 0 ) + return; + + AddDelta( pDelta, baseData.Base(), nData, deltaFieldIndex, weight, pMask ); + + const CUtlVector< int > &depDeltas( deltaComputation.m_DependentDeltas ); + const int nDepDeltas( depDeltas.Count() ); + for ( int j( 0 ); j < nDepDeltas; ++j ) + { + pDelta = GetDeltaState( depDeltas[ j ] ); + + int depFieldIndex = pDelta->FindFieldIndex( pFieldName ); + if ( depFieldIndex < 0 ) + continue; + + AddDelta( pDelta, baseData.Base(), nData, depFieldIndex, weight, pMask ); + } +} + + +//----------------------------------------------------------------------------- +// There's no guarantee that fields are added in any order, nor that only +// standard fields exist... +//----------------------------------------------------------------------------- +template < class T_t > +void CDmeMesh::AddRawDelta( + CDmeVertexDeltaData *pDelta, + CDmrArray< T_t > &baseDataArray, + FieldIndex_t nDeltaFieldIndex, + float weight, + const CDmeSingleIndexedComponent *pMask ) +{ + if ( !pDelta || nDeltaFieldIndex < 0 ) + return; + + const CUtlVector< T_t > &baseData( baseDataArray.Get() ); + const int nData( baseData.Count() ); + T_t *pData( reinterpret_cast< T_t * >( alloca( nData * sizeof( T_t ) ) ) ); + Q_memcpy( pData, baseData.Base(), nData * sizeof( T_t ) ); + + AddDelta( pDelta, pData, nData, nDeltaFieldIndex, weight, pMask ); + + baseDataArray.CopyArray( pData, nData ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void CDmeMesh::AddRawDelta( + CDmeVertexDeltaData *pDelta, + CUtlVector< T_t > &baseData, + FieldIndex_t nDeltaFieldIndex, + float weight, + const CDmeSingleIndexedComponent *pMask ) +{ + if ( !pDelta || nDeltaFieldIndex < 0 ) + return; + + const int nData( baseData.Count() ); + + AddDelta( pDelta, baseData.Base(), nData, nDeltaFieldIndex, weight, pMask ); +} + + +//----------------------------------------------------------------------------- +// Sets the specified base state to the specified delta +// If no delta is specified then the current state is copied from the bind state +// If no base state is specified then the current base state is used +// The specified base state or the current base state cannot be the bind state +//----------------------------------------------------------------------------- +bool CDmeMesh::SetBaseStateToDelta( const CDmeVertexDeltaData *pDelta, CDmeVertexData *pPassedBase /* = NULL */ ) +{ + CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + const CDmeVertexData *pBind = GetBindBaseState(); + + if ( !pBase || !pBind || pBase == pBind ) + return false; + + pBind->CopyTo( pBase ); + + if ( !pDelta ) + return true; + + // This should be cached and recomputed only when states are added + CUtlVector< DeltaComputation_t > compList; + ComputeDependentDeltaStateList( compList ); + + const int nDeltas( compList.Count() ); + for ( int i = 0; i < nDeltas; ++i ) + { + if ( pDelta != GetDeltaState( compList[ i ].m_nDeltaIndex ) ) + continue; + + const int nBaseField( pBase->FieldCount() ); + const int nDeltaField( pDelta->FieldCount() ); + + for ( int j( 0 ); j < nBaseField; ++j ) + { + const CUtlString &baseFieldName( pBase->FieldName( j ) ); + + for ( int k( 0 ); k < nDeltaField; ++k ) + { + const CUtlString &deltaFieldName( pDelta->FieldName( k ) ); + + if ( baseFieldName != deltaFieldName ) + continue; + + const FieldIndex_t baseFieldIndex( pBase->FindFieldIndex( baseFieldName ) ); + const FieldIndex_t deltaFieldIndex( pDelta->FindFieldIndex( deltaFieldName ) ); + if ( baseFieldIndex < 0 || deltaFieldIndex < 0 ) + break; + + CDmAttribute *pBaseData( pBase->GetVertexData( baseFieldIndex ) ); + const CDmAttribute *pDeltaData( pDelta->GetVertexData( deltaFieldIndex ) ); + + if ( pBaseData->GetType() != pDeltaData->GetType() ) + break; + + const CUtlVector< int > &baseIndices( pBase->GetVertexIndexData( baseFieldIndex ) ); + + switch ( pBaseData->GetType() ) + { + case AT_FLOAT_ARRAY: + AddCorrectedDelta( CDmrArray< float >( pBaseData ), baseIndices, compList[ i ], baseFieldName ); + break; + case AT_COLOR_ARRAY: + AddCorrectedDelta( CDmrArray< Vector >( pBaseData ), baseIndices, compList[ i ], baseFieldName ); + break; + case AT_VECTOR2_ARRAY: + AddCorrectedDelta( CDmrArray< Vector2D >( pBaseData ), baseIndices, compList[ i ], baseFieldName ); + break; + case AT_VECTOR3_ARRAY: + AddCorrectedDelta( CDmrArray< Vector >( pBaseData ), baseIndices, compList[ i ], baseFieldName ); + break; + default: + break; + } + break; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::SelectVerticesFromDelta( + CDmeVertexDeltaData *pDelta, + CDmeSingleIndexedComponent *pSelection ) +{ + if ( !pSelection ) + return; + + pSelection->Clear(); + + if ( !pDelta ) + return; + + const FieldIndex_t pField( pDelta->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); + if ( pField < 0 ) + return; + + const CUtlVector< int > &pIndicies( pDelta->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ) ); + + pSelection->AddComponents( pIndicies ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::SelectAllVertices( CDmeSingleIndexedComponent *pSelection, CDmeVertexData *pPassedBase /* = NULL */ ) +{ + const CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + + if ( !pBase ) + { + pBase = GetBindBaseState(); + } + + if ( !pBase ) + return; + + if ( !pSelection ) + return; + + pSelection->Clear(); + + const FieldIndex_t pField( pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); + if ( pField < 0 ) + return; + + CUtlVector< int > indices; + indices.EnsureCount( CDmrArrayConst< Vector >( pBase->GetVertexData( pField ) ).Count() ); + const int nIndices = indices.Count(); + for ( int i = 0; i < nIndices; ++i ) + { + indices[ i ] = i; + } + + pSelection->AddComponents( indices ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::SelectHalfVertices( SelectHalfType_t selectHalfType, CDmeSingleIndexedComponent *pSelection, CDmeVertexData *pPassedBase /* = NULL */ ) +{ + const CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + + if ( !pBase ) + { + pBase = GetBindBaseState(); + } + + if ( !pBase ) + return; + + if ( !pSelection ) + return; + + pSelection->Clear(); + + const FieldIndex_t pField( pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); + if ( pField < 0 ) + return; + + const CDmrArrayConst< Vector > pos( pBase->GetVertexData( pField ) ); + const int nPosCount = pos.Count(); + + CUtlVector< int > indices; + indices.EnsureCapacity( nPosCount ); + + if ( selectHalfType == kRight ) + { + for ( int i = 0; i < nPosCount; ++i ) + { + if ( pos[ i ].x <= 0.0f ) + { + indices.AddToTail( i ); + } + } + } + else + { + for ( int i = 0; i < nPosCount; ++i ) + { + if ( pos[ i ].x >= 0.0f ) + { + indices.AddToTail( i ); + } + } + } + + pSelection->AddComponents( indices ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeMesh::CreateDeltaFieldFromBaseField( + CDmeVertexData::StandardFields_t nStandardFieldIndex, + const CDmrArrayConst< float > &baseArray, + const CDmrArrayConst< float > &bindArray, + CDmeVertexDeltaData *pDelta ) +{ + const int nData( baseArray.Count() ); + if ( nData != bindArray.Count() ) + return false; + + const float *pBaseData( baseArray.Get().Base() ); + const float *pBindData( bindArray.Get().Base() ); + + float *pData( reinterpret_cast< float * >( nData * sizeof( float ) ) ); + Q_memcpy( pData, pBaseData, nData * sizeof( float ) ); + int *pIndices( reinterpret_cast< int * >( nData * sizeof( int ) ) ); + + float v; + + int nDeltaCount( 0 ); + for ( int i = 0; i < nData; ++i ) + { + v = pBaseData[ i ] - pBindData[ i ]; + + // Kind of a magic number but it's because of 16 bit compression of the delta values + if ( fabs( v ) >= ( 1 / 4096.0f ) ) + { + pData[ nDeltaCount ] = v; + pIndices[ nDeltaCount ] = i; + ++nDeltaCount; + } + } + + if ( nDeltaCount <= 0 ) + return true; + + FieldIndex_t fieldIndex( pDelta->CreateField( nStandardFieldIndex ) ); + if ( fieldIndex < 0 ) + return false; + + pDelta->AddVertexData( fieldIndex, nDeltaCount ); + pDelta->SetVertexData( fieldIndex, 0, nDeltaCount, AT_FLOAT, pData ); + pDelta->SetVertexIndices( fieldIndex, 0, nDeltaCount, pIndices ); + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeMesh::CreateDeltaFieldFromBaseField( + CDmeVertexData::StandardFields_t nStandardFieldIndex, + const CDmrArrayConst< Vector2D > &baseArray, + const CDmrArrayConst< Vector2D > &bindArray, + CDmeVertexDeltaData *pDelta ) +{ + const int nData( baseArray.Count() ); + if ( nData != bindArray.Count() ) + return false; + + const Vector2D *pBaseData( baseArray.Get().Base() ); + const Vector2D *pBindData( bindArray.Get().Base() ); + + Vector2D *pData( reinterpret_cast< Vector2D * >( nData * sizeof( Vector2D ) ) ); + Q_memcpy( pData, pBaseData, nData * sizeof( Vector2D ) ); + int *pIndices( reinterpret_cast< int * >( nData * sizeof( int ) ) ); + + Vector2D v; + + int nDeltaCount( 0 ); + for ( int i = 0; i < nData; ++i ) + { + v = pBaseData[ i ] - pBindData[ i ]; + + // Kind of a magic number but it's because of 16 bit compression of the delta values + if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) ) + { + pData[ nDeltaCount ] = v; + pIndices[ nDeltaCount ] = i; + ++nDeltaCount; + } + } + + if ( nDeltaCount <= 0 ) + return true; + + FieldIndex_t fieldIndex( pDelta->CreateField( nStandardFieldIndex ) ); + if ( fieldIndex < 0 ) + return false; + + pDelta->AddVertexData( fieldIndex, nDeltaCount ); + pDelta->SetVertexData( fieldIndex, 0, nDeltaCount, AT_VECTOR2, pData ); + pDelta->SetVertexIndices( fieldIndex, 0, nDeltaCount, pIndices ); + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeMesh::CreateDeltaFieldFromBaseField( + CDmeVertexData::StandardFields_t nStandardFieldIndex, + const CDmrArrayConst< Vector > &baseArray, + const CDmrArrayConst< Vector > &bindArray, + CDmeVertexDeltaData *pDelta ) +{ + const int nData( baseArray.Count() ); + if ( nData != bindArray.Count() ) + return false; + + const Vector *pBaseData( baseArray.Get().Base() ); + const Vector *pBindData( bindArray.Get().Base() ); + + Vector *pData( reinterpret_cast< Vector * >( alloca( nData * sizeof( Vector ) ) ) ); + Q_memcpy( pData, pBaseData, nData * sizeof( Vector ) ); + int *pIndices( reinterpret_cast< int * >( alloca( nData * sizeof( int ) ) ) ); + + Vector v; + + int nDeltaCount( 0 ); + for ( int i = 0; i < nData; ++i ) + { + v = pBaseData[ i ] - pBindData[ i ]; + + // Kind of a magic number but it's because of 16 bit compression of the delta values + if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) + { + pData[ nDeltaCount ] = v; + pIndices[ nDeltaCount ] = i; + ++nDeltaCount; + } + } + + if ( nDeltaCount <= 0 ) + return true; + + FieldIndex_t fieldIndex( pDelta->CreateField( nStandardFieldIndex ) ); + if ( fieldIndex < 0 ) + return false; + + pDelta->AddVertexData( fieldIndex, nDeltaCount ); + pDelta->SetVertexData( fieldIndex, 0, nDeltaCount, AT_VECTOR3, pData ); + pDelta->SetVertexIndices( fieldIndex, 0, nDeltaCount, pIndices ); + + return true; +} + +//----------------------------------------------------------------------------- +// Creates a delta from the difference between the bind base state and the +// specified base state. If pBaseName is NULL the current base state is used +//----------------------------------------------------------------------------- +CDmeVertexDeltaData *CDmeMesh::ModifyOrCreateDeltaStateFromBaseState( const char *pDeltaName, CDmeVertexData *pPassedBase /* = NULL */, bool absolute /* = false */ ) +{ + // Find All States Which Have This Guy + CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + if ( !pBase ) + return NULL; + + CDmeVertexData *pBind = GetBindBaseState(); + if ( !pBind ) + return NULL; + + // It's ok if pBase == pBind + + CUtlVector< int > superiorDeltaStates; + ComputeSuperiorDeltaStateList( pDeltaName, superiorDeltaStates ); + const int nSuperior = superiorDeltaStates.Count(); + + if ( nSuperior > 0 ) + { + UniqueId_t id; + char idBuf[ MAX_PATH ]; + + CDmeVertexData *pTmpBaseState = NULL; + do + { + CreateUniqueId( &id ); + UniqueIdToString( id, idBuf, sizeof( idBuf ) ); + pTmpBaseState = FindBaseState( idBuf ); + } while( pTmpBaseState != NULL ); + + pTmpBaseState = FindOrCreateBaseState( idBuf ); + if ( !pTmpBaseState ) + return NULL; + + for ( int i = 0; i < nSuperior; ++i ) + { + Assert( superiorDeltaStates[ i ] < DeltaStateCount() ); + CDmeVertexDeltaData *pSuperiorDelta = GetDeltaState( superiorDeltaStates[ i ] ); + if ( pSuperiorDelta->GetValue< bool >( "corrected" ) ) + { + // Only fiddle with states that are "corrected" + if ( !SetBaseStateToDelta( pSuperiorDelta, pTmpBaseState ) ) + return NULL; + + if ( !ModifyOrCreateDeltaStateFromBaseState( CUtlString( pSuperiorDelta->GetName() ), pTmpBaseState, true ) ) + return NULL; + } + } + + DeleteBaseState( idBuf ); + } + + ResetDeltaState( pDeltaName ); + CDmeVertexDeltaData *pDelta = FindOrCreateDeltaState( pDeltaName ); + if ( !pDelta ) + return NULL; + + CDmeVertexData::StandardFields_t deltaFields[] = + { + CDmeVertexData::FIELD_POSITION, + CDmeVertexData::FIELD_NORMAL, + CDmeVertexData::FIELD_WRINKLE + }; + + for ( int i = 0; i < sizeof( deltaFields ) / sizeof( deltaFields[ 0 ] ); ++i ) + { + CDmeVertexData::StandardFields_t standardFieldIndex( deltaFields[ i ] ); + const FieldIndex_t baseFieldIndex( pBase->FindFieldIndex( standardFieldIndex ) ); + const FieldIndex_t bindFieldIndex( pBind->FindFieldIndex( standardFieldIndex ) ); + + if ( baseFieldIndex < 0 || bindFieldIndex < 0 ) + continue; + + CDmAttribute *pBaseData( pBase->GetVertexData( baseFieldIndex ) ); + CDmAttribute *pBindData( pBind->GetVertexData( bindFieldIndex ) ); + + if ( pBaseData->GetType() != pBindData->GetType() ) + continue; + + switch ( pBaseData->GetType() ) + { + case AT_FLOAT_ARRAY: + CreateDeltaFieldFromBaseField( standardFieldIndex, CDmrArrayConst< float >( pBaseData ), CDmrArrayConst< float >( pBindData ), pDelta ); + break; + case AT_COLOR_ARRAY: + CreateDeltaFieldFromBaseField( standardFieldIndex, CDmrArrayConst< Vector >( pBaseData ), CDmrArrayConst< Vector >( pBindData ), pDelta ); + break; + case AT_VECTOR2_ARRAY: + CreateDeltaFieldFromBaseField( standardFieldIndex, CDmrArrayConst< Vector2D >( pBaseData ), CDmrArrayConst< Vector2D >( pBindData ), pDelta ); + break; + case AT_VECTOR3_ARRAY: + CreateDeltaFieldFromBaseField( standardFieldIndex, CDmrArrayConst< Vector >( pBaseData ), CDmrArrayConst< Vector >( pBindData ), pDelta ); + break; + default: + break; + } + } + + if ( !strchr( pDelta->GetName(), '_' ) ) + { + const static UtlSymId_t symTargets = g_pDataModel->GetSymbol( "targets" ); + CDmeCombinationOperator *pCombo( FindReferringElement< CDmeCombinationOperator >( this, symTargets ) ); + if ( pCombo ) + { + pCombo->FindOrCreateControl( pDelta->GetName(), false, true ); + } + } + + if ( !absolute ) + { + ComputeAllCorrectedPositionsFromActualPositions(); + } + + return pDelta; +} + + +//----------------------------------------------------------------------------- +// TODO: Uncorrect all superior states and then correct them afterwards +//----------------------------------------------------------------------------- +bool CDmeMesh::DeleteDeltaState( const char *pDeltaName ) +{ + const int nDeltaIndex = FindDeltaStateIndex( pDeltaName ); + if ( nDeltaIndex < 0 ) + return false; + + Assert( m_DeltaStates.Count() == m_DeltaStateWeights[ MESH_DELTA_WEIGHT_NORMAL ].Count() ); + Assert( m_DeltaStates.Count() == m_DeltaStateWeights[ MESH_DELTA_WEIGHT_LAGGED ].Count() ); + CDmeVertexDeltaData *pDelta( m_DeltaStates[ nDeltaIndex ] ); + if ( !pDelta ) + return false; + + m_DeltaStates.Remove( nDeltaIndex ); + m_DeltaStateWeights[ MESH_DELTA_WEIGHT_NORMAL ].Remove( nDeltaIndex ); + m_DeltaStateWeights[ MESH_DELTA_WEIGHT_LAGGED ].Remove( nDeltaIndex ); + g_pDataModel->DestroyElement( pDelta->GetHandle() ); + + const static UtlSymId_t symTargets = g_pDataModel->GetSymbol( "targets" ); + CDmeCombinationOperator *pCombo( FindReferringElement< CDmeCombinationOperator >( this, symTargets ) ); + if ( pCombo ) + { + pCombo->Purge(); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// TODO: Uncorrect all superior states and then correct them afterwards +//----------------------------------------------------------------------------- +bool CDmeMesh::ResetDeltaState( const char *pDeltaName ) +{ + const int nDeltaIndex = FindDeltaStateIndex( pDeltaName ); + if ( nDeltaIndex < 0 ) + return false; + + CDmeVertexDeltaData *pOldDelta = m_DeltaStates[ nDeltaIndex ]; + CDmeVertexDeltaData *pNewDelta = CreateElement< CDmeVertexDeltaData >( pOldDelta->GetName(), GetFileId() ); + if ( !pNewDelta ) + return false; + + m_DeltaStates.Set( nDeltaIndex, pNewDelta ); + g_pDataModel->DestroyElement( pOldDelta->GetHandle() ); + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +class CSelectionHelper +{ +public: + class CVert + { + public: + int m_index; + int m_count; + float m_weight; + }; + + void AddVert( int vIndex, float weight = 1.0f ); + + int AddToSelection( CDmeSingleIndexedComponent *pSelection ) const; + + int RemoveFromSelection( CDmeSingleIndexedComponent *pSelection, bool bAllowEmpty ) const; + +protected: + CUtlVector< CVert > m_verts; + + int BinarySearch( int component ) const; +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CSelectionHelper::AddVert( int vIndex, float weight /* = 1.0f */ ) +{ + // Find the vertex, add it if necessary + const int index = BinarySearch( vIndex ); + + if ( index == m_verts.Count() ) + { + // New Add to end + CVert &v( m_verts[ m_verts.AddToTail() ] ); + v.m_index = vIndex; + v.m_count = 1; + v.m_weight = weight; + } + else if ( vIndex == m_verts[ index ].m_index ) + { + // Existing, increment + CVert &v( m_verts[ index ] ); + Assert( v.m_index == vIndex ); + v.m_count += 1; + v.m_weight += weight; + } + else + { + // New insert before index + CVert &v( m_verts[ m_verts.InsertBefore( index ) ] ); + v.m_index = vIndex; + v.m_count = 1; + v.m_weight = weight; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CSelectionHelper::AddToSelection( CDmeSingleIndexedComponent *pSelection ) const +{ + const int nVerts = m_verts.Count(); + + for ( int i = 0; i < nVerts; ++i ) + { + const CVert &v( m_verts[ i ] ); + Assert( !pSelection->HasComponent( v.m_index ) ); + pSelection->AddComponent( v.m_index, v.m_weight / static_cast< float >( v.m_count ) ); + } + + return nVerts; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CSelectionHelper::RemoveFromSelection( CDmeSingleIndexedComponent *pSelection, bool bAllowEmpty ) const +{ + const int nVerts = m_verts.Count(); + int nVertsRemovedCount = 0; + + for ( int i = 0; i < nVerts; ++i ) + { + const CVert &v( m_verts[ i ] ); + if ( bAllowEmpty || pSelection->Count() > 1 ) + { + pSelection->RemoveComponent( v.m_index ); + ++nVertsRemovedCount; + } + } + + return nVertsRemovedCount; +} + + +//----------------------------------------------------------------------------- +// Searches for the component in the sorted component list and returns the +// index if it's found or if it's not found, returns the index at which it +// should be inserted to maintain the sorted order of the component list +//----------------------------------------------------------------------------- +int CSelectionHelper::BinarySearch( int vIndex ) const +{ + const int nVerts( m_verts.Count() ); + + int left( 0 ); + int right( nVerts - 1 ); + int mid; + + while ( left <= right ) + { + mid = ( left + right ) >> 1; // floor( ( left + right ) / 2.0 ) + if ( vIndex > m_verts[ mid ].m_index ) + { + left = mid + 1; + } + else if ( vIndex < m_verts[ mid ].m_index ) + { + right = mid - 1; + } + else + { + return mid; + } + } + + return left; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::GrowSelection( int nSize, CDmeSingleIndexedComponent *pSelection, CDmMeshComp *pPassedMeshComp ) +{ + if ( nSize <= 0 || !pSelection ) + return; + + CUtlVector< int > sIndices; + CUtlVector< float > sWeights; + pSelection->GetComponents( sIndices, sWeights ); + const int nVertices = sIndices.Count(); + + CDmMeshComp *pMeshComp = pPassedMeshComp ? pPassedMeshComp : new CDmMeshComp( this ); + + CUtlVector< CDmMeshComp::CVert * > neighbours; + + CSelectionHelper sHelper; + + for ( int i = 0; i < nVertices; ++i ) + { + const int nNeighbours = pMeshComp->FindNeighbouringVerts( sIndices[ i ], neighbours ); + for ( int j = 0; j < nNeighbours; ++j ) + { + CDmMeshComp::CVert *pNeighbour = neighbours[ j ]; + Assert( pNeighbour ); + if ( pNeighbour ) + { + const int vIndex = pNeighbour->PositionIndex(); + if ( !pSelection->HasComponent( vIndex ) ) + { + sHelper.AddVert( vIndex, sWeights[ i ] ); + } + } + } + } + + if ( sHelper.AddToSelection( pSelection ) > 0 ) + { + GrowSelection( nSize - 1, pSelection, pMeshComp ); + } + + if ( pMeshComp != pPassedMeshComp ) + { + delete pMeshComp; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::ShrinkSelection( int nSize, CDmeSingleIndexedComponent *pSelection, CDmMeshComp *pPassedMeshComp ) +{ + if ( nSize <= 0 || !pSelection ) + return; + + CUtlVector< int > sIndices; + CUtlVector< float > sWeights; + pSelection->GetComponents( sIndices, sWeights ); + const int nVertices = sIndices.Count(); + + CDmMeshComp *pMeshComp = pPassedMeshComp ? pPassedMeshComp : new CDmMeshComp( this ); + + CUtlVector< CDmMeshComp::CVert * > neighbours; + + CSelectionHelper sHelper; + + for ( int i = 0; i < nVertices; ++i ) + { + bool hasSelectedNeighbour = false; + bool hasUnselectedNeighbour = false; + + const int vIndex = sIndices[ i ]; + const int nNeighbours = pMeshComp->FindNeighbouringVerts( vIndex, neighbours ); + for ( int j = 0; j < nNeighbours; ++j ) + { + const int nvIndex = neighbours[ j ]->PositionIndex(); + if ( pSelection->HasComponent( nvIndex ) ) + { + hasSelectedNeighbour = true; + if ( hasUnselectedNeighbour ) + { + sHelper.AddVert( vIndex ); + break; + } + } + else + { + hasUnselectedNeighbour = true; + if ( hasSelectedNeighbour ) + { + sHelper.AddVert( vIndex ); + break; + } + } + } + } + + if ( sHelper.RemoveFromSelection( pSelection, false ) > 0 ) + { + ShrinkSelection( nSize - 1, pSelection, pMeshComp ); + } + + if ( pMeshComp != pPassedMeshComp ) + { + delete pMeshComp; + } +} + + +CDmeSingleIndexedComponent *CDmeMesh::FeatherSelection( + float falloffDistance, + Falloff_t falloffType, + Distance_t distanceType, + CDmeSingleIndexedComponent *pSelection, + CDmMeshComp *pPassedMeshComp ) +{ + switch ( falloffType ) + { + case SMOOTH: + return FeatherSelection< SMOOTH >( falloffDistance, distanceType, pSelection, pPassedMeshComp ); + case SPIKE: + return FeatherSelection< SPIKE >( falloffDistance, distanceType, pSelection, pPassedMeshComp ); + case DOME: + return FeatherSelection< DOME >( falloffDistance, distanceType, pSelection, pPassedMeshComp ); + default: + return FeatherSelection< LINEAR >( falloffDistance, distanceType, pSelection, pPassedMeshComp ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < int T > +CDmeSingleIndexedComponent *CDmeMesh::FeatherSelection( + float fDistance, Distance_t distanceType, + CDmeSingleIndexedComponent *pSelection, CDmMeshComp *pPassedMeshComp ) +{ + // TODO: Support feathering inward instead of just outward + if ( fDistance <= 0.0f || !pSelection ) + return NULL; + + // Make a new CDmeSingleIndexedComponent to do all of the dirty work + CDmeSingleIndexedComponent *pNewSelection = CreateElement< CDmeSingleIndexedComponent >( "feather", pSelection->GetFileId() ); + pSelection->CopyAttributesTo( pNewSelection ); + + CDmMeshComp *pMeshComp = pPassedMeshComp ? pPassedMeshComp : new CDmMeshComp( this ); + CDmeVertexData *pBase = pMeshComp->BaseState(); + + if ( distanceType == DIST_RELATIVE ) + { + Vector vCenter; + float flRadius; + GetBoundingSphere( vCenter, flRadius, pBase, pSelection ); + fDistance *= flRadius; + } + + const CUtlVector< Vector > &positions( pBase->GetPositionData() ); + const int nPositions = positions.Count(); + + if ( !pBase ) + return NULL; + + CUtlVector< int > sIndices; + + int insideCount = 0; + + CFalloff< T > falloff; + + do + { + insideCount = 0; + CUtlVector< CDmMeshComp::CVert * > neighbours; + CSelectionHelper sHelper; + + pNewSelection->GetComponents( sIndices ); + int nVertices = sIndices.Count(); + + for ( int i = 0; i < nVertices; ++i ) + { + const int nNeighbours = pMeshComp->FindNeighbouringVerts( sIndices[ i ], neighbours ); + + for ( int j = 0; j < nNeighbours; ++j ) + { + const int vIndex = neighbours[ j ]->PositionIndex(); + + if ( pNewSelection->HasComponent( vIndex ) ) + continue; + + const int closestVert = ClosestSelectedVertex( vIndex, pSelection, pBase ); + if ( closestVert < 0 || closestVert >= nPositions ) + continue; + + const float vDistance = positions[ vIndex ].DistTo( positions[ closestVert ] ); + if ( vDistance <= fDistance ) + { + sHelper.AddVert( vIndex, falloff( vDistance / fDistance ) ); + ++insideCount; + } + } + } + + sHelper.AddToSelection( pNewSelection ); + + } while ( insideCount > 0 ); + + return pNewSelection; +} + + +//----------------------------------------------------------------------------- +// Add the specified delta, scaled by the weight value to the DmeVertexData +// base state specified. Optionally the add can be masked by a specified +// weight map. +// +// If a DmeVertexData is not explicitly specified, the current state of the +// mesh is modified unless it's the bind state. The bind state will never +// be modified even if it is explicitly specified. +// +// Only the delta specified is added. No dependent states are added. +//----------------------------------------------------------------------------- +bool CDmeMesh::AddMaskedDelta( + CDmeVertexDeltaData *pDelta, + CDmeVertexData *pDst /* = NULL */, + float weight /* = 1.0f */, + const CDmeSingleIndexedComponent *pMask /* = NULL */ ) +{ + CDmeVertexData *pBase = pDst ? pDst : GetCurrentBaseState(); + + if ( !pBase || pBase == GetBindBaseState() ) + return false; + + bool retVal = true; + + const int nBaseField( pBase->FieldCount() ); + const int nDeltaField( pDelta->FieldCount() ); + + // Try to add every field of the base state + for ( int j( 0 ); j < nBaseField; ++j ) + { + const CUtlString &baseFieldName( pBase->FieldName( j ) ); + + // Find the corresponding field in the delta + for ( int k( 0 ); k < nDeltaField; ++k ) + { + const CUtlString &deltaFieldName( pDelta->FieldName( k ) ); + + if ( baseFieldName != deltaFieldName ) + continue; + + const FieldIndex_t baseFieldIndex( pBase->FindFieldIndex( baseFieldName ) ); + const FieldIndex_t deltaFieldIndex( pDelta->FindFieldIndex( deltaFieldName ) ); + if ( baseFieldIndex < 0 || deltaFieldIndex < 0 ) + break; + + CDmAttribute *pBaseData( pBase->GetVertexData( baseFieldIndex ) ); + CDmAttribute *pDeltaData( pDelta->GetVertexData( deltaFieldIndex ) ); + + if ( pBaseData->GetType() != pDeltaData->GetType() ) + break; + + switch ( pBaseData->GetType() ) + { + case AT_FLOAT_ARRAY: + AddRawDelta( pDelta, CDmrArray< float >( pBaseData ), baseFieldIndex, weight, pMask ); + break; + case AT_COLOR_ARRAY: + // TODO: Color is missing some algebraic operators +// AddRawDelta( pDelta, CDmrArray< Color >( pBaseData ), baseFieldIndex, weight, pMask ); + break; + case AT_VECTOR2_ARRAY: + AddRawDelta( pDelta, CDmrArray< Vector2D >( pBaseData ), baseFieldIndex, weight, pMask ); + break; + case AT_VECTOR3_ARRAY: + AddRawDelta( pDelta, CDmrArray< Vector >( pBaseData ), baseFieldIndex, weight, pMask ); + break; + default: + break; + } + break; + } + } + + return retVal; +} + + +//----------------------------------------------------------------------------- +// Add the specified delta, scaled by the weight value to the DmeVertexData +// base state specified. Optionally the add can be masked by a specified +// weight map. +// +// If a DmeVertexData is not explicitly specified, the current state of the +// mesh is modified unless it's the bind state. The bind state will never +// be modified even if it is explicitly specified. +// +// Only the delta specified is added. No dependent states are added. +//----------------------------------------------------------------------------- +bool CDmeMesh::AddCorrectedMaskedDelta( + CDmeVertexDeltaData *pDelta, + CDmeVertexData *pDst /* = NULL */, + float weight /* = 1.0f */, + const CDmeSingleIndexedComponent *pMask /* = NULL */ ) +{ + CDmeVertexData *pBase = pDst ? pDst : GetCurrentBaseState(); + + if ( !pBase || pBase == GetBindBaseState() ) + return false; + + bool retVal = true; + + const int nBaseField( pBase->FieldCount() ); + const int nDeltaField( pDelta->FieldCount() ); + + // This should be cached and recomputed only when states are added + CUtlVector< DeltaComputation_t > compList; + ComputeDependentDeltaStateList( compList ); + + const int nDeltas( compList.Count() ); + for ( int i = 0; i < nDeltas; ++i ) + { + if ( pDelta != GetDeltaState( compList[ i ].m_nDeltaIndex ) ) + continue; + + // Try to add every field of the base state + for ( int j( 0 ); j < nBaseField; ++j ) + { + const CUtlString &baseFieldName( pBase->FieldName( j ) ); + + // Find the corresponding field in the delta + for ( int k( 0 ); k < nDeltaField; ++k ) + { + const CUtlString &deltaFieldName( pDelta->FieldName( k ) ); + + if ( baseFieldName != deltaFieldName ) + continue; + + const FieldIndex_t baseFieldIndex( pBase->FindFieldIndex( baseFieldName ) ); + const FieldIndex_t deltaFieldIndex( pDelta->FindFieldIndex( deltaFieldName ) ); + if ( baseFieldIndex < 0 || deltaFieldIndex < 0 ) + break; + + CDmAttribute *pBaseData( pBase->GetVertexData( baseFieldIndex ) ); + CDmAttribute *pDeltaData( pDelta->GetVertexData( deltaFieldIndex ) ); + + if ( pBaseData->GetType() != pDeltaData->GetType() ) + break; + + const CUtlVector< int > &baseIndices( pBase->GetVertexIndexData( baseFieldIndex ) ); + + switch ( pBaseData->GetType() ) + { + case AT_FLOAT_ARRAY: + AddCorrectedDelta( CDmrArray< float >( pBaseData ), baseIndices, compList[ i ], baseFieldName, weight, pMask ); + break; + case AT_COLOR_ARRAY: + AddCorrectedDelta( CDmrArray< Vector >( pBaseData ), baseIndices, compList[ i ], baseFieldName, weight, pMask ); + break; + case AT_VECTOR2_ARRAY: + AddCorrectedDelta( CDmrArray< Vector2D >( pBaseData ), baseIndices, compList[ i ], baseFieldName, weight, pMask ); + break; + case AT_VECTOR3_ARRAY: + AddCorrectedDelta( CDmrArray< Vector >( pBaseData ), baseIndices, compList[ i ], baseFieldName, weight, pMask ); + break; + default: + break; + } + break; + } + } + } + + return retVal; +} + + +//----------------------------------------------------------------------------- +// Interpolates between two arrays of values and stores the result in a +// CDmrArray. +// +// result = ( ( 1 - weight ) * a ) + ( weight * b ) +// +//----------------------------------------------------------------------------- +template< class T_t > +bool CDmeMesh::InterpMaskedData( + CDmrArray< T_t > &aData, + const CUtlVector< T_t > &bData, + float weight, + const CDmeSingleIndexedComponent *pMask ) const +{ + const int nDst = aData.Count(); + + if ( bData.Count() != nDst ) + return false; + + // The wacky way of writing these expression is because Vector4D is missing operators + // And this probably works better because of fewer temporaries + + T_t a; + T_t b; + + if ( pMask ) + { + // With a weight mask + float vWeight; + for ( int i = 0; i < nDst; ++i ) + { + if ( pMask->GetWeight( i, vWeight ) ) + { + vWeight *= weight; // Specifically not clamping + a = aData.Get( i ); + a *= ( 1.0f - vWeight ); + b = bData[ i ]; + b *= vWeight; + b += a; + aData.Set( i, b ); + } + } + } + else + { + // Without a weight mask + const float oneMinusWeight( 1.0f - weight ); + for ( int i = 0; i < nDst; ++i ) + { + a = aData.Get( i ); + a *= oneMinusWeight; + b = bData[ i ]; + b *= weight; + b += a; + aData.Set( i, b ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Interpolates between two CDmeVertexData's +// +// paData = ( ( 1 - weight ) * a ) + ( weight * b ) +//----------------------------------------------------------------------------- +bool CDmeMesh::InterpMaskedData( + CDmeVertexData *paData, + const CDmeVertexData *pbData, + float weight, + const CDmeSingleIndexedComponent *pMask ) const +{ + if ( !paData || !pbData || paData == pbData ) + return false; + + const int naField = paData->FieldCount(); + const int nbField = pbData->FieldCount(); + + for ( int i = 0; i < naField; ++i ) + { + const CUtlString &aFieldName( paData->FieldName( i ) ); + + for ( int j = 0; j < nbField; ++j ) + { + const CUtlString &bFieldName( pbData->FieldName( j ) ); + if ( aFieldName != bFieldName ) + continue; + + const FieldIndex_t aFieldIndex( paData->FindFieldIndex( aFieldName ) ); + const FieldIndex_t bFieldIndex( pbData->FindFieldIndex( bFieldName ) ); + + if ( aFieldIndex < 0 || bFieldIndex < 0 ) + break; + + CDmAttribute *paAttr( paData->GetVertexData( aFieldIndex ) ); + const CDmAttribute *pbAttr( pbData->GetVertexData( bFieldIndex ) ); + + if ( paAttr->GetType() != pbAttr->GetType() ) + break; + + if ( paData->GetVertexIndexData( aFieldIndex ).Count() != pbData->GetVertexIndexData( bFieldIndex ).Count() ) + break; + + switch ( paAttr->GetType() ) + { + case AT_FLOAT_ARRAY: + InterpMaskedData( CDmrArray< float >( paAttr ), CDmrArrayConst< float >( pbAttr ).Get(), weight, pMask ); + break; + case AT_COLOR_ARRAY: + InterpMaskedData( CDmrArray< Vector4D >( paAttr ), CDmrArrayConst< Vector4D >( pbAttr ).Get(), weight, pMask ); + break; + case AT_VECTOR2_ARRAY: + InterpMaskedData( CDmrArray< Vector2D >( paAttr ), CDmrArrayConst< Vector2D >( pbAttr ).Get(), weight, pMask ); + break; + case AT_VECTOR3_ARRAY: + InterpMaskedData( CDmrArray< Vector >( paAttr ), CDmrArrayConst< Vector >( pbAttr ).Get(), weight, pMask ); + break; + default: + break; + } + break; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Interpolates between the specified VertexData and the specified Delta +// If pBase is NULL it will become the current state +// If pDelta is NULL then the state to interpolate to will be the bind state +//----------------------------------------------------------------------------- +bool CDmeMesh::InterpMaskedDelta( + CDmeVertexDeltaData *pDelta, + CDmeVertexData *pDst /* = NULL */, + float weight /*= 1.0f */, + const CDmeSingleIndexedComponent *pMask /*= NULL */ ) +{ + CDmeVertexData *pDstBase = pDst ? pDst : GetCurrentBaseState(); + CDmeVertexData *pBind = GetBindBaseState(); + + if ( !pDstBase || !pBind || pDstBase == pBind ) + return false; + + if ( pDelta == NULL ) + { + // Interpolate between specified state and bind state + return InterpMaskedData( pDstBase, pBind, weight, pMask ); + } + + // This should be cached and recomputed only when states are added + CUtlVector< DeltaComputation_t > compList; + ComputeDependentDeltaStateList( compList ); + + bool retVal = false; + + const int nDeltas( compList.Count() ); + for ( int i = 0; i < nDeltas; ++i ) + { + if ( pDelta != GetDeltaState( compList[ i ].m_nDeltaIndex ) ) + continue; + + retVal = true; + + const int nBaseField( pDstBase->FieldCount() ); + const int nBindField( pBind->FieldCount() ); + const int nDeltaField( pDelta->FieldCount() ); + + CUtlVector< float > floatData; + CUtlVector< Vector2D > vector2DData; + CUtlVector< Vector > vectorData; + CUtlVector< Vector4D > vector4DData; + + for ( int j( 0 ); j < nBaseField; ++j ) + { + const CUtlString &baseFieldName( pDstBase->FieldName( j ) ); + + for ( int k = 0; k < nBindField; ++k ) + { + const CUtlString &bindFieldName( pBind->FieldName( k ) ); + if ( baseFieldName != bindFieldName ) + continue; + + for ( int l = 0; l < nDeltaField; ++l ) + { + const CUtlString &deltaFieldName( pDelta->FieldName( l ) ); + if ( bindFieldName != deltaFieldName ) + continue; + + const FieldIndex_t baseFieldIndex( pDstBase->FindFieldIndex( baseFieldName ) ); + const FieldIndex_t bindFieldIndex( pBind->FindFieldIndex( bindFieldName ) ); + const FieldIndex_t deltaFieldIndex( pDelta->FindFieldIndex( deltaFieldName ) ); + + if ( baseFieldIndex < 0 || bindFieldIndex < 0 || deltaFieldIndex < 0 ) + break; + + CDmAttribute *pDstBaseData( pDstBase->GetVertexData( baseFieldIndex ) ); + CDmAttribute *pBindData( pBind->GetVertexData( bindFieldIndex ) ); + CDmAttribute *pDeltaData( pDelta->GetVertexData( deltaFieldIndex ) ); + + if ( pDstBaseData->GetType() != pBindData->GetType() || pBindData->GetType() != pDeltaData->GetType() ) + break; + + const CUtlVector< int > &bindIndices( pBind->GetVertexIndexData( bindFieldIndex ) ); + + switch ( pDstBaseData->GetType() ) + { + case AT_FLOAT_ARRAY: + floatData = CDmrArrayConst< float >( pBindData ).Get(); + AddCorrectedDelta( floatData, bindIndices, compList[ i ], baseFieldName ); + InterpMaskedData( CDmrArray< float >( pDstBaseData ), floatData, weight, pMask ); + break; + case AT_COLOR_ARRAY: + vector4DData = CDmrArrayConst< Vector4D >( pBindData ).Get(); + AddCorrectedDelta( vector4DData, bindIndices, compList[ i ], baseFieldName ); + InterpMaskedData( CDmrArray< Vector4D >( pDstBaseData ), vector4DData, weight, pMask ); + break; + case AT_VECTOR2_ARRAY: + vector2DData = CDmrArrayConst< Vector2D >( pBindData ).Get(); + AddCorrectedDelta( vector2DData, bindIndices, compList[ i ], baseFieldName ); + InterpMaskedData( CDmrArray< Vector2D >( pDstBaseData ), vector2DData, weight, pMask ); + break; + case AT_VECTOR3_ARRAY: + vectorData = CDmrArrayConst< Vector >( pBindData ).Get(); + AddCorrectedDelta( vectorData, bindIndices, compList[ i ], baseFieldName ); + InterpMaskedData( CDmrArray< Vector >( pDstBaseData ), vectorData, weight, pMask ); + break; + default: + break; + } + break; + } + } + } + } + + return retVal; +} + + +//----------------------------------------------------------------------------- +// Returns the index of the closest selected vertex in the mesh to vIndex +// -1 on failure +//----------------------------------------------------------------------------- +int CDmeMesh::ClosestSelectedVertex( int vIndex, CDmeSingleIndexedComponent *pSelection, const CDmeVertexData *pPassedBase /* = NULL */ ) const +{ + const CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + if ( !pBase ) + return -1; + + const CUtlVector< Vector > &positions( pBase->GetPositionData() ); + + if ( vIndex >= positions.Count() ) + return -1; + + const Vector &p( positions[ vIndex ] ); + + CUtlVector< int > verts; + pSelection->GetComponents( verts ); + const int nVerts = verts.Count(); + + if ( nVerts <= 0 ) + return -1; + + float minSqDist = p.DistToSqr( positions[ verts[ 0 ] ] ); + float tmpSqDist; + + int retVal = verts[ 0 ]; + for ( int i = 1; i < nVerts; ++i ) + { + tmpSqDist = p.DistToSqr( positions[ verts[ i ] ] ); + if ( tmpSqDist < minSqDist ) + { + minSqDist = tmpSqDist; + retVal = verts[ i ]; + } + } + + return retVal; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +float CDmeMesh::DistanceBetween( int vIndex0, int vIndex1, const CDmeVertexData *pPassedBase /*= NULL */ ) const +{ + const CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + if ( !pBase ) + return 0.0f; + + const CUtlVector< Vector > &positions( pBase->GetPositionData() ); + const int nPositions = positions.Count(); + + if ( vIndex0 >= nPositions || vIndex1 >= nPositions ) + return 0.0f; + + return positions[ vIndex0 ].DistTo( positions[ vIndex1 ] ); +} + + +//----------------------------------------------------------------------------- +// Sorts DeltaComputation_t's by dimensionality +//----------------------------------------------------------------------------- +int ControlIndexLessFunc( const void *lhs, const void *rhs ) +{ + const int &lVal = *reinterpret_cast< const int * >( lhs ); + const int &rVal = *reinterpret_cast< const int * >( rhs ); + return lVal - rVal; +} + +//----------------------------------------------------------------------------- +// This will compute a list of delta states that are superior to the passed +// delta state name (which has the form of <NAME>[_<NAME>]..., i.e. controls +// separated by underscores. The states will be returned in order from +// most superior to least superior. Since the deltas need to be broken down +// by the control deltas, if any control delta doesn't exist it will return false. +// +// A superior delta state is defined as a delta which has this delta as +// a dependent (or inferior) delta. +// +// Given the network of: +// +// A, B, C +// A_B, A_C, B_C +// A_B_C +// +// A_B_C is superior to A, B, A_B, A_C & B_C +// A_B is superior to A, B & C +// A_C is superior to A, B & C +// B_C is superior to A, B & C +// +// Input Output +// ------- -------------------- +// A A_B_C, A_B, A_C, B_C +// B A_B_C, A_B, A_C, B_C +// C A_B_C, A_B, A_C, B_C +// A_B A_B_C +// A_C A_B_C +// B_C A_B_C +// A_B_C +//----------------------------------------------------------------------------- +bool CDmeMesh::ComputeSuperiorDeltaStateList( const char *pInferiorDeltaName, CUtlVector< int > &superiorDeltaStates ) +{ + // TODO: Compute this data only when the deltas are added, removed or renamed + CUtlVector< DeltaComputation_t > compList; + ComputeDeltaStateComputationList( compList ); + + // Typically the passed delta won't be in the list yet, but it could be, that's ok + // Treat it like it isn't to be sure. + CUtlVector< int > inferiorIndices; + if ( !GetControlDeltaIndices( pInferiorDeltaName, inferiorIndices ) ) + return false; + + const int nInferiorIndices = inferiorIndices.Count(); + qsort( inferiorIndices.Base(), nInferiorIndices, sizeof( int ), ControlIndexLessFunc ); + + CUtlVector< int > superiorIndices; + int nSuperiorIndices; + CDmeVertexDeltaData *pSuperiorDelta; + + for ( int i = compList.Count() - 1; i >= 0; --i ) + { + const DeltaComputation_t &deltaComp = compList[ i ]; + + // For a delta to be superior, it has to have more control inputs than the specified delta + // compList is sorted in order of dimensionality, so safe to abort + if ( nInferiorIndices >= deltaComp.m_nDimensionality ) + break; + + pSuperiorDelta = GetDeltaState( deltaComp.m_nDeltaIndex ); + if ( !pSuperiorDelta ) + continue; + + if ( !GetControlDeltaIndices( pSuperiorDelta, superiorIndices ) ) + continue; + + nSuperiorIndices = superiorIndices.Count(); + + qsort( superiorIndices.Base(), nSuperiorIndices, sizeof( int ), ControlIndexLessFunc ); + + int nFound = 0; + int si = 0; + for ( int ii = 0; ii < nInferiorIndices; ++ii ) + { + const int &iIndex = inferiorIndices[ ii ]; + while ( si < nSuperiorIndices && iIndex != superiorIndices[ si ] ) + { + ++si; + } + + if ( si < nSuperiorIndices ) + { + ++nFound; + } + } + + if ( nFound == nInferiorIndices ) + { + superiorDeltaStates.AddToTail( deltaComp.m_nDeltaIndex ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Removes the passed base state from the list of base states in the mesh +// if it exists in the list of base states in the mesh, but doesn't delete +// the element itself +//----------------------------------------------------------------------------- +bool CDmeMesh::RemoveBaseState( CDmeVertexData *pBase ) +{ + const int nBaseStates = m_BaseStates.Count(); + for ( int i = 0; i < nBaseStates; ++i ) + { + if ( m_BaseStates[ i ] == pBase ) + { + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Adds an existing element to the list of base states of the mesh if it +// isn't already one of the base states +//----------------------------------------------------------------------------- +CDmeVertexData *CDmeMesh::FindOrAddBaseState( CDmeVertexData *pBase ) +{ + const int nBaseStates = m_BaseStates.Count(); + for ( int i = 0; i < nBaseStates; ++i ) + { + if ( m_BaseStates[ i ] == pBase ) + { + return pBase; + } + } + + return m_BaseStates[ m_BaseStates.AddToTail( pBase ) ]; +} + + +//----------------------------------------------------------------------------- +// TODO: Current state is insufficient as long as the current state isn't +// created from the current delta weights +//----------------------------------------------------------------------------- +void CDmeMesh::GetBoundingSphere( + Vector &c, float &r, + CDmeVertexData *pPassedBase /* = NULL */, CDmeSingleIndexedComponent *pPassedSelection /* = NULL */ ) const +{ + c.Zero(); + r = 0.0f; + + const CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + if ( !pBase ) + return; + + const FieldIndex_t pIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + if ( pIndex < 0 ) + return; + + const CUtlVector< Vector > &pData( pBase->GetPositionData() ); + const int nPositions = pData.Count(); + + if ( pPassedSelection ) + { + const int nSelectionCount = pPassedSelection->Count(); + int nIndex; + float fWeight; + for ( int i = 0; i < nSelectionCount; ++i ) + { + pPassedSelection->GetComponent( i, nIndex, fWeight ); + c += pData[ nIndex ]; + } + + c /= static_cast< float >( nSelectionCount ); + + float sqDist; + for ( int i = 0; i < nSelectionCount; ++i ) + { + for ( int iPos = 0; iPos < nPositions; ++iPos ) + { + sqDist = c.DistToSqr( pData[ iPos ] ); + if ( sqDist > r ) + { + r = sqDist; + } + } + } + } + else + { + for ( int i = 0; i < nPositions; ++i ) + { + c += pData[ i ]; + } + + c /= static_cast< float >( nPositions ); + + float sqDist; + for ( int i = 0; i < nPositions; ++i ) + { + for ( int iPos = 0; iPos < nPositions; ++iPos ) + { + sqDist = c.DistToSqr( pData[iPos] ); + if ( sqDist > r ) + { + r = sqDist; + } + } + } + } + + r = sqrt( r ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::GetBoundingBox( Vector &min, Vector &max, CDmeVertexData *pPassedBase /* = NULL */, CDmeSingleIndexedComponent *pPassedSelection /* = NULL */ ) const +{ + min.Zero(); + max.Zero(); + + const CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + if ( !pBase ) + return; + + const FieldIndex_t pIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + if ( pIndex < 0 ) + return; + + const CUtlVector< Vector > &pData( pBase->GetPositionData() ); + const int nPositions = pData.Count(); + + if ( pPassedSelection ) + { + const int nSelectionCount = pPassedSelection->Count(); + + if ( nSelectionCount > 0 ) + { + int nIndex; + float fWeight; + + pPassedSelection->GetComponent( 0, nIndex, fWeight ); + min = pData[ nIndex ]; + max = min; + + for ( int i = 1; i < nSelectionCount; ++i ) + { + pPassedSelection->GetComponent( i, nIndex, fWeight ); + + const Vector &p = pData[ nIndex ]; + if ( p.x < min.x ) + { + min.x = p.x; + } + else if ( p.x > max.x ) + { + max.x = p.x; + } + + if ( p.y < min.y ) + { + min.y = p.y; + } + else if ( p.y > max.y ) + { + max.y = p.y; + } + + if ( p.z < min.z ) + { + min.z = p.z; + } + else if ( p.z > max.z ) + { + max.z = p.z; + } + } + } + } + else + { + if ( nPositions > 0 ) + { + min = pData[ 0 ]; + max = min; + + for ( int i = 1; i < nPositions; ++i ) + { + const Vector &p = pData[ i ]; + if ( p.x < min.x ) + { + min.x = p.x; + } + else if ( p.x > max.x ) + { + max.x = p.x; + } + + if ( p.y < min.y ) + { + min.y = p.y; + } + else if ( p.y > max.y ) + { + max.y = p.y; + } + + if ( p.z < min.z ) + { + min.z = p.z; + } + else if ( p.z > max.z ) + { + max.z = p.z; + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +bool CDmeMesh::SetBaseDataToDeltas( + CDmeVertexData *pBase, + CDmeVertexData::StandardFields_t nStandardField, CDmrArrayConst< T_t > &srcData, CDmrArray< T_t > &dstData, bool bDoStereo, bool bDoLag ) +{ + const int nDataCount = dstData.Count(); + if ( srcData.Count() != nDataCount ) + return false; + + // Create the temp buffer for the data + T_t *pData = reinterpret_cast< T_t * >( alloca( nDataCount * sizeof( T_t ) ) ); + + // Copy the data from the src base state + memcpy( pData, srcData.Base(), nDataCount * sizeof( T_t ) ); + + const int nCount = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL].Count(); + Assert( m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL].Count() == m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED].Count() ); + + if ( bDoStereo ) + { + for ( int i = 0; i < nCount; ++i ) + { + float flLeftWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].x; + float flRightWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].y; + float flLeftWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].x; + float flRightWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].y; + if ( flLeftWeight <= 0.0f && flRightWeight <= 0.0f && ( !bDoLag || ( flLeftWeightLagged <= 0.0f && flRightWeightLagged <= 0.0f ) ) ) + continue; + + AddStereoVertexDelta< T_t >( pBase, pData, sizeof( T_t ), nStandardField, i, bDoLag ); + } + } + else + { + for ( int i = 0; i < nCount; ++i ) + { + float flWeight = m_DeltaStateWeights[MESH_DELTA_WEIGHT_NORMAL][i].x; + float flWeightLagged = m_DeltaStateWeights[MESH_DELTA_WEIGHT_LAGGED][i].x; + if ( flWeight < 0.0f && ( !bDoLag || flWeightLagged <= 0.0f ) ) + continue; + + AddVertexDelta< T_t >( pBase, pData, sizeof( T_t ), nStandardField, i, bDoLag ); + } + } + + dstData.SetMultiple( 0, nDataCount, pData ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Sets the specified based state to the version of the mesh specified by the +// current weighted deltas +// It's ok to modify the bind state... if you know what you're doing +//----------------------------------------------------------------------------- +bool CDmeMesh::SetBaseStateToDeltas( CDmeVertexData *pPassedBase /*= NULL */ ) +{ + CDmeVertexData *pBind = GetBindBaseState(); + CDmeVertexData *pBase = pPassedBase ? pPassedBase : GetCurrentBaseState(); + + if ( !pBind || !pBase ) + return false; + + CDmeVertexData::StandardFields_t deltaFields[] = + { + CDmeVertexData::FIELD_POSITION, + CDmeVertexData::FIELD_NORMAL, + CDmeVertexData::FIELD_WRINKLE + }; + + const bool bDoStereo = ( pBind->FindFieldIndex( CDmeVertexDeltaData::FIELD_BALANCE ) >= 0 ); + + for ( int i = 0; i < sizeof( deltaFields ) / sizeof( deltaFields[ 0 ] ); ++i ) + { + const CDmeVertexDeltaData::StandardFields_t nStandardField = deltaFields[ i ]; + const int nSrcField = pBind->FindFieldIndex( nStandardField ); + const int nDstField = pBase->FindFieldIndex( nStandardField ); + if ( nSrcField < 0 || nDstField < 0 ) + continue; + + const CDmAttribute *pSrcAttr = pBind->GetVertexData( nSrcField ); + CDmAttribute *pDstAttr = pBase->GetVertexData( nDstField ); + if ( !pSrcAttr || !pDstAttr || pSrcAttr->GetType() != pDstAttr->GetType() ) + continue; + + switch ( pDstAttr->GetType() ) + { + case AT_FLOAT_ARRAY: + SetBaseDataToDeltas( pBind, nStandardField, CDmrArrayConst< float >( pSrcAttr ), CDmrArray< float >( pDstAttr ), bDoStereo, false ); + break; + case AT_VECTOR3_ARRAY: + SetBaseDataToDeltas( pBind, nStandardField, CDmrArrayConst< Vector >( pSrcAttr ), CDmrArray< Vector >( pDstAttr ), bDoStereo, false ); + break; + default: + Assert( 0 ); + break; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Replace all instances of a material with a different material +//----------------------------------------------------------------------------- +void CDmeMesh::ReplaceMaterial( const char *pOldMaterialName, const char *pNewMaterialName ) +{ + char pOldFixedName[MAX_PATH]; + char pNewFixedName[MAX_PATH]; + char pFixedName[MAX_PATH]; + if ( pOldMaterialName ) + { + V_FixupPathName( pOldFixedName, sizeof(pOldFixedName), pOldMaterialName ); + } + V_FixupPathName( pNewFixedName, sizeof(pNewFixedName), pNewMaterialName ); + V_FixSlashes( pNewFixedName, '/' ); + + CDmeMaterial *pReplacementMaterial = NULL; + + int nCount = m_FaceSets.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmeFaceSet *pFaceSet = m_FaceSets[i]; + CDmeMaterial *pMaterial = pFaceSet->GetMaterial(); + if ( pOldMaterialName ) + { + const char *pMaterialName = pMaterial->GetMaterialName(); + V_FixupPathName( pFixedName, sizeof(pFixedName), pMaterialName ); + if ( Q_stricmp( pFixedName, pOldFixedName ) ) + continue; + } + + if ( !pReplacementMaterial ) + { + pReplacementMaterial = CreateElement< CDmeMaterial >( pMaterial->GetName(), pMaterial->GetFileId() ); + pReplacementMaterial->SetMaterial( pNewFixedName ); + } + pFaceSet->SetMaterial( pReplacementMaterial ); + } +} + + +//----------------------------------------------------------------------------- +// Cleans up delta data that is referring to normals which have been merged out +//----------------------------------------------------------------------------- +static void CollapseRedundantDeltaNormals( CDmeVertexDeltaData *pDmeDelta, const CUtlVector< int > &normalMap ) +{ + if ( !pDmeDelta ) + return; + + FieldIndex_t nNormalFieldIndex = pDmeDelta->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + if ( nNormalFieldIndex < 0 ) + return; // No normal deltas + + const CUtlVector< Vector > &oldNormalData = pDmeDelta->GetNormalData(); + const CUtlVector< int > &oldNormalIndices = pDmeDelta->GetVertexIndexData( nNormalFieldIndex ); + + Assert( oldNormalData.Count() == oldNormalIndices.Count() ); + + CUtlVector< bool > done; + done.SetCount( normalMap.Count() ); + Q_memset( done.Base(), 0, done.Count() * sizeof( bool ) ); + + CUtlVector< Vector > newNormalData; + CUtlVector< int > newNormalIndices; + + for ( int i = 0; i < oldNormalIndices.Count(); ++i ) + { + const int nNewIndex = normalMap[ oldNormalIndices[i] ]; + if ( nNewIndex < 0 || done[ nNewIndex ] ) + continue; + + done[ nNewIndex ] = true; + newNormalData.AddToTail( oldNormalData[i] ); + newNormalIndices.AddToTail( nNewIndex ); + } + + pDmeDelta->RemoveAllVertexData( nNormalFieldIndex ); + nNormalFieldIndex = pDmeDelta->CreateField( CDmeVertexDeltaData::FIELD_NORMAL ); + pDmeDelta->AddVertexData( nNormalFieldIndex, newNormalData.Count() ); + pDmeDelta->SetVertexData( nNormalFieldIndex, 0, newNormalData.Count(), AT_VECTOR3, newNormalData.Base() ); + pDmeDelta->SetVertexIndices( nNormalFieldIndex, 0, newNormalIndices.Count(), newNormalIndices.Base() ); +} + + +//----------------------------------------------------------------------------- +// Remove redundant normals from a DMX Mesh +// Looks at all of the normals around each position vertex and merges normals +// which are numerically similar (within flNormalBlend which by default in +// studiomdl is within 2 degrees) around that vertex +// +// If this would result in more normals being created, then don't do anything +// return false. +//----------------------------------------------------------------------------- +static bool CollapseRedundantBaseNormals( CDmeVertexData *pDmeVertexData, CUtlVector< int > &normalMap, float flNormalBlend ) +{ + if ( !pDmeVertexData ) + return false; + + FieldIndex_t nPositionFieldIndex = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + FieldIndex_t nNormalFieldIndex = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + if ( nPositionFieldIndex < 0 || nNormalFieldIndex < 0 ) + return false; + + const CUtlVector< Vector > &oldNormalData = pDmeVertexData->GetNormalData(); + const CUtlVector< int > &oldNormalIndices = pDmeVertexData->GetVertexIndexData( nNormalFieldIndex ); + + CUtlVector< Vector > newNormalData; + CUtlVector< int > newNormalIndices; + + newNormalIndices.SetCount( oldNormalIndices.Count() ); + for ( int i = 0; i < newNormalIndices.Count(); ++i ) + { + newNormalIndices[i] = -1; + } + + const int nPositionDataCount = pDmeVertexData->GetPositionData().Count(); + for ( int i = 0; i < nPositionDataCount; ++i ) + { + int nNewNormalDataIndex = newNormalData.Count(); + + const CUtlVector< int > &vertexIndices = pDmeVertexData->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, i ); + for ( int j = 0; j < vertexIndices.Count(); ++j ) + { + bool bUnique = true; + const int nVertexIndex = vertexIndices[j]; + const Vector &vNormal = oldNormalData[ oldNormalIndices[ vertexIndices[j] ] ]; + + for ( int k = nNewNormalDataIndex; k < newNormalData.Count(); ++k ) + { + if ( DotProduct( vNormal, newNormalData[k] ) > flNormalBlend ) + { + newNormalIndices[ nVertexIndex ] = k; + bUnique = false; + break; + } + } + + if ( !bUnique ) + continue; + + newNormalIndices[ nVertexIndex ] = newNormalData.AddToTail( vNormal ); + } + } + + for ( int i = 0; i < newNormalIndices.Count(); ++i ) + { + if ( newNormalIndices[i] == -1 ) + { + newNormalIndices[i] = newNormalData.AddToTail( oldNormalData[ oldNormalIndices[i] ] ); + } + } + + // If it's the same or more don't do anything + if ( newNormalData.Count() >= oldNormalData.Count() ) + return false; + + normalMap.SetCount( oldNormalData.Count() ); + for ( int i = 0; i < normalMap.Count(); ++i ) + { + normalMap[i] = -1; + } + + Assert( newNormalIndices.Count() == oldNormalIndices.Count() ); + for ( int i = 0; i < oldNormalIndices.Count(); ++i ) + { + if ( normalMap[ oldNormalIndices[i] ] == -1 ) + { + normalMap[ oldNormalIndices[i] ] = newNormalIndices[i]; + } + else + { + Assert( normalMap[ oldNormalIndices[i] ] == newNormalIndices[i] ); + } + } + + pDmeVertexData->RemoveAllVertexData( nNormalFieldIndex ); + nNormalFieldIndex = pDmeVertexData->CreateField( CDmeVertexDeltaData::FIELD_NORMAL ); + pDmeVertexData->AddVertexData( nNormalFieldIndex, newNormalData.Count() ); + pDmeVertexData->SetVertexData( nNormalFieldIndex, 0, newNormalData.Count(), AT_VECTOR3, newNormalData.Base() ); + pDmeVertexData->SetVertexIndices( nNormalFieldIndex, 0, newNormalIndices.Count(), newNormalIndices.Base() ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Collapse all normals with the same numerical value into the same normal +//----------------------------------------------------------------------------- +static bool CollapseRedundantBaseNormalsAggressive( CDmeVertexData *pDmeVertexData, float flNormalBlend ) +{ + if ( !pDmeVertexData ) + return false; + + FieldIndex_t nNormalFieldIndex = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + if ( nNormalFieldIndex < 0 ) + return false; + + const CUtlVector< Vector > &oldNormalData = pDmeVertexData->GetNormalData(); + const CUtlVector< int > &oldNormalIndices = pDmeVertexData->GetVertexIndexData( nNormalFieldIndex ); + + CUtlVector< int > normalMap; + normalMap.SetCount( oldNormalData.Count() ); + + CUtlVector< Vector > newNormalData; + + for ( int i = 0; i < oldNormalData.Count(); ++i ) + { + bool bUnique = true; + const Vector &vNormal = oldNormalData[ i ]; + + for ( int j = 0; j < newNormalData.Count(); ++j ) + { + if ( DotProduct( vNormal, newNormalData[j] ) > flNormalBlend ) + { + normalMap[ i ] = j; + bUnique = false; + break; + } + } + + if ( !bUnique ) + continue; + + normalMap[ i ] = newNormalData.AddToTail( vNormal ); + } + + // If it's the same then don't do anything. + if ( newNormalData.Count() >= oldNormalData.Count() ) + return false; + + CUtlVector< int > newNormalIndices; + newNormalIndices.SetCount( oldNormalIndices.Count() ); + + for ( int i = 0; i < oldNormalIndices.Count(); ++i ) + { + newNormalIndices[i] = normalMap[ oldNormalIndices[i] ]; + } + + pDmeVertexData->RemoveAllVertexData( nNormalFieldIndex ); + nNormalFieldIndex = pDmeVertexData->CreateField( CDmeVertexDeltaData::FIELD_NORMAL ); + pDmeVertexData->AddVertexData( nNormalFieldIndex, newNormalData.Count() ); + pDmeVertexData->SetVertexData( nNormalFieldIndex, 0, newNormalData.Count(), AT_VECTOR3, newNormalData.Base() ); + pDmeVertexData->SetVertexIndices( nNormalFieldIndex, 0, newNormalIndices.Count(), newNormalIndices.Base() ); + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::NormalizeNormals() +{ + Vector vNormal; + + for ( int i = 0; i < this->BaseStateCount(); ++i ) + { + CDmeVertexData *pDmeVertexData = GetBaseState( i ); + if ( !pDmeVertexData ) + continue; + + FieldIndex_t nNormalIndex = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + if ( nNormalIndex < 0 ) + continue; + + CDmAttribute *pDmNormalAttr = pDmeVertexData->GetVertexData( nNormalIndex ); + if ( !pDmNormalAttr ) + continue; + + CDmrArray< Vector > normalData( pDmNormalAttr ); + for ( int j = 0; j < normalData.Count(); ++j ) + { + vNormal = normalData.Get( j ); + VectorNormalize( vNormal ); + normalData.Set( j, vNormal ); + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeMesh::CollapseRedundantNormals( float flNormalBlend ) +{ + NormalizeNormals(); + + CDmeVertexData *pDmeBind = GetBindBaseState(); + if ( !pDmeBind ) + return; + + CUtlVector< int > normalMap; + + const int nDeltaStateCount = DeltaStateCount(); + if ( nDeltaStateCount <= 0 ) + { + // No deltas + if ( CollapseRedundantBaseNormalsAggressive( pDmeBind, flNormalBlend ) ) + { + // Collapse any other states + for ( int i = 0; i < BaseStateCount(); ++i ) + { + CDmeVertexData *pDmeVertexData = GetBaseState( i ); + if ( !pDmeVertexData || pDmeVertexData == pDmeBind ) + continue; + + CollapseRedundantBaseNormalsAggressive( pDmeVertexData, flNormalBlend ); + } + } + } + else + { + // Collapse the base state + if ( CollapseRedundantBaseNormals( pDmeBind, normalMap, flNormalBlend ) ) + { + // Collapse any delta states using the baseState normal map + for ( int i = 0; i < DeltaStateCount(); ++i ) + { + CDmeVertexDeltaData *pDmeDeltaData = GetDeltaState( i ); + if ( !pDmeDeltaData ) + continue; + + CollapseRedundantDeltaNormals( pDmeDeltaData, normalMap ); + } + + // Collapse any other states + for ( int i = 0; i < BaseStateCount(); ++i ) + { + CDmeVertexData *pDmeVertexData = GetBaseState( i ); + if ( !pDmeVertexData || pDmeVertexData == pDmeBind ) + continue; + + CollapseRedundantBaseNormals( pDmeVertexData, normalMap, flNormalBlend ); + } + } + } +}
\ No newline at end of file diff --git a/movieobjects/dmemodel.cpp b/movieobjects/dmemodel.cpp new file mode 100644 index 0000000..a4145e3 --- /dev/null +++ b/movieobjects/dmemodel.cpp @@ -0,0 +1,335 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Dme version of a skeletal model (gets compiled into a MDL) +// +//============================================================================= +#include "movieobjects/dmemodel.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datacache/imdlcache.h" +#include "materialsystem/imaterialsystem.h" +#include "tier2/tier2.h" +#include "studio.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeModel, CDmeModel ); + + +//----------------------------------------------------------------------------- +// Stack of DmeModels currently being rendered. Used to set up render state +//----------------------------------------------------------------------------- +CUtlStack< CDmeModel * > CDmeModel::s_ModelStack; +static CUtlVector< matrix3x4_t > s_PoseToWorld; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeModel::OnConstruction() +{ + m_JointTransforms.Init( this, "jointTransforms" ); + m_BaseStates.Init( this, "baseStates" ); +} + +void CDmeModel::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Add joint +//----------------------------------------------------------------------------- +int CDmeModel::AddJoint( CDmeDag *pJoint ) +{ + int nIndex = GetJointTransformIndex( pJoint->GetTransform() ); + if ( nIndex >= 0 ) + return nIndex; + + return m_JointTransforms.AddToTail( pJoint->GetTransform() ); +} + + +//----------------------------------------------------------------------------- +// Add joint +//----------------------------------------------------------------------------- +CDmeJoint *CDmeModel::AddJoint( const char *pJointName, CDmeDag *pParent ) +{ + CDmeJoint *pJoint = CreateElement<CDmeJoint>( pJointName, GetFileId() ); + CDmeTransform *pTransform = pJoint->GetTransform(); + pTransform->SetName( pJointName ); + + if ( !pParent ) + { + pParent = this; + } + pParent->AddChild( pJoint ); + m_JointTransforms.AddToTail( pTransform ); + return pJoint; +} + + +//----------------------------------------------------------------------------- +// Returns the number of joint transforms we know about +//----------------------------------------------------------------------------- +int CDmeModel::GetJointTransformCount() const +{ + return m_JointTransforms.Count(); +} + + +//----------------------------------------------------------------------------- +// Determines joint transform index given a joint transform +//----------------------------------------------------------------------------- +int CDmeModel::GetJointTransformIndex( CDmeTransform *pTransform ) const +{ + int nCount = m_JointTransforms.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( pTransform == m_JointTransforms[i] ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Determines joint transform index given a joint +//----------------------------------------------------------------------------- +int CDmeModel::GetJointTransformIndex( CDmeDag *pJoint ) const +{ + return GetJointTransformIndex( pJoint->GetTransform() ); +} + + +//----------------------------------------------------------------------------- +// Determines joint transform index given a joint name +//----------------------------------------------------------------------------- +CDmeTransform *CDmeModel::GetJointTransform( int nIndex ) +{ + return m_JointTransforms[ nIndex ]; +} + +const CDmeTransform *CDmeModel::GetJointTransform( int nIndex ) const +{ + return m_JointTransforms[ nIndex ]; +} + + +//----------------------------------------------------------------------------- +// Finds a base state by name, returns NULL if not found +//----------------------------------------------------------------------------- +CDmeTransformList *CDmeModel::FindBaseState( const char *pBaseStateName ) +{ + int nCount = m_BaseStates.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( m_BaseStates[i]->GetName(), pBaseStateName ) ) + return m_BaseStates[i]; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Captures the current joint transforms into a base state +//----------------------------------------------------------------------------- +void CDmeModel::CaptureJointsToBaseState( const char *pBaseStateName ) +{ + CDmeTransformList *pTransformList = FindBaseState( pBaseStateName ); + if ( !pTransformList ) + { + pTransformList = CreateElement<CDmeTransformList>( pBaseStateName, GetFileId() ); + m_BaseStates.AddToTail( pTransformList ); + } + + // Make the transform list have the correct number of elements + int nJointCount = m_JointTransforms.Count(); + int nCurrentCount = pTransformList->GetTransformCount(); + if ( nJointCount > nCurrentCount ) + { + for ( int i = nCurrentCount; i < nJointCount; ++i ) + { + CDmeTransform *pTransform = CreateElement<CDmeTransform>( m_JointTransforms[i]->GetName(), pTransformList->GetFileId() ); + pTransformList->m_Transforms.AddToTail( pTransform ); + } + } + else if ( nJointCount < nCurrentCount ) + { + pTransformList->m_Transforms.RemoveMultiple( nJointCount, nCurrentCount - nJointCount ); + } + + // Copy the state over + for ( int i = 0; i < nJointCount; ++i ) + { + matrix3x4_t mat; + m_JointTransforms[i]->GetTransform( mat ); + pTransformList->SetTransform( i, mat ); + } +} + + +//----------------------------------------------------------------------------- +// Loads up joint transforms for this model +//----------------------------------------------------------------------------- +void CDmeModel::LoadJointTransform( CDmeDag *pJoint, CDmeTransformList *pBindPose, const matrix3x4_t &parentToWorld, const matrix3x4_t &parentToBindPose, bool bSetHardwareState ) +{ + CDmeTransform *pTransform = pJoint->GetTransform(); + + // Determines joint transform index; no index, no traversing lower in the hierarchy + int nJointIndex = GetJointTransformIndex( pTransform ); + if ( nJointIndex < 0 ) + return; + + // FIXME: Sucky search here necessary to find bone matrix index + matrix3x4_t jointToWorld, jointToParent; + pTransform->GetTransform( jointToParent ); + ConcatTransforms( parentToWorld, jointToParent, jointToWorld ); + + matrix3x4_t bindJointToParent, bindPoseToJoint, bindPoseToWorld, jointToBindPose; + if ( pBindPose ) + { + if ( nJointIndex >= pBindPose->GetTransformCount() ) + { + Warning( "Model is in an invalid state! There are different numbers of bones in the bind pose and joint transform list!\n" ); + return; + } + pBindPose->GetTransform( nJointIndex )->GetTransform( bindJointToParent ); + } + else + { + MatrixCopy( jointToParent, bindJointToParent ); + } + ConcatTransforms( parentToBindPose, bindJointToParent, jointToBindPose ); + + MatrixInvert( jointToBindPose, bindPoseToJoint ); + ConcatTransforms( jointToWorld, bindPoseToJoint, bindPoseToWorld ); + + if ( bSetHardwareState ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->LoadBoneMatrix( nJointIndex, bindPoseToWorld ); + } + MatrixCopy( bindPoseToWorld, s_PoseToWorld[ nJointIndex ] ); + + int nChildCount = pJoint->GetChildCount(); + for ( int i = 0; i < nChildCount; ++i ) + { + CDmeDag *pChildJoint = pJoint->GetChild(i); + if ( !pChildJoint ) + continue; + + LoadJointTransform( pChildJoint, pBindPose, jointToWorld, jointToBindPose, bSetHardwareState ); + } +} + + +//----------------------------------------------------------------------------- +// Sets up the render state for the model +//----------------------------------------------------------------------------- +CDmeModel::SetupBoneRetval_t CDmeModel::SetupBoneMatrixState( const matrix3x4_t& shapeToWorld, bool bForceSoftwareSkin ) +{ + int nJointCount = m_JointTransforms.Count(); + if ( nJointCount <= 0 ) + return NO_SKIN_DATA; + + int nBoneBatchCount = g_pMaterialSystemHardwareConfig->MaxVertexShaderBlendMatrices(); + bool bSetHardwareState = ( nJointCount <= nBoneBatchCount ) && !bForceSoftwareSkin; + + s_PoseToWorld.EnsureCount( nJointCount ); + + // Finds a base state by name, returns NULL if not found + CDmeTransformList *pBindPose = FindBaseState( "bind" ); + + matrix3x4_t parentToBindPose; + SetIdentityMatrix( parentToBindPose ); + + int nChildCount = GetChildCount(); + for ( int i = 0; i < nChildCount; ++i ) + { + CDmeDag *pChildJoint = GetChild(i); + if ( !pChildJoint ) + continue; + + LoadJointTransform( pChildJoint, pBindPose, shapeToWorld, parentToBindPose, bSetHardwareState ); + } + + return bSetHardwareState ? BONES_SET_UP : TOO_MANY_BONES; +} + +matrix3x4_t *CDmeModel::SetupModelRenderState( const matrix3x4_t& shapeToWorld, bool bHasSkinningData, bool bForceSoftwareSkin ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + if ( bHasSkinningData && ( s_ModelStack.Count() > 0 ) ) + { + SetupBoneRetval_t retVal = s_ModelStack.Top()->SetupBoneMatrixState( shapeToWorld, bForceSoftwareSkin ); + if ( retVal == TOO_MANY_BONES ) + { + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadIdentity( ); + return s_PoseToWorld.Base(); + } + if ( retVal != NO_SKIN_DATA ) + return NULL; + } + + if ( bForceSoftwareSkin ) + { + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadIdentity( ); + s_PoseToWorld.EnsureCount( 1 ); + MatrixCopy( shapeToWorld, s_PoseToWorld[0] ); + return s_PoseToWorld.Base(); + } + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadMatrix( shapeToWorld ); + return NULL; +} + +void CDmeModel::CleanupModelRenderState() +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadIdentity(); +} + + +//----------------------------------------------------------------------------- +// Recursively render the Dag hierarchy +//----------------------------------------------------------------------------- +void CDmeModel::Draw( CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + s_ModelStack.Push( this ); + BaseClass::Draw( pDrawSettings ); + s_ModelStack.Pop( ); +} + + +//----------------------------------------------------------------------------- +// Set if Z is the up axis of the model +//----------------------------------------------------------------------------- +void CDmeModel::ZUp( bool bZUp ) +{ + SetValue( "upAxis", bZUp ? "Z" : "Y" ); +} + + +//----------------------------------------------------------------------------- +// Returns true if the DmeModel is Z Up. +// NOTE: Since Y & Z are the only supported modes and Y is the default +// because that's how DmeModel data was originally defined, +// assume Y is up if the m_UpAxis attribute is not "Z" +//----------------------------------------------------------------------------- +bool CDmeModel::IsZUp() const +{ + const char *pszZUp = this->GetValueString( "upAxis" ); + + return ( pszZUp && *pszZUp ) ? StringHasPrefix( pszZUp, "Z" ) : false; +}
\ No newline at end of file diff --git a/movieobjects/dmemorphoperator.cpp b/movieobjects/dmemorphoperator.cpp new file mode 100644 index 0000000..95b0f98 --- /dev/null +++ b/movieobjects/dmemorphoperator.cpp @@ -0,0 +1,157 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmemorphoperator.h" +#include "movieobjects/dmevertexdata.h" +#include "movieobjects/dmemesh.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMorphOperator, CDmeMorphOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMorphOperator::OnConstruction() +{ + m_mesh.Init( this, "mesh", FATTRIB_HAS_CALLBACK ); + m_deltaStateWeights.Init( this, "deltaStateWeights", FATTRIB_MUSTCOPY ); + m_baseStateName.Init( this, "baseStateName", FATTRIB_TOPOLOGICAL ); +} + +void CDmeMorphOperator::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// accessors +//----------------------------------------------------------------------------- +uint CDmeMorphOperator::NumDeltaStateWeights() +{ + return m_deltaStateWeights.Count(); +} + +CDmElement *CDmeMorphOperator::GetDeltaStateWeight( uint i ) +{ + return m_deltaStateWeights[ i ]; +} + +CDmeMesh *CDmeMorphOperator::GetMesh() +{ + return m_mesh.GetElement(); +} + + +//----------------------------------------------------------------------------- +// This function gets called whenever an attribute changes +//----------------------------------------------------------------------------- +void CDmeMorphOperator::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + if ( pAttribute == m_mesh.GetAttribute() ) + { + CDmeMesh *pMesh = GetMesh(); + if ( pMesh ) + { +#if 0 // right now, the file already contains these weights, and re-creating them breaks the channel connections + m_deltaStateWeights.RemoveAll(); + + uint dn = pMesh->NumDeltaStates(); + for ( uint di = 0; di < dn; ++di ) + { + CDmElement *pDeltaState = pMesh->GetDeltaState( di ); + const char *name = pDeltaState->GetName(); + + CDmElement *pDeltaWeight = CreateElement< CDmElement >( name, GetFileId() ); + pDeltaWeight->SetAttributeValue( "weight", 0.0f ); + + m_deltaStateWeights.AddToTail( pDeltaWeight->GetHandle() ); + } +#endif + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMorphOperator::Operate() +{ + CDmeMesh *mesh = GetMesh(); + + uint mn = NumDeltaStateWeights(); + for ( uint mi = 0; mi < mn; ++mi ) + { + CDmElement *pDeltaState = GetDeltaStateWeight( mi ); + const char *deltaName = pDeltaState->GetName(); + float deltaWeight = pDeltaState->GetValue< float >( "weight" ); + + int di = mesh->FindDeltaStateIndex( deltaName ); + if ( di != -1 ) + { + mesh->SetDeltaStateWeight( di, MESH_DELTA_WEIGHT_NORMAL, deltaWeight ); + } + else + { + Msg( "MorphOperator::Operate: invalid delta state name: %s\n", deltaName ); + } + } +} + +// hack to avoid MSVC complaining about multiply defined symbols +namespace MorphOp +{ +void AddAttr( CUtlVector< CDmAttribute * > &attrs, CDmAttribute *pAttr ) +{ + if ( pAttr == NULL ) + return; + attrs.AddToTail( pAttr ); +} + +void AddVertexAttributes( CUtlVector< CDmAttribute * > &attrs, CDmElement *pObject ) +{ + AddAttr( attrs, pObject->GetAttribute( "coordinates" ) ); + AddAttr( attrs, pObject->GetAttribute( "normals" ) ); + AddAttr( attrs, pObject->GetAttribute( "textureCoordinates" ) ); + // TODO - add colors, occlusionFactors, boneIndices*, boneWeights*, tangents +} +}; +using namespace MorphOp; + +void CDmeMorphOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + uint nWeights = NumDeltaStateWeights(); + for ( uint wi = 0; wi < nWeights; ++wi ) + { + CDmElement *pDelta = GetDeltaStateWeight( wi ); + AddAttr( attrs, pDelta->GetAttribute( "weight" ) ); + } + + CDmeMesh *pMesh = GetMesh(); + CDmeVertexData *pBaseState = pMesh->FindBaseState( m_baseStateName.Get() ); + AddVertexAttributes( attrs, pBaseState ); + + uint nDeltas = pMesh->DeltaStateCount(); + for ( uint di = 0; di < nDeltas; ++di ) + { + CDmElement *pDeltaState = pMesh->GetDeltaState( di ); + AddAttr( attrs, pDeltaState->GetAttribute( "indices" ) ); + AddVertexAttributes( attrs, pDeltaState ); + } +} + +void CDmeMorphOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + AddVertexAttributes( attrs, GetMesh() ); +} diff --git a/movieobjects/dmemouseinput.cpp b/movieobjects/dmemouseinput.cpp new file mode 100644 index 0000000..6215d2a --- /dev/null +++ b/movieobjects/dmemouseinput.cpp @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmemouseinput.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" + +#include "vgui/iinput.h" +#include "vgui/ipanel.h" +#include "tier3/tier3.h" + +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeMouseInput, CDmeMouseInput ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeMouseInput::OnConstruction() +{ + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + + m_xOrigin = 0.0f; + m_yOrigin = 0.0f; +} + +void CDmeMouseInput::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// IsDirty - ie needs to operate +//----------------------------------------------------------------------------- +bool CDmeMouseInput::IsDirty() +{ + float flX, flY; + GetNormalizedCursorPos( flX, flY ); + flX -= m_xOrigin; + flY -= m_yOrigin; + + return ( flX != GetValue< float >( "x" ) ) || ( flY != GetValue< float >( "y" ) ); +} + +void CDmeMouseInput::Operate() +{ + float flX, flY; + GetNormalizedCursorPos( flX, flY ); + + SetValue( "x", flX - m_xOrigin ); + SetValue( "y", flY - m_yOrigin ); + +// Msg( "CDmeMouseInput::Operate() at <%f, %f>\n", flX, flY ); +} + +void CDmeMouseInput::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ +} + +void CDmeMouseInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( GetAttribute( "x" ) ); + attrs.AddToTail( GetAttribute( "y" ) ); +} + +void CDmeMouseInput::ResetOrigin( float dx, float dy ) +{ + GetNormalizedCursorPos( m_xOrigin, m_yOrigin ); + m_xOrigin += dx; + m_yOrigin += dy; +} + +void CDmeMouseInput::GetNormalizedCursorPos( float &flX, float &flY ) +{ + int x, y; + g_pVGuiInput->GetCursorPos( x, y ); + + vgui::VPANEL vpanel = g_pVGuiInput->GetFocus(); + if ( !vpanel ) + { + flX = flY = 0.0f; + return; + } + + int x0, y0; + g_pVGuiPanel->GetPos( vpanel, x0, y0 ); + + int w, h; + g_pVGuiPanel->GetSize( vpanel, w, h ); + + flX = ( x - x0 ) / float(w); + flY = ( y - y0 ) / float(h); +} diff --git a/movieobjects/dmeoperator.cpp b/movieobjects/dmeoperator.cpp new file mode 100644 index 0000000..e0f0707 --- /dev/null +++ b/movieobjects/dmeoperator.cpp @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeoperator.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ABSTRACT_ELEMENT( DmeOperator, CDmeOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeOperator::OnConstruction() +{ +} + +void CDmeOperator::OnDestruction() +{ +} + +//----------------------------------------------------------------------------- +// IsDirty - ie needs to operate +//----------------------------------------------------------------------------- +bool CDmeOperator::IsDirty() +{ + return BaseClass::IsDirty(); +} diff --git a/movieobjects/dmepackoperators.cpp b/movieobjects/dmepackoperators.cpp new file mode 100644 index 0000000..96abf22 --- /dev/null +++ b/movieobjects/dmepackoperators.cpp @@ -0,0 +1,321 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmepackoperators.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmattribute.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// CDmePackColorOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackColorOperator, CDmePackColorOperator ); + +void CDmePackColorOperator::OnConstruction() +{ + m_color.Init( this, "color" ); + m_red .Init( this, "red" ); + m_green.Init( this, "green" ); + m_blue .Init( this, "blue" ); + m_alpha.Init( this, "alpha" ); +} + +void CDmePackColorOperator::OnDestruction() +{ +} + +bool CDmePackColorOperator::IsDirty() +{ + const Color &c = m_color.Get(); +// float s = 255.999f; +// return c.r() != s*m_red.Get() || c.g() != s*m_green.Get() || c.b() != s*m_blue.Get() || c.a() != s*m_alpha.Get(); + return c.r() != m_red.Get() || c.g() != m_green.Get() || c.b() != m_blue.Get() || c.a() != m_alpha.Get(); +} + +void CDmePackColorOperator::Operate() +{ +// float s = 255.999f; +// m_color.Set( Color( s*m_red.Get(), s*m_green.Get(), s*m_blue.Get(), s*m_alpha.Get() ) ); + m_color.Set( Color( m_red.Get(), m_green.Get(), m_blue.Get(), m_alpha.Get() ) ); +} + +void CDmePackColorOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_red.GetAttribute() ); + attrs.AddToTail( m_green.GetAttribute() ); + attrs.AddToTail( m_blue.GetAttribute() ); + attrs.AddToTail( m_alpha.GetAttribute() ); +} + +void CDmePackColorOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_color.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmePackVector2Operator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackVector2Operator, CDmePackVector2Operator ); + +void CDmePackVector2Operator::OnConstruction() +{ + m_vector.Init( this, "vector" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); +} + +void CDmePackVector2Operator::OnDestruction() +{ +} + +bool CDmePackVector2Operator::IsDirty() +{ + const Vector2D &v = m_vector.Get(); + return v.x != m_x.Get() || v.y != m_y.Get(); +} + +void CDmePackVector2Operator::Operate() +{ + m_vector.Set( Vector2D( m_x.Get(), m_y.Get() ) ); +} + +void CDmePackVector2Operator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); +} + +void CDmePackVector2Operator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vector.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmePackVector3Operator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackVector3Operator, CDmePackVector3Operator ); + +void CDmePackVector3Operator::OnConstruction() +{ + m_vector.Init( this, "vector" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); +} + +void CDmePackVector3Operator::OnDestruction() +{ +} + +bool CDmePackVector3Operator::IsDirty() +{ + const Vector &v = m_vector.Get(); + return v.x != m_x.Get() || v.y != m_y.Get() || v.z != m_z.Get(); +} + +void CDmePackVector3Operator::Operate() +{ + m_vector.Set( Vector( m_x.Get(), m_y.Get(), m_z.Get() ) ); +} + +void CDmePackVector3Operator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); +} + +void CDmePackVector3Operator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vector.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmePackVector4Operator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackVector4Operator, CDmePackVector4Operator ); + +void CDmePackVector4Operator::OnConstruction() +{ + m_vector.Init( this, "vector" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); + m_w.Init( this, "w" ); +} + +void CDmePackVector4Operator::OnDestruction() +{ +} + +bool CDmePackVector4Operator::IsDirty() +{ + const Vector4D &v = m_vector.Get(); + return v.x != m_x.Get() || v.y != m_y.Get() || v.z != m_z.Get() || v.w != m_w.Get(); +} + +void CDmePackVector4Operator::Operate() +{ + m_vector.Set( Vector4D( m_x.Get(), m_y.Get(), m_z.Get(), m_w.Get() ) ); +} + +void CDmePackVector4Operator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); + attrs.AddToTail( m_w.GetAttribute() ); +} + +void CDmePackVector4Operator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vector.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmePackQAngleOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackQAngleOperator, CDmePackQAngleOperator ); + +void CDmePackQAngleOperator::OnConstruction() +{ + m_qangle.Init( this, "qangle" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); +} + +void CDmePackQAngleOperator::OnDestruction() +{ +} + +bool CDmePackQAngleOperator::IsDirty() +{ + const QAngle &q = m_qangle.Get(); + return q.x != m_x.Get() || q.y != m_y.Get() || q.z != m_z.Get(); +} + +void CDmePackQAngleOperator::Operate() +{ + m_qangle.Set( QAngle( m_x.Get(), m_y.Get(), m_z.Get() ) ); +} + +void CDmePackQAngleOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); +} + +void CDmePackQAngleOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_qangle.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmePackQuaternionOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackQuaternionOperator, CDmePackQuaternionOperator ); + +void CDmePackQuaternionOperator::OnConstruction() +{ + m_quaternion.Init( this, "quaternion" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); + m_w.Init( this, "w" ); +} + +void CDmePackQuaternionOperator::OnDestruction() +{ +} + +bool CDmePackQuaternionOperator::IsDirty() +{ + const Quaternion &q = m_quaternion.Get(); + return q.x != m_x.Get() || q.y != m_y.Get() || q.z != m_z.Get() || q.w != m_w.Get(); +} + +void CDmePackQuaternionOperator::Operate() +{ + m_quaternion.Set( Quaternion( m_x.Get(), m_y.Get(), m_z.Get(), m_w.Get() ) ); +} + +void CDmePackQuaternionOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); + attrs.AddToTail( m_w.GetAttribute() ); +} + +void CDmePackQuaternionOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_quaternion.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmePackVMatrixOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePackVMatrixOperator, CDmePackVMatrixOperator ); + +void CDmePackVMatrixOperator::OnConstruction() +{ + m_vmatrix.Init( this, "vmatrix" ); + char name[ 4 ]; + for ( uint i = 0; i < 16; ++i ) + { + Q_snprintf( name, sizeof(name), "m%d%d", i >> 2, i & 0x3 ); + m_cells[ i ].Init( this, name ); + } +} + +void CDmePackVMatrixOperator::OnDestruction() +{ +} + +bool CDmePackVMatrixOperator::IsDirty() +{ + const VMatrix &v = m_vmatrix.Get(); + for ( uint i = 0; i < 16; ++i ) + { + if ( *( v[ i ] ) != m_cells[ i ].Get() ) + return true; + } + return false; +} + +void CDmePackVMatrixOperator::Operate() +{ + VMatrix v; + for ( uint i = 0; i < 16; ++i ) + { + *( v[ i ] ) = m_cells[ i ].Get(); + } + m_vmatrix.Set( v ); +} + +void CDmePackVMatrixOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + for ( uint i = 0; i < 16; ++i ) + { + attrs.AddToTail( m_cells[i].GetAttribute() ); + } +} + +void CDmePackVMatrixOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vmatrix.GetAttribute() ); +} diff --git a/movieobjects/dmeparticlesystemdefinition.cpp b/movieobjects/dmeparticlesystemdefinition.cpp new file mode 100644 index 0000000..d279d44 --- /dev/null +++ b/movieobjects/dmeparticlesystemdefinition.cpp @@ -0,0 +1,598 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmeparticlesystemdefinition.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmeeditortypedictionary.h" +#include "toolutils/enginetools_int.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/convar.h" +#include "particles/particles.h" +#include "dme_controls/attributeintchoicepanel.h" +#include "dme_controls/attributeboolchoicepanel.h" +#include "dme_controls/attributestringchoicepanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + + +//----------------------------------------------------------------------------- +// Human readable string for the particle functions +//----------------------------------------------------------------------------- +static const char *s_pParticleFuncTypeName[PARTICLE_FUNCTION_COUNT] = +{ + "Renderer", // FUNCTION_RENDERER = 0, + "Operator", // FUNCTION_OPERATOR, + "Initializer", // FUNCTION_INITIALIZER, + "Emitter", // FUNCTION_EMITTER, + "Children", // FUNCTION_CHILDREN, + "ForceGenerator", // FUNCTION_FORCEGENERATOR + "Constraint", // FUNCTION_CONSTRAINT +}; + +const char *GetParticleFunctionTypeName( ParticleFunctionType_t type ) +{ + return s_pParticleFuncTypeName[type]; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleFunction, CDmeParticleFunction ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeParticleFunction::OnConstruction() +{ + m_bSkipNextResolve = false; +} + +void CDmeParticleFunction::OnDestruction() +{ + DestroyElement( m_hTypeDictionary, TD_DEEP ); +} + + +//----------------------------------------------------------------------------- +// Construct an appropriate editor attribute info +//----------------------------------------------------------------------------- +static void CreateEditorAttributeInfo( CDmeEditorType *pEditorType, const char *pAttributeName, const char *pWidgetInfo ) +{ + if ( !pWidgetInfo ) + return; + + CCommand parse; + parse.Tokenize( pWidgetInfo ); + if ( parse.ArgC() == 1 ) + { + CDmeEditorAttributeInfo *pInfo = CreateElement< CDmeEditorAttributeInfo >( "field info" ); + pEditorType->AddAttributeInfo( pAttributeName, pInfo ); + pInfo->m_Widget = parse[0]; + return; + } + + if ( parse.ArgC() == 2 ) + { + CDmeEditorChoicesInfo *pInfo = NULL; + if ( !Q_stricmp( parse[0], "intchoice" ) ) + { + pInfo = CreateElement< CDmeEditorIntChoicesInfo >( "field info" ); + } + + if ( !Q_stricmp( parse[0], "boolchoice" ) ) + { + pInfo = CreateElement< CDmeEditorBoolChoicesInfo >( "field info" ); + } + + if ( !Q_stricmp( parse[0], "stringchoice" ) ) + { + pInfo = CreateElement< CDmeEditorStringChoicesInfo >( "field info" ); + } + + if ( !Q_stricmp( parse[0], "elementchoice" ) ) + { + pInfo = CreateElement< CDmeEditorChoicesInfo >( "field info" ); + } + + if ( pInfo ) + { + pInfo->SetChoiceType( parse[1] ); + pEditorType->AddAttributeInfo( pAttributeName, pInfo ); + pInfo->m_Widget = parse[0]; + return; + } + } +} + + +//----------------------------------------------------------------------------- +// Used for backward compat +//----------------------------------------------------------------------------- +void CDmeParticleFunction::AddMissingFields( const DmxElementUnpackStructure_t *pUnpack ) +{ + DestroyElement( m_hTypeDictionary, TD_DEEP ); + m_hTypeDictionary = CreateElement< CDmeEditorTypeDictionary >( "particleFunctionDict" ); + CDmeEditorType *pEditorType = CreateElement< CDmeEditorType >( GetTypeString() ); + + for ( ; pUnpack->m_pAttributeName; ++pUnpack ) + { + CreateEditorAttributeInfo( pEditorType, pUnpack->m_pAttributeName, (const char *)pUnpack->m_pUserData ); + + // Can happen if 'name' or 'functionName' is used + if ( HasAttribute( pUnpack->m_pAttributeName ) ) + continue; + + CDmAttribute *pAttribute = AddAttribute( pUnpack->m_pAttributeName, pUnpack->m_AttributeType ); + if ( pUnpack->m_pDefaultString ) + { + int nLen = Q_strlen( pUnpack->m_pDefaultString ); + CUtlBuffer bufParse( pUnpack->m_pDefaultString, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + pAttribute->Unserialize( bufParse ); + } + } + + m_hTypeDictionary->AddEditorType( pEditorType ); +} + + +//----------------------------------------------------------------------------- +// Sets the particle operator +//----------------------------------------------------------------------------- +void CDmeParticleFunction::UpdateAttributes( const DmxElementUnpackStructure_t *pUnpack ) +{ + // Delete all old attributes + CDmAttribute *pNext; + for( CDmAttribute *pAttr = FirstAttribute(); pAttr; pAttr = pNext ) + { + pNext = pAttr->NextAttribute(); + if ( pAttr->IsFlagSet( FATTRIB_EXTERNAL | FATTRIB_STANDARD ) ) + continue; + + RemoveAttributeByPtr( pAttr ); + } + + AddMissingFields( pUnpack ); +} + + +//----------------------------------------------------------------------------- +// Marks a particle system as a new instance +// This is basically a workaround to prevent newly-copied particle functions +// from recompiling themselves a zillion times +//----------------------------------------------------------------------------- +void CDmeParticleFunction::MarkNewInstance() +{ + m_bSkipNextResolve = true; +} + + +//----------------------------------------------------------------------------- +// Don't bother resolving during unserialization, the owning def will handle it +//----------------------------------------------------------------------------- +void CDmeParticleFunction::OnElementUnserialized() +{ + BaseClass::OnElementUnserialized(); + MarkNewInstance(); +} + + +//----------------------------------------------------------------------------- +// Recompiles the particle system when a change occurs +//----------------------------------------------------------------------------- +void CDmeParticleFunction::Resolve() +{ + BaseClass::Resolve(); + + if ( m_bSkipNextResolve ) + { + m_bSkipNextResolve = false; + return; + } + + for( CDmAttribute* pAttr = FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() ) + { + if ( !pAttr->IsFlagSet( FATTRIB_DIRTY ) ) + continue; + + // Find all CDmeParticleSystemDefinitions referring to this function + DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( GetHandle() ); + while ( i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); + + // NOTE: This could cause the same particle system definition to recompile + // multiple times if it refers to the same function multiple times, + // but we don't expect that to happen, so we won't bother checking for it + CDmeParticleSystemDefinition *pDef = CastElement<CDmeParticleSystemDefinition>( pAttribute->GetOwner() ); + if ( pDef && pDef->GetFileId() == GetFileId() ) + { + pDef->RecompileParticleSystem(); + } + i = g_pDataModel->NextAttributeReferencingElement( i ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Returns the editor type dictionary +//----------------------------------------------------------------------------- +CDmeEditorTypeDictionary* CDmeParticleFunction::GetEditorTypeDictionary() +{ + return m_hTypeDictionary; +} + + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleOperator, CDmeParticleOperator ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeParticleOperator::OnConstruction() +{ + m_FunctionName.Init( this, "functionName" ); +} + +void CDmeParticleOperator::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the particle operator +//----------------------------------------------------------------------------- +void CDmeParticleOperator::SetFunction( IParticleOperatorDefinition *pDefinition ) +{ + m_FunctionName = pDefinition->GetName(); + const DmxElementUnpackStructure_t *pUnpack = pDefinition->GetUnpackStructure(); + UpdateAttributes( pUnpack ); +} + +const char *CDmeParticleOperator::GetFunctionType() const +{ + return m_FunctionName; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleChild, CDmeParticleChild ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeParticleChild::OnConstruction() +{ + m_Child.Init( this, "child", FATTRIB_NEVERCOPY ); +} + +void CDmeParticleChild::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the particle system child +//----------------------------------------------------------------------------- +void CDmeParticleChild::SetChildParticleSystem( CDmeParticleSystemDefinition *pDef, IParticleOperatorDefinition *pDefinition ) +{ + // FIXME: Convert system name into a + m_Child = pDef; + const DmxElementUnpackStructure_t *pUnpack = pDefinition->GetUnpackStructure(); + UpdateAttributes( pUnpack ); +} + +const char *CDmeParticleChild::GetFunctionType() const +{ + const CDmeParticleSystemDefinition *pChild = m_Child; + return pChild ? pChild->GetName() : ""; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY_INSTALL_EXPLICITLY( DmeParticleSystemDefinition, CDmeParticleSystemDefinition ); + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeParticleSystemDefinition::OnConstruction() +{ + m_ParticleFunction[FUNCTION_RENDERER].Init( this, "renderers" ); + m_ParticleFunction[FUNCTION_OPERATOR].Init( this, "operators" ); + m_ParticleFunction[FUNCTION_INITIALIZER].Init( this, "initializers" ); + m_ParticleFunction[FUNCTION_EMITTER].Init( this, "emitters" ); + m_ParticleFunction[FUNCTION_CHILDREN].Init( this, "children" ); + m_ParticleFunction[FUNCTION_FORCEGENERATOR].Init( this, "forces" ); + m_ParticleFunction[FUNCTION_CONSTRAINT].Init( this, "constraints" ); + m_bPreventNameBasedLookup.Init( this, "preventNameBasedLookup" ); + + m_hTypeDictionary = CreateElement< CDmeEditorTypeDictionary >( "particleSystemDefinitionDict" ); + CDmeEditorType *pEditorType = CreateElement< CDmeEditorType >( "DmeParticleSystemDefinition" ); + + const DmxElementUnpackStructure_t *pUnpack = g_pParticleSystemMgr->GetParticleSystemDefinitionUnpackStructure(); + for ( ; pUnpack->m_pAttributeName; ++pUnpack ) + { + CreateEditorAttributeInfo( pEditorType, pUnpack->m_pAttributeName, (const char *)pUnpack->m_pUserData ); + + CDmAttribute *pAttribute = AddAttribute( pUnpack->m_pAttributeName, pUnpack->m_AttributeType ); + if ( pUnpack->m_pDefaultString ) + { + int nLen = Q_strlen( pUnpack->m_pDefaultString ); + CUtlBuffer bufParse( pUnpack->m_pDefaultString, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + pAttribute->Unserialize( bufParse ); + } + } + + m_hTypeDictionary->AddEditorType( pEditorType ); +} + +void CDmeParticleSystemDefinition::OnDestruction() +{ + DestroyElement( m_hTypeDictionary, TD_DEEP ); +} + + +//----------------------------------------------------------------------------- +// Returns the editor type dictionary +//----------------------------------------------------------------------------- +CDmeEditorTypeDictionary* CDmeParticleSystemDefinition::GetEditorTypeDictionary() +{ + return m_hTypeDictionary; +} + + +//----------------------------------------------------------------------------- +// Remove obsolete attributes +//----------------------------------------------------------------------------- +static void RemoveObsoleteAttributes( CDmElement *pElement, const DmxElementUnpackStructure_t *pUnpack ) +{ + // Delete all obsolete attributes + CDmAttribute *pNext; + for( CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr; pAttr = pNext ) + { + pNext = pAttr->NextAttribute(); + if ( pAttr->IsFlagSet( FATTRIB_EXTERNAL | FATTRIB_STANDARD ) ) + continue; + + bool bFound = false; + for ( const DmxElementUnpackStructure_t *pTrav = pUnpack; pTrav->m_pAttributeName; ++pTrav ) + { + if ( !Q_stricmp( pTrav->m_pAttributeName, pAttr->GetName() ) ) + { + bFound = true; + break; + } + } + + if ( !bFound ) + { + pElement->RemoveAttributeByPtr( pAttr ); + } + } +} + +//----------------------------------------------------------------------------- +// Used for automatic handling of backward compatability +//----------------------------------------------------------------------------- +void CDmeParticleSystemDefinition::OnElementUnserialized() +{ + BaseClass::OnElementUnserialized(); + + RemoveObsoleteAttributes( this, g_pParticleSystemMgr->GetParticleSystemDefinitionUnpackStructure() ); + + // Add missing fields that are new + for ( int i = 0; i < PARTICLE_FUNCTION_COUNT; ++i ) + { + ParticleFunctionType_t type = (ParticleFunctionType_t)i; + CUtlVector< IParticleOperatorDefinition *> &list = g_pParticleSystemMgr->GetAvailableParticleOperatorList( type ); + int nAvailType = list.Count(); + int nCount = GetParticleFunctionCount( type ); + for ( int j = 0; j < nCount; ++j ) + { + CDmeParticleFunction *pFunction = GetParticleFunction( type, j ); + + if ( i == FUNCTION_CHILDREN ) + { + RemoveObsoleteAttributes( pFunction, list[0]->GetUnpackStructure() ); + pFunction->AddMissingFields( list[0]->GetUnpackStructure() ); + continue; + } + + for ( int k = 0; k < nAvailType; ++k ) + { + if ( Q_stricmp( pFunction->GetName(), list[k]->GetName() ) ) + continue; + + RemoveObsoleteAttributes( pFunction, list[k]->GetUnpackStructure() ); + pFunction->AddMissingFields( list[k]->GetUnpackStructure() ); + break; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Check to see if any attributes changed +//----------------------------------------------------------------------------- +void CDmeParticleSystemDefinition::Resolve() +{ + BaseClass::Resolve(); + for( CDmAttribute* pAttr = FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() ) + { + if ( pAttr->IsFlagSet( FATTRIB_DIRTY ) ) + { + RecompileParticleSystem(); + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Add, remove +//----------------------------------------------------------------------------- +CDmeParticleFunction* CDmeParticleSystemDefinition::AddOperator( ParticleFunctionType_t type, const char *pFunctionName ) +{ + CUtlVector< IParticleOperatorDefinition *> &list = g_pParticleSystemMgr->GetAvailableParticleOperatorList( type ); + + int nCount = list.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( Q_stricmp( pFunctionName, list[i]->GetName() ) ) + continue; + + CDmeParticleOperator *pFunction = CreateElement< CDmeParticleOperator >( pFunctionName, GetFileId() ); + m_ParticleFunction[type].AddToTail( pFunction ); + pFunction->SetFunction( list[i] ); + return pFunction; + } + return NULL; +} + +CDmeParticleFunction* CDmeParticleSystemDefinition::AddChild( CDmeParticleSystemDefinition *pChild ) +{ + Assert( pChild ); + + CUtlVector< IParticleOperatorDefinition *> &list = g_pParticleSystemMgr->GetAvailableParticleOperatorList( FUNCTION_CHILDREN ); + Assert( list.Count() == 1 ); + CDmeParticleChild *pFunction = CreateElement< CDmeParticleChild >( pChild->GetName(), GetFileId() ); + m_ParticleFunction[FUNCTION_CHILDREN].AddToTail( pFunction ); + pFunction->SetChildParticleSystem( pChild, list[0] ); + return pFunction; +} + +//----------------------------------------------------------------------------- +// Remove +void CDmeParticleSystemDefinition::RemoveFunction( ParticleFunctionType_t type, CDmeParticleFunction *pFunction ) +{ + int nIndex = FindFunction( type, pFunction ); + RemoveFunction( type, nIndex ); +} + +void CDmeParticleSystemDefinition::RemoveFunction( ParticleFunctionType_t type, int nIndex ) +{ + if ( nIndex >= 0 ) + { + m_ParticleFunction[type].Remove(nIndex); + } +} + + +//----------------------------------------------------------------------------- +// Find +//----------------------------------------------------------------------------- +int CDmeParticleSystemDefinition::FindFunction( ParticleFunctionType_t type, CDmeParticleFunction *pParticleFunction ) +{ + int nCount = m_ParticleFunction[type].Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( pParticleFunction == m_ParticleFunction[type][i] ) + return i; + } + return -1; +} + +int CDmeParticleSystemDefinition::FindFunction( ParticleFunctionType_t type, const char *pFunctionName ) +{ + int nCount = m_ParticleFunction[type].Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pFunctionName, m_ParticleFunction[type][i]->GetFunctionType() ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Iteration +//----------------------------------------------------------------------------- +int CDmeParticleSystemDefinition::GetParticleFunctionCount( ParticleFunctionType_t type ) const +{ + return m_ParticleFunction[type].Count(); +} + +CDmeParticleFunction *CDmeParticleSystemDefinition::GetParticleFunction( ParticleFunctionType_t type, int nIndex ) +{ + return m_ParticleFunction[type][nIndex]; +} + + +//----------------------------------------------------------------------------- +// Reordering +//----------------------------------------------------------------------------- +void CDmeParticleSystemDefinition::MoveFunctionUp( ParticleFunctionType_t type, CDmeParticleFunction *pElement ) +{ + int nIndex = FindFunction( type, pElement ); + if ( nIndex > 0 ) + { + m_ParticleFunction[type].Swap( nIndex, nIndex - 1 ); + } +} + +void CDmeParticleSystemDefinition::MoveFunctionDown( ParticleFunctionType_t type, CDmeParticleFunction *pElement ) +{ + int nIndex = FindFunction( type, pElement ); + int nLastIndex = m_ParticleFunction[type].Count() - 1; + if ( nIndex >= 0 && nIndex < nLastIndex ) + { + m_ParticleFunction[type].Swap( nIndex, nIndex + 1 ); + } +} + + +//----------------------------------------------------------------------------- +// Marks a particle system as a new instance +// This is basically a workaround to prevent newly-copied particle functions +// from recompiling themselves a zillion times +//----------------------------------------------------------------------------- +void CDmeParticleSystemDefinition::MarkNewInstance() +{ + for ( int i = 0; i < PARTICLE_FUNCTION_COUNT; ++i ) + { + int nCount = m_ParticleFunction[i].Count(); + for ( int j = 0; j < nCount; ++j ) + { + m_ParticleFunction[i][j]->MarkNewInstance(); + } + } +} + + +//----------------------------------------------------------------------------- +// Recompiles the particle system when a change occurs +//----------------------------------------------------------------------------- +void CDmeParticleSystemDefinition::RecompileParticleSystem() +{ + const char *pFileFormat = "pcf"; + const char *pEncoding = g_pDataModel->GetDefaultEncoding( pFileFormat ); + int nFlags = g_pDataModel->IsEncodingBinary( pEncoding ) ? 0 : CUtlBuffer::TEXT_BUFFER; + CUtlBuffer buf( 0, 0, nFlags ); + if ( g_pDataModel->Serialize( buf, pEncoding, pFileFormat, GetHandle() ) ) + { + g_pParticleSystemMgr->ReadParticleConfigFile( buf, true, NULL ); + } +} + + diff --git a/movieobjects/dmephonememapping.cpp b/movieobjects/dmephonememapping.cpp new file mode 100644 index 0000000..03a1c5d --- /dev/null +++ b/movieobjects/dmephonememapping.cpp @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmephonememapping.h" +#include "datamodel/dmelementfactoryhelper.h" + +//----------------------------------------------------------------------------- +// CDmePhonemeMapping +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmePhonemeMapping, CDmePhonemeMapping ); + +void CDmePhonemeMapping::OnConstruction() +{ + m_Preset.Init( this, "preset" ); + m_Weight.InitAndSet( this, "weight", 1.0f ); +} + +void CDmePhonemeMapping::OnDestruction() +{ +}
\ No newline at end of file diff --git a/movieobjects/dmeselection.cpp b/movieobjects/dmeselection.cpp new file mode 100644 index 0000000..7528f88 --- /dev/null +++ b/movieobjects/dmeselection.cpp @@ -0,0 +1,577 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + + +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmeselection.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeComponent, CDmeComponent ); + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeComponent::OnConstruction() +{ + m_Type.InitAndSet( this, "componentType", COMP_INVALID ); + m_bComplete.InitAndSet( this, "complete", false ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeComponent::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeComponent::Resolve() +{ +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSingleIndexedComponent, CDmeSingleIndexedComponent ); + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::OnConstruction() +{ + m_CompleteCount.InitAndSet( this, "completeCount", 0 ); + m_Components.Init( this, "components" ); + m_Weights.Init( this, "weights" ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::Resolve() +{ +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmeSingleIndexedComponent::Count() const +{ + return IsComplete() ? m_CompleteCount.Get() : m_Components.Count(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeSingleIndexedComponent::SetType( Component_t type ) +{ + switch ( type ) + { + case COMP_VTX: + case COMP_FACE: + m_Type.Set( type ); + return true; + default: + break; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::AddComponent( int component, float weight /*= 1.0f */ ) +{ + if ( IsComplete() ) + return; + + const int index( BinarySearch( component ) ); + Assert( index >= 0 ); + Assert( index <= m_Components.Count() ); + if ( index == m_Components.Count() ) + { + m_Components.AddToTail( component ); + m_Weights.AddToTail( weight ); + } + else if ( component == m_Components.Get( index ) ) + { + Assert( index < m_Weights.Count() ); + m_Weights.Set( index, weight ); + } + else + { + m_Components.InsertBefore( index, component ); + m_Weights.InsertBefore( index, weight ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::AddComponents( const CUtlVector< int > &components ) +{ + const int nComponents( components.Count() ); + for ( int i( 0 ); i < nComponents; ++i ) + { + AddComponent( components[ i ] ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::AddComponents( + const CUtlVector< int > &components, const CUtlVector< float > &weights ) +{ + const int nComponents( min( components.Count(), weights.Count() ) ); + for ( int i( 0 ); i < nComponents; ++i ) + { + AddComponent( components[ i ], weights[ i ] ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::RemoveComponent( int component ) +{ + Assert( !IsComplete() ); // TODO: Convert from complete to complete - component + + const int cIndex = BinarySearch( component ); + if ( cIndex >= m_Components.Count() || m_Components[ cIndex ] != component ) // Component not in selection + return; + + m_Components.Remove( cIndex ); + m_Weights.Remove( cIndex ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeSingleIndexedComponent::GetComponent( int index, int &component, float &weight ) const +{ + if ( index < Count() ) + { + if ( IsComplete() ) + { + component = index; + weight = 1.0f; + } + else + { + component = m_Components[ index ]; + weight = m_Weights[ index ]; + } + + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::GetComponents( CUtlVector< int > &components, CUtlVector< float > &weights ) const +{ + if ( IsComplete() ) + { + const int nComponents = Count(); + + int *pComponents = reinterpret_cast< int * >( alloca( nComponents * sizeof( int ) ) ); + float *pWeights = reinterpret_cast< float * >( alloca( nComponents * sizeof( float ) ) ); + + for ( int i = 0; i < nComponents; ++i ) + { + pComponents[ i ] = i; + pWeights[ i ] = 1.0f; + } + + components.CopyArray( pComponents, nComponents ); + weights.CopyArray( pWeights, nComponents ); + } + else + { + components.RemoveAll(); + components.CopyArray( m_Components.Base(), m_Components.Count() ); + + weights.RemoveAll(); + weights.CopyArray( m_Weights.Base(), m_Weights.Count() ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::GetComponents( CUtlVector< int > &components ) const +{ + if ( IsComplete() ) + { + const int nComponents = Count(); + + int *pComponents = reinterpret_cast< int * >( alloca( nComponents * sizeof( int ) ) ); + + for ( int i = 0; i < nComponents; ++i ) + { + pComponents[ i ] = i; + } + + components.CopyArray( pComponents, nComponents ); + } + else + { + components.RemoveAll(); + components.CopyArray( m_Components.Base(), m_Components.Count() ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::SetComplete( int nComplete ) +{ + m_bComplete.Set( true ); + m_CompleteCount.Set( max( 0, nComplete ) ); + m_Components.Purge(); + m_Weights.Purge(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmeSingleIndexedComponent::GetComplete() const +{ + return IsComplete() ? m_CompleteCount.Get() : 0; +} + + +//----------------------------------------------------------------------------- +// Reset to an empty selection +//----------------------------------------------------------------------------- +inline void CDmeSingleIndexedComponent::Clear() +{ + CDmeComponent::Clear(); + m_CompleteCount.Set( 0 ); + m_Components.RemoveAll(); + m_Weights.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Searches for the component in the sorted component list and returns the +// index if it's found or if it's not found, returns the index at which it +// should be inserted to maintain the sorted order of the component list +//----------------------------------------------------------------------------- +int CDmeSingleIndexedComponent::BinarySearch( int component ) const +{ + const CUtlVector< int > &components( m_Components.Get() ); + const int nComponents( components.Count() ); + + int left( 0 ); + int right( nComponents - 1 ); + int mid; + + while ( left <= right ) + { + mid = ( left + right ) >> 1; // floor( ( left + right ) / 2.0 ) + if ( component > m_Components[ mid ] ) + { + left = mid + 1; + } + else if ( component < m_Components[ mid ] ) + { + right = mid - 1; + } + else + { + return mid; + } + } + + return left; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeSingleIndexedComponent::HasComponent( int component ) const +{ + if ( IsComplete() ) + return true; + + const int cIndex = BinarySearch( component ); + if ( cIndex >= m_Components.Count() ) + return false; + + if ( m_Components[ cIndex ] == component ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmeSingleIndexedComponent::GetWeight( int component, float &weight ) const +{ + Assert( !IsComplete() ); + + const int cIndex = BinarySearch( component ); + if ( cIndex >= m_Components.Count() ) + return false; + + if ( m_Components[ cIndex ] == component ) + { + weight = m_Weights[ cIndex ]; + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::Subtract( const CDmeSingleIndexedComponent &rhs ) +{ + const int nLhs = Count(); + const int nRhs = rhs.Count(); + + int l = 0; + int r = 0; + + if ( IsComplete() ) + { + // TODO + Assert( 0 ); + } + else + { + CUtlVector< int > newComponents; + newComponents.EnsureCapacity( nLhs ); + CUtlVector< float > newWeights; + newWeights.EnsureCapacity( nLhs ); + + while ( l < nLhs || r < nRhs ) + { + // In LHS but not RHS + while ( l < nLhs && ( r >= nRhs || m_Components[ l ] < rhs.m_Components[ r ] ) ) + { + newComponents.AddToTail( m_Components[ l ] ); + newWeights.AddToTail( m_Weights[ l ] ); + ++l; + } + + // In RHS but not LHS + while ( r < nRhs && ( l >= nLhs || m_Components[ l ] > rhs.m_Components[ r ] ) ) + { + ++r; + } + + // In Both LHS & RHS + while ( l < nLhs && r < nRhs && m_Components[ l ] == rhs.m_Components[ r ] ) + { + ++l; + ++r; + } + } + + m_Components.CopyArray( newComponents.Base(), newComponents.Count() ); + m_Weights.CopyArray( newWeights.Base(), newWeights.Count() ); + } + + m_CompleteCount.Set( 0 ); + m_bComplete.Set( false ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::Add( const CDmeSingleIndexedComponent &rhs ) +{ + int nLhs = Count(); + const int nRhs = rhs.Count(); + + int l = 0; + int r = 0; + + if ( IsComplete() ) + { + if ( rhs.IsComplete() && nRhs > nLhs ) + { + m_CompleteCount.Set( nRhs ); + } + else + { + while ( r < nRhs ) + { + if ( rhs.m_Components[ r ] >= nLhs ) + { + // Got one that's greater than the complete count of this one + + CUtlVector< int > newComponents; + newComponents.EnsureCapacity( nLhs + nRhs - r ); + + CUtlVector< float > newWeights; + newWeights.EnsureCapacity( nLhs + nRhs - r ); + + GetComponents( newComponents, newWeights ); + + while ( r < nRhs ) + { + newComponents.AddToTail( rhs.m_Components[ r ] ); + newWeights.AddToTail( rhs.m_Weights[ r ] ); + ++r; + } + + m_Components.CopyArray( newComponents.Base(), newComponents.Count() ); + m_Weights.CopyArray( newWeights.Base(), newWeights.Count() ); + + m_CompleteCount.Set( 0 ); + m_bComplete.Set( false ); + + break; + } + + ++r; + } + } + } + else + { + CUtlVector< int > newComponents; + newComponents.EnsureCapacity( nLhs + nRhs * 0.5 ); // Just an estimate assuming 50% of the components in rhs aren't in lhs + CUtlVector< float > newWeights; + newWeights.EnsureCapacity( nLhs + nRhs * 0.5 ); // Just an estimate + + while ( l < nLhs || r < nRhs ) + { + while ( l < nLhs && ( r >= nRhs || m_Components[ l ] < rhs.m_Components[ r ] ) ) + { + newComponents.AddToTail( m_Components[ l ] ); + newWeights.AddToTail( m_Weights[ l ] ); + ++l; + } + + // In RHS but not LHS + while ( r < nRhs && ( l >= nLhs || m_Components[ l ] > rhs.m_Components[ r ] ) ) + { + newComponents.AddToTail( rhs.m_Components[ r ] ); + newWeights.AddToTail( rhs.m_Weights[ r ] ); + ++r; + } + + // In Both LHS & RHS + while ( l < nLhs && r < nRhs && m_Components[ l ] == rhs.m_Components[ r ] ) + { + newComponents.AddToTail( m_Components[ l ] ); + newWeights.AddToTail( m_Weights[ l ] ); + ++l; + ++r; + } + } + + m_Components.CopyArray( newComponents.Base(), newComponents.Count() ); + m_Weights.CopyArray( newWeights.Base(), newWeights.Count() ); + } + + m_CompleteCount.Set( 0 ); + m_bComplete.Set( false ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeSingleIndexedComponent::Intersection( const CDmeSingleIndexedComponent &rhs ) +{ + const int nLhs = Count(); + const int nRhs = rhs.Count(); + + int l = 0; + int r = 0; + + if ( IsComplete() ) + { + // TODO + Assert( 0 ); + } + else + { + CUtlVector< int > newComponents; + newComponents.EnsureCapacity( nLhs ); + CUtlVector< float > newWeights; + newWeights.EnsureCapacity( nLhs ); + + while ( l < nLhs || r < nRhs ) + { + // In LHS but not RHS + while ( l < nLhs && ( r >= nRhs || m_Components[ l ] < rhs.m_Components[ r ] ) ) + { + ++l; + } + + // In RHS but not LHS + while ( r < nRhs && ( l >= nLhs || m_Components[ l ] > rhs.m_Components[ r ] ) ) + { + ++r; + } + + // In Both LHS & RHS + while ( l < nLhs && r < nRhs && m_Components[ l ] == rhs.m_Components[ r ] ) + { + newComponents.AddToTail( m_Components[ l ] ); + newWeights.AddToTail( m_Weights[ l ] ); + ++l; + ++r; + } + } + + m_Components.CopyArray( newComponents.Base(), newComponents.Count() ); + m_Weights.CopyArray( newWeights.Base(), newWeights.Count() ); + } + + m_CompleteCount.Set( 0 ); + m_bComplete.Set( false ); +}
\ No newline at end of file diff --git a/movieobjects/dmeshader.cpp b/movieobjects/dmeshader.cpp new file mode 100644 index 0000000..8408124 --- /dev/null +++ b/movieobjects/dmeshader.cpp @@ -0,0 +1,260 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeshader.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects_interfaces.h" + +#include "materialsystem/IShader.h" +#include "materialsystem/imaterialsystem.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeShader, CDmeShader ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeShader::OnConstruction() +{ + m_ShaderName.Init( this, "shaderName" ); + + m_ShaderName = "wireframe"; + m_pShader = NULL; +} + +void CDmeShader::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Shader name access +//----------------------------------------------------------------------------- +void CDmeShader::SetShaderName( const char *pShaderName ) +{ + m_ShaderName = pShaderName; +} + +const char *CDmeShader::GetShaderName() const +{ + return m_ShaderName; +} + + +//----------------------------------------------------------------------------- +// Finds a shader +//----------------------------------------------------------------------------- +IShader *CDmeShader::FindShader() +{ + int nCount = MaterialSystem()->ShaderCount(); + IShader **ppShaderList = (IShader**)_alloca( nCount * sizeof(IShader*) ); + MaterialSystem()->GetShaders( 0, nCount, ppShaderList ); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( m_ShaderName, ppShaderList[i]->GetName() ) ) + return ppShaderList[i]; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Remove all shader parameters that don't exist in the new shader +//----------------------------------------------------------------------------- +void CDmeShader::RemoveUnusedShaderParams( IShader *pShader ) +{ + IDmAttribute* pAttribute = FirstAttribute(); + IDmAttribute* pNextAttribute = NULL; + for ( ; pAttribute; pAttribute = pNextAttribute ) + { + pNextAttribute = pAttribute->NextAttribute(); + + // Don't remove name, type, or id + if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) ) + continue; + + const char *pShaderParam = pAttribute->GetName(); + int nCount = pShader->GetNumParams(); + int i; + for ( i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pShaderParam, pShader->GetParamName( i ) ) ) + break; + } + + // No match? Remove it! + if ( i == nCount ) + { + RemoveAttributeByPtr( pAttribute ); + } + } +} + + +//----------------------------------------------------------------------------- +// Add attribute for shader parameter +//----------------------------------------------------------------------------- +IDmAttribute* CDmeShader::AddAttributeForShaderParameter( IShader *pShader, int nIndex ) +{ + ShaderParamType_t paramType = pShader->GetParamType( nIndex ); + const char *pParamName = pShader->GetParamName( nIndex ); + + IDmAttribute *pAttribute = NULL; + switch ( paramType ) + { + case SHADER_PARAM_TYPE_INTEGER: + pAttribute = AddAttributeTyped<int>( pParamName ); + break; + + case SHADER_PARAM_TYPE_BOOL: + pAttribute = AddAttributeTyped<bool>( pParamName ); + break; + + case SHADER_PARAM_TYPE_FLOAT: + pAttribute = AddAttributeTyped<float>( pParamName ); + break; + + case SHADER_PARAM_TYPE_STRING: + pAttribute = AddAttributeTyped<CUtlString>( pParamName ); + break; + + case SHADER_PARAM_TYPE_COLOR: + pAttribute = AddAttributeTyped<Color>( pParamName ); + break; + + case SHADER_PARAM_TYPE_VEC2: + pAttribute = AddAttributeTyped<Vector2D>( pParamName ); + break; + + case SHADER_PARAM_TYPE_VEC3: + pAttribute = AddAttributeTyped<Vector>( pParamName ); + break; + + case SHADER_PARAM_TYPE_VEC4: + pAttribute = AddAttributeTyped<Vector4D>( pParamName ); + break; + + case SHADER_PARAM_TYPE_FOURCC: + Assert( 0 ); + break; + + case SHADER_PARAM_TYPE_MATRIX: + pAttribute = AddAttributeTyped<VMatrix>( pParamName ); + break; + + case SHADER_PARAM_TYPE_TEXTURE: + pAttribute = AddAttributeTyped<CDmElementRef>( pParamName ); + break; + + case SHADER_PARAM_TYPE_MATERIAL: + pAttribute = AddAttributeTyped<CDmElementRef>( pParamName ); + break; + + default: + break; + } + return pAttribute; +} + + +//----------------------------------------------------------------------------- +// Add all shader parameters that don't currently exist +//----------------------------------------------------------------------------- +void CDmeShader::AddNewShaderParams( IShader *pShader ) +{ + int nCount = pShader->GetNumParams(); + int i; + for ( i = 0; i < nCount; ++i ) + { + const char *pParamName = pShader->GetParamName( i ); + + IDmAttribute* pAttribute = NULL; + for ( pAttribute = FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + // Don't remove name, type, or id + if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) ) + continue; + + const char *pAttributeName = pAttribute->GetName(); + if ( !Q_stricmp( pAttributeName, pParamName ) ) + break; + } + + // No match? Add it! + if ( pAttribute != NULL ) + continue; + + pAttribute = AddAttributeForShaderParameter( pShader, i ); + if ( pAttribute ) + { + const char *pDefault = pShader->GetParamDefault( i ); + + SetAttributeValueFromString( pParamName, pDefault ); + } + } +} + + +//----------------------------------------------------------------------------- +// resolve +//----------------------------------------------------------------------------- +void CDmeShader::Resolve() +{ + if ( !m_ShaderName.IsDirty() || !MaterialSystem() ) + return; + + // First, find the shader + IShader *pShader = FindShader(); + + // Remove all shader parameters that don't exist in the new shader + RemoveUnusedShaderParams( pShader ); + + // Add all shader parameters that don't currently exist + AddNewShaderParams( pShader ); +} + + +//----------------------------------------------------------------------------- +// Returns a procedural material to be associated with this shader +//----------------------------------------------------------------------------- +void CDmeShader::CreateMaterial( const char *pMaterialName ) +{ + KeyValues *pVMTKeyValues = new KeyValues( GetShaderName() ); + + IDmAttribute* pAttribute = FirstAttribute(); + IDmAttribute* pNextAttribute = NULL; + for ( ; pAttribute; pAttribute = pNextAttribute ) + { + pNextAttribute = pAttribute->NextAttribute(); + + // Don't remove name, type, or id + if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) ) + continue; + + const char *pShaderParam = pAttribute->GetName(); + int nCount = pShader->GetNumParams(); + int i; + for ( i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( pShaderParam, pShader->GetParamName( i ) ) ) + break; + } + + // No match? Remove it! + if ( i == nCount ) + { + RemoveAttributeByPtr( pAttribute ); + } + } + + pVMTKeyValues->SetInt( "$model", 1 ); + pVMTKeyValues->SetFloat( "$decalscale", 0.05f ); + pVMTKeyValues->SetString( "$basetexture", "error" ); + return MaterialSystem()->CreateMaterial( pMaterialName, pVMTKeyValues ); +} diff --git a/movieobjects/dmeshape.cpp b/movieobjects/dmeshape.cpp new file mode 100644 index 0000000..33a1f20 --- /dev/null +++ b/movieobjects/dmeshape.cpp @@ -0,0 +1,115 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeshape.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects_interfaces.h" +#include "movieobjects/dmedag.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeShape, CDmeShape ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeShape::OnConstruction() +{ + m_visible.InitAndSet( this, "visible", true ); +} + +void CDmeShape::OnDestruction() +{ +} + +void CDmeShape::Draw( const matrix3x4_t &shapeToWorld, CDmeDrawSettings *pDrawSettings /* = NULL */ ) +{ + Assert( 0 ); +} + + +//----------------------------------------------------------------------------- +// The default bounding sphere is empty at the origin +//----------------------------------------------------------------------------- +void CDmeShape::GetBoundingSphere( Vector &c, float &r ) const +{ + c.Zero(); + r = 0.0f; +} +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmeShape::GetParentCount() const +{ + int nReferringDags = 0; + + DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( GetHandle() ); + while ( i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); + CDmeDag *pDag = CastElement< CDmeDag >( pAttribute->GetOwner() ); + const static UtlSymId_t symShape = g_pDataModel->GetSymbol( "shape" ); + if ( pDag && pAttribute->GetNameSymbol() == symShape && pDag->GetFileId() == GetFileId() ) + { + ++nReferringDags; + } + + i = g_pDataModel->NextAttributeReferencingElement( i ); + } + + return nReferringDags; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeDag *CDmeShape::GetParent( int nParentIndex /*= 0 */ ) const +{ + int nReferringDags = 0; + + DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( GetHandle() ); + while ( i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID ) + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); + CDmeDag *pDag = CastElement< CDmeDag >( pAttribute->GetOwner() ); + const static UtlSymId_t symShape = g_pDataModel->GetSymbol( "shape" ); + if ( pDag && pAttribute->GetNameSymbol() == symShape && pDag->GetFileId() == GetFileId() ) + { + if ( nReferringDags == nParentIndex ) + return pDag; + + ++nReferringDags; + } + + i = g_pDataModel->NextAttributeReferencingElement( i ); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeShape::GetShapeToWorldTransform( matrix3x4_t &mat, int nParentIndex /*= 0 */ ) const +{ + CDmeDag *pDag = GetParent( nParentIndex ); + + if ( pDag ) + { + pDag->GetShapeToWorldTransform( mat ); + } + else + { + SetIdentityMatrix( mat ); + } +} diff --git a/movieobjects/dmesound.cpp b/movieobjects/dmesound.cpp new file mode 100644 index 0000000..d94c732 --- /dev/null +++ b/movieobjects/dmesound.cpp @@ -0,0 +1,93 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmesound.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects_interfaces.h" +#include "tier2/tier2.h" +#include "filesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeSound, CDmeSound ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeSound::OnConstruction() +{ + m_SoundName.Init( this, "soundname" ); + m_GameSoundName.Init( this, "gameSoundName" ); +} + +void CDmeSound::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// For sounds that are relative paths (instead of GameSound names), get full path +//----------------------------------------------------------------------------- +bool CDmeSound::ComputeSoundFullPath( char *pBuf, int nBufLen ) +{ + if ( !m_SoundName[0] ) + { + pBuf[0] = 0; + return false; + } + + // Compute the full path of the sound + char pRelativePath[MAX_PATH]; + Q_snprintf( pRelativePath, sizeof(pRelativePath), "sound\\%s", m_SoundName.Get() ); + return g_pFullFileSystem->RelativePathToFullPath( pRelativePath, "GAME", pBuf, nBufLen ) != NULL; +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeGameSound, CDmeGameSound ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeGameSound::OnConstruction() +{ + m_Volume .Init( this, "volume" ); + m_Level .Init( this, "level" ); + m_Pitch .Init( this, "pitch" ); + + m_IsStatic .Init( this, "static" ); + m_Channel .Init( this, "channel" ); + m_Flags .Init( this, "flags" ); + +// m_Source .Init( this, "source" ); +// m_FollowSource.Init( this, "followsource" ); + m_Origin .Init( this, "origin" ); + m_Direction .Init( this, "direction" ); +} + +void CDmeGameSound::OnDestruction() +{ +} + +CDmElement *CDmeGameSound::FindOrAddPhonemeExtractionSettings() +{ + if ( HasAttribute( "PhonemeExtractionSettings" ) ) + return GetValueElement< CDmElement >( "PhonemeExtractionSettings" ); + + CDmElement *settings = CreateElement< CDmElement >( "PhonemeExtractionSettings", GetFileId() ); + if ( !settings ) + return NULL; + + SetValue( "PhonemeExtractionSettings", settings ); + return settings; +} diff --git a/movieobjects/dmetestmesh.cpp b/movieobjects/dmetestmesh.cpp new file mode 100644 index 0000000..f3e57af --- /dev/null +++ b/movieobjects/dmetestmesh.cpp @@ -0,0 +1,1757 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmetestmesh.h" +#include "movieobjects/dmetransform.h" +#include "movieobjects_interfaces.h" + +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "mathlib/vector.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imesh.h" +#include "datacache/imdlcache.h" +#include "istudiorender.h" +#include "studio.h" +#include "bone_setup.h" +#include "materialsystem/ivertextexture.h" +#include "morphdata.h" +#include "tier3/tier3.h" + +#include <strstream> +#include <fstream> +#include <algorithm> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTestMesh, CDmeTestMesh ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeTestMesh::OnConstruction() +{ + m_MDLHandle = MDLHANDLE_INVALID; + m_pMaterial = NULL; + m_pMesh = NULL; + m_pMorph = NULL; + m_pControlCage = NULL; + SetValue( "transform", g_pDataModel->IsUnserializing() ? NULL : CreateElement< CDmeTransform >( "transform", GetFileId() ) ); + SetValue( "mdlfilename", "models/alyx.mdl" ); + SetValue( "morphfilename", "models/alyx.morph" ); + SetValue( "skin", 0 ); + SetValue( "body", 0 ); + SetValue( "sequence", 0 ); + SetValue( "lod", 0 ); + SetValue( "playbackrate", 1.0f ); + SetValue( "time", 0.0f ); + SetValue( "subdivlevel", 1 ); +} + +void CDmeTestMesh::OnDestruction() +{ + UnloadMorphData(); + UnreferenceMDL(); + DestroyControlCage(); + DestroyMesh(); +} + + +//----------------------------------------------------------------------------- +// Addref/Release the MDL handle +//----------------------------------------------------------------------------- +void CDmeTestMesh::ReferenceMDL( const char *pMDLName ) +{ + if ( !g_pMDLCache ) + return; + + if ( pMDLName && pMDLName[0] ) + { + Assert( m_MDLHandle == MDLHANDLE_INVALID ); + m_MDLHandle = g_pMDLCache->FindMDL( pMDLName ); + } +} + +void CDmeTestMesh::UnreferenceMDL() +{ + if ( !g_pMDLCache ) + return; + + if ( m_MDLHandle != MDLHANDLE_INVALID ) + { + g_pMDLCache->Release( m_MDLHandle ); + m_MDLHandle = MDLHANDLE_INVALID; + } +} + + +//----------------------------------------------------------------------------- +// Creates the mesh to draw +//----------------------------------------------------------------------------- +void CDmeTestMesh::CreateMesh() +{ + DestroyMesh(); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + m_pMaterial = g_pMaterialSystem->FindMaterial( "shadertest/vertextexturetest", NULL, false ); + m_pMesh = pRenderContext->CreateStaticMesh( m_pMaterial, 0, "dmemesh" ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( m_pMesh, MATERIAL_TRIANGLES, 8, 36 ); + + // Draw a simple cube + static Vector s_pPositions[8] = + { + Vector( -10, -10, -10 ), + Vector( 10, -10, -10 ), + Vector( -10, 10, -10 ), + Vector( 10, 10, -10 ), + Vector( -10, -10, 10 ), + Vector( 10, -10, 10 ), + Vector( -10, 10, 10 ), + Vector( 10, 10, 10 ), + }; + + static Vector2D s_pTexCoords[8] = + { + Vector2D( 0, 0 ), + Vector2D( 0.5, 0 ), + Vector2D( 0, 0.5 ), + Vector2D( 0.5, 0.5 ), + Vector2D( 0.5, 0.5 ), + Vector2D( 1, 0.5 ), + Vector2D( 0.5, 1 ), + Vector2D( 1, 1 ), + }; + + static unsigned char s_pColor[8][3] = + { + { 255, 255, 255 }, + { 0, 255, 255 }, + { 255, 0, 255 }, + { 255, 255, 0 }, + { 255, 0, 0 }, + { 0, 255, 0 }, + { 0, 0, 255 }, + { 0, 0, 0 }, + }; + + static int s_pIndices[12][3] = + { + { 0, 1, 5 }, { 0, 5, 4 }, + { 4, 5, 7 }, { 4, 7, 6 }, + { 0, 4, 6 }, { 0, 6, 2 }, + { 0, 2, 3 }, { 0, 3, 1 }, + { 1, 3, 7 }, { 1, 7, 5 }, + { 2, 6, 7 }, { 2, 7, 3 }, + }; + + for ( int i = 0; i < 8; ++i ) + { + meshBuilder.Position3fv( s_pPositions[ i ].Base() ); + meshBuilder.TexCoord2fv( 0, s_pTexCoords[ i ].Base() ); +// meshBuilder.TexCoord2f( 1, i, 0.0f ); + meshBuilder.Color3ubv( s_pColor[ i ] ); + meshBuilder.AdvanceVertex(); + } + + for ( int i = 0; i < 12; ++i ) + { + meshBuilder.FastIndex( s_pIndices[i][0] ); + meshBuilder.FastIndex( s_pIndices[i][1] ); + meshBuilder.FastIndex( s_pIndices[i][2] ); + } + + meshBuilder.End(); +} + +void CDmeTestMesh::DestroyMesh() +{ + if ( m_pMesh ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->DestroyStaticMesh( m_pMesh ); + m_pMesh = NULL; + } +} + + + +//----------------------------------------------------------------------------- +// Morph data +//----------------------------------------------------------------------------- +void CDmeTestMesh::LoadMorphData( const char *pMorphFile, int nVertexCount ) +{ + UnloadMorphData(); + + IMorphData *pMorphData = CreateMorphData(); + m_pMorph = pMorphData->Compile( pMorphFile, m_pMaterial, nVertexCount ); + DestroyMorphData( pMorphData ); +} + +void CDmeTestMesh::UnloadMorphData() +{ + if ( m_pMorph ) + { + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->DestroyMorph( m_pMorph ); + m_pMorph = NULL; + } +} + + +//----------------------------------------------------------------------------- +// This function gets called whenever an attribute changes +//----------------------------------------------------------------------------- +void CDmeTestMesh::Resolve() +{ + CDmAttribute *pMDLFilename = GetAttribute( "mdlfilename" ); + if ( pMDLFilename && pMDLFilename->IsFlagSet( FATTRIB_DIRTY ) ) + { + UnreferenceMDL(); + ReferenceMDL( GetValueString( "mdlfilename" ) ); + return; + } + + CDmAttribute *pMorphFilename = GetAttribute( "morphfilename" ); + if ( pMorphFilename && pMorphFilename->IsFlagSet( FATTRIB_DIRTY ) ) + { + CreateMesh(); + + UnloadMorphData(); + LoadMorphData( GetValueString( "morphfilename" ), 8 ); + return; + } +} + + +//----------------------------------------------------------------------------- +// Loads the model matrix based on the transform +//----------------------------------------------------------------------------- +void CDmeTestMesh::LoadModelMatrix( CDmeTransform *pTransform ) +{ + // FIXME: Should this go into the DmeTransform node? + matrix3x4_t transform; + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pTransform->GetTransform( transform ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadMatrix( transform ); +} + + +//----------------------------------------------------------------------------- +// A subvision mesh +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// NOTES: +// The subdivision mesh is fast because it assumes a very particular ordering +// and definition of the data so that it can determine all subdivided data by +// inspection without any searching. Here's the layout: +// +// First, a face stores a list of edge indices which reference the edges +// that make up the face. A face is assumed to traverse its vertices in CCW order. +// We define the "relative edge index" for an edge within a face as the +// order in which that edge is visited while traversing the edges in CCW order, +// so 0 is the first visited edge, and 1 is the next, etc. +// +// First, edges are defined in a specific way. The edge is assumed to be +// *directed*, starting at vertex 0 and leading toward vertex 1. Now imagine the +// two faces that shared this edge and that they both traverse their edges in +// a right-handed, or CCW direction. Face 0 associated with the edge, to maintain +// a CCW ordering, must traverse the edge in a *reverse* direction, heading from +// vertex 1 to vertex 0. Face 1 associated with the edge traverses the edge +// in a forward direction, from vertex 0 to vertex 1. +// +// When subdivision happens, it occurs in a very specific way also. First, when +// creating the new vertices, for uniform subdivision, we create a new vertex +// per face, a new vertex per edge, and adjust all existing vertices. When creating +// these vertices in the subdivided mesh, we first add the face midpoint vertices, +// then the edge midpoint vertices, then the vertices from the un-subdivided mesh, to +// the m_Vertices array of the subdivided mesh. +// +// Edge subdivision always works in a uniform way: For each edge in the unsubdivided +// mesh, 4 edges are created from the edge midpoint, connecting to the two +// face midpoint vertices and the two edge endpoints. In order to maintain the +// specific ordering of the edges described above, we define the edges in the +// following manner: +// * Subdivided edge 0 : Starts at face 0 midpoint, ends at edge midpoint +// * Subdivided edge 1 : Starts at edge midpoint, ends at face 1 midpoint +// * Subdivided edge 2 : Starts at original edge's vertex 0, ends at edge midpoint +// * Subdivided edge 3 : Starts at edge midpoint, ends at original edge's vertex 1 +// +// Face subdivision *also* always works in a uniform way: For each face in the +// unsubdivided mesh, N new faces are created, one for each edge in the unsubdivided +// face. The faces are ordered in a very specific way: +// * Subdivided face 0 : Starts at the face midpoint, goes to unsubdivided edge 0's midpoint, +// winds around the edge until it hits unsubdivided edge 1's midpoint, +// then heads back to the face midpoint. +// * Subdivided face 1 : Starts at the face midpoint, goes to unsubdivided edge 1's midpoint, +// winds around the edge until it hits unsubdivided edge 2's midpoint, +// then heads back to the face midpoint. +// etc. +//----------------------------------------------------------------------------- +struct SubdivVertex_t +{ + Vector m_vecPosition; + Vector m_vecNormal; + Vector m_vecTexCoord; + int m_nValence; +}; + +// NOTE: The edge is always defined such that the edge going from vertex[0] to vertex[1] +// is counter-clockwise when seen from face[1] and and clockwise when seen from face[0]. +struct Edge_t +{ + int m_pFace[2]; + int m_pRelativeEdgeIndex[2]; // Goes from 0-N always, specifies the Nth edge of the polygon it's part of for each of the two faces + int m_pVertex[2]; +}; + +struct Face_t +{ + int m_nFirstEdgeIndex; + int m_nEdgeCount; + + // Stores the index of the first face in the subdivided mesh + // isn't actually a part of the mesh data, but I'm storing it here to reduce number of allocations to make + mutable int m_nFirstSubdividedFace; +}; + +struct SubdivMesh_t +{ + CUtlVector<SubdivVertex_t> m_Vertices; + CUtlVector<Edge_t> m_Edges; + + // Positive values mean read from m_Edges[x], use m_pVertex[0] for leading vertex + // Negative values mean read from m_Edges[-1-x], use m_pVertex[1] for leading vertex + CUtlVector<int> m_EdgeIndices; + CUtlVector<Face_t> m_Faces; + + int m_nTotalIndexCount; + int m_nTotalLineCount; +}; + + +//----------------------------------------------------------------------------- +// Clears a mesh +//----------------------------------------------------------------------------- +static void ClearMesh( SubdivMesh_t &dest ) +{ + dest.m_Vertices.RemoveAll(); + dest.m_Edges.RemoveAll(); + dest.m_EdgeIndices.RemoveAll(); + dest.m_Faces.RemoveAll(); + dest.m_nTotalIndexCount = 0; + dest.m_nTotalLineCount = 0; +} + + +//----------------------------------------------------------------------------- +// Gets the leading vertex of an edge +//----------------------------------------------------------------------------- +static inline int GetLeadingEdgeVertexIndex( const SubdivMesh_t &src, int nEdge ) +{ + if ( nEdge >= 0 ) + { + const Edge_t &edge = src.m_Edges[nEdge]; + return edge.m_pVertex[0]; + } + + const Edge_t &edge = src.m_Edges[ -1 - nEdge ]; + return edge.m_pVertex[1]; +} + +static inline const SubdivVertex_t &GetLeadingEdgeVertex( const SubdivMesh_t &src, int nEdge ) +{ + return src.m_Vertices[ GetLeadingEdgeVertexIndex( src, nEdge ) ]; +} + + +//----------------------------------------------------------------------------- +// Adds face midpoints to a mesh +//----------------------------------------------------------------------------- +static void AddFaceMidpointsToMesh( const SubdivMesh_t &src, SubdivMesh_t &dest ) +{ + int nCurrSubdividedFace = 0; + + int nSrcFaceCount = src.m_Faces.Count(); + for ( int i = 0; i < nSrcFaceCount; ++i ) + { + int nEdgeCount = src.m_Faces[i].m_nEdgeCount; + int nEdgeIndex = src.m_Faces[i].m_nFirstEdgeIndex; + + Assert( nEdgeCount != 0 ); + + int v = dest.m_Vertices.AddToTail( ); + SubdivVertex_t &vert = dest.m_Vertices[v]; + vert.m_vecPosition.Init(); + vert.m_vecTexCoord.Init(); + vert.m_nValence = nEdgeCount; + + for ( int j = 0; j < nEdgeCount; ++j, ++nEdgeIndex ) + { + // NOTE: Instead of calling GetLeadingEdgeVertex, + // I could add both vertices for each edge + multiply by 0.5 + int nEdge = src.m_EdgeIndices[nEdgeIndex]; + + const SubdivVertex_t &srcVert = GetLeadingEdgeVertex( src, nEdge ); + vert.m_vecPosition += srcVert.m_vecPosition; + vert.m_vecTexCoord += srcVert.m_vecTexCoord; + } + + vert.m_vecPosition /= nEdgeCount; + vert.m_vecTexCoord /= nEdgeCount; + + // Store off the face index in the dest mesh of the first subdivided face for this guy. + src.m_Faces[i].m_nFirstSubdividedFace = nCurrSubdividedFace; + nCurrSubdividedFace += nEdgeCount; + } +} + + +//----------------------------------------------------------------------------- +// Adds edge midpoints to a mesh +//----------------------------------------------------------------------------- +static void AddEdgeMidpointsToMesh( const SubdivMesh_t &src, SubdivMesh_t &dest ) +{ + int nSrcEdgeCount = src.m_Edges.Count(); + for ( int i = 0; i < nSrcEdgeCount; ++i ) + { + const Edge_t &edge = src.m_Edges[i]; + + int v = dest.m_Vertices.AddToTail( ); + SubdivVertex_t &vert = dest.m_Vertices[v]; + vert.m_nValence = 4; + + const SubdivVertex_t *pSrcVert = &src.m_Vertices[ edge.m_pVertex[0] ]; + vert.m_vecPosition = pSrcVert->m_vecPosition; + vert.m_vecTexCoord = pSrcVert->m_vecTexCoord; + + pSrcVert = &src.m_Vertices[ edge.m_pVertex[1] ]; + vert.m_vecPosition += pSrcVert->m_vecPosition; + vert.m_vecTexCoord += pSrcVert->m_vecTexCoord; + + // NOTE: We know that the first n vertices added to dest correspond to the src face midpoints + pSrcVert = &dest.m_Vertices[ edge.m_pFace[0] ]; + vert.m_vecPosition += pSrcVert->m_vecPosition; + vert.m_vecTexCoord += pSrcVert->m_vecTexCoord; + + pSrcVert = &dest.m_Vertices[ edge.m_pFace[1] ]; + vert.m_vecPosition += pSrcVert->m_vecPosition; + vert.m_vecTexCoord += pSrcVert->m_vecTexCoord; + + vert.m_vecPosition /= 4.0f; + vert.m_vecTexCoord /= 4.0f; + } +} + + +//----------------------------------------------------------------------------- +// Adds edge midpoints to a mesh +//----------------------------------------------------------------------------- +static void AddModifiedVerticesToMesh( const SubdivMesh_t &src, SubdivMesh_t &dest ) +{ + int nSrcVertexCount = src.m_Vertices.Count(); + + // This computes the equation v(i+1) = ((N-2)/N) * v(i) + (1/N^2) * sum( ei + fi ) + int nFirstDestVertex = dest.m_Vertices.Count(); + for ( int i = 0; i < nSrcVertexCount; ++i ) + { + int v = dest.m_Vertices.AddToTail( ); + SubdivVertex_t &vert = dest.m_Vertices[v]; + + int nValence = src.m_Vertices[i].m_nValence; + vert.m_nValence = nValence; + float flScale = (float)(nValence - 2) / nValence; + VectorScale( src.m_Vertices[i].m_vecPosition, flScale, vert.m_vecPosition ); + VectorScale( src.m_Vertices[i].m_vecTexCoord, flScale, vert.m_vecTexCoord ); + } + + int nSrcEdgeCount = src.m_Edges.Count(); + for ( int i = 0; i < nSrcEdgeCount; ++i ) + { + const Edge_t &edge = src.m_Edges[i]; + for ( int j = 0; j < 2; ++j ) + { + int nDestVertIndex = nFirstDestVertex + edge.m_pVertex[j]; + SubdivVertex_t &destVertex = dest.m_Vertices[nDestVertIndex]; + + float ooValenceSq = 1.0f / destVertex.m_nValence; + ooValenceSq *= ooValenceSq; + + // This adds in the contribution from the source vertex at the opposite edge + const SubdivVertex_t &srcOtherVert = src.m_Vertices[ edge.m_pVertex[ 1 - j ] ]; + VectorMA( destVertex.m_vecPosition, ooValenceSq, srcOtherVert.m_vecPosition, destVertex.m_vecPosition ); + VectorMA( destVertex.m_vecTexCoord, ooValenceSq, srcOtherVert.m_vecTexCoord, destVertex.m_vecTexCoord ); + + // This adds in the contribution from the two faces it's part of + // NOTE: Usage of dest here is correct; this grabs the vertex that + // was created that was in the middle of the source mesh's face + const SubdivVertex_t *pSrcFace = &dest.m_Vertices[ edge.m_pFace[ 0 ] ]; + VectorMA( destVertex.m_vecPosition, 0.5f * ooValenceSq, pSrcFace->m_vecPosition, destVertex.m_vecPosition ); + VectorMA( destVertex.m_vecTexCoord, 0.5f * ooValenceSq, pSrcFace->m_vecTexCoord, destVertex.m_vecTexCoord ); + pSrcFace = &dest.m_Vertices[ edge.m_pFace[ 1 ] ]; + VectorMA( destVertex.m_vecPosition, 0.5f * ooValenceSq, pSrcFace->m_vecPosition, destVertex.m_vecPosition ); + VectorMA( destVertex.m_vecTexCoord, 0.5f * ooValenceSq, pSrcFace->m_vecTexCoord, destVertex.m_vecTexCoord ); + } + } +} + + +//----------------------------------------------------------------------------- +// Adds unique subdivided edges so they aren't repeated. +//----------------------------------------------------------------------------- +static void AddSubdividedEdges( const SubdivMesh_t &src, SubdivMesh_t &dest ) +{ + // NOTE: We iterate over each edge in sequence and add edges + // between face 0, then face 1, then vertex 0, then vertex 1. + // The vertex index for the vert at the center of original face N is N. + // The vertex index for the vert at the center of original edge N is nSrcFaceCount + N; + // The vertex index for the vert at original vertex N is nSrcFaceCount + nSrcEdgeCount + N; + int nSrcFaceCount = src.m_Faces.Count(); + int nSrcEdgeCount = src.m_Edges.Count(); + + for ( int i = 0; i < nSrcEdgeCount; ++i ) + { + const Edge_t &srcEdge = src.m_Edges[i]; + + int e = dest.m_Edges.AddMultipleToTail( 4 ); + Edge_t *pDstEdge = &dest.m_Edges[e]; + + // Grab the two source faces + const Face_t *pFaces[2]; + pFaces[0] = &src.m_Faces[ srcEdge.m_pFace[0] ]; + pFaces[1] = &src.m_Faces[ srcEdge.m_pFace[1] ]; + + // Get the first subdivided face index + relative edge index + int pSubdividedFaceIndex[2]; + pSubdividedFaceIndex[0] = pFaces[0]->m_nFirstSubdividedFace; + pSubdividedFaceIndex[1] = pFaces[1]->m_nFirstSubdividedFace; + + // Get the relative edge index + int pRelativeEdgeIndex[2]; + pRelativeEdgeIndex[0] = srcEdge.m_pRelativeEdgeIndex[0]; + pRelativeEdgeIndex[1] = srcEdge.m_pRelativeEdgeIndex[1]; + + int pPrevRelativeEdgeIndex[2]; + pPrevRelativeEdgeIndex[0] = (srcEdge.m_pRelativeEdgeIndex[0] - 1); + if ( pPrevRelativeEdgeIndex[0] < 0 ) + { + pPrevRelativeEdgeIndex[0] = pFaces[0]->m_nEdgeCount - 1; + } + pPrevRelativeEdgeIndex[1] = (srcEdge.m_pRelativeEdgeIndex[1] - 1); + if ( pPrevRelativeEdgeIndex[1] < 0 ) + { + pPrevRelativeEdgeIndex[1] = pFaces[1]->m_nEdgeCount - 1; + } + + // This ordering maintains clockwise order + pDstEdge[0].m_pVertex[0] = srcEdge.m_pFace[0]; + pDstEdge[0].m_pVertex[1] = nSrcFaceCount + i; + pDstEdge[0].m_pFace[0] = pSubdividedFaceIndex[0] + pPrevRelativeEdgeIndex[0]; + pDstEdge[0].m_pFace[1] = pSubdividedFaceIndex[0] + pRelativeEdgeIndex[0]; + pDstEdge[0].m_pRelativeEdgeIndex[0] = 3; + pDstEdge[0].m_pRelativeEdgeIndex[1] = 0; + + pDstEdge[1].m_pVertex[0] = nSrcFaceCount + i; + pDstEdge[1].m_pVertex[1] = srcEdge.m_pFace[1]; + pDstEdge[1].m_pFace[0] = pSubdividedFaceIndex[1] + pRelativeEdgeIndex[1]; + pDstEdge[1].m_pFace[1] = pSubdividedFaceIndex[1] + pPrevRelativeEdgeIndex[1]; + pDstEdge[1].m_pRelativeEdgeIndex[0] = 0; + pDstEdge[1].m_pRelativeEdgeIndex[1] = 3; + + pDstEdge[2].m_pVertex[0] = nSrcFaceCount + nSrcEdgeCount + srcEdge.m_pVertex[0]; + pDstEdge[2].m_pVertex[1] = nSrcFaceCount + i; + pDstEdge[2].m_pFace[0] = pSubdividedFaceIndex[0] + pRelativeEdgeIndex[0]; + pDstEdge[2].m_pFace[1] = pSubdividedFaceIndex[1] + pPrevRelativeEdgeIndex[1]; + pDstEdge[2].m_pRelativeEdgeIndex[0] = 1; + pDstEdge[2].m_pRelativeEdgeIndex[1] = 2; + + pDstEdge[3].m_pVertex[0] = nSrcFaceCount + i; + pDstEdge[3].m_pVertex[1] = nSrcFaceCount + nSrcEdgeCount + srcEdge.m_pVertex[1]; + pDstEdge[3].m_pFace[0] = pSubdividedFaceIndex[0] + pPrevRelativeEdgeIndex[0]; + pDstEdge[3].m_pFace[1] = pSubdividedFaceIndex[1] + pRelativeEdgeIndex[1]; + pDstEdge[3].m_pRelativeEdgeIndex[0] = 2; + pDstEdge[3].m_pRelativeEdgeIndex[1] = 1; + } +} + + +//----------------------------------------------------------------------------- +// Adds unique subdivided faces +//----------------------------------------------------------------------------- +static void AddSubdividedFaces( const SubdivMesh_t &src, SubdivMesh_t &dest ) +{ + dest.m_nTotalIndexCount = 0; + dest.m_nTotalLineCount = 0; + int nSrcFaceCount = src.m_Faces.Count(); + for ( int i = 0; i < nSrcFaceCount; ++i ) + { + int nEdgeCount = src.m_Faces[i].m_nEdgeCount; + const int *pSrcEdgeIndex = &src.m_EdgeIndices[ src.m_Faces[i].m_nFirstEdgeIndex ]; + + int ei = dest.m_EdgeIndices.AddMultipleToTail( nEdgeCount * 4 ); + int *pDestEdgeIndex = &dest.m_EdgeIndices[ ei ]; + int *pPrevDestEdgeIndex = &pDestEdgeIndex[(nEdgeCount - 1) * 4]; + for ( int j = 0; j < nEdgeCount; ++j ) + { + // Add another quad. + dest.m_nTotalIndexCount += 6; + dest.m_nTotalLineCount += 4; + + // Add a face for every edge. Note that subdivided face N + // is the face whose goes through edge N. + int f = dest.m_Faces.AddToTail(); + Face_t *pDestFace = &dest.m_Faces[f]; + pDestFace->m_nEdgeCount = 4; + pDestFace->m_nFirstEdgeIndex = ei + (j * 4); + + // Fill it with bogus data + pDestFace->m_nFirstSubdividedFace = -1; + + // Now add in the edge indices to refer to the edges created in AddSubdividedEdges. + // Note that the new edge index == the old edge index * 4, since we always + // create 4 edges for every edge in the source list. + int *pCurrDestEdgeIndex = &pDestEdgeIndex[j*4]; + int nSrcEdgeIndex = pSrcEdgeIndex[j]; + if ( nSrcEdgeIndex >= 0 ) + { + // This means this polygon is the '1' index in the edge; it's following this edge CCW. + int nDestEdgeIndex = nSrcEdgeIndex * 4; + pCurrDestEdgeIndex[0] = -1 - (nDestEdgeIndex + 1); // We're following this edge backwards + pCurrDestEdgeIndex[1] = nDestEdgeIndex + 3; + pPrevDestEdgeIndex[2] = nDestEdgeIndex + 2; + pPrevDestEdgeIndex[3] = nDestEdgeIndex + 1; + } + else + { + // This means this polygon is the '0' index in the edge; it's following this edge CW. + int nDestEdgeIndex = (-1 - nSrcEdgeIndex) * 4; + pCurrDestEdgeIndex[0] = nDestEdgeIndex; + pCurrDestEdgeIndex[1] = -1 - (nDestEdgeIndex + 2); // We're following this edge backwards + pPrevDestEdgeIndex[2] = -1 - (nDestEdgeIndex + 3); // We're following this edge backwards + pPrevDestEdgeIndex[3] = -1 - (nDestEdgeIndex); // We're following this edge backwards + } + + pPrevDestEdgeIndex = pCurrDestEdgeIndex; + } + } +} + + +//----------------------------------------------------------------------------- +// Subdivides a mesh +//----------------------------------------------------------------------------- +static void SubdivideMesh( const SubdivMesh_t &src, SubdivMesh_t &dest ) +{ + // Preallocate space for dest data + int nSrcFaceCount = src.m_Faces.Count(); + int nSrcEdgeCount = src.m_Edges.Count(); + dest.m_Vertices.EnsureCapacity( nSrcFaceCount + nSrcEdgeCount + src.m_Vertices.Count() ); + dest.m_Edges.EnsureCapacity( nSrcEdgeCount * 4 ); + dest.m_EdgeIndices.EnsureCapacity( nSrcFaceCount * 16 ); + dest.m_Faces.EnsureCapacity( nSrcFaceCount * 4 ); // This is only true if we have valence 4 everywhere. + + // First, compute midpoints of each face, add them to the mesh + AddFaceMidpointsToMesh( src, dest ); + + // Next, for each edge, compute a new point which is the average of the edge points and the face midpoints + AddEdgeMidpointsToMesh( src, dest ); + + // Add modified versions of the vertices in the src mesh based on the new computed points and add them to the dest mesh + AddModifiedVerticesToMesh( src, dest ); + + // Add subdivided edges based on the previous edges + AddSubdividedEdges( src, dest ); + + // Add subdivided faces referencing the subdivided edges + AddSubdividedFaces( src, dest ); +} + + +//----------------------------------------------------------------------------- +// Creates/destroys the subdiv control cage +//----------------------------------------------------------------------------- +void CDmeTestMesh::CreateControlCage( ) +{ + DestroyControlCage(); + m_pControlCage = new SubdivMesh_t; + + // Draw a simple cube + static Vector s_pPositions[8] = + { + Vector( -30, -30, -30 ), + Vector( 30, -30, -30 ), + Vector( -30, 30, -30 ), + Vector( 30, 30, -30 ), + Vector( -30, -30, 30 ), + Vector( 30, -30, 30 ), + Vector( -30, 30, 30 ), + Vector( 30, 30, 30 ), + }; + + static Vector2D s_pTexCoords[8] = + { + Vector2D( 0, 0 ), + Vector2D( 0.5, 0 ), + Vector2D( 0, 0.5 ), + Vector2D( 0.5, 0.5 ), + Vector2D( 0.5, 0.5 ), + Vector2D( 1, 0.5 ), + Vector2D( 0.5, 1 ), + Vector2D( 1, 1 ), + }; + + // Indices into the vertex array + static int s_pEdges[12][2] = + { + { 0, 4 }, { 4, 6 }, { 6, 2 }, { 2, 0 }, // 0 -> -x + { 1, 3 }, { 3, 7 }, { 7, 5 }, { 5, 1 }, // 1 -> +x + { 0, 1 }, { 5, 4 }, // 2 -> -y + { 6, 7 }, { 3, 2 }, // 3 -> +y + // 4 -> -z + // 5 -> +z + }; + + // Indices into the face array associated w/ the edges above + static int s_pEdgeFaces[12][2] = + { + { 2, 0 }, { 5, 0 }, { 3, 0 }, { 4, 0 }, // 0 -> -x + { 4, 1 }, { 3, 1 }, { 5, 1 }, { 2, 1 }, // 1 -> +x + { 4, 2 }, { 5, 2 }, // 2 -> -y + { 5, 3 }, { 4, 3 }, // 3 -> +y + // 4 -> -z + // 5 -> +z + }; + + // In what order does edge s_pEdges[i] appear on faces s_pEdgeFaces[i][0] and s_pEdgeFaces[i][1] + // in the list s_pIndices[s_pEdgeFaces[i][j]] below? Note the #s 0, 1, 2, and 3 should appear 6 times each in this array + // representing the fact that each face has a 0th,1st,2nd, and 3rd edge. + static int s_pRelativeEdgeIndex[12][2] = + { + { 3, 0 }, { 3, 1 }, { 0, 2 }, { 0, 3 }, // 0 -> -x + { 2, 0 }, { 2, 1 }, { 1, 2 }, { 1, 3 }, // 1 -> +x + { 3, 0 }, { 0, 2 }, // 2 -> -y + { 2, 1 }, { 1, 3 }, // 3 -> +y + // 4 -> -z + // 5 -> +z + }; + + static int s_pIndices[6][5] = + { + { 0, 4, 6, 2, 0 }, // 0 -> -x + { 1, 3, 7, 5, 1 }, // 1 -> +x + { 0, 1, 5, 4, 0 }, // 2 -> -y + { 2, 6, 7, 3, 2 }, // 3 -> +y + { 0, 2, 3, 1, 0 }, // 4 -> -z + { 4, 5, 7, 6, 4 }, // 5 -> +z + }; + + // Add vertices + int i; + for ( i = 0; i < 8; ++i ) + { + int v = m_pControlCage->m_Vertices.AddToTail(); + SubdivVertex_t &vert = m_pControlCage->m_Vertices[v]; + vert.m_vecPosition = s_pPositions[i]; + vert.m_vecNormal = vec3_origin; + vert.m_vecTexCoord.AsVector2D() = s_pTexCoords[i]; + vert.m_nValence = 3; + } + + // Add unique edges + for ( i = 0; i < 12; ++i ) + { + int e = m_pControlCage->m_Edges.AddToTail(); + Edge_t &edge = m_pControlCage->m_Edges[e]; + edge.m_pVertex[0] = s_pEdges[i][0]; + edge.m_pVertex[1] = s_pEdges[i][1]; + edge.m_pFace[0] = s_pEdgeFaces[i][0]; + edge.m_pFace[1] = s_pEdgeFaces[i][1]; + edge.m_pRelativeEdgeIndex[0] = s_pRelativeEdgeIndex[i][0]; + edge.m_pRelativeEdgeIndex[1] = s_pRelativeEdgeIndex[i][1]; + } + + m_pControlCage->m_nTotalIndexCount = 0; + m_pControlCage->m_nTotalLineCount = 0; + for ( i = 0; i < 6; ++i ) + { + int f = m_pControlCage->m_Faces.AddToTail(); + Face_t &face = m_pControlCage->m_Faces[f]; + face.m_nFirstEdgeIndex = m_pControlCage->m_EdgeIndices.Count(); + face.m_nEdgeCount = 4; + + // Place an invalid value here + face.m_nFirstSubdividedFace = -1; + + // Two triangles per quad + m_pControlCage->m_nTotalIndexCount += 6; + m_pControlCage->m_nTotalLineCount += 4; + + for ( int j = 0; j < 4; ++j ) + { + int k; + for ( k = 0; k < 12; ++k ) + { + if ( (s_pIndices[i][j] == s_pEdges[k][0]) && (s_pIndices[i][j+1] == s_pEdges[k][1]) ) + { + m_pControlCage->m_EdgeIndices.AddToTail( k ); + break; + } + if ( (s_pIndices[i][j] == s_pEdges[k][1]) && (s_pIndices[i][j+1] == s_pEdges[k][0]) ) + { + m_pControlCage->m_EdgeIndices.AddToTail( -1-k ); + break; + } + } + Assert( k != 12 ); + } + } +} + +void CDmeTestMesh::DestroyControlCage( ) +{ + if ( m_pControlCage ) + { + delete m_pControlCage; + m_pControlCage = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Draws a subdiv mesh +//----------------------------------------------------------------------------- +void CDmeTestMesh::DrawSubdivMesh( const SubdivMesh_t &mesh ) +{ + if ( !g_pMaterialSystem ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "debug/debugwireframe", NULL, false ); + pRenderContext->Bind( pMaterial ); + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + CMeshBuilder meshBuilder; + + int nVertexCount = mesh.m_Vertices.Count(); + +// meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertexCount, mesh.m_nTotalIndexCount ); + meshBuilder.Begin( pMesh, MATERIAL_LINES, nVertexCount, mesh.m_nTotalLineCount * 2 ); + + for ( int i = 0; i < nVertexCount; ++i ) + { + meshBuilder.Position3fv( mesh.m_Vertices[ i ].m_vecPosition.Base() ); + meshBuilder.TexCoord2fv( 0, mesh.m_Vertices[ i ].m_vecTexCoord.Base() ); + meshBuilder.TexCoord2f( 1, i, 0.0f ); + meshBuilder.Color3ub( 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + } + + int nFaceCount = mesh.m_Faces.Count(); + for ( int i = 0; i < nFaceCount; ++i ) + { + int nEdgeCount = mesh.m_Faces[i].m_nEdgeCount; + const int *pEdgeIndex = &mesh.m_EdgeIndices[ mesh.m_Faces[i].m_nFirstEdgeIndex ]; + int nPrevIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[nEdgeCount-1] ); + for ( int j = 0; j < nEdgeCount; ++j ) + { + int nCurrIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[j] ); + meshBuilder.FastIndex( nPrevIndex ); + meshBuilder.FastIndex( nCurrIndex ); + nPrevIndex = nCurrIndex; + } + } + + /* + int nFaceCount = mesh.m_Faces.Count(); + for ( int i = 0; i < nFaceCount; ++i ) + { + int nEdgeCount = mesh.m_Faces[i].m_nEdgeCount; + const int *pEdgeIndex = &mesh.m_EdgeIndices[ mesh.m_Faces[i].m_nFirstEdgeIndex ]; + int nRootIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[0] ); + int nPrevIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[1] ); + for ( int j = 0; j < nEdgeCount - 2; ++j ) + { + int nCurrIndex = GetLeadingEdgeVertexIndex( mesh, pEdgeIndex[j+2] ); + meshBuilder.FastIndex( nRootIndex ); + meshBuilder.FastIndex( nPrevIndex ); + meshBuilder.FastIndex( nCurrIndex ); + nPrevIndex = nCurrIndex; + } + } + */ + + meshBuilder.End(); + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Draws a subdivided box +//----------------------------------------------------------------------------- +void CDmeTestMesh::DrawSubdividedBox() +{ + if ( !g_pMaterialSystem ) + return; + + if ( !m_pControlCage ) + { + CreateControlCage( ); + } + + int nSubdivLevel = GetValue<int>( "subdivlevel" ); + if ( nSubdivLevel == 0 ) + { + DrawSubdivMesh( *m_pControlCage ); + return; + } + + // Construct the initial mesh + SubdivMesh_t subdivMesh[2]; + SubdivideMesh( *m_pControlCage, subdivMesh[0] ); + + // Compute the subdivided vertices + int nCurrMesh = 0; + while ( --nSubdivLevel > 0 ) + { + ClearMesh( subdivMesh[1 - nCurrMesh] ); + SubdivideMesh( subdivMesh[nCurrMesh], subdivMesh[1 - nCurrMesh] ); + if (( subdivMesh[1 - nCurrMesh].m_nTotalLineCount * 2 >= 32768 ) || ( subdivMesh[1 - nCurrMesh].m_Vertices.Count() >= 32768 )) + break; + nCurrMesh = 1 - nCurrMesh; + } + + // Draw the subdivided mesh + DrawSubdivMesh( subdivMesh[nCurrMesh] ); +} + + +//----------------------------------------------------------------------------- +// Draws the mesh +//----------------------------------------------------------------------------- +void CDmeTestMesh::DrawBox( CDmeTransform *pTransform ) +{ + if ( !g_pMaterialSystem ) + return; + + // FIXME: Hack! + if ( !m_pMorph || !m_pMesh ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + // Set up morph factors + float pMorphFactors[32]; + for ( int i = 0; i < 32; ++i ) + { + pMorphFactors[i] = 0.5f + 0.5f * sin( 2 * 3.14 * ( Plat_FloatTime() / 5.0f + (float)i / 32.0f ) ); + } + pMorphFactors[1] = 1.0f - pMorphFactors[0]; + pRenderContext->SetMorphTargetFactors( 0, pMorphFactors, 32 ); + + // FIXME: Should this call be made from the application rendering the mesh? + LoadModelMatrix( pTransform ); + + pRenderContext->BindMorph( m_pMorph ); + + pRenderContext->Bind( m_pMaterial ); + m_pMesh->Draw(); + + pRenderContext->BindMorph( NULL ); +} + + +//----------------------------------------------------------------------------- +// Draws the mesh +//----------------------------------------------------------------------------- +void CDmeTestMesh::Draw( const matrix3x4_t& shapeToWorld, CDmeDrawSettings *pDrawSettings ) +{ + if ( !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender ) + return; + +#if 0 +// DrawSubdividedBox( pTransform ); + DrawBox( pTransform ); + return; + +#elif 0 + if ( m_MDLHandle == MDLHANDLE_INVALID ) + return; + + // Color + alpha modulation + Vector white(1.0f, 1.0f, 1.0f); + g_pStudioRender->SetColorModulation( white.Base() ); + g_pStudioRender->SetAlphaModulation( 1.0f ); + + DrawModelInfo_t info; + info.m_pStudioHdr = g_pMDLCache->GetStudioHdr( m_MDLHandle ); + info.m_pHardwareData = g_pMDLCache->GetHardwareData( m_MDLHandle ); + info.m_Decals = STUDIORENDER_DECAL_INVALID; + info.m_Skin = GetAttributeValueInt( "skin" ); + info.m_Body = GetAttributeValueInt( "body" ); + info.m_HitboxSet = 0; + info.m_pClientEntity = NULL; + info.m_ppColorMeshes = NULL; + info.m_bStaticLighting = false; + info.m_Lod = GetAttributeValueInt( "lod" ); + + // FIXME: Deal with lighting + for ( int i = 0; i < 6; ++ i ) + { + info.m_vecAmbientCube[i].Init( 1, 1, 1 ); + } + + info.m_nLocalLightCount = 0; +// info.m_LocalLightDescs; + + matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( info.m_pStudioHdr->numbones ); + SetUpBones( pTransform, info.m_pStudioHdr->numbones, pBoneToWorld ); + g_pStudioRender->UnlockBoneMatrices(); + + // Root transform + matrix3x4_t rootToWorld; + pTransform->GetTransform( rootToWorld ); + + Vector vecModelOrigin; + MatrixGetColumn( rootToWorld, 3, vecModelOrigin ); + g_pStudioRender->DrawModel( NULL, info, pBoneToWorld, vecModelOrigin, STUDIORENDER_DRAW_ENTIRE_MODEL ); +#else + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + +#if 1 + matrix3x4_t mat; + if ( m_bones.size() == 1 ) + { + pRenderContext->MatrixMode( MATERIAL_MODEL ); + m_bones[0]->GetTransform( mat ); + pRenderContext->LoadMatrix( mat ); +// pRenderContext->LoadMatrix( m_bones[0] ); // m_PoseToWorld[0] + } + + pRenderContext->SetNumBoneWeights( 2 ); // pStrip->numBones + + uint bn = m_bones.size(); + for ( uint bi = 0; bi < bn; ++bi ) + { + m_bones[bi]->GetTransform( mat ); + +#if 0 // hack to see whether bones are actually affecting the model + float f = 100.0f; + Vector translation; + MatrixGetColumn( mat, 3, &translation ); + translation.x += (bi&1) ? f : -f; + translation.y += (bi&2) ? f : -f; + translation.z += (bi&4) ? f : -f; + MatrixSetColumn( translation, 3, mat ); +#endif + pRenderContext->LoadBoneMatrix( bi, mat ); + } +#else + pRenderContext->MatrixMode( MATERIAL_MODEL ); + matrix3x4_t mat; + Assert( !m_bones.empty() ); + m_bones[0]->GetTransform( mat ); + pRenderContext->LoadMatrix( mat ); +#endif + + IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "Models/shadertest/unlitgenericmodel", NULL, false ); +// IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "debug/debugwireframevertexcolor", NULL, false ); +// IMaterial *pMaterial = g_pMaterialSystem->FindMaterial( "debug/debugwireframe", NULL, false ); + pRenderContext->Bind( pMaterial ); + + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + + int mn = m_submeshes.size(); + for ( int mi = 0; mi < mn; ++mi ) + { + CMeshBuilder meshBuilder; + std::vector< int > &indices = m_submeshes[mi]->indices; + std::vector< vertex_t > &vertices = m_submeshes[mi]->vertices; + + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, vertices.size(), indices.size() ); + + int vn = vertices.size(); + for ( int vi = 0; vi < vn; ++vi ) + { + vertex_t &vertex = vertices[vi]; + meshBuilder.Position3fv( vertex.coord.Base() ); + meshBuilder.Normal3fv ( vertex.normal.Base() ); + meshBuilder.TexCoord2fv( 0, vertex.texcoord.Base() ); + switch ( vertex.skinning[0].index ) + { + case 0: meshBuilder.Color3f(1,0,0); break; + case 1: meshBuilder.Color3f(0,1,0); break; + case 2: meshBuilder.Color3f(0,0,1); break; + case 3: meshBuilder.Color3f(1,1,0); break; + case 4: meshBuilder.Color3f(0,1,1); break; + case 5: meshBuilder.Color3f(1,0,1); break; + case 6: meshBuilder.Color3f(0,0,0); break; + case 7: meshBuilder.Color3f(1,1,1); break; + default: meshBuilder.Color3f(0.5f,0.5f,0.5f); break; + } + + int bn = vertex.skinning.size(); + for ( int bi = 0; bi < bn; ++bi ) + { + meshBuilder.BoneMatrix( bi, vertex.skinning[bi].index ); + meshBuilder.BoneWeight( bi, vertex.skinning[bi].weight ); + } + + meshBuilder.AdvanceVertex(); + } + + int in = indices.size(); + for ( int ii = 0; ii < in; ++ii ) + { + meshBuilder.FastIndex( indices[ii] ); + } + + meshBuilder.End(); + pMesh->Draw(); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Returns a mask indicating which bones to set up +//----------------------------------------------------------------------------- +int CDmeTestMesh::BoneMask( void ) +{ + int nLod = GetValue<int>( "lod" ); + return BONE_USED_BY_VERTEX_AT_LOD( nLod ); +} + +void CDmeTestMesh::SetUpBones( CDmeTransform *pTransform, int nMaxBoneCount, matrix3x4_t *pBoneToWorld ) +{ + // Default to middle of the pose parameter range + float pPoseParameter[MAXSTUDIOPOSEPARAM]; + for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i ) + { + pPoseParameter[i] = 0.5f; + } + + CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache ); + + int nSequence = GetValue<int>( "sequence" ); + float flPlaybackRate = GetValue<float>( "playbackrate" ); + float flTime = GetValue<float>( "time" ); + + int nFrameCount = Studio_MaxFrame( &studioHdr, nSequence, pPoseParameter ); + if ( nFrameCount == 0 ) + { + nFrameCount = 1; + } + float flCycle = ( flTime * flPlaybackRate ) / nFrameCount; + + // FIXME: We're always wrapping; may want to determing if we should clamp + flCycle -= (int)(flCycle); + + Vector pos[MAXSTUDIOBONES]; + Quaternion q[MAXSTUDIOBONES]; + + IBoneSetup boneSetup( &studioHdr, BoneMask(), pPoseParameter ); + boneSetup.InitPose( pos, q ); + boneSetup.AccumulatePose( pos, q, nSequence, flCycle, 1.0f, flTime, NULL ); + + // FIXME: Try enabling this? +// CalcAutoplaySequences( pStudioHdr, NULL, pos, q, pPoseParameter, BoneMask( ), flTime ); + + // Root transform + matrix3x4_t rootToWorld; + pTransform->GetTransform( rootToWorld ); + + if ( studioHdr.numBones() < nMaxBoneCount ) + { + nMaxBoneCount = studioHdr.numBones(); + } + + for ( int i = 0; i < nMaxBoneCount; i++ ) + { + // If it's not being used, fill with NAN for errors +#ifdef _DEBUG + if ( !(studioHdr.pBone( i )->flags & BoneMask())) + { + int j, k; + for (j = 0; j < 3; j++) + { + for (k = 0; k < 4; k++) + { + pBoneToWorld[i][j][k] = VEC_T_NAN; + } + } + continue; + } +#endif + + matrix3x4_t boneMatrix; + QuaternionMatrix( q[i], boneMatrix ); + MatrixSetColumn( pos[i], 3, boneMatrix ); + + if (studioHdr.pBone(i)->parent == -1) + { + ConcatTransforms (rootToWorld, boneMatrix, pBoneToWorld[ i ]); + } + else + { + ConcatTransforms ( pBoneToWorld[ studioHdr.pBone(i)->parent ], boneMatrix, pBoneToWorld[ i ] ); + } + } +} + + +//----------------------------------------------------------------------------- +// FIXME: This trashy glue code is really not acceptable. Figure out a way of making it unnecessary. +//----------------------------------------------------------------------------- +const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const +{ + MDLHandle_t handle = g_pMDLCache->FindMDL( pModelName ); + *cache = (void*)handle; + return g_pMDLCache->GetStudioHdr( handle ); +} + +virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const +{ + return g_pMDLCache->GetVirtualModel( (MDLHandle_t)virtualModel ); +} + +byte *studiohdr_t::GetAnimBlock( int i ) const +{ + return g_pMDLCache->GetAnimBlock( (MDLHandle_t)virtualModel, i ); +} + +int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const +{ + return g_pMDLCache->GetAutoplayList( (MDLHandle_t)virtualModel, pOut ); +} + +const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const +{ + return g_pMDLCache->GetStudioHdr( (MDLHandle_t)cache ); +} + +//----------------------------------------------------------------------------- +// First attempt at making a hacky SMD loader - clean this up later +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// SMD format: +// +// format key: +// #n = integer +// .x = float +// 'a' = literal string +// $s = string +// " = the literal quote character +// // = comment - not in file!!! +// +// 'version' #version // right now, #version = 1 +// +// 'nodes' // bone naming and hierarchy +// #bone "$bonename" #parent // one of these per bone - can be in any order, but generally sequential +// 'end' +// +// 'skeleton' // joint animation (and begin pose) +// 'time' #time // repeat time + joints block once per frame +// #bone .x .y .z .rx .ry .rz // bone/translation/rotation - can traverse bones in any order, and even skip them +// 'end' +// +// 'triangles' // actual vertex data - as non-indexed triangle lists +// $texturefilename // repeat texture + 3 vertex lines for each triangle +// #bone .x .y .z .nx .ny .nz .tu .tv #count #bone0 .weight0 // boneN & weightN may or may not exist for N={0..511} +// #bone .x .y .z .nx .ny .nz .tu .tv #count #bone0 .weight0 // boneN & weightN may or may not exist for N={0..511} +// #bone .x .y .z .nx .ny .nz .tu .tv #count #bone0 .weight0 // boneN & weightN may or may not exist for N={0..511} +// 'end' +// +// 'vertexanimation' // morph targets +// 'time' #time // repeat time + vertices block once per vertex +// #vertex .x .y .z .nx .ny .nz // vertex/position/normal +// 'end' +// +//----------------------------------------------------------------------------- + +// TODO - check out lookup_index for whether it's looking for exact vertex matches, or within a float tolerance +// DONE - lookup_index checks materiaks, coords and texcoords for exact match, and normals for within 2 degrees + +const int MAXNAME = 128; +const int MAXLINE = 4096; +const int MAXCMD = 1024; +const int MAXBONEWEIGHTS = 3; +const int MAXTEXNAME = 64; + +void ReadBonesFromSMD( std::vector< CDmeTransform* > &bones, std::istream &is, DmFileId_t fileid ) +{ + uint index; + int parent; + char name[ MAXNAME ]; + + char line[ MAXLINE ]; + + while ( is.getline( line, MAXLINE ) ) + { + if ( sscanf( line, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3 ) + { + if ( index != bones.size() ) + { + Warning( "ReadBonesFromSMD: reading node %d out of order\n", index ); + } + if ( index >= bones.size() ) + { + bones.resize( index + 1 ); + } + + bones[index] = CreateElement< CDmeTransform >( name, fileid ); + if ( parent > 0 ) + { + if ( ( uint( parent ) >= bones.size() ) || ( bones[ parent ] == NULL ) ) + { + Warning( "ReadBonesFromSMD: reading node %d before parent\n", index, parent ); + } + else + { + Assert( 0 ); // this code is so badly bit-rotten... +// bones[parent]->AddChild( bones[index]->GetHandle() ); + } + } + } + else + { + if ( strncmp( line, "end", 3 ) != 0 ) + { + Warning( "ReadBonesFromSMD: expected 'end' or bone, found %s\n", line ); + } + return; + } + } +} + +void clip_rotations( RadianEuler& rot ) +{ + // remap rotations to [ -M_PI .. M_PI ) + for ( int j = 0; j < 3; j++ ) { + if ( rot[j] != -M_PI ) // keep -M_PI as is + { + rot[j] = fmod( (double)rot[j], M_PI ); + } + } +} + +void ReadSkeletalAnimationFromSMD( std::vector< CDmeTransform* > &bones, std::istream &is ) +{ + char line[ MAXLINE ]; + + char cmd[ MAXCMD ]; + + int time = INT_MIN; + int startframe = -1; + int endframe = -1; + +#if 1 + // Root transform + matrix3x4_t rootToWorld; + SetIdentityMatrix( rootToWorld ); +// GetTransform()->GetTransform( rootToWorld ); +#endif + + while ( is.getline( line, MAXLINE ) ) + { + int index; + Vector pos; + RadianEuler rot; + + if ( sscanf( line, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7 ) + { + if ( startframe < 0 ) + { + Warning( "ReadSkeletalAnimationFromSMD: missing frame start\n" ); + } + +// clip_rotations( rot ); + Quaternion quat; + AngleQuaternion( rot, quat ); +#if 0 + matrix3x4_t boneMatrix; + QuaternionMatrix( quat, boneMatrix ); + MatrixSetColumn( pos, 3, boneMatrix ); + + if ( bones[index]->NumParents() > 0 ) + { + DmElementHandle_t hParent = bones[index]->GetParent( 0 ); + CDmeTransform *parentXform = GetElement< CDmeTransform >( hParent ); + matrix3x4_t parentMatrix, newMatrix; + parentXform->GetTransform( parentMatrix ); +// ConcatTransforms( parentMatrix, boneMatrix, newMatrix ); + SetIdentityMatrix( newMatrix ); + MatrixAngles( newMatrix, quat, pos ); + } + else + { + matrix3x4_t parentMatrix, newMatrix; +// ConcatTransforms( rootToWorld, boneMatrix, newMatrix ); + SetIdentityMatrix( newMatrix ); + MatrixAngles( newMatrix, quat, pos ); + } +#endif + bones[index]->SetValue( "orientation", quat ); + bones[index]->SetValue( "position", pos ); + + // TODO - save animation data - currently just overwriting w/ last frame + } + else if ( sscanf( line, "%1023s %d", cmd, &index ) ) + { + if ( strcmp( cmd, "time" ) == 0 ) + { + time = index; + if ( startframe == -1 ) + { + startframe = index; + } + if ( time < startframe ) + { + Error( "ReadSkeletalAnimationFromSMD: time %d found after time %d\n", time, startframe ); + } + if ( time > endframe ) + { + endframe = time; + } + time -= startframe; + /* + if ( time != anim.size() ) + { + Warning( "ReadSkeletalAnimationFromSMD: reading keyframe %d out of order\n", time ); + } + if ( time >= anim.size() ) + { + anim.resize( time + 1 ); + anim[time] = new bone_t[nodes.size()]; + } + + if ( time > 0 ) + { + if ( anim[time-1] ) + { + std::copy( anim[time-1], anim[time-1] + nodes.size(), anim[time] ); + } + else + { + Warning( "ReadSkeletalAnimationFromSMD: missing skeletal keyframe %d\n", time-1 ); + } + } + */ + } + else if ( strcmp( cmd, "end" ) == 0 ) + { +// Build_Reference( nodes, anim, matrices ); // skip - leave this for dmemesh generation + return; + } + else + { + Warning( "ReadSkeletalAnimationFromSMD: expected bone, time or end, found %s\n", line ); + } + } + else + { + Warning( "ReadSkeletalAnimationFromSMD: expected bone, time or end, found %s\n", line ); + } + } + Error( "ReadSkeletalAnimationFromSMD: unexpected EOF\n" ); +} + +float vertex_t::normal_tolerance = cos( DEG2RAD( 2.0f )); + +void SortAndBalanceBones( std::vector< skinning_info_t > &skinning ) +{ + // TODO - studiomdl collapses (sums) duplicate bone weights - is this necessary?!?! + + std::sort( skinning.begin(), skinning.end() ); + + // throw away bone weights < 0.05f + while ( skinning.size() > 1 && skinning.back().weight >= 0.05f ) + { + skinning.pop_back(); + } + Assert( !skinning.empty() ); + + if ( skinning.size() > MAXBONEWEIGHTS ) + { + skinning.resize( MAXBONEWEIGHTS ); + } + + float weightSum = 0.0f; + for ( uint i = 0; i < skinning.size(); ++i ) + { + weightSum += skinning[i].weight; + } + + if ( weightSum <= 0.0f ) + { + for ( uint i = 0; i < skinning.size(); ++i ) + { + skinning[i].weight = weightSum; + } + } + else + { + float weightScale = 1.0f / weightSum; + for ( uint i = 0; i < skinning.size(); ++i ) + { + skinning[i].weight *= weightScale; + } + } +} + +int ReadVertexFromSMD( std::vector< vertex_t > &vertices, int numbones, std::istream &is ) +{ + int boneIndex; + is >> boneIndex; + + if ( boneIndex < 0 || boneIndex >= numbones ) + { + Error( "ReadVertexFromSMD: invalid bone index: %d\n", boneIndex ); + } + + vertex_t vert; + is >> vert.coord.x >> vert.coord.y >> vert.coord.z; + is >> vert.normal.x >> vert.normal.y >> vert.normal.z; + is >> vert.texcoord.x >> vert.texcoord.y; + + // invert v + vert.texcoord.y = 1.0f - vert.texcoord.y; + + char line[MAXLINE]; + is.getline( line, MAXLINE ); + std::istrstream istr( line ); + + int nBones = 0; + istr >> nBones; + Assert( istr.good() || nBones == 0 ); + + if ( nBones == 0 ) + { + vert.skinning.push_back( skinning_info_t( boneIndex, 1.0f ) ); + } + else + { + vert.skinning.reserve( nBones ); + for ( int i = 0; i < nBones; ++i ) + { + skinning_info_t info; + istr >> info.index >> info.weight; + vert.skinning.push_back( info ); + + if ( info.index < 0 || info.index >= numbones ) + { + Error( "ReadVertexFromSMD: invalid bone index: %d\n", info.index ); + } + } + } + + std::vector< vertex_t >::iterator vi = std::find( vertices.begin(), vertices.end(), vert ); + if ( vi != vertices.end() ) + return vi - vertices.begin(); + + SortAndBalanceBones( vert.skinning ); + + vertices.push_back( vert ); + return vertices.size() - 1; +} + +bool IsEnd( char const* pLine ) +{ + if ( strncmp( "end", pLine, 3 ) != 0 ) + return false; + return ( pLine[3] == '\0' ) || ( pLine[3] == '\n' ); +} + +void ReadTrianglesFromSMD( std::vector< submesh_t* > &meshes, int numbones, std::istream &is ) +{ + Vector vmin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vmax( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + + char line[ MAXLINE ]; + + char texname[ MAXTEXNAME ]; + + while ( is.getline( line, MAXLINE ) ) + { + if ( IsEnd( line ) ) + break; + + int lineLen = is.gcount(); + if ( lineLen >= MAXTEXNAME ) + { + Warning( "ReadTrianglesFromSMD: expected a texture name, found %s\n", line ); + continue; + } + + // the studiomdl comment here is "strip off trailing smag" whatever smag is... + strncpy( texname, line, MAXTEXNAME ); + int i; + for ( i = strlen( texname ) - 1; i >= 0 && ! isgraph( texname[i] ); i-- ) + { + } + texname[i + 1] = '\0'; + + // Skip empty names (studiomdl comment: "weird source problem, skip them") + // Skip null texture references + if ( texname[0] == '\0' || + stricmp( texname, "null.bmp" ) == 0 || + stricmp( texname, "null.tga" ) == 0 ) + { + is.getline( line, MAXLINE ); + is.getline( line, MAXLINE ); + is.getline( line, MAXLINE ); + continue; + } + + // find mesh with matching texture - starting with last one created + int mi; + for ( mi = meshes.size() - 1; mi >= 0; --mi ) + { + if ( stricmp( meshes[mi]->texname.c_str(), texname ) == 0 ) + break; + } + + // if no mesh with texname found, create a new one + if ( mi < 0 ) + { + mi = meshes.size(); + meshes.push_back( new submesh_t( texname ) ); + } + submesh_t *mesh = meshes[mi]; + + mesh->indices.push_back( ReadVertexFromSMD( mesh->vertices, numbones, is ) ); + mesh->indices.push_back( ReadVertexFromSMD( mesh->vertices, numbones, is ) ); + mesh->indices.push_back( ReadVertexFromSMD( mesh->vertices, numbones, is ) ); + +#if 0 + // flip triangle - the default in studiomdl + int numIndices = mesh->indices.size(); + std::swap( mesh->indices[numIndices-1], mesh->indices[numIndices-2] ); +#endif + } +} + +void RemapBonesOnSubmesh( submesh_t *pMesh, std::vector< CDmeTransform* > &bones ) +{ + std::vector<int> vertsPerBone( bones.size() ); // initializes all counts to 0 + + // find vertex-per-bone counts + int vn = pMesh->vertices.size(); + for ( int vi = 0; vi < vn; ++vi ) + { + vertex_t &vert = pMesh->vertices[vi]; + int bn = vert.skinning.size(); + for ( int bi = 0; bi < bn; ++bi ) + { + ++vertsPerBone[vert.skinning[bi].index]; + } + } + + std::vector<int> boneMap( bones.size() ); + + // copy only used bones into mesh's internal bone list and write mapping + int bn = vertsPerBone.size(); + for ( int bi = 0; bi < bn; ++bi ) + { + if ( vertsPerBone[bi] == 0 ) + { + boneMap[bi] = -1; + } + else + { + boneMap[bi] = pMesh->bones.size(); + pMesh->bones.push_back( bones[bi] ); + } + } + + // remap mesh's verts to use the interal bone indexing + for ( int vi = 0; vi < vn; ++vi ) + { + vertex_t &vert = pMesh->vertices[vi]; + int bn = vert.skinning.size(); + for ( int bi = 0; bi < bn; ++bi ) + { + vert.skinning[bi].index = boneMap[vert.skinning[bi].index]; + } + } +} + +CDmeTestMesh *CDmeTestMesh::ReadMeshFromSMD( char *pFilename, DmFileId_t fileid ) +{ + std::ifstream is( pFilename ); + if ( !is ) + { + Warning( "Unable to open file %s\n", pFilename ); + return NULL; + } + + CDmeTestMesh *pMesh = CreateElement< CDmeTestMesh >( "New Mesh", fileid ); + + char line[ MAXLINE ]; + + char cmd[ MAXCMD ]; + int option; + + while ( is.getline( line, MAXLINE ) ) + { + int numRead = sscanf( line, "%1023s %d", cmd, &option ); + + if ( ( numRead == EOF ) || ( numRead == 0 ) ) + continue; // blank line + + if ( strcmp( cmd, "version" ) == 0 ) + { + if ( option != 1 ) + { + Error( "ReadMeshFromSMD: bad version\n" ); + } + } + else if ( strcmp( cmd, "nodes" ) == 0 ) + { + pMesh->m_bones.clear(); + ReadBonesFromSMD( pMesh->m_bones, is, fileid ); + } + else if ( strcmp( cmd, "skeleton" ) == 0 ) + { + ReadSkeletalAnimationFromSMD( pMesh->m_bones, is ); + } + else if ( strcmp( cmd, "triangles" ) == 0 ) + { + ReadTrianglesFromSMD( pMesh->m_submeshes, pMesh->m_bones.size(), is ); + } + else if ( strcmp( cmd, "vertexanimation" ) == 0 ) + { +// Grab_Vertexanimation( psource ); + return pMesh; // TODO - implement Grab_Vertexanimation!!! + } + else + { + Warning( "unknown studio command\n" ); + } + } + +#if 0 + // remap only the needed bones to hopefully fit within maxbone contraints + int mn = pMesh->m_submeshes.size(); + for ( int mi = 0; mi < mn; ++mi) + { + RemapBonesOnSubmesh( pMesh->m_submeshes[mi], pMesh->m_bones ); + Msg( "remapping %d bones on mesh to %d bones on submesh %d\n", + pMesh->m_bones.size(), + pMesh->m_submeshes[mi]->bones.size(), + mi ); + } +#endif + + return pMesh; +} diff --git a/movieobjects/dmetexture.cpp b/movieobjects/dmetexture.cpp new file mode 100644 index 0000000..39214d1 --- /dev/null +++ b/movieobjects/dmetexture.cpp @@ -0,0 +1,290 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmetexture.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects_interfaces.h" + +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialsystem.h" + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeBaseTexture, CDmeBaseTexture ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeBaseTexture::OnConstruction() +{ + m_pVTFTexture = CreateVTFTexture(); + + m_bClampS.Init( this, "clampS" ); + m_bClampT.Init( this, "clampT" ); + m_bClampU.Init( this, "clampU" ); + m_bNoLod.Init( this, "noMipmapLOD" ); + m_bNiceFiltered.Init( this, "niceFiltered" ); + m_bNormalMap.Init( this, "normalMap" ); + m_flBumpScale.Init( this, "bumpScale" ); + m_nCompressType.Init( this, "compressType" ); + m_nFilterType.Init( this, "filterType" ); + m_nMipmapType.Init( this, "mipmapType" ); +} + +void CDmeBaseTexture::OnDestruction() +{ + if ( m_pVTFTexture ) + { + DestroyVTFTexture( m_pVTFTexture ); + m_pVTFTexture = NULL; + } +} + + +//----------------------------------------------------------------------------- +// accessor for cached ITexture +//----------------------------------------------------------------------------- +ITexture *CDmeBaseTexture::GetCachedTexture() +{ + return m_Texture; +} + + +//----------------------------------------------------------------------------- +// Computes texture flags +//----------------------------------------------------------------------------- +int CDmeBaseTexture::CalcTextureFlags( int nDepth ) const +{ + int nFlags = 0; + if ( m_bClampS ) + { + nFlags |= TEXTUREFLAGS_CLAMPS; + } + if ( m_bClampT ) + { + nFlags |= TEXTUREFLAGS_CLAMPT; + } + if ( m_bClampU ) + { + nFlags |= TEXTUREFLAGS_CLAMPU; + } + if ( m_bNoLod ) + { + nFlags |= TEXTUREFLAGS_NOLOD; + } + if ( m_bNiceFiltered ) + { + nFlags |= TEXTUREFLAGS_NICEFILTERED; + } + if ( m_bNormalMap ) + { + nFlags |= TEXTUREFLAGS_NORMAL; + } + if ( m_bNormalMap ) + { + nFlags |= TEXTUREFLAGS_NORMAL; + } + if ( m_bNoDebugOverride ) + { + nFlags |= TEXTUREFLAGS_NODEBUGOVERRIDE; + } + + switch ( m_nCompressType ) + { + case DMETEXTURE_COMPRESS_DEFAULT: + case DMETEXTURE_COMPRESS_DXT1: + break; + case DMETEXTURE_COMPRESS_NONE: + nFlags |= TEXTUREFLAGS_NOCOMPRESS; + break; + case DMETEXTURE_COMPRESS_DXT5: + nFlags |= TEXTUREFLAGS_HINT_DXT5; + break; + } + + switch ( m_nFilterType ) + { + case DMETEXTURE_FILTER_DEFAULT: + case DMETEXTURE_FILTER_BILINEAR: + break; + case DMETEXTURE_FILTER_ANISOTROPIC: + nFlags |= TEXTUREFLAGS_ANISOTROPIC; + break; + case DMETEXTURE_FILTER_TRILINEAR: + nFlags |= TEXTUREFLAGS_TRILINEAR; + break; + case DMETEXTURE_FILTER_POINT: + nFlags |= TEXTUREFLAGS_POINTSAMPLE; + break; + } + + switch ( m_nMipmapType ) + { + case DMETEXTURE_MIPMAP_DEFAULT: + case DMETEXTURE_MIPMAP_ALL_LEVELS: + break; + case DMETEXTURE_MIPMAP_NONE: + nFlags |= TEXTUREFLAGS_NOMIP; + break; + } + + if ( nDepth > 1 ) + { + // FIXME: Volume textures don't currently support DXT compression + nFlags &= ~TEXTUREFLAGS_HINT_DXT5; + nFlags |= TEXTUREFLAGS_NOCOMPRESS; + + // FIXME: Volume textures don't currently support NICE filtering + nFlags &= ~TEXTUREFLAGS_NICEFILTERED; + } + + return nFlags; +} + + +//----------------------------------------------------------------------------- +// Computes the desired texture format based on flags +//----------------------------------------------------------------------------- +ImageFormat CDmeBaseTexture::ComputeDesiredImageFormat( ImageFormat srcFormat, int nWidth, int nHeight, int nDepth, int nFlags ) +{ + // HDRFIXME: Need to figure out what format to use here. + if ( srcFormat == IMAGE_FORMAT_RGB323232F ) + return IMAGE_FORMAT_RGBA16161616F; + + /* + if( bDUDVTarget) + { + if ( bCopyAlphaToLuminance && ( nFlags & ( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ) ) ) + return IMAGE_FORMAT_UVLX8888; + return IMAGE_FORMAT_UV88; + } + */ + + // can't compress textures that are smaller than 4x4 + if ( (nFlags & TEXTUREFLAGS_NOCOMPRESS) || (nFlags & TEXTUREFLAGS_PROCEDURAL) || + ( nWidth < 4 ) || ( nHeight < 4 ) || ( nDepth > 1 ) ) + { + if ( nFlags & ( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ) ) + return IMAGE_FORMAT_BGRA8888; + return IMAGE_FORMAT_BGR888; + } + + if( nFlags & TEXTUREFLAGS_HINT_DXT5 ) + return IMAGE_FORMAT_DXT5; + + // compressed with alpha blending + if ( nFlags & TEXTUREFLAGS_EIGHTBITALPHA ) + return IMAGE_FORMAT_DXT5; + + if ( nFlags & TEXTUREFLAGS_ONEBITALPHA ) + return IMAGE_FORMAT_DXT5; // IMAGE_FORMAT_DXT1_ONEBITALPHA + + return IMAGE_FORMAT_DXT1; +} + + +//----------------------------------------------------------------------------- +// +// Normal texture +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTexture, CDmeTexture ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeTexture::OnConstruction() +{ + m_Images.Init( this, "images" ); +} + +void CDmeTexture::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Build a VTF representing the +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// resolve +//----------------------------------------------------------------------------- +void CDmeTexture::Resolve() +{ + // FIXME: Could change this to not shutdown if only the bits have changed + m_Texture.Shutdown(); + + int nFrameCount = m_Images.Count(); + if ( nFrameCount == 0 ) + return; + + // Use the size of the 0th image + CDmeImage *pImage = m_Images[0]; + int nWidth = pImage->m_Width; + int nHeight = pImage->m_Height; + int nDepth = pImage->m_Depth; + ImageFormat srcFormat = pImage->Format(); + + // FIXME: How should this work exactly? + int nFlags = CalcTextureFlags( nDepth ); + + m_pVTFTexture->Init( nWidth, nHeight, nDepth, srcFormat, nFlags, nFrameCount ); + + + ImageFormat format = ComputeDesiredImageFormat( pImage->Format(), nWidth, nHeight, nDepth, nFlags ); + +// m_Texture.InitProceduralTexture( GetName(), "DmeTexture", nWidth, nHeight, nDepth, nFrameCount, format, nFlags ); +// m_Texture->SetTextureRegenerator( this ); + + // Fill in the texture bits +} + +/* +//----------------------------------------------------------------------------- +// Fill in the texture bits +//----------------------------------------------------------------------------- +void CDmeTexture::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) +{ +} +*/ + +//----------------------------------------------------------------------------- +// +// Cube texture +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeCubeTexture, CDmeCubeTexture ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeCubeTexture::OnConstruction() +{ + m_ImagesPosX.Init( this, "imagesPosX" ); + m_ImagesNegX.Init( this, "imagesNegX" ); + m_ImagesPosY.Init( this, "imagesPosY" ); + m_ImagesNegY.Init( this, "imagesNegY" ); + m_ImagesPosZ.Init( this, "imagesPosZ" ); + m_ImagesNegZ.Init( this, "imagesNegZ" ); +} + +void CDmeCubeTexture::OnDestruction() +{ +} diff --git a/movieobjects/dmetimeframe.cpp b/movieobjects/dmetimeframe.cpp new file mode 100644 index 0000000..4dff9f1 --- /dev/null +++ b/movieobjects/dmetimeframe.cpp @@ -0,0 +1,95 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmetimeframe.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTimeFrame, CDmeTimeFrame ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeTimeFrame::OnConstruction() +{ + m_Start .InitAndSet( this, "startTime", 0, FATTRIB_HAS_CALLBACK ); + m_Duration.InitAndSet( this, "durationTime", 0, FATTRIB_HAS_CALLBACK ); + m_Offset .InitAndSet( this, "offsetTime", 0 ); + m_Scale .InitAndSet( this, "scale", 1.0f ); +} + +void CDmeTimeFrame::OnDestruction() +{ +} + + +void CDmeTimeFrame::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + BaseClass::OnAttributeChanged( pAttribute ); + + // notify parent clip that the time has changed + if ( pAttribute == m_Start.GetAttribute() || pAttribute == m_Duration.GetAttribute() ) + { + InvokeOnAttributeChangedOnReferrers( GetHandle(), pAttribute ); + } +} + +void CDmeTimeFrame::SetEndTime( DmeTime_t endTime, bool bChangeDuration ) +{ + if ( bChangeDuration ) + { + m_Duration = endTime.GetTenthsOfMS() - m_Start; + } + else + { + m_Start = endTime.GetTenthsOfMS() - m_Duration; + } +} + +void CDmeTimeFrame::SetTimeScale( float flScale, DmeTime_t scaleCenter, bool bChangeDuration ) +{ +#ifdef _DEBUG + DmeTime_t preCenterTime = ToChildMediaTime( scaleCenter, false ); +#endif + + float ratio = m_Scale / flScale; + int t = scaleCenter.GetTenthsOfMS() - m_Start; + + if ( bChangeDuration ) + { + int newDuration = int( m_Duration * ratio ); + + if ( scaleCenter.GetTenthsOfMS() != m_Start ) + { + int newStart = int( ( m_Start - scaleCenter.GetTenthsOfMS() ) * ratio + scaleCenter.GetTenthsOfMS() ); + SetStartTime( DmeTime_t( newStart ) ); + } + + int newStart = m_Start; + int newOffset = int( ( t + m_Offset ) * ratio + newStart - scaleCenter.GetTenthsOfMS() ); + SetTimeOffset( DmeTime_t( newOffset ) ); + SetDuration( DmeTime_t( newDuration ) ); + } + else + { + int newOffset = int( ( t + m_Offset ) * ratio - t ); + SetTimeOffset( DmeTime_t( newOffset ) ); + } + + SetTimeScale( flScale ); + +#ifdef _DEBUG + DmeTime_t postCenterTime = ToChildMediaTime( scaleCenter, false ); + Assert( abs( preCenterTime - postCenterTime ) <= DMETIME_MINDELTA ); +#endif +} diff --git a/movieobjects/dmetimeselection.cpp b/movieobjects/dmetimeselection.cpp new file mode 100644 index 0000000..c1e4667 --- /dev/null +++ b/movieobjects/dmetimeselection.cpp @@ -0,0 +1,336 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "movieobjects/dmetimeselection.h" +#include "interpolatortypes.h" +#include "datamodel/dmelementfactoryhelper.h" +// #include "dme_controls/RecordingState.h" + +IMPLEMENT_ELEMENT_FACTORY( DmeTimeSelection, CDmeTimeSelection ); + +void CDmeTimeSelection::OnConstruction() +{ + m_bEnabled.InitAndSet( this, "enabled", false ); + m_bRelative.InitAndSet( this, "relative", false ); + + DmeTime_t one( 1.0f ); + + m_falloff[ 0 ].InitAndSet( this, "falloff_left", -one.GetTenthsOfMS() ); + m_falloff[ 1 ].InitAndSet( this, "falloff_right", one.GetTenthsOfMS() ); + + m_hold[ 0 ].Init( this, "hold_left" ); + m_hold[ 1 ].Init( this, "hold_right" ); + + m_nFalloffInterpolatorType[ 0 ].InitAndSet( this, "interpolator_left", INTERPOLATE_LINEAR_INTERP ); + m_nFalloffInterpolatorType[ 1 ].InitAndSet( this, "interpolator_right", INTERPOLATE_LINEAR_INTERP ); + + m_threshold.InitAndSet( this, "threshold", 0.0005f ); + + m_nRecordingState.InitAndSet( this, "recordingstate", 3 /*AS_PLAYBACK : HACK THIS SHOULD MOVE TO A PUBLIC HEADER*/ ); +} + + +void CDmeTimeSelection::OnDestruction() +{ +} + +static int g_InterpolatorTypes[] = +{ + INTERPOLATE_LINEAR_INTERP, + INTERPOLATE_EASE_IN, + INTERPOLATE_EASE_OUT, + INTERPOLATE_EASE_INOUT, +}; + +float CDmeTimeSelection::AdjustFactorForInterpolatorType( float factor, int side ) +{ + Vector points[ 4 ]; + points[ 0 ].Init(); + points[ 1 ].Init( 0.0, 0.0, 0.0f ); + points[ 2 ].Init( 1.0f, 1.0f, 0.0f ); + points[ 3 ].Init(); + + Vector out; + Interpolator_CurveInterpolate + ( + GetFalloffInterpolatorType( side ), + points[ 0 ], // unused + points[ 1 ], + points[ 2 ], + points[ 3 ], // unused + factor, + out + ); + return out.y; // clamp( out.y, 0.0f, 1.0f ); +} + + +//----------------------------------------------------------------------------- +// per-type averaging methods +//----------------------------------------------------------------------------- +float CDmeTimeSelection::GetAmountForTime( DmeTime_t t, DmeTime_t curtime ) +{ + Assert( IsEnabled() ); + + float minfrac = 0.0f; + + // FIXME, this is slow, we should cache this maybe? + DmeTime_t times[ 4 ]; + times[ 0 ] = GetAbsFalloff( curtime, 0 ); + times[ 1 ] = GetAbsHold( curtime, 0 ); + times[ 2 ] = GetAbsHold( curtime, 1 ); + times[ 3 ] = GetAbsFalloff( curtime, 1 ); + + Vector points[ 4 ]; + points[ 0 ].Init(); + points[ 1 ].Init( 0.0, 0.0, 0.0f ); + points[ 2 ].Init( 1.0f, 1.0f, 0.0f ); + points[ 3 ].Init(); + + if ( t >= times[ 0 ] && t < times[ 1 ] ) + { + float f = GetFractionOfTimeBetween( t, times[ 0 ], times[ 1 ], true ); + + Vector out; + + Interpolator_CurveInterpolate + ( + GetFalloffInterpolatorType( 0 ), + points[ 0 ], // unused + points[ 1 ], + points[ 2 ], + points[ 3 ], // unused + f, + out + ); + return clamp( out.y, minfrac, 1.0f ); + } + + if ( t >= times[ 1 ] && t <= times[ 2 ] ) + { + return 1.0f; + } + + if ( t > times[ 2 ] && t <= times[ 3 ] ) + { + float f = 1.0f - GetFractionOfTimeBetween( t, times[ 2 ], times[ 3 ], true ); + + Vector out; + + Interpolator_CurveInterpolate + ( + GetFalloffInterpolatorType( 1 ), + points[ 0 ], // unused + points[ 1 ], + points[ 2 ], + points[ 3 ], // unused + f, + out + ); + return clamp( out.y, minfrac, 1.0f ); + } + return minfrac; +} + +void CDmeTimeSelection::GetAlphaForTime( DmeTime_t t, DmeTime_t curtime, byte& alpha ) +{ + Assert( IsEnabled() ); + + byte minAlpha = 31; + + // FIXME, this is slow, we should cache this maybe? + DmeTime_t times[ 4 ]; + times[ 0 ] = GetAbsFalloff( curtime, 0 ); + times[ 1 ] = GetAbsHold( curtime, 0 ); + times[ 2 ] = GetAbsHold( curtime, 1 ); + times[ 3 ] = GetAbsFalloff( curtime, 1 ); + + DmeTime_t dt1, dt2; + dt1 = times[ 1 ] - times[ 0 ]; + dt2 = times[ 3 ] - times[ 2 ]; + + if ( dt1 > DmeTime_t( 0 ) && + t >= times[ 0 ] && t < times[ 1 ] ) + { + float frac = GetFractionOfTime( t - times[ 0 ], dt1, false ); + alpha = clamp( alpha * frac, minAlpha, 255 ); + return; + } + if ( dt2 > DmeTime_t( 0 ) && + t > times[ 2 ] && t <= times[ 3 ] ) + { + float frac = GetFractionOfTime( times[ 3 ] - t, dt2, false ); + alpha = clamp( alpha * frac, minAlpha, 255 ); + return; + } + if ( t < times[ 0 ] ) + alpha = minAlpha; + else if ( t > times[ 3 ] ) + alpha = minAlpha; +} + +int CDmeTimeSelection::GetFalloffInterpolatorType( int side ) +{ + return m_nFalloffInterpolatorType[ side ]; +} + +void CDmeTimeSelection::SetFalloffInterpolatorType( int side, int interpolatorType ) +{ + m_nFalloffInterpolatorType[ side ] = interpolatorType; +} + +bool CDmeTimeSelection::IsEnabled() const +{ + return m_bEnabled; +} + +void CDmeTimeSelection::SetEnabled( bool state ) +{ + m_bEnabled = state; +} + +bool CDmeTimeSelection::IsRelative() const +{ + return m_bRelative; +} + +void CDmeTimeSelection::SetRelative( DmeTime_t time, bool state ) +{ + bool changed = m_bRelative != state; + m_bRelative = state; + if ( changed ) + { + if ( state ) + ConvertToRelative( time ); + else + ConvertToAbsolute( time ); + } +} + +DmeTime_t CDmeTimeSelection::GetAbsFalloff( DmeTime_t time, int side ) +{ + if ( m_bRelative ) + { + return DmeTime_t( m_falloff[ side ] ) + time; + } + return DmeTime_t( m_falloff[ side ] ); +} + +DmeTime_t CDmeTimeSelection::GetAbsHold( DmeTime_t time, int side ) +{ + if ( m_bRelative ) + { + return DmeTime_t( m_hold[ side ] ) + time; + } + return DmeTime_t( m_hold[ side ] ); +} + +DmeTime_t CDmeTimeSelection::GetRelativeFalloff( DmeTime_t time, int side ) +{ + if ( m_bRelative ) + { + return DmeTime_t( m_falloff[ side ] ); + } + return DmeTime_t( m_falloff[ side ] ) - time; +} + +DmeTime_t CDmeTimeSelection::GetRelativeHold( DmeTime_t time, int side ) +{ + if ( m_bRelative ) + { + return DmeTime_t( m_hold[ side ] ); + } + return DmeTime_t( m_hold[ side ] ) - time; +} + +void CDmeTimeSelection::ConvertToRelative( DmeTime_t time ) +{ + m_falloff[ 0 ] -= time.GetTenthsOfMS(); + m_falloff[ 1 ] -= time.GetTenthsOfMS(); + m_hold[ 0 ] -= time.GetTenthsOfMS(); + m_hold[ 1 ] -= time.GetTenthsOfMS(); +} + +void CDmeTimeSelection::ConvertToAbsolute( DmeTime_t time ) +{ + m_falloff[ 0 ] += time.GetTenthsOfMS(); + m_falloff[ 1 ] += time.GetTenthsOfMS(); + m_hold[ 0 ] += time.GetTenthsOfMS(); + m_hold[ 1 ] += time.GetTenthsOfMS(); +} + +void CDmeTimeSelection::SetAbsFalloff( DmeTime_t time, int side, DmeTime_t absfallofftime ) +{ + DmeTime_t newTime; + if ( m_bRelative ) + { + newTime = absfallofftime - time; + } + else + { + newTime = absfallofftime; + } + + m_falloff[ side ] = newTime.GetTenthsOfMS(); +} + +void CDmeTimeSelection::SetAbsHold( DmeTime_t time, int side, DmeTime_t absholdtime ) +{ + DmeTime_t newTime; + if ( m_bRelative ) + { + newTime = absholdtime - time; + } + else + { + newTime = absholdtime; + } + + m_hold[ side ] = newTime.GetTenthsOfMS(); +} + +void CDmeTimeSelection::CopyFrom( const CDmeTimeSelection& src ) +{ + m_bEnabled = src.m_bEnabled; + m_bRelative = src.m_bRelative; + m_threshold = src.m_threshold; + + for ( int i = 0 ; i < 2; ++i ) + { + m_falloff[ i ] = src.m_falloff[ i ]; + m_hold[ i ] = src.m_hold[ i ]; + m_nFalloffInterpolatorType[ i ] = src.m_nFalloffInterpolatorType[ i ]; + } + + m_nRecordingState = src.m_nRecordingState; +} + +void CDmeTimeSelection::GetCurrent( DmeTime_t pTimes[TS_TIME_COUNT] ) +{ + pTimes[TS_LEFT_FALLOFF].SetTenthsOfMS( m_falloff[ 0 ] ); + pTimes[TS_LEFT_HOLD].SetTenthsOfMS( m_hold[ 0 ] ); + pTimes[TS_RIGHT_HOLD].SetTenthsOfMS( m_hold[ 1 ] ); + pTimes[TS_RIGHT_FALLOFF].SetTenthsOfMS( m_falloff[ 1 ] ); +} + +void CDmeTimeSelection::SetCurrent( DmeTime_t* pTimes ) +{ + m_falloff[ 0 ] = pTimes[ TS_LEFT_FALLOFF ].GetTenthsOfMS(); + m_hold[ 0 ] = pTimes[ TS_LEFT_HOLD ].GetTenthsOfMS(); + m_hold[ 1 ] = pTimes[ TS_RIGHT_HOLD ].GetTenthsOfMS(); + m_falloff[ 1 ] = pTimes[ TS_RIGHT_FALLOFF ].GetTenthsOfMS(); +} + +float CDmeTimeSelection::GetThreshold() +{ + return m_threshold; +} + + +void CDmeTimeSelection::SetRecordingState( RecordingState_t state ) +{ + m_nRecordingState = ( int )state; +} + +RecordingState_t CDmeTimeSelection::GetRecordingState() const +{ + return ( RecordingState_t )m_nRecordingState.Get(); +}
\ No newline at end of file diff --git a/movieobjects/dmetrack.cpp b/movieobjects/dmetrack.cpp new file mode 100644 index 0000000..431c87b --- /dev/null +++ b/movieobjects/dmetrack.cpp @@ -0,0 +1,818 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmetrack.h" + +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmeclip.h" +#include "movieobjects/dmetrackgroup.h" + +#include "movieobjects_interfaces.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// The solo track +//----------------------------------------------------------------------------- +DmElementHandle_t CDmeTrack::m_hSoloTrack[ DMECLIP_TYPE_COUNT ] = +{ + DMELEMENT_HANDLE_INVALID, + DMELEMENT_HANDLE_INVALID, + DMELEMENT_HANDLE_INVALID, + DMELEMENT_HANDLE_INVALID, +}; + + +//----------------------------------------------------------------------------- +// CDmeTrack - common container class for clip objects +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTrack, CDmeTrack ); + +void CDmeTrack::OnConstruction() +{ + m_hOwner = DMELEMENT_HANDLE_INVALID; + + m_Flags.ClearAllFlags(); + m_Clips.Init( this, "children" ); + m_Collapsed.InitAndSet( this, "collapsed", true ); + m_Mute.InitAndSet( this, "mute", false ); + m_Synched.InitAndSet( this, "synched", true ); + m_ClipType.InitAndSet( this, "clipType", DMECLIP_UNKNOWN, FATTRIB_HAS_CALLBACK | FATTRIB_HAS_PRE_CALLBACK ); + + m_Volume.InitAndSet( this, "volume", 1.0 ); + +} + +void CDmeTrack::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Methods of IDmElement +//----------------------------------------------------------------------------- +void CDmeTrack::OnAttributeChanged( CDmAttribute *pAttribute ) +{ + BaseClass::OnAttributeChanged( pAttribute ); + + // Attach callbacks to detected sorted conditions if we're a film clip + if ( pAttribute == m_ClipType.GetAttribute() ) + { + if ( m_ClipType == DMECLIP_FILM ) + { + m_Flags.ClearFlag( IS_SORTED ); + } + return; + } + + // This gets called when start/end time of children change, or if the array changes + // This is a hack, since any OnAttributeChanged call that gets chained here from another element will trigger this + // At some point, we'll probably have to start sending more data through OnAttributeChanged, (like an event string or chain path) + // or perhaps add a new callback OnElementChanged() with this data + if ( pAttribute == m_Clips.GetAttribute() || ( pAttribute->GetOwner() != this ) ) + { + if ( !m_Flags.IsFlagSet( SUPPRESS_DIRTY_ORDERING ) ) + { + m_Flags.ClearFlag( IS_SORTED ); + } + return; + } +} + + +//----------------------------------------------------------------------------- +// Clip type +//----------------------------------------------------------------------------- +DmeClipType_t CDmeTrack::GetClipType() const +{ + return (DmeClipType_t)m_ClipType.Get(); +} + +void CDmeTrack::SetClipType( DmeClipType_t type ) +{ + m_ClipType = type; +} + +void CDmeTrack::SetCollapsed( bool state ) +{ + m_Collapsed = state; + +} + +bool CDmeTrack::IsCollapsed() const +{ + return m_Collapsed.Get(); +} + +void CDmeTrack::SetMute( bool state ) +{ + m_Mute = state; +} + +//----------------------------------------------------------------------------- +// Volume +//----------------------------------------------------------------------------- +void CDmeTrack::SetVolume( float state ) +{ + m_Volume = state; +} +float CDmeTrack::GetVolume() const +{ + return m_Volume.Get(); +} + +// Is this track synched to the film track? +void CDmeTrack::SetSynched( bool bState ) +{ + m_Synched = bState; +} + +bool CDmeTrack::IsSynched() const +{ + return m_Synched; +} + +bool CDmeTrack::IsMute( bool bCheckSoloing ) const +{ + // if we're muted, don't play regardless of whether we're solo + CDmeTrack *pSoloTrack = bCheckSoloing ? GetSoloTrack() : NULL; + return m_Mute.Get() || ( pSoloTrack != this && pSoloTrack != NULL ); +} + +int CDmeTrack::GetClipCount() const +{ + return m_Clips.Count(); +} + +CDmeClip *CDmeTrack::GetClip( int i ) const +{ + return m_Clips[ i ]; +} + +const CUtlVector< DmElementHandle_t > &CDmeTrack::GetClips( ) const +{ + return m_Clips.Get(); +} + +void CDmeTrack::AddClip( CDmeClip *clip ) +{ + if ( clip->GetClipType() == GetClipType() ) + { + // FIXME: In the case of a non-overlapped track, + // we could optimize this to insert the clip in sorted order, + // then fix overlaps (fixing overlaps requires a sorted list) + Assert( FindClip( clip ) < 0 ); + m_Clips.AddToTail( clip ); + } +} + +void CDmeTrack::RemoveClip( int i ) +{ + // NOTE: Removal shouldn't cause sort order or fixup to become invalid + CSuppressAutoFixup suppress( this, SUPPRESS_OVERLAP_FIXUP | SUPPRESS_DIRTY_ORDERING ); + m_Clips.Remove( i ); +} + +bool CDmeTrack::RemoveClip( CDmeClip *clip ) +{ + Assert( clip->GetClipType() == GetClipType() ); + int i = FindClip( clip ); + if ( i != -1 ) + { + RemoveClip( i ); + return true; + } + return false; +} + +void CDmeTrack::RemoveAllClips() +{ + CSuppressAutoFixup suppress( this, SUPPRESS_OVERLAP_FIXUP | SUPPRESS_DIRTY_ORDERING ); + m_Clips.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Returns the solo track, if any +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrack::GetSoloTrack( DmeClipType_t clipType ) +{ + return GetElement< CDmeTrack >( m_hSoloTrack[ clipType ] ); +} + +void CDmeTrack::SetSoloTrack( DmeClipType_t clipType, CDmeTrack *pTrack ) +{ + m_hSoloTrack[ clipType ] = pTrack->GetHandle(); +} + +bool CDmeTrack::IsSoloTrack() const +{ + return m_hSoloTrack[ GetClipType() ] == GetHandle(); +} + +CDmeTrack *CDmeTrack::GetSoloTrack() const +{ + return GetSoloTrack( GetClipType() ); +} + +void CDmeTrack::SetSoloTrack( ) +{ + m_hSoloTrack[ GetClipType() ] = GetHandle(); +} + + +//----------------------------------------------------------------------------- +// Methods related to finding clips +//----------------------------------------------------------------------------- +int CDmeTrack::FindClip( CDmeClip *clip ) +{ + Assert( clip->GetClipType() == GetClipType() ); + int c = m_Clips.Count(); + for ( int i = c - 1; i >= 0; --i ) + { + if ( m_Clips[ i ] == clip ) + return i; + } + return -1; +} + +CDmeClip *CDmeTrack::FindNamedClip( const char *name ) +{ + int c = m_Clips.Count(); + for ( int i = c - 1; i >= 0; --i ) + { + CDmeClip *child = m_Clips[ i ]; + if ( child && !Q_stricmp( child->GetName(), name ) ) + return child; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Find clips at, intersecting or within a particular time interval +//----------------------------------------------------------------------------- +void CDmeTrack::FindClipsAtTime( DmeTime_t time, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( flags & DMESKIP_INVISIBLE ) && IsCollapsed() ) + return; + + if ( ( flags & DMESKIP_MUTED ) && IsMute() ) + return; + + int nClipCount = GetClipCount(); + for ( int j = 0; j < nClipCount; ++j ) + { + CDmeClip *pSubClip = GetClip( j ); + if ( !pSubClip ) + continue; + if ( ( flags & DMESKIP_MUTED ) && pSubClip->IsMute() ) + continue; + + if ( time.IsInRange( pSubClip->GetStartTime(), pSubClip->GetEndTime() ) ) + { + clips.AddToTail( pSubClip ); + } + } +} + +void CDmeTrack::FindClipsIntersectingTime( DmeTime_t startTime, DmeTime_t endTime, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( flags & DMESKIP_INVISIBLE ) && IsCollapsed() ) + return; + + if ( ( flags & DMESKIP_MUTED ) && IsMute() ) + return; + + int nClipCount = GetClipCount(); + for ( int j = 0; j < nClipCount; ++j ) + { + CDmeClip *pSubClip = GetClip( j ); + if ( !pSubClip ) + continue; + if ( ( flags & DMESKIP_MUTED ) && pSubClip->IsMute() ) + continue; + + DmeTime_t clipStart = pSubClip->GetStartTime(); + DmeTime_t clipEnd = pSubClip->GetEndTime(); + if ( clipEnd >= startTime && clipStart < endTime ) + { + clips.AddToTail( pSubClip ); + } + } +} + +void CDmeTrack::FindClipsWithinTime( DmeTime_t startTime, DmeTime_t endTime, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( flags & DMESKIP_INVISIBLE ) && IsCollapsed() ) + return; + + if ( ( flags & DMESKIP_MUTED ) && IsMute() ) + return; + + int nClipCount = GetClipCount(); + for ( int j = 0; j < nClipCount; ++j ) + { + CDmeClip *pSubClip = GetClip( j ); + if ( !pSubClip ) + continue; + if ( ( flags & DMESKIP_MUTED ) && pSubClip->IsMute() ) + continue; + + DmeTime_t clipStart = pSubClip->GetStartTime(); + DmeTime_t clipEnd = pSubClip->GetEndTime(); + if ( clipStart >= startTime && clipEnd <= endTime ) + { + clips.AddToTail( pSubClip ); + } + } +} + + +//----------------------------------------------------------------------------- +// Methods related to shifting clips +//----------------------------------------------------------------------------- +void CDmeTrack::ShiftAllClips( DmeTime_t dt ) +{ + if ( dt == DmeTime_t( 0 ) ) + return; + + CSuppressAutoFixup suppress( this, SUPPRESS_OVERLAP_FIXUP | SUPPRESS_DIRTY_ORDERING ); + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = m_Clips[ i ]; + pSubClip->SetStartTime( pSubClip->GetStartTime() + dt ); + } +} + +void CDmeTrack::ShiftAllClipsAfter( DmeTime_t startTime, DmeTime_t dt, bool bTestStartingTime ) +{ + if ( dt == DmeTime_t( 0 ) ) + return; + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + DmeTime_t testTime = bTestStartingTime ? pSubClip->GetStartTime() : pSubClip->GetEndTime(); + if ( startTime < testTime ) + { + pSubClip->SetStartTime( pSubClip->GetStartTime() + dt ); + } + } +} + +void CDmeTrack::ShiftAllClipsBefore( DmeTime_t endTime, DmeTime_t dt, bool bTestEndingTime ) +{ + if ( dt == DmeTime_t( 0 ) ) + return; + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + DmeTime_t testTime = bTestEndingTime ? pSubClip->GetEndTime() : pSubClip->GetStartTime(); + if ( endTime > testTime ) + { + DmeTime_t startTime = pSubClip->GetStartTime(); + pSubClip->SetStartTime( startTime + dt ); + } + } +} + + +//----------------------------------------------------------------------------- +// A version that works only on film clips +//----------------------------------------------------------------------------- +void CDmeTrack::ShiftAllFilmClipsAfter( CDmeClip *pClip, DmeTime_t dt, bool bShiftClip ) +{ + Assert( IsFilmTrack() ); + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) || ( dt == DmeTime_t( 0 ) ) ) + return; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + int c = GetClipCount(); + for ( int i = c; --i >= 0; ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( pSubClip == pClip ) + { + if ( bShiftClip ) + { + pSubClip->SetStartTime( pSubClip->GetStartTime() + dt ); + } + return; + } + pSubClip->SetStartTime( pSubClip->GetStartTime() + dt ); + } + + // Clip wasn't found! + Assert( 0 ); +} + +void CDmeTrack::ShiftAllFilmClipsBefore( CDmeClip *pClip, DmeTime_t dt, bool bShiftClip ) +{ + Assert( IsFilmTrack() ); + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) || ( dt == DmeTime_t( 0 ) ) ) + return; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + + if ( pSubClip == pClip ) + { + if ( bShiftClip ) + { + pSubClip->SetStartTime( pSubClip->GetStartTime() + dt ); + } + return; + } + pSubClip->SetStartTime( pSubClip->GetStartTime() + dt ); + } + + // Clip wasn't found! + Assert( 0 ); +} + + +//----------------------------------------------------------------------------- +// Method to sort clips by start time +//----------------------------------------------------------------------------- +struct SortInfo_t +{ + DmeTime_t m_startTime; + CDmeClip *m_pClip; +}; + +static int ClipStartLessFunc( const void * lhs, const void * rhs ) +{ + SortInfo_t *pInfo1 = (SortInfo_t*)lhs; + SortInfo_t *pInfo2 = (SortInfo_t*)rhs; + if ( pInfo1->m_startTime == pInfo2->m_startTime ) + return 0; + + return pInfo1->m_startTime < pInfo2->m_startTime ? -1 : 1; +} + +void CDmeTrack::SortClipsByStartTime( ) +{ + // If we're not a film clip, then we haven't installed callbacks to make sorting fast. + // The IS_SORTED flag is some random state + if ( (m_ClipType == DMECLIP_FILM) && m_Flags.IsFlagSet( IS_SORTED ) ) + return; + m_Flags.SetFlag( IS_SORTED ); + + int c = GetClipCount(); + if ( c <= 1 ) + return; + + DmeTime_t lastTime; + SortInfo_t *pSortInfo = (SortInfo_t*)_alloca( c * sizeof(SortInfo_t) ); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip(i); + pSortInfo[i].m_startTime = pSubClip ? pSubClip->GetStartTime() : DmeTime_t::InvalidTime(); + pSortInfo[i].m_pClip = pSubClip; + if ( lastTime > pSortInfo[i].m_startTime ) + { + m_Flags.ClearFlag( IS_SORTED ); + } + lastTime = pSortInfo[i].m_startTime; + } + if ( m_Flags.IsFlagSet( IS_SORTED ) ) + return; + + m_Flags.SetFlag( IS_SORTED ); + qsort( pSortInfo, c, sizeof(SortInfo_t), ClipStartLessFunc ); + + CSuppressAutoFixup suppress( this, SUPPRESS_OVERLAP_FIXUP | SUPPRESS_DIRTY_ORDERING ); + + m_Clips.RemoveAll(); + + for ( int i = 0; i < c; ++i ) + { + m_Clips.AddToTail( pSortInfo[i].m_pClip ); + } +} + + +//----------------------------------------------------------------------------- +// Shifts all clips to be non-overlapping +//----------------------------------------------------------------------------- +void CDmeTrack::FixOverlaps() +{ + int c = GetClipCount(); + if ( c <= 1 ) + return; + + SortClipsByStartTime(); + + CSuppressAutoFixup suppress( this, SUPPRESS_OVERLAP_FIXUP | SUPPRESS_DIRTY_ORDERING ); + + // Cull NULL clips + int nActualCount = 0; + CDmeClip **pClips = (CDmeClip**)_alloca( c * sizeof(CDmeClip*) ); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pCurr = GetClip( i ); + if ( pCurr && ((i == 0) || (pClips[i-1] != pCurr)) ) + { + pClips[nActualCount++] = pCurr; + } + } + + if ( nActualCount <= 1 ) + return; + + CDmeClip *pPrev = pClips[0]; + for ( int i = 1; i < nActualCount; ++i ) + { + CDmeClip *pCurr = pClips[i]; + + DmeTime_t prevEndTime = pPrev->GetEndTime(); + DmeTime_t startTime = pCurr->GetStartTime(); + + if ( startTime < prevEndTime ) + { + pCurr->SetStartTime( prevEndTime ); + } + + pPrev = pCurr; + } +} + + +//----------------------------------------------------------------------------- +// Finds a clip at a particular time +//----------------------------------------------------------------------------- +CDmeClip* CDmeTrack::FindFilmClipAtTime( DmeTime_t localTime ) +{ + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) ) + return NULL; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( pSubClip && pSubClip->GetStartTime() <= localTime && pSubClip->GetEndTime() > localTime ) + return pSubClip; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Find first clip in a specific time range +//----------------------------------------------------------------------------- +CDmeClip* CDmeTrack::FindFirstFilmClipIntesectingTime( DmeTime_t localStartTime, DmeTime_t localEndTime ) +{ + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) ) + return NULL; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( !pSubClip ) + continue; + if ( ( localStartTime < pSubClip->GetEndTime() ) && ( localEndTime >= pSubClip->GetStartTime() ) ) + return static_cast<CDmeFilmClip*>( pSubClip ); + if ( localEndTime <= pSubClip->GetStartTime() ) + break; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Inserts space in a film track for a film clip +//----------------------------------------------------------------------------- +void CDmeTrack::InsertSpaceInFilmTrack( DmeTime_t localStartTime, DmeTime_t localEndTime ) +{ + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) ) + return; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + CDmeClip *pClip = FindFirstFilmClipIntesectingTime( localStartTime, localEndTime ); + if ( pClip ) + { + DmeTime_t filmStart = pClip->GetStartTime(); + DmeTime_t dt = localEndTime - filmStart; + ShiftAllFilmClipsAfter( pClip, dt, true ); + } + + return; +} + + +//----------------------------------------------------------------------------- +// Returns the next/previous clip in a film track +//----------------------------------------------------------------------------- +CDmeClip* CDmeTrack::FindPrevFilmClip( CDmeClip *pClip ) +{ + Assert( IsFilmTrack() ); + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) ) + return NULL; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + if ( !pClip ) + return m_Clips[ m_Clips.Count() - 1 ]; + + // FIXME: Could use a binary search here based on time. + // Probably doesn't matter though, since there will usually not be a ton of tracks + CDmeClip *pPrevClip = NULL; + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( pSubClip == pClip ) + return pPrevClip; + + pPrevClip = pSubClip; + } + + return NULL; +} + +CDmeClip* CDmeTrack::FindNextFilmClip( CDmeClip *pClip ) +{ + Assert( IsFilmTrack() ); + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) ) + return NULL; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + if ( !pClip ) + return m_Clips[ 0 ]; + + CDmeClip *pNextClip = NULL; + + int c = GetClipCount(); + for ( int i = c; --i >= 0; ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( pSubClip == pClip ) + return pNextClip; + + pNextClip = pSubClip; + } + + return NULL; +} + + +void CDmeTrack::FindAdjacentFilmClips( CDmeClip *pClip, CDmeClip *&pPrevClip, CDmeClip *&pNextClip ) +{ + pPrevClip = pNextClip = NULL; + + Assert( IsFilmTrack() ); + if ( !IsFilmTrack() || !pClip || ( m_Clips.Count() == 0 ) ) + return; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( pSubClip == pClip ) + { + pNextClip = ( i != c-1 ) ? GetClip( i+1 ) : NULL; + return; + } + + pPrevClip = pSubClip; + } + + pPrevClip = NULL; +} + + +//----------------------------------------------------------------------------- +// Gets the start/end time of the owning clip in local time +//----------------------------------------------------------------------------- +void CDmeTrack::FindAdjacentFilmClips( DmeTime_t localTime, CDmeClip *&pPrevClip, CDmeClip *&pNextClip ) +{ + pPrevClip = pNextClip = NULL; + + Assert( IsFilmTrack() ); + if ( !IsFilmTrack() || ( m_Clips.Count() == 0 ) ) + return; + + // This algorithm requires sorted clips + SortClipsByStartTime(); + + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pSubClip = GetClip( i ); + if ( localTime >= pSubClip->GetEndTime() ) + { + pPrevClip = pSubClip; + } + if ( localTime < pSubClip->GetStartTime() ) + { + pNextClip = pSubClip; + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Fills all gaps in a film track with slugs +//----------------------------------------------------------------------------- +void CDmeTrack::FillAllGapsWithSlugs( const char *pSlugName, DmeTime_t startTime, DmeTime_t endTime ) +{ + if ( !IsFilmTrack() ) + return; + + FixOverlaps(); + + // Create temporary slugs to fill in the gaps + bool bSlugAdded = false; + int c = GetClipCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeClip *pFilmClip = GetClip(i); + DmeTime_t clipStartTime = pFilmClip->GetStartTime(); + if ( clipStartTime > startTime ) + { + // There's a gap, create a slug + CDmeFilmClip *pSlug = CreateSlugClip( pSlugName, startTime, clipStartTime, GetFileId() ); + + // This will add the slug to the end; so we don't have to + // worry about iterating over it (we've cached off the initial count) + AddClip( pSlug ); + + bSlugAdded = true; + } + startTime = pFilmClip->GetEndTime(); + } + + if ( endTime > startTime ) + { + // There's a gap, create a temporary slug + CDmeFilmClip *pSlug = CreateSlugClip( pSlugName, startTime, endTime, GetFileId() ); + + // This will add the slug to the end; so we don't have to + // worry about iterating over it (we've cached off the initial count) + AddClip( pSlug ); + + bSlugAdded = true; + } + + if ( bSlugAdded ) + { + FixOverlaps(); + } +} + +//----------------------------------------------------------------------------- +// helper methods +//----------------------------------------------------------------------------- +CDmeTrackGroup *GetParentTrackGroup( CDmeTrack *pTrack ) +{ + DmAttributeReferenceIterator_t hAttr = g_pDataModel->FirstAttributeReferencingElement( pTrack->GetHandle() ); + for ( ; hAttr != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; hAttr = g_pDataModel->NextAttributeReferencingElement( hAttr ) ) + { + CDmAttribute *pAttr = g_pDataModel->GetAttribute( hAttr ); + if ( !pAttr ) + continue; + + CDmeTrackGroup *pTrackGroup = CastElement< CDmeTrackGroup >( pAttr->GetOwner() ); + if ( pTrackGroup ) + return pTrackGroup; + } + return NULL; +} diff --git a/movieobjects/dmetrackgroup.cpp b/movieobjects/dmetrackgroup.cpp new file mode 100644 index 0000000..0070329 --- /dev/null +++ b/movieobjects/dmetrackgroup.cpp @@ -0,0 +1,524 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmetrackgroup.h" + +#include <limits.h> +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "movieobjects/dmetrack.h" +#include "movieobjects/dmeclip.h" + +#include "movieobjects_interfaces.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// CDmeTrackGroup - contains a list of tracks +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTrackGroup, CDmeTrackGroup ); + +void CDmeTrackGroup::OnConstruction() +{ + m_hOwner = DMELEMENT_HANDLE_INVALID; + + m_Tracks.Init( this, "tracks", FATTRIB_MUSTCOPY | FATTRIB_HAS_ARRAY_CALLBACK ); + m_bIsVisible.InitAndSet( this, "visible", true ); + m_bMute.Init( this, "mute" ); + m_nDisplaySize.InitAndSet( this, "displaySize", 110 ); + m_bMinimized.InitAndSet( this, "minimized", true ); + m_nMaxTrackCount = INT_MAX; + m_Volume.InitAndSet( this, "volume", 1.0 ); + +} + +void CDmeTrackGroup::OnDestruction() +{ + // NOTE: The track owner handles may still be pointing to us when we get destructed, + // but their handles will be invalid, so GetTrackGroup on a track + // will correctly return NULL. +} + + +//----------------------------------------------------------------------------- +// Max track count +//----------------------------------------------------------------------------- +void CDmeTrackGroup::SetMaxTrackCount( int nCount ) +{ + m_nMaxTrackCount = nCount; +} + + +//----------------------------------------------------------------------------- +// Mute +//----------------------------------------------------------------------------- +void CDmeTrackGroup::SetMute( bool state ) +{ + m_bMute = state; +} + +bool CDmeTrackGroup::IsMute( ) const +{ + return m_bMute; +} + +//----------------------------------------------------------------------------- +// Volume +//----------------------------------------------------------------------------- +void CDmeTrackGroup::SetVolume( float state ) +{ + m_Volume = state; +} +float CDmeTrackGroup::GetVolume() const +{ + return m_Volume.Get(); +} + +//----------------------------------------------------------------------------- +// Owning clip +//----------------------------------------------------------------------------- +CDmeClip *CDmeTrackGroup::GetOwnerClip() +{ + return GetElement< CDmeClip >( m_hOwner ); +} + +void CDmeTrackGroup::SetOwnerClip( CDmeClip *pClip ) +{ + m_hOwner = pClip ? pClip->GetHandle() : DMELEMENT_HANDLE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Are we a film track group? +//----------------------------------------------------------------------------- +bool CDmeTrackGroup::IsFilmTrackGroup() +{ + CDmeClip *pOwnerClip = GetOwnerClip(); + if ( pOwnerClip ) + return pOwnerClip->GetFilmTrackGroup() == this; + return m_nMaxTrackCount == 1; +} + + +//----------------------------------------------------------------------------- +// Is a particular clip typed able to be added? +//----------------------------------------------------------------------------- +bool CDmeTrackGroup::IsSubClipTypeAllowed( DmeClipType_t type ) +{ + if ( IsFilmTrackGroup() ) + { + if ( type != DMECLIP_FILM ) + return false; + } + else + { + if ( type == DMECLIP_FILM ) + return false; + } + + CDmeClip *pOwnerClip = GetOwnerClip(); + Assert( pOwnerClip ); + if ( !pOwnerClip ) + return true; + + return pOwnerClip->IsSubClipTypeAllowed( type ); +} + + +//----------------------------------------------------------------------------- +// Track addition/removal +//----------------------------------------------------------------------------- +void CDmeTrackGroup::AddTrack( CDmeTrack *pTrack ) +{ + // FIXME: Should check if track with same name already exists??? + if ( GetTrackIndex( pTrack ) < 0 ) + { + // Tracks can only exist in one track group + Assert( GetTrackIndex( pTrack ) >= 0 ); + m_Tracks.AddToTail( pTrack ); + Assert( m_nMaxTrackCount >= m_Tracks.Count() ); + } +} + +CDmeTrack* CDmeTrackGroup::AddTrack( const char *pTrackName, DmeClipType_t trackType ) +{ + CDmeTrack *pTrack = CreateElement< CDmeTrack >( pTrackName, GetFileId() ); + pTrack->SetClipType( trackType ); + pTrack->SetCollapsed( false ); + m_Tracks.AddToTail( pTrack ); + Assert( m_nMaxTrackCount >= m_Tracks.Count() ); + return pTrack; +} + +CDmeTrack* CDmeTrackGroup::FindOrAddTrack( const char *pTrackName, DmeClipType_t trackType ) +{ + CDmeTrack *pTrack = FindTrack( pTrackName ); + if ( pTrack ) + { + // If we found it, but it's the wrong type, no dice + if ( pTrack->GetClipType() != trackType ) + return NULL; + } + else + { + pTrack = AddTrack( pTrackName, trackType ); + } + return pTrack; +} + +void CDmeTrackGroup::RemoveTrack( int nIndex ) +{ + m_Tracks.Remove( nIndex ); +} + +void CDmeTrackGroup::RemoveTrack( CDmeTrack *pTrack ) +{ + int i = GetTrackIndex( pTrack ); + if ( i >= 0 ) + { + m_Tracks.Remove( i ); + } +} + +void CDmeTrackGroup::RemoveTrack( const char *pTrackName ) +{ + if ( !pTrackName ) + { + pTrackName = DMETRACK_DEFAULT_NAME; + } + + int c = m_Tracks.Count(); + for ( int i = c; --i >= 0; ) + { + if ( !Q_strcmp( m_Tracks[i]->GetName(), pTrackName ) ) + { + m_Tracks.Remove( i ); + return; + } + } +} + + +//----------------------------------------------------------------------------- +// Track finding +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrackGroup::FindTrack( const char *pTrackName ) const +{ + if ( !pTrackName ) + { + pTrackName = DMETRACK_DEFAULT_NAME; + } + + int c = m_Tracks.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeTrack *pTrack = m_Tracks[i]; + if ( !pTrack ) + continue; + + if ( !Q_strcmp( pTrack->GetName(), pTrackName ) ) + return pTrack; + } + return NULL; +} + +int CDmeTrackGroup::GetTrackIndex( CDmeTrack *pTrack ) const +{ + int nTracks = m_Tracks.Count(); + for ( int i = 0 ; i < nTracks; ++i ) + { + if ( pTrack == m_Tracks[i] ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Creates the film track group [for internal use only] +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrackGroup::CreateFilmTrack() +{ + Assert( GetTrackCount() == 0 ); + return AddTrack( "Film", DMECLIP_FILM ); +} + + +//----------------------------------------------------------------------------- +// Returns the film track, if any +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrackGroup::GetFilmTrack() +{ + if ( !IsFilmTrackGroup() ) + return NULL; + + if ( GetTrackCount() > 0 ) + { + Assert( GetTrackCount() == 1 ); + return m_Tracks[0]; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Adding/removing clips from tracks +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrackGroup::AddClip( CDmeClip *pClip, const char *pTrackName ) +{ + DmeClipType_t type = pClip->GetClipType(); + if ( !pTrackName ) + { + pTrackName = DMETRACK_DEFAULT_NAME; + } + + CDmeTrack *pTrack = FindOrAddTrack( pTrackName, type ); + if ( pTrack ) + { + pTrack->AddClip( pClip ); + } + return pTrack; +} + +bool CDmeTrackGroup::RemoveClip( CDmeClip *pClip ) +{ + CDmeTrack *pTrack = FindTrackForClip( pClip ); + if ( pTrack ) + return pTrack->RemoveClip( pClip ); + return false; +} + + +//----------------------------------------------------------------------------- +// Changing clip track +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrackGroup::ChangeTrack( CDmeClip *pClip, const char *pNewTrack ) +{ + // Add, then remove, to avoid refcount problems + // Don't remove if it wasn't added for some reason. + CDmeTrack *pOldTrack = FindTrackForClip( pClip ); + CDmeTrack *pTrack = AddClip( pClip, pNewTrack ); + if ( pTrack && pOldTrack ) + { + pOldTrack->RemoveClip( pClip ); + } + return pTrack; +} + + +//----------------------------------------------------------------------------- +// Finding clips in tracks +//----------------------------------------------------------------------------- +CDmeTrack *CDmeTrackGroup::FindTrackForClip( CDmeClip *pClip ) const +{ + int nTrackIndex = -1; + if ( !FindTrackForClip( pClip, &nTrackIndex, NULL ) ) + return NULL; + + return GetTrack( nTrackIndex ); +} + + +bool CDmeTrackGroup::FindTrackForClip( CDmeClip *pClip, int *pTrackIndex, int *pClipIndex ) const +{ + DmeClipType_t type = pClip->GetClipType(); + int c = GetTrackCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeTrack *pTrack = GetTrack( i ); + if ( !pTrack ) + continue; + + if ( pTrack->GetClipType() != type ) + continue; + + int nClipCount = pTrack->GetClipCount(); + for ( int j = 0; j < nClipCount; ++j ) + { + if ( pTrack->GetClip( j ) == pClip ) + { + if ( pTrackIndex ) + { + *pTrackIndex = i; + } + if ( pClipIndex ) + { + *pClipIndex = j; + } + return true; + } + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Finding clips in tracks by time +//----------------------------------------------------------------------------- +void CDmeTrackGroup::FindClipsAtTime( DmeClipType_t clipType, DmeTime_t time, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( flags & DMESKIP_INVISIBLE ) && ( !IsVisible() || IsMinimized() ) ) + return; + + if ( ( flags & DMESKIP_MUTED ) && IsMute() ) + return; + + int c = GetTrackCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeTrack *pTrack = GetTrack( i ); + if ( !pTrack ) + continue; + + if ( ( clipType != DMECLIP_UNKNOWN ) && ( pTrack->GetClipType() != clipType ) ) + continue; + + pTrack->FindClipsAtTime( time, flags, clips ); + } +} + + +void CDmeTrackGroup::FindClipsIntersectingTime( DmeClipType_t clipType, DmeTime_t startTime, DmeTime_t endTime, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( flags & DMESKIP_INVISIBLE ) && ( !IsVisible() || IsMinimized() ) ) + return; + + if ( ( flags & DMESKIP_MUTED ) && IsMute() ) + return; + + int c = GetTrackCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeTrack *pTrack = GetTrack( i ); + if ( !pTrack ) + continue; + + if ( ( clipType != DMECLIP_UNKNOWN ) && ( pTrack->GetClipType() != clipType ) ) + continue; + + pTrack->FindClipsIntersectingTime( startTime, endTime, flags, clips ); + } +} + + +void CDmeTrackGroup::FindClipsWithinTime( DmeClipType_t clipType, DmeTime_t startTime, DmeTime_t endTime, DmeClipSkipFlag_t flags, CUtlVector< CDmeClip * >& clips ) const +{ + if ( ( flags & DMESKIP_INVISIBLE ) && ( !IsVisible() || IsMinimized() ) ) + return; + + if ( ( flags & DMESKIP_MUTED ) && IsMute() ) + return; + + int c = GetTrackCount(); + for ( int i = 0; i < c; ++i ) + { + CDmeTrack *pTrack = GetTrack( i ); + if ( !pTrack ) + continue; + + if ( ( clipType != DMECLIP_UNKNOWN ) && ( pTrack->GetClipType() != clipType ) ) + continue; + + pTrack->FindClipsWithinTime( startTime, endTime, flags, clips ); + } +} + + +//----------------------------------------------------------------------------- +// Removes empty tracks +//----------------------------------------------------------------------------- +void CDmeTrackGroup::RemoveEmptyTracks() +{ + int tc = GetTrackCount(); + for ( int i = tc; --i >= 0; ) + { + CDmeTrack *pTrack = GetTrack( i ); + if ( pTrack->GetClipCount() == 0 ) + { + RemoveTrack( i ); + } + } +} + + +//----------------------------------------------------------------------------- +// Sort tracks by track type, then alphabetically +//----------------------------------------------------------------------------- +static int TrackLessFunc( const void * lhs, const void * rhs ) +{ + CDmeTrack *pInfo1 = *(CDmeTrack**)lhs; + CDmeTrack *pInfo2 = *(CDmeTrack**)rhs; + if ( pInfo1->GetClipType() < pInfo2->GetClipType() ) + return -1; + if ( pInfo1->GetClipType() > pInfo2->GetClipType() ) + return 1; + return Q_strcmp( pInfo1->GetName(), pInfo2->GetName() ); +} + +void CDmeTrackGroup::SortTracksByType() +{ + int tc = GetTrackCount(); + if ( tc == 0 ) + return; + + CDmeTrack **ppTrack = (CDmeTrack**)_alloca( tc * sizeof(CDmeTrack*) ); + for ( int i = 0; i < tc; ++i ) + { + ppTrack[i] = GetTrack(i); + } + + qsort( ppTrack, tc, sizeof(CDmeTrack*), TrackLessFunc ); + + m_Tracks.RemoveAll(); + + for ( int i = 0; i < tc; ++i ) + { + m_Tracks.AddToTail( ppTrack[i] ); + } +} + + +//----------------------------------------------------------------------------- +// Returns the flattened clip count +//----------------------------------------------------------------------------- +int CDmeTrackGroup::GetSubClipCount() const +{ + int nCount = 0; + DMETRACKGROUP_FOREACH_CLIP_START( this, pTrack, pClip ) + ++nCount; + DMETRACKGROUP_FOREACH_CLIP_END() + return nCount; +} + +void CDmeTrackGroup::GetSubClips( CDmeClip **ppClips ) +{ + int nCount = 0; + DMETRACKGROUP_FOREACH_CLIP_START( this, pTrack, pClip ) + ppClips[nCount++] = pClip; + DMETRACKGROUP_FOREACH_CLIP_END() +} + +//----------------------------------------------------------------------------- +// helper methods +//----------------------------------------------------------------------------- +CDmeFilmClip *GetParentClip( CDmeTrackGroup *pTrackGroup ) +{ + DmAttributeReferenceIterator_t hAttr = g_pDataModel->FirstAttributeReferencingElement( pTrackGroup->GetHandle() ); + for ( ; hAttr != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; hAttr = g_pDataModel->NextAttributeReferencingElement( hAttr ) ) + { + CDmAttribute *pAttr = g_pDataModel->GetAttribute( hAttr ); + if ( !pAttr ) + continue; + + CDmeFilmClip *pFilmClip = CastElement< CDmeFilmClip >( pAttr->GetOwner() ); + if ( pFilmClip ) + return pFilmClip; + } + return NULL; +} diff --git a/movieobjects/dmetransform.cpp b/movieobjects/dmetransform.cpp new file mode 100644 index 0000000..ab7c456 --- /dev/null +++ b/movieobjects/dmetransform.cpp @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmetransform.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "mathlib/vector.h" +#include "mathlib/mathlib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTransform, CDmeTransform ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +void CDmeTransform::OnConstruction() +{ + m_Position.Init( this, "position" ); + m_Orientation.Init( this, "orientation" ); +} + +void CDmeTransform::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// FIXME: Replace this with actual methods to do editing +//----------------------------------------------------------------------------- +void CDmeTransform::SetTransform( const matrix3x4_t &transform ) +{ + Vector origin; + Quaternion angles; + MatrixAngles( transform, angles, origin ); + m_Orientation.Set( angles ); + m_Position.Set( origin ); +} + +void CDmeTransform::GetTransform( matrix3x4_t &transform ) +{ + QuaternionMatrix( m_Orientation.Get(), m_Position.Get(), transform ); +} + +const Vector &CDmeTransform::GetPosition() const +{ + return m_Position.Get(); +} + +void CDmeTransform::SetPosition( const Vector &vecPosition ) +{ + m_Position = vecPosition; +} + +const Quaternion &CDmeTransform::GetOrientation() const +{ + return m_Orientation.Get(); +} + +void CDmeTransform::SetOrientation( const Quaternion &orientation ) +{ + m_Orientation = orientation; +} + + +CDmAttribute *CDmeTransform::GetPositionAttribute() +{ + return m_Position.GetAttribute(); +} + +CDmAttribute *CDmeTransform::GetOrientationAttribute() +{ + return m_Orientation.GetAttribute(); +} diff --git a/movieobjects/dmetransforminput.cpp b/movieobjects/dmetransforminput.cpp new file mode 100644 index 0000000..081381d --- /dev/null +++ b/movieobjects/dmetransforminput.cpp @@ -0,0 +1,98 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmetransforminput.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTranslationInput, CDmeTranslationInput ); + + +void CDmeTranslationInput::OnConstruction() +{ + m_translation.Init( this, "translation" ); +} + +void CDmeTranslationInput::OnDestruction() +{ +} + +bool CDmeTranslationInput::IsDirty() +{ + return true; +} + +void CDmeTranslationInput::Operate() +{ +} + +void CDmeTranslationInput::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ +} + +void CDmeTranslationInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_translation.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeRotationInput, CDmeRotationInput ); + + +void CDmeRotationInput::OnConstruction() +{ + m_orientation.Init( this, "orientation" ); + m_angles.Init( this, "angles" ); +} + +void CDmeRotationInput::OnDestruction() +{ +} + +bool CDmeRotationInput::IsDirty() +{ + return true; +} + +void CDmeRotationInput::Operate() +{ +} + +void CDmeRotationInput::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ +} + +void CDmeRotationInput::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_orientation.GetAttribute() ); + attrs.AddToTail( m_angles.GetAttribute() ); +} + +void CDmeRotationInput::SetRotation( const Quaternion& quat ) +{ + QAngle qangle; + QuaternionAngles( quat, qangle ); + m_angles = qangle; + m_orientation = quat; +} + +void CDmeRotationInput::SetRotation( const QAngle& qangle ) +{ + Quaternion quat; + AngleQuaternion( qangle, quat ); + m_orientation = quat; + m_angles = qangle; +} diff --git a/movieobjects/dmetransformlist.cpp b/movieobjects/dmetransformlist.cpp new file mode 100644 index 0000000..e6b9a30 --- /dev/null +++ b/movieobjects/dmetransformlist.cpp @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// An element that contains a transformlist +// +//============================================================================= +#include "movieobjects/dmetransformlist.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTransformList, CDmeTransformList ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeTransformList::OnConstruction() +{ + m_Transforms.Init( this, "transforms" ); +} + +void CDmeTransformList::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the transform +//----------------------------------------------------------------------------- +void CDmeTransformList::SetTransform( int nIndex, const matrix3x4_t& mat ) +{ + m_Transforms[nIndex]->SetTransform( mat ); +} diff --git a/movieobjects/dmetransformoperator.cpp b/movieobjects/dmetransformoperator.cpp new file mode 100644 index 0000000..e331347 --- /dev/null +++ b/movieobjects/dmetransformoperator.cpp @@ -0,0 +1,107 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The transform operator class - shortcut to setting transform values from floats +// +//============================================================================= +#include "movieobjects/dmetransformoperator.h" +#include "movieobjects/dmetransform.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeTransformOperator, CDmeTransformOperator ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeTransformOperator::OnConstruction() +{ + m_transform.Init( this, "transform" ); + m_positionX.Init( this, "positionX" ); + m_positionY.Init( this, "positionY" ); + m_positionZ.Init( this, "positionZ" ); + m_orientationX.Init( this, "orientationX" ); + m_orientationY.Init( this, "orientationY" ); + m_orientationZ.Init( this, "orientationZ" ); + m_orientationW.Init( this, "orientationW" ); +} + +void CDmeTransformOperator::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeTransformOperator::Operate() +{ + CDmeTransform *pTransform = m_transform.GetElement(); + if ( pTransform == NULL ) + return; + + Vector position = pTransform->GetValue< Vector >( "position" ); + Quaternion orientation = pTransform->GetValue< Quaternion >( "orientation" ); + + position.x = m_positionX.Get(); + position.y = m_positionY.Get(); + position.z = m_positionZ.Get(); + + orientation.x = m_orientationX.Get(); + orientation.y = m_orientationY.Get(); + orientation.z = m_orientationZ.Get(); + orientation.w = m_orientationW.Get(); + + pTransform->SetValue( "position", position ); + pTransform->SetValue( "orientation", orientation ); +} + +// hack to avoid MSVC complaining about multiply defined symbols +namespace TransformOp +{ +void AddAttr( CUtlVector< CDmAttribute * > &attrs, CDmAttribute *pAttr ) +{ + if ( pAttr == NULL ) + return; + attrs.AddToTail( pAttr ); +} +}; +using namespace TransformOp; + +void CDmeTransformOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + AddAttr( attrs, m_positionX.GetAttribute() ); + AddAttr( attrs, m_positionY.GetAttribute() ); + AddAttr( attrs, m_positionZ.GetAttribute() ); + AddAttr( attrs, m_orientationX.GetAttribute() ); + AddAttr( attrs, m_orientationY.GetAttribute() ); + AddAttr( attrs, m_orientationZ.GetAttribute() ); + AddAttr( attrs, m_orientationW.GetAttribute() ); +} + +void CDmeTransformOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + CDmeTransform *pTransform = m_transform.GetElement(); + if ( pTransform == NULL ) + return; + + AddAttr( attrs, pTransform->GetAttribute( "position" ) ); + AddAttr( attrs, pTransform->GetAttribute( "orientation" ) ); +} + +void CDmeTransformOperator::SetTransform( CDmeTransform *pTransform ) +{ + m_transform.Set( pTransform ); +} + +const CDmeTransform *CDmeTransformOperator::GetTransform() const +{ + return m_transform.GetElement(); +} diff --git a/movieobjects/dmeunpackoperators.cpp b/movieobjects/dmeunpackoperators.cpp new file mode 100644 index 0000000..d9e8c8b --- /dev/null +++ b/movieobjects/dmeunpackoperators.cpp @@ -0,0 +1,335 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects/dmeunpackoperators.h" +#include "movieobjects_interfaces.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmattribute.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// CDmeUnpackColorOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackColorOperator, CDmeUnpackColorOperator ); + +void CDmeUnpackColorOperator::OnConstruction() +{ + m_color.Init( this, "color" ); + m_red .Init( this, "red" ); + m_green.Init( this, "green" ); + m_blue .Init( this, "blue" ); + m_alpha.Init( this, "alpha" ); +} + +void CDmeUnpackColorOperator::OnDestruction() +{ +} + +bool CDmeUnpackColorOperator::IsDirty() +{ + const Color &c = m_color.Get(); +// float s = 255.999f; +// return c.r() != s*m_red.Get() || c.g() != s*m_green.Get() || c.b() != s*m_blue.Get() || c.a() != s*m_alpha.Get(); + return c.r() != m_red.Get() || c.g() != m_green.Get() || c.b() != m_blue.Get() || c.a() != m_alpha.Get(); +} + +void CDmeUnpackColorOperator::Operate() +{ +// float s = 255.999f; +// m_color.Set( Color( s*m_red.Get(), s*m_green.Get(), s*m_blue.Get(), s*m_alpha.Get() ) ); + m_red .Set( m_color.Get().r() ); + m_green.Set( m_color.Get().g() ); + m_blue .Set( m_color.Get().b() ); + m_alpha.Set( m_color.Get().a() ); +} + +void CDmeUnpackColorOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_color.GetAttribute() ); +} + +void CDmeUnpackColorOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_red.GetAttribute() ); + attrs.AddToTail( m_green.GetAttribute() ); + attrs.AddToTail( m_blue.GetAttribute() ); + attrs.AddToTail( m_alpha.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmeUnpackVector2Operator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackVector2Operator, CDmeUnpackVector2Operator ); + +void CDmeUnpackVector2Operator::OnConstruction() +{ + m_vector.Init( this, "vector" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); +} + +void CDmeUnpackVector2Operator::OnDestruction() +{ +} + +bool CDmeUnpackVector2Operator::IsDirty() +{ + const Vector2D &v = m_vector.Get(); + return v.x != m_x.Get() || v.y != m_y.Get(); +} + +void CDmeUnpackVector2Operator::Operate() +{ + m_x.Set( m_vector.Get().x ); + m_y.Set( m_vector.Get().y ); +} + +void CDmeUnpackVector2Operator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vector.GetAttribute() ); +} + +void CDmeUnpackVector2Operator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmeUnpackVector3Operator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackVector3Operator, CDmeUnpackVector3Operator ); + +void CDmeUnpackVector3Operator::OnConstruction() +{ + m_vector.Init( this, "vector" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); +} + +void CDmeUnpackVector3Operator::OnDestruction() +{ +} + +bool CDmeUnpackVector3Operator::IsDirty() +{ + const Vector &v = m_vector.Get(); + return v.x != m_x.Get() || v.y != m_y.Get() || v.z != m_z.Get(); +} + +void CDmeUnpackVector3Operator::Operate() +{ + m_x.Set( m_vector.Get().x ); + m_y.Set( m_vector.Get().y ); + m_z.Set( m_vector.Get().z ); +} + +void CDmeUnpackVector3Operator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vector.GetAttribute() ); +} + +void CDmeUnpackVector3Operator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmeUnpackVector4Operator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackVector4Operator, CDmeUnpackVector4Operator ); + +void CDmeUnpackVector4Operator::OnConstruction() +{ + m_vector.Init( this, "vector" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); + m_w.Init( this, "w" ); +} + +void CDmeUnpackVector4Operator::OnDestruction() +{ +} + +bool CDmeUnpackVector4Operator::IsDirty() +{ + const Vector4D &v = m_vector.Get(); + return v.x != m_x.Get() || v.y != m_y.Get() || v.z != m_z.Get() || v.w != m_w.Get(); +} + +void CDmeUnpackVector4Operator::Operate() +{ + m_x.Set( m_vector.Get().x ); + m_y.Set( m_vector.Get().y ); + m_z.Set( m_vector.Get().z ); + m_w.Set( m_vector.Get().w ); +} + +void CDmeUnpackVector4Operator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vector.GetAttribute() ); +} + +void CDmeUnpackVector4Operator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); + attrs.AddToTail( m_w.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmeUnpackQAngleOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackQAngleOperator, CDmeUnpackQAngleOperator ); + +void CDmeUnpackQAngleOperator::OnConstruction() +{ + m_qangle.Init( this, "qangle" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); +} + +void CDmeUnpackQAngleOperator::OnDestruction() +{ +} + +bool CDmeUnpackQAngleOperator::IsDirty() +{ + const QAngle &q = m_qangle.Get(); + return q.x != m_x.Get() || q.y != m_y.Get() || q.z != m_z.Get(); +} + +void CDmeUnpackQAngleOperator::Operate() +{ + m_x.Set( m_qangle.Get().x ); + m_y.Set( m_qangle.Get().y ); + m_z.Set( m_qangle.Get().z ); +} + +void CDmeUnpackQAngleOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_qangle.GetAttribute() ); +} + +void CDmeUnpackQAngleOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmeUnpackQuaternionOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackQuaternionOperator, CDmeUnpackQuaternionOperator ); + +void CDmeUnpackQuaternionOperator::OnConstruction() +{ + m_quaternion.Init( this, "quaternion" ); + m_x.Init( this, "x" ); + m_y.Init( this, "y" ); + m_z.Init( this, "z" ); + m_w.Init( this, "w" ); +} + +void CDmeUnpackQuaternionOperator::OnDestruction() +{ +} + +bool CDmeUnpackQuaternionOperator::IsDirty() +{ + const Quaternion &q = m_quaternion.Get(); + return q.x != m_x.Get() || q.y != m_y.Get() || q.z != m_z.Get() || q.w != m_w.Get(); +} + +void CDmeUnpackQuaternionOperator::Operate() +{ + m_x.Set( m_quaternion.Get().x ); + m_y.Set( m_quaternion.Get().y ); + m_z.Set( m_quaternion.Get().z ); + m_w.Set( m_quaternion.Get().w ); +} + +void CDmeUnpackQuaternionOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_quaternion.GetAttribute() ); +} + +void CDmeUnpackQuaternionOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_x.GetAttribute() ); + attrs.AddToTail( m_y.GetAttribute() ); + attrs.AddToTail( m_z.GetAttribute() ); + attrs.AddToTail( m_w.GetAttribute() ); +} + + +//----------------------------------------------------------------------------- +// CDmeUnpackVMatrixOperator +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeUnpackVMatrixOperator, CDmeUnpackVMatrixOperator ); + +void CDmeUnpackVMatrixOperator::OnConstruction() +{ + m_vmatrix.Init( this, "vmatrix" ); + char name[ 4 ]; + for ( uint i = 0; i < 16; ++i ) + { + Q_snprintf( name, sizeof(name), "m%d%d", i >> 2, i & 0x3 ); + m_cells[ i ].Init( this, name ); + } +} + +void CDmeUnpackVMatrixOperator::OnDestruction() +{ +} + +bool CDmeUnpackVMatrixOperator::IsDirty() +{ + const VMatrix &v = m_vmatrix.Get(); + for ( uint i = 0; i < 16; ++i ) + { + if ( *( v[ i ] ) != m_cells[ i ].Get() ) + return true; + } + return false; +} + +void CDmeUnpackVMatrixOperator::Operate() +{ + VMatrix v; + for ( uint i = 0; i < 16; ++i ) + { + m_cells[ i ].Set( *( v[ i ] ) ); + } + m_vmatrix.Set( v ); +} + +void CDmeUnpackVMatrixOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + attrs.AddToTail( m_vmatrix.GetAttribute() ); +} + +void CDmeUnpackVMatrixOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) +{ + for ( uint i = 0; i < 16; ++i ) + { + attrs.AddToTail( m_cells[i].GetAttribute() ); + } +} diff --git a/movieobjects/dmevertexdata.cpp b/movieobjects/dmevertexdata.cpp new file mode 100644 index 0000000..6de6f7a --- /dev/null +++ b/movieobjects/dmevertexdata.cpp @@ -0,0 +1,1023 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmevertexdata.h" +#include "movieobjects_interfaces.h" +#include <limits.h> +#include "tier3/tier3.h" +#include "tier0/dbg.h" +#include "datamodel/dmelementfactoryhelper.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Standard vertex fields +//----------------------------------------------------------------------------- +static char *g_pStandardFieldNames[] = +{ + "positions", + "normals", + "tangents", + "textureCoordinates", + "colors", + "jointWeights", + "jointIndices", + "balance", + "speed", + "wrinkle", + "weight" +}; + +static DmAttributeType_t g_pStandardFieldTypes[] = +{ + AT_VECTOR3_ARRAY, + AT_VECTOR3_ARRAY, + AT_VECTOR4_ARRAY, + AT_VECTOR2_ARRAY, + AT_COLOR_ARRAY, + AT_FLOAT_ARRAY, + AT_INT_ARRAY, + AT_FLOAT_ARRAY, + AT_FLOAT_ARRAY, + AT_FLOAT_ARRAY, + AT_FLOAT_ARRAY +}; + + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeVertexDataBase, CDmeVertexDataBase ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::OnConstruction() +{ + m_nVertexCount = 0; + memset( m_pStandardFieldIndex, 0xFF, sizeof(m_pStandardFieldIndex) ); + m_VertexFormat.Init( this, "vertexFormat" ); + + m_nJointCount.Init( this, "jointCount" ); + m_bFlipVCoordinates.Init( this, "flipVCoordinates" ); +} + +void CDmeVertexDataBase::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Updates info for fast lookups for well-known fields +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::UpdateStandardFieldInfo( int nFieldIndex, const char *pFieldName, DmAttributeType_t attrType ) +{ + COMPILE_TIME_ASSERT( ARRAYSIZE(g_pStandardFieldNames) == STANDARD_FIELD_COUNT ); + COMPILE_TIME_ASSERT( ARRAYSIZE(g_pStandardFieldTypes) == STANDARD_FIELD_COUNT ); + + for ( int i = 0; i < STANDARD_FIELD_COUNT; ++i ) + { + if ( !Q_stricmp( pFieldName, g_pStandardFieldNames[i] ) ) + { + if ( attrType != g_pStandardFieldTypes[i] ) + { + Warning( "Standard field %s has incorrect attribute type!\n", pFieldName ); + return; + } + m_pStandardFieldIndex[i] = nFieldIndex; + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Computes information about how to find particular fields +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::ComputeFieldInfo() +{ + // Clear existing field info, + // but keep the old names around so field indices remain constant + int nCurrentCount = m_FieldInfo.Count(); + for ( int i = 0; i < nCurrentCount; ++i ) + { + m_FieldInfo[i].m_pIndexData = NULL; + m_FieldInfo[i].m_pVertexData = NULL; + } + + // FIXME: Want to maintain field indices as constants for all time + int nFieldCount = m_VertexFormat.Count(); + for ( int i = 0; i < nFieldCount; ++i ) + { + const char *pFieldName = m_VertexFormat[i]; + int nLen = Q_strlen( pFieldName ) + 21; + char *pIndicesName = (char*)_alloca( nLen ); + Q_snprintf( pIndicesName, nLen, "%sIndices", pFieldName ); + + CDmAttribute *pVerticesArray = GetAttribute( pFieldName ); + if ( !pVerticesArray || !IsArrayType( pVerticesArray->GetType() ) ) + continue; + + CDmAttribute *pIndicesArray = NULL; + if ( Q_stricmp( pFieldName, g_pStandardFieldNames[FIELD_JOINT_WEIGHTS] ) && + Q_stricmp( pFieldName, g_pStandardFieldNames[FIELD_JOINT_INDICES] ) ) + { + pIndicesArray = GetAttribute( pIndicesName ); + if ( !pIndicesArray || pIndicesArray->GetType() != AT_INT_ARRAY ) + continue; + } + + FieldIndex_t nFieldIndex = FindFieldIndex( pFieldName ); + if ( nFieldIndex < 0 ) + { + nFieldIndex = m_FieldInfo.AddToTail(); + m_FieldInfo[nFieldIndex].m_Name = pFieldName; + m_FieldInfo[nFieldIndex].m_bInverseMapDirty = true; + UpdateStandardFieldInfo( nFieldIndex, pFieldName, pVerticesArray->GetType() ); + } + m_FieldInfo[nFieldIndex].m_pVertexData = pVerticesArray; + m_FieldInfo[nFieldIndex].m_pIndexData = pIndicesArray; + } +} + + +//----------------------------------------------------------------------------- +// Computes the vertex count ( min of the index buffers ) +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::ComputeVertexCount() +{ + int nCount = m_FieldInfo.Count(); + if ( nCount == 0 ) + { + m_nVertexCount = 0; + return; + } + + m_nVertexCount = INT_MAX; + for ( int i = 0; i < nCount; ++i ) + { + if ( !m_FieldInfo[i].m_pIndexData ) + continue; + + CDmrGenericArray array( m_FieldInfo[i].m_pIndexData ); + int nFieldCount = array.Count(); + if ( nFieldCount < m_nVertexCount ) + { + m_nVertexCount = nFieldCount; + } + } +} + + +//----------------------------------------------------------------------------- +// resolve internal data from changed attributes +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::Resolve() +{ + BaseClass::Resolve(); + + if ( m_VertexFormat.IsDirty() ) + { + ComputeFieldInfo(); + } + + if ( !IsVertexDeltaData() ) + { + ComputeVertexCount(); + } + + // Mark inverse map dirty if necessary + int nCount = m_FieldInfo.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_FieldInfo[i].m_pIndexData && m_FieldInfo[i].m_pIndexData->IsFlagSet( FATTRIB_DIRTY ) ) + { + m_FieldInfo[i].m_bInverseMapDirty = true; + } + } +} + + +//----------------------------------------------------------------------------- +// Returns indices into the various fields +//----------------------------------------------------------------------------- +int CDmeVertexDataBase::GetPositionIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_POSITION ); +} + +int CDmeVertexDataBase::GetNormalIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_NORMAL ); +} + +int CDmeVertexDataBase::GetTangentIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_TANGENT ); +} + +int CDmeVertexDataBase::GetTexCoordIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_TEXCOORD ); +} + +int CDmeVertexDataBase::GetColorIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_COLOR ); +} + +int CDmeVertexDataBase::GetBalanceIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_BALANCE ); +} + +int CDmeVertexDataBase::GetMorphSpeedIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_MORPH_SPEED ); +} + +int CDmeVertexDataBase::GetWrinkleIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_WRINKLE ); +} + +int CDmeVertexDataBase::GetWeightIndex( int nVertexIndex ) const +{ + return GetFieldIndex( nVertexIndex, FIELD_WEIGHT ); +} + + +//----------------------------------------------------------------------------- +// Vertex accessors +//----------------------------------------------------------------------------- +const Vector& CDmeVertexDataBase::GetPosition( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_POSITION]; + if ( nFieldIndex < 0 ) + return vec3_origin; + + CDmrArrayConst<int> indices( GetIndexData( nFieldIndex ) ); + CDmrArrayConst<Vector> vertexData( GetVertexData( nFieldIndex ) ); + return vertexData[ indices[nIndex] ]; +} + +const float *CDmeVertexDataBase::GetJointWeights( int nVertexIndex ) const +{ + Assert( IsVertexDeltaData() || nVertexIndex < m_nVertexCount ); + FieldIndex_t nPosFieldIndex = m_pStandardFieldIndex[FIELD_POSITION]; + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_WEIGHTS]; + if ( nPosFieldIndex < 0 || nFieldIndex < 0 ) + return NULL; + + CDmrArrayConst<int> indices = GetIndexData( nPosFieldIndex ); + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return &vertexData[ indices[ nVertexIndex ] * m_nJointCount ]; +} + + +//----------------------------------------------------------------------------- +// Same as GetJointWeights except it uses a direct position index instead of +// the vertex index to access the data +//----------------------------------------------------------------------------- +const float *CDmeVertexDataBase::GetJointPositionWeights( int nPositionIndex ) const +{ + Assert( !IsVertexDeltaData() ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_WEIGHTS]; + if ( nFieldIndex < 0 ) + return NULL; + + CDmrArrayConst< float > jointWeights = GetVertexData( nFieldIndex ); + Assert( nPositionIndex * m_nJointCount < jointWeights.Count() ); + return &jointWeights[ nPositionIndex * m_nJointCount ]; +} + +const int *CDmeVertexDataBase::GetJointIndices( int nVertexIndex ) const +{ + Assert( IsVertexDeltaData() || nVertexIndex < m_nVertexCount ); + FieldIndex_t nPosFieldIndex = m_pStandardFieldIndex[FIELD_POSITION]; + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_INDICES]; + if ( nPosFieldIndex < 0 || nFieldIndex < 0 ) + return NULL; + + CDmrArrayConst<int> indices = GetIndexData( nPosFieldIndex ); + CDmrArrayConst<int> vertexData = GetVertexData( nFieldIndex ); + return &vertexData[ indices[ nVertexIndex ] * m_nJointCount ]; +} + + +//----------------------------------------------------------------------------- +// Same as GetJointIndices except it uses a direct position index instead of +// the vertex index to access the data +//----------------------------------------------------------------------------- +const int *CDmeVertexDataBase::GetJointPositionIndices( int nPositionIndex ) const +{ + Assert( !IsVertexDeltaData() ); + FieldIndex_t nJointIndicesField = m_pStandardFieldIndex[ FIELD_JOINT_INDICES ]; + if ( nJointIndicesField < 0 ) + return NULL; + + CDmrArrayConst<int> jointIndices = GetVertexData( nJointIndicesField ); + Assert( nPositionIndex * m_nJointCount < jointIndices.Count() ); + return &jointIndices[ nPositionIndex * m_nJointCount ]; +} + +const Vector& CDmeVertexDataBase::GetNormal( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_NORMAL]; + if ( nFieldIndex < 0 ) + return vec3_origin; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<Vector> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + +const Vector4D& CDmeVertexDataBase::GetTangent( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_TANGENT]; + if ( nFieldIndex < 0 ) + return vec4_origin; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<Vector4D> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + +const Vector2D& CDmeVertexDataBase::GetTexCoord( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_TEXCOORD]; + if ( nFieldIndex < 0 ) + return vec2_origin; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<Vector2D> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + +static Color s_Black( 0, 0, 0, 255 ); +const Color& CDmeVertexDataBase::GetColor( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_COLOR]; + if ( nFieldIndex < 0 ) + return s_Black; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<Color> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + +float CDmeVertexDataBase::GetBalance( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_BALANCE]; + if ( nFieldIndex < 0 ) + return 0.5f; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + +float CDmeVertexDataBase::GetMorphSpeed( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_MORPH_SPEED]; + if ( nFieldIndex < 0 ) + return 1.0f; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + +float CDmeVertexDataBase::GetWrinkle( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_WRINKLE]; + if ( nFieldIndex < 0 ) + return 1.0f; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +float CDmeVertexDataBase::GetWeight( int nIndex ) const +{ + Assert( IsVertexDeltaData() || nIndex < m_nVertexCount ); + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_WEIGHT]; + if ( nFieldIndex < 0 ) + return 1.0f; + + CDmrArrayConst<int> indices = GetIndexData( nFieldIndex ); + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData[ indices[ nIndex ] ]; +} + + +//----------------------------------------------------------------------------- +// Adds a field to the vertex format +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::FindOrAddVertexField( const char *pFieldName ) +{ + int i; + int nFormatCount = m_VertexFormat.Count(); + for ( i = 0; i < nFormatCount; ++i ) + { + if ( !Q_stricmp( pFieldName, m_VertexFormat[i] ) ) + return; + } + m_VertexFormat.AddToTail( pFieldName ); +} + + +//----------------------------------------------------------------------------- +// Returns the field index of a particular field +//----------------------------------------------------------------------------- +FieldIndex_t CDmeVertexDataBase::CreateField( const char *pFieldName, DmAttributeType_t type ) +{ + Assert( Q_stricmp( pFieldName, g_pStandardFieldNames[FIELD_JOINT_WEIGHTS] ) ); + Assert( Q_stricmp( pFieldName, g_pStandardFieldNames[FIELD_JOINT_INDICES] ) ); + if ( !Q_stricmp( pFieldName, g_pStandardFieldNames[FIELD_JOINT_WEIGHTS] ) || + !Q_stricmp( pFieldName, g_pStandardFieldNames[FIELD_JOINT_INDICES] ) ) + { + return -1; + } + + AddAttribute( pFieldName, type ); + + int nLen = Q_strlen( pFieldName ) + 21; + char *pIndicesName = (char*)_alloca( nLen ); + Q_snprintf( pIndicesName, nLen, "%sIndices", pFieldName ); + AddAttribute( pIndicesName, AT_INT_ARRAY ); + + FindOrAddVertexField( pFieldName ); + + // FIXME: Not hugely efficient, is there a better way of doing this? + // Necessary to return a field index for the name + ComputeFieldInfo(); + FieldIndex_t nFieldIndex = FindFieldIndex( pFieldName ); + if ( !IsVertexDeltaData() && m_nVertexCount > 0 ) + { + CDmrArray<int> indices( GetIndexData( nFieldIndex ) ); + indices.EnsureCount( m_nVertexCount ); + } + return nFieldIndex; +} + + +//----------------------------------------------------------------------------- +// Creates a field given a file ID +//----------------------------------------------------------------------------- +FieldIndex_t CDmeVertexDataBase::CreateField( StandardFields_t fieldId ) +{ + return CreateField( g_pStandardFieldNames[fieldId], g_pStandardFieldTypes[fieldId] ); +} + + +//----------------------------------------------------------------------------- +// Use this to create vertex fields for joint weights + indices +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::CreateJointWeightsAndIndices( int nJointCount, FieldIndex_t *pJointWeightsField, FieldIndex_t *pJointIndicesField ) +{ + m_nJointCount = nJointCount; + + AddAttribute( g_pStandardFieldNames[FIELD_JOINT_WEIGHTS], AT_FLOAT_ARRAY ); + AddAttribute( g_pStandardFieldNames[FIELD_JOINT_INDICES], AT_INT_ARRAY ); + + FindOrAddVertexField( g_pStandardFieldNames[FIELD_JOINT_WEIGHTS] ); + FindOrAddVertexField( g_pStandardFieldNames[FIELD_JOINT_INDICES] ); + + + // FIXME: Not hugely efficient, is there a better way of doing this? + // Necessary to return a field index for the name + ComputeFieldInfo(); + *pJointWeightsField = FindFieldIndex( g_pStandardFieldNames[FIELD_JOINT_WEIGHTS] ); + *pJointIndicesField = FindFieldIndex( g_pStandardFieldNames[FIELD_JOINT_INDICES] ); +} + + +//----------------------------------------------------------------------------- +// Adds a new vertex; creates a new entry in all vertex data fields +// Returns the vertex index +//----------------------------------------------------------------------------- +int CDmeVertexDataBase::AddVertexData( FieldIndex_t nFieldIndex, int nCount ) +{ + CDmrGenericArray array( m_FieldInfo[nFieldIndex].m_pVertexData ); + int nDataCount = array.Count(); + array.EnsureCount( nDataCount + nCount ); + + // DmeMeshDeltaData must have the same number of vertices + indices + if ( IsVertexDeltaData() ) + { + CDmrArray<int> indices( GetIndexData( nFieldIndex ) ); + Assert( nDataCount == indices.Count() ); + indices.EnsureCount( nDataCount + nCount ); + } + + return nDataCount; +} + + +//----------------------------------------------------------------------------- +// Sets vertex data +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::SetVertexData( FieldIndex_t nFieldIndex, int nFirstVertex, int nCount, DmAttributeType_t valueType, const void *pData ) +{ + CDmrGenericArray array( m_FieldInfo[nFieldIndex].m_pVertexData ); + Assert( nFirstVertex + nCount <= array.Count() ); + array.SetMultiple( nFirstVertex, nCount, valueType, pData ); +} + +void CDmeVertexDataBase::SetVertexIndices( FieldIndex_t nFieldIndex, int nFirstIndex, int nCount, const int *pIndices ) +{ + CDmrArray<int> array( GetIndexData( nFieldIndex ) ); + Assert( nFirstIndex + nCount <= array.Count() ); + array.SetMultiple( nFirstIndex, nCount, pIndices ); +} + + +//----------------------------------------------------------------------------- +// Removes all vertex data associated with a particular field +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::RemoveAllVertexData( FieldIndex_t nFieldIndex ) +{ + CDmrGenericArray array( m_FieldInfo[nFieldIndex].m_pVertexData ); + array.RemoveAll(); + if ( IsVertexDeltaData() ) + { + CDmrArray<int> arrayDelta( m_FieldInfo[nFieldIndex].m_pIndexData ); + arrayDelta.RemoveAll(); + } +} + + +//----------------------------------------------------------------------------- +// Returns the field index of a particular field +//----------------------------------------------------------------------------- +FieldIndex_t CDmeVertexDataBase::FindFieldIndex( const char *pFieldName ) const +{ + int nCount = m_FieldInfo.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( m_FieldInfo[i].m_Name, pFieldName ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Returns well-known vertex data +//----------------------------------------------------------------------------- +static CUtlVector<Vector4D> s_EmptyVector4D; +static CUtlVector<Vector> s_EmptyVector; +static CUtlVector<Vector2D> s_EmptyVector2D; +static CUtlVector<Color> s_EmptyColor; +static CUtlVector<float> s_EmptyFloat; + +const CUtlVector<Vector> &CDmeVertexDataBase::GetPositionData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_POSITION ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyVector; + + CDmrArrayConst<Vector> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<Vector> &CDmeVertexDataBase::GetNormalData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_NORMAL ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyVector; + + CDmrArrayConst<Vector> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<Vector4D> &CDmeVertexDataBase::GetTangentData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_TANGENT ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyVector4D; + + CDmrArrayConst<Vector4D> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<Vector2D> &CDmeVertexDataBase::GetTextureCoordData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_TEXCOORD ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyVector2D; + + CDmrArrayConst<Vector2D> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<Color> &CDmeVertexDataBase::GetColorData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_COLOR ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyColor; + + CDmrArrayConst<Color> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const float *CDmeVertexDataBase::GetJointWeightData( int nDataIndex ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_WEIGHTS]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return NULL; + + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return &vertexData[ nDataIndex * m_nJointCount ]; +} + +const int *CDmeVertexDataBase::GetJointIndexData( int nDataIndex ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_INDICES]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return NULL; + + CDmrArrayConst<int> vertexData = GetVertexData( nFieldIndex ); + return &vertexData.Element( nDataIndex * m_nJointCount ); +} + +const CUtlVector<float> &CDmeVertexDataBase::GetBalanceData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_BALANCE ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyFloat; + + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<float> &CDmeVertexDataBase::GetMorphSpeedData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_MORPH_SPEED ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyFloat; + + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<float> &CDmeVertexDataBase::GetWrinkleData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_WRINKLE ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyFloat; + + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + +const CUtlVector<float> &CDmeVertexDataBase::GetWeightData( ) const +{ + FieldIndex_t nFieldIndex = m_pStandardFieldIndex[ FIELD_WEIGHT ]; + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyFloat; + + CDmrArrayConst<float> vertexData = GetVertexData( nFieldIndex ); + return vertexData.Get(); +} + + +//----------------------------------------------------------------------------- +// Returns well-known index data +//----------------------------------------------------------------------------- +static CUtlVector<int> s_EmptyInt; +const CUtlVector<int> &CDmeVertexDataBase::GetVertexIndexData( FieldIndex_t nFieldIndex ) const +{ + if ( nFieldIndex < 0 || nFieldIndex >= m_FieldInfo.Count() ) + return s_EmptyInt; + + CDmrArrayConst<int> indexData = GetIndexData( nFieldIndex ); + return indexData.Get(); +} + +const CUtlVector<int> &CDmeVertexDataBase::GetVertexIndexData( StandardFields_t fieldId ) const +{ + return GetVertexIndexData( m_pStandardFieldIndex[fieldId] ); +} + + +//----------------------------------------------------------------------------- +// Returns an inverse map from vertex data index to vertex index +//----------------------------------------------------------------------------- +const CUtlVector< int > &CDmeVertexDataBase::FindVertexIndicesFromDataIndex( FieldIndex_t nFieldIndex, int nDataIndex ) +{ + if ( nFieldIndex < 0 ) + return s_EmptyInt; + + FieldInfo_t &info = m_FieldInfo[nFieldIndex]; + if ( info.m_bInverseMapDirty ) + { + CDmrArrayConst<int> array( info.m_pIndexData ); + CDmrGenericArray vertexArray( info.m_pVertexData ); + + int nDataCount = vertexArray.Count(); + int nCount = array.Count(); + + // Clear out the utlvectors + info.m_InverseMap.RemoveAll(); + info.m_InverseMap.SetCount( nDataCount ); + + for ( int i = 0; i < nCount; ++i ) + { + int nIndex = array[ i ]; + info.m_InverseMap[nIndex].AddToTail( i ); + } + info.m_bInverseMapDirty = false; + } + + return info.m_InverseMap[ nDataIndex ]; +} + +const CUtlVector< int > &CDmeVertexDataBase::FindVertexIndicesFromDataIndex( StandardFields_t fieldId, int nDataIndex ) +{ + // NOTE! Wrinkles don't exist in the base state, therefore we use the index to index + // into the TEXCOORD base state fields instead of the wrinkle fields + if ( fieldId == FIELD_WRINKLE ) + { + fieldId = FIELD_TEXCOORD; + } + + return FindVertexIndicesFromDataIndex( m_pStandardFieldIndex[fieldId], nDataIndex ); +} + + +//----------------------------------------------------------------------------- +// Do we have skinning data? +//----------------------------------------------------------------------------- +bool CDmeVertexDataBase::HasSkinningData() const +{ + if ( m_nJointCount == 0 ) + return false; + FieldIndex_t nWeightFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_WEIGHTS]; + if ( nWeightFieldIndex < 0 ) + return false; + FieldIndex_t nIndexFieldIndex = m_pStandardFieldIndex[FIELD_JOINT_INDICES]; + if ( nIndexFieldIndex < 0 ) + return false; + + CDmrArrayConst<float> weightData = GetVertexData( nWeightFieldIndex ); + CDmrArrayConst<int> indexData = GetVertexData( nIndexFieldIndex ); + return ( weightData.Count() > 0 && indexData.Count() > 0 ); +} + + +//----------------------------------------------------------------------------- +// Do we need tangent data? (Utility method for applications to know if they should call ComputeDefaultTangentData) +//----------------------------------------------------------------------------- +bool CDmeVertexDataBase::NeedsTangentData() const +{ + FieldIndex_t posField = m_pStandardFieldIndex[CDmeVertexDataBase::FIELD_POSITION]; + FieldIndex_t normalField = m_pStandardFieldIndex[CDmeVertexDataBase::FIELD_NORMAL]; + FieldIndex_t uvField = m_pStandardFieldIndex[CDmeVertexDataBase::FIELD_TEXCOORD]; + FieldIndex_t tangentField = m_pStandardFieldIndex[CDmeVertexDataBase::FIELD_TANGENT]; + return ( posField >= 0 && uvField >= 0 && normalField >= 0 && tangentField < 0 ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmeVertexDataBase::FieldCount() const +{ + return m_VertexFormat.Count(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDmeVertexDataBase::FieldName( int i ) const +{ + if ( i < 0 || i >= m_VertexFormat.Count() ) + return NULL; + + return m_VertexFormat[ i ]; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::CopyFrom( CDmeVertexDataBase *pSrc ) +{ + pSrc->CopyTo( this ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmeVertexDataBase::CopyTo( CDmeVertexDataBase *pDst ) const +{ + // Preserve the name of the destination + const CUtlString dstName = pDst->GetName(); + + CopyAttributesTo( pDst ); + + pDst->SetName( dstName ); // The Copy really copies everything! + pDst->Resolve(); +} + + +//----------------------------------------------------------------------------- +// Expose this class to the scene database +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmeVertexData, CDmeVertexData ); +IMPLEMENT_ELEMENT_FACTORY( DmeVertexDeltaData, CDmeVertexDeltaData ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDmeVertexData::OnConstruction() +{ +} + +void CDmeVertexData::OnDestruction() +{ +} + +void CDmeVertexDeltaData::OnConstruction() +{ + m_bCorrected.InitAndSet( this, "corrected", false ); +} + +void CDmeVertexDeltaData::OnDestruction() +{ +} + + +//----------------------------------------------------------------------------- +// Method to add vertex indices for normal vertex data +//----------------------------------------------------------------------------- +int CDmeVertexData::AddVertexIndices( int nIndexCount ) +{ + int nFirstVertex = m_nVertexCount; + m_nVertexCount += nIndexCount; + int nCount = m_FieldInfo.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( m_FieldInfo[i].m_pIndexData ) + { + CDmrArray<int> indices( m_FieldInfo[i].m_pIndexData ); + indices.EnsureCount( m_nVertexCount ); + } + } + return nFirstVertex; +} + + +//----------------------------------------------------------------------------- +// Computes max positional delta length +//----------------------------------------------------------------------------- +float CDmeVertexDeltaData::ComputeMaxDeflection( ) +{ + float flMaxDeflection = 0.0f; + + const CUtlVector<Vector> &pos = GetPositionData(); + int nCount = pos.Count(); + for ( int i = 0; i < nCount; ++i ) + { + float flDeflection = pos[i].Length(); + if ( flMaxDeflection < flDeflection ) + { + flMaxDeflection = flDeflection; + } + } + return flMaxDeflection; +} + + +//----------------------------------------------------------------------------- +// Computes wrinkle data from position deltas +//----------------------------------------------------------------------------- +void CDmeVertexDeltaData::GenerateWrinkleDelta( CDmeVertexData *pBindState, float flScale, bool bOverwrite ) +{ + FieldIndex_t nPosIndex = FindFieldIndex( FIELD_POSITION ); + if ( nPosIndex < 0 ) + return; + + FieldIndex_t nBaseTexCoordIndex = pBindState->FindFieldIndex( FIELD_TEXCOORD ); + if ( nBaseTexCoordIndex < 0 ) + return; + + FieldIndex_t nWrinkleIndex = FindFieldIndex( FIELD_WRINKLE ); + if ( nWrinkleIndex < 0 ) + { + nWrinkleIndex = CreateField( FIELD_WRINKLE ); + } + else if ( !bOverwrite ) + return; + + RemoveAllVertexData( nWrinkleIndex ); + if ( flScale == 0.0f ) + return; + + const float flMaxDeflection( ComputeMaxDeflection() ); + if ( flMaxDeflection == 0.0f ) + return; + + const double scaledInverseMaxDeflection = static_cast< double >( flScale ) / static_cast< double >( flMaxDeflection ); + + const CUtlVector<int> &positionIndices = GetVertexIndexData( nPosIndex ); + const CUtlVector<Vector> &pos = GetPositionData(); + const CUtlVector<int> &baseTexCoordIndices = pBindState->GetVertexIndexData( nBaseTexCoordIndex ); + + CDmrArrayConst<Vector2D> texData( pBindState->GetVertexData( nBaseTexCoordIndex ) ); + int nBaseTexCoordCount = texData.Count(); + int nBufSize = ( ( nBaseTexCoordCount + 7 ) >> 3 ); + unsigned char *pUsedBits = (unsigned char*)_alloca( nBufSize ); + memset( pUsedBits, 0, nBufSize ); + + int nCount = pos.Count(); + for ( int i = 0; i < nCount; ++i ) + { + float flWrinkleDelta = static_cast< float >( static_cast< double >( pos[i].Length() ) * scaledInverseMaxDeflection ); + Assert( fabs( flWrinkleDelta ) <= fabs( flScale ) ); + + // NOTE: This will produce bad behavior in cases where two positions share the + // same texcoord, which shouldn't theoretically happen. + const CUtlVector< int > &baseVerts = pBindState->FindVertexIndicesFromDataIndex( FIELD_POSITION, positionIndices[i] ); + int nBaseVertCount = baseVerts.Count(); + for ( int j = 0; j < nBaseVertCount; ++j ) + { + // See if we have a delta for this texcoord... + int nTexCoordIndex = baseTexCoordIndices[ baseVerts[j] ]; + if ( pUsedBits[ nTexCoordIndex >> 3 ] & ( 1 << ( nTexCoordIndex & 0x7 ) ) ) + continue; + + pUsedBits[ nTexCoordIndex >> 3 ] |= 1 << ( nTexCoordIndex & 0x7 ); + + int nDeltaIndex = AddVertexData( nWrinkleIndex, 1 ); + SetVertexIndices( nWrinkleIndex, nDeltaIndex, 1, &nTexCoordIndex ); + SetVertexData( nWrinkleIndex, nDeltaIndex, 1, AT_FLOAT, &flWrinkleDelta ); + } + } +} + +//----------------------------------------------------------------------------- +// Computes weight data from position deltas +//----------------------------------------------------------------------------- +float CDmeVertexDeltaData::GenerateWeightDelta( CDmeVertexData *pBindState ) +{ + FieldIndex_t nPosIndex = FindFieldIndex( FIELD_POSITION ); + if ( nPosIndex < 0 ) + return 0.0; + + FieldIndex_t nFieldIndex = FindFieldIndex( FIELD_WEIGHT ); + if ( nFieldIndex < 0 ) + { + nFieldIndex = CreateField( FIELD_WEIGHT ); + } + + RemoveAllVertexData( nFieldIndex ); + + const float maxDeflection( static_cast< double >( ComputeMaxDeflection() ) ); + + if ( maxDeflection == 0.0 ) + return maxDeflection; + + const CUtlVector<Vector> &pos( GetPositionData() ); + const CUtlVector< int > &posIndices( GetVertexIndexData( nPosIndex ) ); + + float flDeltaDistance; + int nDeltaIndex; + const int nCount = pos.Count(); + for ( int i = 0; i < nCount; ++i ) + { + flDeltaDistance = pos[ i ].Length(); + + nDeltaIndex = AddVertexData( nFieldIndex, 1 ); + SetVertexData( nFieldIndex, nDeltaIndex, 1, AT_FLOAT, &flDeltaDistance ); + } + + SetVertexIndices( nFieldIndex, 0, posIndices.Count(), posIndices.Base() ); + + return maxDeflection; +}
\ No newline at end of file diff --git a/movieobjects/dmmeshcomp.cpp b/movieobjects/dmmeshcomp.cpp new file mode 100644 index 0000000..d2aab1c --- /dev/null +++ b/movieobjects/dmmeshcomp.cpp @@ -0,0 +1,480 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Implementation of CDmMeshComp - CDmeMesh computation class +// +//============================================================================= + + +// Valve includes +#include "movieobjects/dmmeshcomp.h" +#include "movieobjects/dmefaceset.h" +#include "movieobjects/dmemesh.h" +#include "movieobjects/dmevertexdata.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//============================================================================= +// +//============================================================================= +CDmMeshComp::CDmMeshComp( CDmeMesh *pMesh, CDmeVertexData *pPassedBase ) +: m_pMesh( pMesh ) +, m_pBase( NULL ) +{ + m_pBase = pPassedBase ? pPassedBase : pMesh->GetCurrentBaseState(); + if ( !m_pBase ) + return; + + const FieldIndex_t pIndex( m_pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); + if ( pIndex < 0 ) + return; + + const CUtlVector< Vector > &pPositionData( m_pBase->GetPositionData() ); + const CUtlVector<int> &pPositionIndices( m_pBase->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ) ); + + const int nVertices( pPositionData.Count() ); + if ( nVertices <= 0 ) + return; + + // Create vertices + // TODO: check for duplicates in pPositionData - that would break this algorithm + m_verts.EnsureCapacity( nVertices ); + for ( int i = 0; i < nVertices; ++i ) + { + const CUtlVector< int > &vertexIndices = m_pBase->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, i ); + m_verts.AddToTail( new CVert( i, &vertexIndices, &pPositionData[ i ] ) ); + } + + // Create edges and faces + const int nFaceSets( pMesh->FaceSetCount() ); + for ( int i = 0; i < nFaceSets; ++i ) + { + CDmeFaceSet *pFaceSet( pMesh->GetFaceSet( i ) ); + const int nIndices( pFaceSet->NumIndices() ); + if ( nIndices < 4 ) // At least a triangle and a -1 + continue; + + m_faces.EnsureCapacity( m_faces.Count() + nIndices / 4 ); // # new faces <= nIndices/4 (tri + -1) + m_edges.EnsureCapacity( m_edges.Count() + nIndices / 2 ); // # new edges <= 2*new faces + + int facePosIndex( -1 ); + int edgePosIndex0( -1 ); + int edgePosIndex1( -1 ); + + CUtlVector< CVert * > verts; + CUtlVector< CEdge * > edges; + CUtlVector< bool > edgeReverseMap; + bool bReverse = false; + + for ( int j( 0 ); j < nIndices; ++j ) + { + const int faceVertexIndex( pFaceSet->GetIndex( j ) ); + + if ( faceVertexIndex < 0 ) + { + // End of face + edgePosIndex0 = edgePosIndex1; + edgePosIndex1 = facePosIndex; + + Assert( edgePosIndex0 >= 0 ); + Assert( edgePosIndex1 >= 0 ); + + edges.AddToTail( FindOrCreateEdge( edgePosIndex0, edgePosIndex1, &bReverse ) ); + edgeReverseMap.AddToTail( bReverse ); + + CreateFace( verts, edges, edgeReverseMap ); + + facePosIndex = -1; + verts.RemoveAll(); + edges.RemoveAll(); + edgeReverseMap.RemoveAll(); + continue; + } + + if ( facePosIndex < 0 ) + { + // First vertex + facePosIndex = pPositionIndices[ faceVertexIndex ]; + edgePosIndex1 = facePosIndex; + verts.AddToTail( m_verts[ edgePosIndex1 ] ); + continue; + } + + // 2nd through last vertex + edgePosIndex0 = edgePosIndex1; + edgePosIndex1 = pPositionIndices[ faceVertexIndex ]; + verts.AddToTail( m_verts[ edgePosIndex1 ] ); + + Assert( edgePosIndex0 >= 0 ); + Assert( edgePosIndex1 >= 0 ); + + edges.AddToTail( FindOrCreateEdge( edgePosIndex0, edgePosIndex1, &bReverse ) ); + edgeReverseMap.AddToTail( bReverse ); + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshComp::~CDmMeshComp() +{ + m_verts.PurgeAndDeleteElements(); + m_edges.PurgeAndDeleteElements(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshComp::CVert::CVert( int nPositionIndex, const CUtlVector< int > *pVertexIndices, const Vector *pPosition ) +: m_positionIndex( nPositionIndex ) +, m_pVertexIndices( pVertexIndices ) +, m_pPosition( pPosition ) +, m_edges( 8, 8 ) +{ +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshComp::CVert::CVert( const CVert &src ) +: m_positionIndex( src.m_positionIndex ) +, m_pVertexIndices( src.m_pVertexIndices ) +, m_pPosition( src.m_pPosition ) +, m_edges( 8, 8 ) +{ + m_edges.AddMultipleToTail( src.m_edges.Count(), src.m_edges.Base() ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmMeshComp::CVert::PositionIndex() const +{ + return m_positionIndex; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const Vector *CDmMeshComp::CVert::Position() const +{ + return m_pPosition; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const CUtlVector< int > *CDmMeshComp::CVert::VertexIndices() const +{ + return m_pVertexIndices; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshComp::CVert::operator==( const CVert &rhs ) const +{ + return ( m_pPosition->DistToSqr( *rhs.m_pPosition ) < FLT_EPSILON ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshComp::CEdge::CEdge() +: m_pVert0( NULL ) +, m_pVert1( NULL ) +, m_faceCount( 0 ) +{ +} + + +//----------------------------------------------------------------------------- +// Returns the vertex position index given the edge relative vertex index +//----------------------------------------------------------------------------- +int CDmMeshComp::CEdge::GetVertPositionIndex( int edgeRelativeVertexIndex ) const +{ + if ( edgeRelativeVertexIndex == 0 && m_pVert0 ) + return m_pVert0->PositionIndex(); + + if ( edgeRelativeVertexIndex == 1 && m_pVert1 ) + return m_pVert1->PositionIndex(); + + return -1; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshComp::CVert *CDmMeshComp::CEdge::GetVert( int edgeRelativeVertexIndex ) const +{ + if ( edgeRelativeVertexIndex == 0 ) + return m_pVert0; + + if ( edgeRelativeVertexIndex == 1 ) + return m_pVert1; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Returns true if the edge starts and stops at the same position in space +// The order of the vertices is not checked +//----------------------------------------------------------------------------- +bool CDmMeshComp::CEdge::operator==( const CEdge &rhs ) const +{ + return ( + ( *m_pVert0 == *rhs.m_pVert0 && *m_pVert1 == *rhs.m_pVert1 ) || + ( *m_pVert0 == *rhs.m_pVert1 && *m_pVert1 == *rhs.m_pVert0 ) ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +Vector CDmMeshComp::CEdge::EdgeVector() const +{ + if ( m_pVert0 && m_pVert1 ) + return *m_pVert1->Position() - *m_pVert0->Position(); + + return vec3_origin; +} + + +//----------------------------------------------------------------------------- +// Finds or Creates an edge... Can still return NULL if vertices do not exist +//----------------------------------------------------------------------------- +CDmMeshComp::CEdge *CDmMeshComp::FindOrCreateEdge( int vIndex0, int vIndex1, bool *pReverse /* = NULL */ ) +{ + CEdge *pEdge = FindEdge( vIndex0, vIndex1, pReverse ); + if ( pEdge ) + return pEdge; + + CVert *pVert0 = m_verts[ vIndex0 ]; + if ( pVert0 == NULL ) + return NULL; + + CVert *pVert1 = m_verts[ vIndex1 ]; + if ( pVert1 == NULL ) + return NULL; + + pEdge = m_edges[ m_edges.AddToTail( new CEdge() ) ]; + pEdge->m_pVert0 = pVert0; + pEdge->m_pVert1 = pVert1; + pVert0->m_edges.AddToTail( pEdge ); + if ( vIndex0 != vIndex1 ) + pVert1->m_edges.AddToTail( pEdge ); + + if ( pReverse ) + { + *pReverse = false; + } + + return pEdge; +} + + +//----------------------------------------------------------------------------- +// Returns the edge between vIndex0 & vIndex1 (or vice versa), NULL if not found +//----------------------------------------------------------------------------- +CDmMeshComp::CEdge *CDmMeshComp::FindEdge( int vIndex0, int vIndex1, bool *pReverse /* = NULL */ ) +{ + CUtlVector< CEdge * > &edges = m_verts[ vIndex0 ]->m_edges; + for ( int i = 0; i < edges.Count(); i++ ) + { + CEdge *e = edges[ i ]; + + if ( e->GetVertPositionIndex( 0 ) == vIndex0 && e->GetVertPositionIndex( 1 ) == vIndex1 ) + { + if ( pReverse ) + { + *pReverse = false; + } + return e; + } + + if ( e->GetVertPositionIndex( 1 ) == vIndex0 && e->GetVertPositionIndex( 0 ) == vIndex1 ) + { + if ( pReverse ) + { + *pReverse = true; + } + return e; + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshComp::CFace *CDmMeshComp::CreateFace( const CUtlVector< CVert * > &verts, const CUtlVector< CEdge * > &edges, const CUtlVector< bool > &edgeReverseMap ) +{ + CFace *pFace = &m_faces[ m_faces.AddToTail() ]; + + pFace->m_verts.RemoveAll(); + pFace->m_verts.AddVectorToTail( verts ); + + pFace->m_edges.RemoveAll(); + pFace->m_edges.AddVectorToTail( edges ); + + pFace->m_edgeReverseMap.RemoveAll(); + pFace->m_edgeReverseMap.AddVectorToTail( edgeReverseMap ); + + for ( int nEdgeIndex = edges.Count() - 1; nEdgeIndex >= 0; --nEdgeIndex ) + { + edges[ nEdgeIndex ]->m_faceCount += 1; + } + + return pFace; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmMeshComp::FindFacesWithVert( int vIndex, CUtlVector< CFace * > &faces ) +{ + // TODO: optimize this by adding a vector of face pointers to each vertex + faces.RemoveAll(); + + for ( int fi( m_faces.Head() ); fi != m_faces.InvalidIndex(); fi = m_faces.Next( fi ) ) + { + CFace &face( m_faces[ fi ] ); + for ( int i = 0; i < face.m_verts.Count(); ++i ) + { + if ( face.m_verts[ i ]->PositionIndex() == vIndex ) + { + faces.AddToTail( &face ); + break; + } + } + } + + return faces.Count(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmMeshComp::FindNeighbouringVerts( int vIndex, CUtlVector< CVert * > &verts ) +{ + verts.RemoveAll(); + + const CUtlVector< CEdge * > & edges = m_verts[ vIndex ]->m_edges; + + for ( int i = 0; i < edges.Count(); ++i ) + { + CEdge *e = edges[ i ]; + if ( e->GetVertPositionIndex( 0 ) == vIndex ) + { + verts.AddToTail( e->GetVert( 1 ) ); + } + else + { + verts.AddToTail( e->GetVert( 0 ) ); + } + } + + return verts.Count(); +} + + +//----------------------------------------------------------------------------- +// Find all edges that are only used by 1 face +//----------------------------------------------------------------------------- +int CDmMeshComp::GetBorderEdges( CUtlVector< CUtlVector< CEdge * > > &borderEdgesList ) +{ + // TODO: optimize this by stepping from edge to edge to build chains, using CVert::m_edges + int retVal = 0; + + borderEdgesList.RemoveAll(); + + bool connected; + + for ( int ei = 0; ei < m_edges.Count(); ei++ ) + { + CEdge *pEdge = m_edges[ ei ]; + if ( pEdge->IsBorderEdge() ) + { + ++retVal; + connected = false; + + for ( int i = borderEdgesList.Count() - 1; !connected && i >= 0; --i ) + { + CUtlVector< CEdge * > &borderEdges = borderEdgesList[ i ]; + for ( int j = borderEdges.Count() - 1; j >= 0; --j ) + { + if ( borderEdges[ j ]->ConnectedTo( pEdge ) ) + { + borderEdges.AddToTail( pEdge ); + connected = true; + break; + } + } + } + + if ( !connected ) + { + CUtlVector< CEdge * > &borderEdges = borderEdgesList[ borderEdgesList.AddToTail() ]; + borderEdges.AddToTail( pEdge ); + } + } + } + + // Shrink the borderEdgesList to minimum number required + + bool anyConnected = false; + do + { + anyConnected = false; + + for ( int i = borderEdgesList.Count() - 1; i >= 0; --i ) + { + CUtlVector< CEdge * > &srcBorderEdges = borderEdgesList[ i ]; + for ( int j = srcBorderEdges.Count() - 1; j >= 0; --j ) + { + CEdge *pSrcEdge = srcBorderEdges[ j ]; + connected = false; + + for ( int k = 0; !connected && k < i; ++k ) + { + CUtlVector< CEdge * > &dstBorderEdges = borderEdgesList[ k ]; + for ( int l = dstBorderEdges.Count() - 1; l >= 0; --l ) + { + if ( dstBorderEdges[ l ]->ConnectedTo( pSrcEdge ) ) + { + connected = true; + anyConnected = true; + dstBorderEdges.AddToTail( pSrcEdge ); + srcBorderEdges.Remove( j ); + break; + } + } + } + } + + if ( srcBorderEdges.Count() == 0 ) + { + borderEdgesList.Remove( i ); + } + } + } while( anyConnected ); + + return retVal; +} diff --git a/movieobjects/dmmeshutils.cpp b/movieobjects/dmmeshutils.cpp new file mode 100644 index 0000000..9492085 --- /dev/null +++ b/movieobjects/dmmeshutils.cpp @@ -0,0 +1,3313 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Functions which do things to a DmeMesh +// +//============================================================================= + + +// Valve includes +#include "movieobjects/dmeanimationset.h" +#include "movieobjects/dmecombinationoperator.h" +#include "movieobjects/dmemodel.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmemesh.h" +#include "movieobjects/dmefaceset.h" +#include "movieobjects/dmematerial.h" +#include "movieobjects/dmevertexdata.h" +#include "movieobjects/dmmeshcomp.h" // TODO: This has to be included before dmmeshutils.h +#include "movieobjects/dmmeshutils.h" +#include "tier1/utlstack.h" +#include "tier2/p4helpers.h" +#include "tier1/utlstring.h" +#include "tier1/utlstringmap.h" +#include "tier1/utlbuffer.h" +#include "tier1/fmtstr.h" +#include "filesystem.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::RemoveLargeAxisAlignedPlanarFaces( CDmeMesh *pMesh ) +{ + CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); + if ( !pBase ) + return false; + + const int posIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + if ( posIndex < 0 ) + return false; + + const CUtlVector< Vector > &posData( CDmrArrayConst< Vector >( pBase->GetVertexData( posIndex ) ).Get() ); + if ( posData.Count() <= 0 ) + return false; + + const CUtlVector< int > &posIndices( CDmrArrayConst< int >( pBase->GetIndexData( posIndex ) ).Get() ); + if ( posIndices.Count() <= 0 ) + return false; + + bool bMeshChanged = false; + + CUtlVector< int > emptyFaceSets; + + int faceStartIndex = 0; + int faceCurrentIndex = 0; + + int faceVertexCount = 0; + + bool bPlanarX = true; + bool bPlanarY = true; + bool bPlanarZ = true; + + Vector p; + + CUtlVector< int > removeStart; + CUtlVector< int > removeCount; + + const int nFaceSets = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSets; ++i ) + { + CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + const int nFaceIndices = pFaceSet->NumIndices(); + if ( nFaceIndices <= 0 ) + continue; + + faceStartIndex = 0; + + faceCurrentIndex = pFaceSet->GetIndex( 0 ); + if ( faceCurrentIndex < 0 ) + continue; + + faceVertexCount = 0; + + bPlanarX = true; + bPlanarY = true; + bPlanarZ = true; + + removeStart.RemoveAll(); + removeCount.RemoveAll(); + + p = posData[ posIndices[ faceCurrentIndex ] ]; + + for ( int j = 1; j < nFaceIndices; ++j ) + { + faceCurrentIndex = pFaceSet->GetIndex( j ); + + if ( faceCurrentIndex < 0 ) + { + // End of a face + + if ( faceVertexCount > 4 && ( bPlanarX || bPlanarY || bPlanarZ ) ) + { + removeStart.AddToTail( faceStartIndex ); + removeCount.AddToTail( j - faceStartIndex + 1 ); + } + + faceStartIndex = j + 1; + + if ( faceStartIndex < nFaceIndices ) + { + p = posData[ posIndices[ pFaceSet->GetIndex( faceStartIndex ) ] ]; + } + + faceVertexCount = 0; + + bPlanarX = true; + bPlanarY = true; + bPlanarZ = true; + + continue; + } + + Assert( faceCurrentIndex < posIndices.Count() ); + Assert( posIndices[ faceCurrentIndex ] < posData.Count() ); + const Vector &vPos = posData[ posIndices[ faceCurrentIndex ] ]; + + if ( vPos.x != p.x ) + bPlanarX = false; + + if ( vPos.y != p.y ) + bPlanarY = false; + + if ( vPos.z != p.z ) + bPlanarZ = false; + + ++faceVertexCount; + } + + Assert( removeStart.Count() == removeCount.Count() ); + for ( int j = removeStart.Count() - 1; j >= 0; --j ) + { + pFaceSet->RemoveMultiple( removeStart[ j ], removeCount[ j ] ); + bMeshChanged = true; + } + + if ( pFaceSet->GetIndexCount() == 0 ) + { + emptyFaceSets.AddToTail( i ); + } + } + + for ( int i = emptyFaceSets.Count() - 1; i >= 0; --i ) + { + pMesh->RemoveFaceSet( emptyFaceSets[ i ] ); + bMeshChanged = true; + } + + if ( bMeshChanged ) + { + PurgeUnusedData( pMesh ); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::RemoveFacesWithMaterial( CDmeMesh *pMesh, const char *pMaterialName ) +{ + bool bMeshChanged = false; + + CUtlVector< int > emptyFaceSets; + + const int nFaceSets = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSets; ++i ) + { + CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + if ( !Q_strcmp( pFaceSet->GetMaterial()->GetMaterialName(), pMaterialName ) ) + { + emptyFaceSets.AddToTail( i ); + bMeshChanged = true; + } + } + + for ( int i = emptyFaceSets.Count() - 1; i >= 0; --i ) + { + pMesh->RemoveFaceSet( emptyFaceSets[ i ] ); + bMeshChanged = true; + } + + if ( bMeshChanged ) + { + PurgeUnusedData( pMesh ); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::RemoveFacesWithMoreThanNVerts( CDmeMesh *pMesh, const int nVertexCount ) +{ + CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); + if ( !pBase ) + return false; + + bool bMeshChanged = false; + + CUtlVector< int > emptyFaceSets; + + int faceStartIndex = 0; + int faceCurrentIndex = 0; + + int faceVertexCount = 0; + + CUtlVector< int > removeStart; + CUtlVector< int > removeCount; + + const int nFaceSets = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSets; ++i ) + { + CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + const int nFaceIndices = pFaceSet->NumIndices(); + if ( nFaceIndices <= 0 ) + continue; + + faceStartIndex = 0; + + faceCurrentIndex = pFaceSet->GetIndex( 0 ); + if ( faceCurrentIndex < 0 ) + continue; + + faceVertexCount = 0; + + removeStart.RemoveAll(); + removeCount.RemoveAll(); + + for ( int j = 1; j < nFaceIndices; ++j ) + { + faceCurrentIndex = pFaceSet->GetIndex( j ); + + if ( faceCurrentIndex < 0 ) + { + // End of a face + + if ( faceVertexCount > nVertexCount ) + { + removeStart.AddToTail( faceStartIndex ); + removeCount.AddToTail( j - faceStartIndex + 1 ); + } + + faceStartIndex = j + 1; + + faceVertexCount = 0; + + continue; + } + + ++faceVertexCount; + } + + Assert( removeStart.Count() == removeCount.Count() ); + for ( int j = removeStart.Count() - 1; j >= 0; --j ) + { + pFaceSet->RemoveMultiple( removeStart[ j ], removeCount[ j ] ); + bMeshChanged = true; + } + + if ( pFaceSet->GetIndexCount() == 0 ) + { + emptyFaceSets.AddToTail( i ); + } + } + + for ( int i = emptyFaceSets.Count() - 1; i >= 0; --i ) + { + pMesh->RemoveFaceSet( emptyFaceSets[ i ] ); + bMeshChanged = true; + } + + if ( bMeshChanged ) + { + PurgeUnusedData( pMesh ); + return true; + } + + // Nothing remove + return false; +} + + +//----------------------------------------------------------------------------- +// Figures out which vertexIndices are missing +// Returned list will be in sorted order +//----------------------------------------------------------------------------- +void ComputeVertexIndexMap( CDmeMesh *pMesh, int nMaxVertexCount, CUtlVector< int > &vertexIndexMap ) +{ + bool *pVertexFound = reinterpret_cast< bool * >( alloca( nMaxVertexCount * sizeof( bool ) ) ); + memset( pVertexFound, 0, nMaxVertexCount * sizeof( bool ) ); + + // Loop through all the face sets to find out the highest vertex index + const int nFaceSetCount = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSetCount; ++i ) + { + const CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + const int nFaceSetIndices = pFaceSet->NumIndices(); + for ( int j = 0; j < nFaceSetIndices; ++j ) + { + const int &nIndex = pFaceSet->GetIndex( j ); + if ( nIndex >= 0 ) + { + Assert( nIndex < nMaxVertexCount ); + pVertexFound[ nIndex ] = true; + } + } + } + + int nMissingCount = 0; + for ( int i = 0; i < nMaxVertexCount; ++i ) + { + if ( !pVertexFound[ i ] ) + { + ++nMissingCount; + } + } + + vertexIndexMap.SetSize( nMaxVertexCount ); + for ( int i = 0; i < nMaxVertexCount; ++i ) + { + vertexIndexMap[ i ] = i; + } + + for ( int i = nMaxVertexCount - 1; i >= 0; --i ) + { + if ( !pVertexFound[ i ] ) + { + vertexIndexMap.Remove( i ); + } + } + + // Build up the reverse map + int *pReverseVertexIndexMap = reinterpret_cast< int * >( alloca( nMaxVertexCount * sizeof( int ) ) ); + for ( int i = 0; i < nFaceSetCount; ++i ) + { + pReverseVertexIndexMap[ i ] = -1; + } + + for ( int i = vertexIndexMap.Count() - 1; i >= 0; --i ) + { + pReverseVertexIndexMap[ vertexIndexMap[ i ] ] = i; + } + + // Fix up the face set indices to compensate for the ones which are going to be removed + for ( int i = 0; i < nFaceSetCount; ++i ) + { + CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + const int nFaceSetIndices = pFaceSet->NumIndices(); + for ( int j = 0; j < nFaceSetIndices; ++j ) + { + const int &nIndex = pFaceSet->GetIndex( j ); + if ( nIndex >= 0 ) + { + Assert( pReverseVertexIndexMap[ nIndex ] >= 0 ); + pFaceSet->SetIndex( j, pReverseVertexIndexMap[ nIndex ] ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Returns the highest vertex index used by the face sets of the mesh + 1 +//----------------------------------------------------------------------------- +int GetMaxVertexCount( const CDmeMesh *pMesh ) +{ + int nMaxVertexIndex = 0; + + // Loop through all the face sets to find out the highest vertex index + const int nFaceSetCount = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSetCount; ++i ) + { + const CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + const int nFaceSetIndices = pFaceSet->NumIndices(); + for ( int j = 0; j < nFaceSetIndices; ++j ) + { + const int &nIndex = pFaceSet->GetIndex( j ); + + if ( nIndex > nMaxVertexIndex ) + { + nMaxVertexIndex = nIndex; + } + } + } + + return nMaxVertexIndex + 1; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void RemapData( + CDmrArray< T_t > data, + const CUtlVector< int > &newToOldMap ) +{ + const int nNewToOldMapCount = newToOldMap.Count(); + + T_t *pNewData = reinterpret_cast< T_t * >( alloca( nNewToOldMapCount * sizeof( T_t ) ) ); + for ( int i = 0; i < nNewToOldMapCount; ++i ) + { + pNewData[ i ] = data.Get( newToOldMap[ i ] ); + } + + data.RemoveMultiple( nNewToOldMapCount, data.Count() - nNewToOldMapCount ); + data.SetMultiple( 0, nNewToOldMapCount, pNewData ); +} + + +//----------------------------------------------------------------------------- +// Computes the map of new data indices to old data indices +//----------------------------------------------------------------------------- +void RemoveUnusedData( + CDmeMesh *pMesh, + CDmeVertexData *pVertexData, + bool bBind, + const char *pFieldName, + int *pIndices, + int nIndicesCount, + CDmrGenericArray &data ) +{ + const int nDataCount = data.Count(); + + bool *pDataIndexFound = reinterpret_cast< bool * >( alloca( nDataCount * sizeof( bool ) ) ); + memset( pDataIndexFound, 0, nDataCount * sizeof( bool ) ); + + // Figure out which data is used + for ( int i = 0; i < nIndicesCount; ++i ) + { + Assert( pIndices[ i ] >= 0 && pIndices[ i ] < nDataCount ); + pDataIndexFound[ pIndices[ i ] ] = true; + } + + int nMissingCount = 0; + for ( int i = 0; i < nDataCount; ++i ) + { + if ( !pDataIndexFound[ i ] ) + { + ++nMissingCount; + } + } + + // Compute the New to Old data map + CUtlVector< int > newToOldDataMap; + newToOldDataMap.SetSize( nDataCount ); + for ( int i = 0; i < nDataCount; ++i ) + { + newToOldDataMap[ i ] = i; + } + + for ( int i = nDataCount - 1; i >= 0; --i ) + { + if ( !pDataIndexFound[ i ] ) + { + newToOldDataMap.Remove( i ); + } + } + + // Fix up the data + CDmAttribute *pDataAttr = data.GetAttribute(); + const DmAttributeType_t dataAttrType = pDataAttr->GetType(); + switch ( dataAttrType ) + { + case AT_FLOAT_ARRAY: + RemapData( CDmrArray< float >( pDataAttr ), newToOldDataMap ); + break; + case AT_VECTOR2_ARRAY: + RemapData( CDmrArray< Vector2D >( pDataAttr ), newToOldDataMap ); + break; + case AT_VECTOR3_ARRAY: + RemapData( CDmrArray< Vector >( pDataAttr ), newToOldDataMap ); + break; + case AT_VECTOR4_ARRAY: + RemapData( CDmrArray< Vector4D >( pDataAttr ), newToOldDataMap ); + break; + case AT_QUATERNION_ARRAY: + RemapData( CDmrArray< Quaternion >( pDataAttr ), newToOldDataMap ); + break; + case AT_COLOR_ARRAY: + RemapData( CDmrArray< Color >( pDataAttr ), newToOldDataMap ); + break; + default: + Assert( 0 ); + break; + } + + // Compute Old To New Data Map + int *pOldToNewDataMap = reinterpret_cast< int * >( alloca( nDataCount * sizeof( int ) ) ); + for ( int i = 0; i < nDataCount; ++i ) + { + pOldToNewDataMap[ i ] = -1; + } + + for ( int i = newToOldDataMap.Count() - 1; i >= 0; --i ) + { + pOldToNewDataMap[ newToOldDataMap[ i ] ] = i; + } + + // Fix up the indices + for ( int i = 0; i < nIndicesCount; ++i ) + { + pIndices[ i ] = pOldToNewDataMap[ pIndices[ i ] ]; + } + + // TODO: Fix up "jointWeight & "jointIndices" if this is "position" + if ( !Q_strcmp( pFieldName, "position" ) ) + { + const int nFields = pVertexData->FieldCount(); + for ( int i = 0; i < nFields; ++i ) + { + + } + } + + // If this is the bind state then fix up any delta states + if ( !bBind ) + return; + + // Fix up any Delta states + const int nDeltaStateCount = pMesh->DeltaStateCount(); + for ( int i = 0; i < nDeltaStateCount; ++i ) + { + CDmeVertexDeltaData *pDelta = pMesh->GetDeltaState( i ); + const int nDeltaFieldCount = pDelta->FieldCount(); + for ( int j = 0; j < nDeltaFieldCount; ++j ) + { + if ( !Q_strcmp( pFieldName, pDelta->FieldName( j ) ) ) + { + CDmrArray< int > deltaIndices = pDelta->GetIndexData( j ); + CDmrGenericArray deltaData = pDelta->GetVertexData( j ); + Assert( deltaIndices.Count() == deltaData.Count() ); + + for ( int k = deltaIndices.Count() - 1; k >= 0; --k ) + { + const int oldIndex = deltaIndices.Get( k ); + const int &newIndex = pOldToNewDataMap[ oldIndex ]; + if ( newIndex < 0 ) + { + deltaIndices.Remove( k ); + deltaData.Remove( k ); + } + else if ( newIndex != oldIndex ) + { + deltaIndices.Set( k, newIndex ); + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void RemoveUnusedVerticesFromBaseState( + CDmeMesh *pMesh, + CDmeVertexData *pVertexData, + const CUtlVector< int > &newToOldIndexMap ) +{ + const int nNewToOldIndexMapCount = newToOldIndexMap.Count(); + int *pNewVertexIndices = reinterpret_cast< int * >( alloca( nNewToOldIndexMapCount * sizeof( int ) ) ); + + // See if this is the bind state for the mesh + const bool bBind = !Q_strcmp( pVertexData->GetName(), "bind" ); + + const int nFieldCount = pVertexData->FieldCount(); + for ( int i = 0; i < nFieldCount; ++i ) + { + const char *pFieldName = pVertexData->FieldName( i ); + // TODO: Checking by name is lame... should be a lookup to map fieldIndex to a standard field index + if ( !Q_strcmp( pFieldName, "jointWeights" ) || !Q_strcmp( pFieldName, "jointIndices" ) ) + { + // TODO: Handle when positions are Remapped + continue; + } + + CDmrArray< int > indices = pVertexData->GetIndexData( i ); + + // Create the new index array accounting for missing indices + for ( int j = 0; j < nNewToOldIndexMapCount; ++j ) + { + Assert( newToOldIndexMap[ j ] < indices.Count() ); + pNewVertexIndices[ j ] = indices.Get( newToOldIndexMap[ j ] ); + } + + CDmrGenericArray data = pVertexData->GetVertexData( i ); + + // This will also update pNewVertexIndices + RemoveUnusedData( pMesh, pVertexData, bBind, pFieldName, pNewVertexIndices, nNewToOldIndexMapCount, CDmrGenericArray( pVertexData->GetVertexData( i ) ) ); + + // Shrink the indices array + indices.RemoveMultiple( nNewToOldIndexMapCount, indices.Count() - nNewToOldIndexMapCount ); + + // Set the new index values + indices.SetMultiple( 0, nNewToOldIndexMapCount, pNewVertexIndices ); + } + + // Update the vertex count + pVertexData->Resolve(); +} + + +//----------------------------------------------------------------------------- +// Removes unused data from the mesh +// Unused means a 'vertex' that isn't referred to by any face +// Once all unused vertices are removed, unused data is removed from each +// bit of data +// TODO: Also loop through each field of data, see which ones are no longer +// being referred to and then purge the data as well +// Would also have to purge delta data at the same time +// Would also have to purge joints at the same time (for position) +//----------------------------------------------------------------------------- +bool CDmMeshUtils::PurgeUnusedData( CDmeMesh *pMesh ) +{ + // Get the maximum vertex index of the mesh + const int nMaxVertexCount = GetMaxVertexCount( pMesh ); + + // Now find any missing indices + CUtlVector< int > vertexIndexMap; + ComputeVertexIndexMap( pMesh, nMaxVertexCount, vertexIndexMap ); + + // Remove the redundant vertices from all base states + for ( int i = pMesh->BaseStateCount() - 1; i >= 0; --i ) + { + RemoveUnusedVerticesFromBaseState( pMesh, pMesh->GetBaseState( i ), vertexIndexMap ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::Mirror( CDmeMesh *pMesh, int axis /*= kXAxis */ ) +{ + CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); + if ( !pBase ) + return false; + + CUtlVector< int > mirrorMap; + if ( !MirrorVertices( pMesh, pBase, axis, mirrorMap ) ) + return false; + + int vertexIndex; + int faceStart = 0; + + CUtlVector< int > newFaceIndices; + + const int nFaceSets = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSets; ++i ) + { + CDmeFaceSet *pSrcFaceSet = pMesh->GetFaceSet( i ); + const int nFaceSetIndices = pSrcFaceSet->NumIndices(); + if ( nFaceSetIndices <= 0 ) + continue; + + CDmeFaceSet *pDstFaceSet = pSrcFaceSet; + + // See if a new face set needs to be created + + CDmeMaterial *pSrcMaterial = pSrcFaceSet->GetMaterial(); + const char *pSrcMaterialName = pSrcMaterial->GetMaterialName(); + const int nNameLen = Q_strlen( pSrcMaterialName ); + if ( nNameLen >= 2 ) + { + CUtlString materialName; + + if ( !Q_stricmp( pSrcMaterialName + nNameLen - 2, "_l" ) ) + { + materialName = pSrcMaterialName; + materialName.SetLength( nNameLen - 2 ); + materialName += "_r"; + } + else if ( !Q_stricmp( pSrcMaterialName + nNameLen - 2, "_r" ) ) + { + materialName = pSrcMaterialName; + materialName.SetLength( nNameLen - 2 ); + materialName += "_l"; + } + else if ( nNameLen >= 5 && !Q_stricmp( pSrcMaterialName + nNameLen - 5, "_left" ) ) + { + materialName = pSrcMaterialName; + materialName.SetLength( nNameLen - 5 ); + materialName += "_right"; + } + else if ( nNameLen >= 6 && !Q_stricmp( pSrcMaterialName + nNameLen - 6, "_right" ) ) + { + materialName = pSrcMaterialName; + materialName.SetLength( nNameLen - 6 ); + materialName += "_left"; + } + + if ( materialName.Length() ) + { + pDstFaceSet = CreateElement< CDmeFaceSet >( materialName, pMesh->GetFileId() ); + CDmeMaterial *pDstMaterial = CreateElement< CDmeMaterial >( materialName, pDstFaceSet->GetFileId() ); + pDstMaterial->SetMaterial( materialName ); + pDstFaceSet->SetMaterial( pDstMaterial ); + pMesh->AddFaceSet( pDstFaceSet ); + } + } + + faceStart = 0; + + for ( int j = 0; j < nFaceSetIndices; ++j ) + { + vertexIndex = pSrcFaceSet->GetIndex( j ); + + if ( vertexIndex < 0 ) + { + newFaceIndices.RemoveAll(); + + for ( int k = j - 1; k >= faceStart; --k ) + { + newFaceIndices.AddToTail( mirrorMap[ pSrcFaceSet->GetIndex( k ) ] ); + } + newFaceIndices.AddToTail( -1 ); + + const int oldNumIndices = pDstFaceSet->NumIndices(); + + pDstFaceSet->AddIndices( newFaceIndices.Count() ); + pDstFaceSet->SetIndices( oldNumIndices, newFaceIndices.Count(), newFaceIndices.Base() ); + + // End of face + faceStart = j + 1; + continue; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Initializes the CUtlVector to a linear ramp where utlVector[ i ] == i +//----------------------------------------------------------------------------- +template < typename T_t > +void RampInit( CUtlVector< T_t > &utlVector, const int nCount ) +{ + utlVector.SetCount( nCount ); + for ( int i = 0; i < nCount; ++i ) + { + utlVector[ i ] = i; + } +} + + +//----------------------------------------------------------------------------- +// Build Data Mirror Map +// Returns a pointer to the memory holding the indices for the map or NULL +//----------------------------------------------------------------------------- +const int *CDmMeshUtils::BuildDataMirrorMap( CDmeVertexData *pBase, int axis, CDmeVertexData::StandardFields_t standardField, CUtlVector< int > &dataMirrorMap ) +{ + const FieldIndex_t fieldIndex = pBase->FindFieldIndex( standardField ); + if ( fieldIndex < 0 ) + return NULL; + + const CUtlVector< int > &indices( CDmrArrayConst< int >( pBase->GetIndexData( fieldIndex ) ).Get() ); + + CDmAttribute *pData = pBase->GetVertexData( fieldIndex ); + if ( standardField == CDmeVertexData::FIELD_POSITION || standardField == CDmeVertexData::FIELD_NORMAL ) + { + const Vector mirrorOrigin( 0.0f, 0.0f, 0.0f ); + const float mirrorAxisVal = mirrorOrigin[ axis ]; + + CDmrArray< Vector > data( pBase->GetVertexData( fieldIndex ) ); + Vector v; + + const int nDataCount = data.Count(); + dataMirrorMap.SetCount( nDataCount ); + + int nMirrorDataCount = nDataCount; + for ( int i = 0; i < nDataCount; ++i ) + { + if ( fabs( data[ i ][ axis ] - mirrorAxisVal ) > FLT_EPSILON * 1000.0f ) + { + dataMirrorMap[ i ] = nMirrorDataCount++; + } + else + { + dataMirrorMap[ i ] = i; + v = data[ i ]; + v[ axis ] = mirrorOrigin[ axis ]; + data.Set( i, v ); + } + } + } + else if ( standardField == CDmeVertexData::FIELD_TEXCOORD ) + { + const Vector2D mirrorOrigin( 0.5f, 0.5f ); + const float mirrorAxisVal = mirrorOrigin[ axis % 2 ]; + + const CUtlVector< Vector2D > &data( CDmrArrayConst< Vector2D >( pBase->GetVertexData( fieldIndex ) ).Get() ); + const int nDataCount = data.Count(); + dataMirrorMap.SetCount( nDataCount ); + + int nMirrorDataCount = nDataCount; + for ( int i = 0; i < nDataCount; ++i ) + { + if ( fabs( data[ i ][ axis ] - mirrorAxisVal ) > FLT_EPSILON * 1000.0f ) + { + dataMirrorMap[ i ] = nMirrorDataCount++; + } + else + { + dataMirrorMap[ i ] = i; + } + } + } + else + { + RampInit( dataMirrorMap, CDmrGenericArrayConst( pData ).Count() ); + } + + return indices.Base(); +} + + +//----------------------------------------------------------------------------- +// y = mirrorMap[ x ] means that if y < 0 then original position x is not +// mirrored. Otherwise y is the index into the vertex indices of the mirrored +// version of vertex +//----------------------------------------------------------------------------- +bool CDmMeshUtils::MirrorVertices( CDmeMesh *pMesh, CDmeVertexData *pBase, int axis, CUtlVector< int > &mirrorMap ) +{ + mirrorMap.RemoveAll(); + + if ( !pMesh || !pBase || axis < kXAxis || axis > kZAxis ) + return false; + + const int posIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + if ( posIndex < 0 ) + return false; + + const CUtlVector< int > &posIndices( CDmrArrayConst< int >( pBase->GetIndexData( posIndex ) ).Get() ); + const int nIndices = posIndices.Count(); + Assert( nIndices == pBase->VertexCount() ); + CUtlVector< int > posMirrorMap; + + if ( !BuildDataMirrorMap( pBase, axis, CDmeVertexData::FIELD_POSITION, posMirrorMap ) ) + return false; + + CUtlVector< int > normalMirrorMap; + const int *pNormalIndices = BuildDataMirrorMap( pBase, axis, CDmeVertexData::FIELD_NORMAL, normalMirrorMap ); + + CUtlVector< int > uvMirrorMap; + const int *pUVIndices = BuildDataMirrorMap( pBase, axis, CDmeVertexData::FIELD_TEXCOORD, uvMirrorMap ); + + RampInit( mirrorMap, nIndices ); + int mirrorCount = 0; + { + bool mirror; + + Vector tmpVec; + Vector2D tmpVec2D; + + for ( int i = 0; i < nIndices; ++i ) + { + mirror = false; + + if ( posMirrorMap[ posIndices[ i ] ] != posIndices[ i ] ) + { + mirror = true; + } + + if ( pNormalIndices && normalMirrorMap[ pNormalIndices[ i ] ] != pNormalIndices[ i ] ) + { + mirror = true; + } + + if ( pUVIndices && uvMirrorMap[ pUVIndices[ i ] ] != pUVIndices[ i ] ) + { + mirror = true; + } + + if ( mirror ) + { + mirrorMap[ i ] = nIndices + mirrorCount; + ++mirrorCount; + } + } + } + + const int nBaseState = pMesh->BaseStateCount(); + for ( int i = 0; i < nBaseState; ++i ) + { + pBase = pMesh->GetBaseState( i ); + const int nVertexCount = pBase->VertexCount(); + MirrorVertices( pBase, axis, nVertexCount, mirrorCount, mirrorMap, posMirrorMap, normalMirrorMap, uvMirrorMap ); + } + + const int nDeltaState = pMesh->DeltaStateCount(); + for ( int i = 0; i < nDeltaState; ++i ) + { + CDmeVertexDeltaData *pDelta = pMesh->GetDeltaState( i ); + MirrorDelta( pDelta, axis, posMirrorMap, normalMirrorMap, uvMirrorMap ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +inline void MirrorData( Vector &d, const int &axis ) +{ + d[ axis ] *= -1.0f; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +inline void MirrorData( Vector2D &d, const int &axis ) +{ + d[ axis ] = ( d[ axis ] - 0.5f ) * -1.0f + 0.5f; +} + + +//----------------------------------------------------------------------------- +// Mirror 3D things like positions & normals +//----------------------------------------------------------------------------- +template < class T_t > +void MirrorVertexData( + CDmeVertexData *pBase, + FieldIndex_t fieldIndex, + int axis, + int nOrigVertexCount, + int nMirrorCount, + const CDmrArrayConst< T_t > &origData, + const CUtlVector< int > &origIndices, + const CUtlVector< int > &mirrorMap, + const CUtlVector< int > &dataMirrorMap ) +{ + if ( nMirrorCount <= 0 ) + return; + + Assert( origIndices.Count() == nOrigVertexCount + nMirrorCount ); + Assert( mirrorMap.Count() == nOrigVertexCount ); + Assert( dataMirrorMap.Count() == origData.Count() ); + + const int nData = origData.Count(); + T_t *pMirrorData = reinterpret_cast< T_t * >( alloca( nMirrorCount * sizeof( T_t ) ) ); + int *pMirrorIndices = reinterpret_cast< int * >( alloca( nMirrorCount * sizeof( int ) ) ); + + T_t mirrorData; + int nMirrorIndex = 0; + int nMirrorDataCount = -1; + for ( int i = 0; i < nOrigVertexCount; ++i ) + { + if ( mirrorMap[ i ] != i ) + { + // Vertex must be mirrored + + if ( dataMirrorMap[ origIndices[ i ] ] != origIndices[ i ] ) + { + // Data referred to by vertex i must be mirror (this may be done a redundant number of times) + const T_t &origDataRef( origData[ origIndices[ i ] ] ); + mirrorData = origDataRef; + MirrorData( mirrorData, axis ); + pMirrorData[ dataMirrorMap[ origIndices[ i ] ] - nData ] = mirrorData; + if ( ( dataMirrorMap[ origIndices[ i ] ] - nData ) > nMirrorDataCount ) + { + nMirrorDataCount = dataMirrorMap[ origIndices[ i ] ] - nData; + } + pMirrorIndices[ nMirrorIndex ] = dataMirrorMap[ origIndices[ i ] ]; + } + else + { + // The data does not need to be mirrored + pMirrorIndices[ nMirrorIndex ] = origIndices[ i ]; + } + + ++nMirrorIndex; + } + else + { + Assert( dataMirrorMap[ origIndices[ i ] ] == origIndices[ i ] ); + } + } + ++nMirrorDataCount; + + Assert( nMirrorCount == nMirrorIndex ); + Assert( nMirrorDataCount <= nMirrorCount ); + + const DmAttributeType_t dmAttributeType = ArrayTypeToValueType( origData.GetAttribute()->GetType() ); + + pBase->AddVertexData( fieldIndex, nMirrorDataCount ); + pBase->SetVertexData( fieldIndex, nData, nMirrorDataCount, dmAttributeType, pMirrorData ); + + pBase->SetVertexIndices( fieldIndex, nOrigVertexCount, nMirrorCount, pMirrorIndices ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::MirrorVertices( + CDmeVertexData *pBase, + int axis, + int nOldVertexCount, + int nMirrorCount, + const CUtlVector< int > &mirrorMap, + const CUtlVector< int > &posMirrorMap, + const CUtlVector< int > &normalMirrorMap, + const CUtlVector< int > &uvMirrorMap ) +{ + if ( !pBase || axis < kXAxis || axis > kZAxis ) + return false; + + pBase->AddVertexIndices( nMirrorCount ); + + const int posFieldIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + const int normalFieldIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + const int uvFieldIndex = pBase->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + + const int nFields = pBase->FieldCount(); + for ( int i = 0; i < nFields; ++i ) + { + CDmAttribute *pBaseData( pBase->GetVertexData( i ) ); + const CUtlVector< int > &baseIndices( pBase->GetVertexIndexData( i ) ); + Assert( baseIndices.Count() == nOldVertexCount + nMirrorCount ); + Assert( mirrorMap.Count() == nOldVertexCount ); + + switch ( pBaseData->GetType() ) + { + case AT_VECTOR2_ARRAY: + if ( i == uvFieldIndex ) + { + MirrorVertexData( pBase, i, axis % 2, nOldVertexCount, nMirrorCount, CDmrArrayConst< Vector2D >( pBaseData ), baseIndices, mirrorMap, uvMirrorMap ); + continue; + } + break; + case AT_VECTOR3_ARRAY: + if ( i == posFieldIndex ) + { + MirrorVertexData( pBase, i, axis, nOldVertexCount, nMirrorCount, CDmrArrayConst< Vector >( pBaseData ), baseIndices, mirrorMap, posMirrorMap ); + continue; + } + else if ( i == normalFieldIndex ) + { + MirrorVertexData( pBase, i, axis, nOldVertexCount, nMirrorCount, CDmrArrayConst< Vector >( pBaseData ), baseIndices, mirrorMap, normalMirrorMap ); + continue; + } + break; + default: + break; + } + + MirrorVertices( pBase, i, nOldVertexCount, nMirrorCount, baseIndices, mirrorMap ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// This does the default case of mirroring which is no mirroring at all! +// No data is changed, the extra indices are added to the index +//----------------------------------------------------------------------------- +void CDmMeshUtils::MirrorVertices( + CDmeVertexData *pBase, + FieldIndex_t fieldIndex, + int nOldVertexCount, + int nMirrorCount, + const CUtlVector< int > &baseIndices, + const CUtlVector< int > &mirrorMap ) +{ + if ( nMirrorCount <= 0 ) + return; + + Assert( baseIndices.Count() == nOldVertexCount + nMirrorCount ); + Assert( mirrorMap.Count() == nOldVertexCount ); + int *pIndices = reinterpret_cast< int * >( alloca( nMirrorCount * sizeof( int ) ) ); + + { + int pIndex = 0; + for ( int i = 0; i < nOldVertexCount; ++i ) + { + if ( mirrorMap[ i ] != i ) + { + pIndices[ pIndex ] = baseIndices[ mirrorMap[ i ] - nOldVertexCount ]; + ++pIndex; + } + } + Assert( pIndex == nMirrorCount ); + } + + pBase->SetVertexIndices( fieldIndex, nOldVertexCount, nMirrorCount, pIndices ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void MirrorDeltaData( + CDmeVertexDeltaData *pDelta, + FieldIndex_t fieldIndex, + int axis, + const CDmrArrayConst< T_t > &origData, + const CUtlVector< int > &origIndices, + const CUtlVector< int > &dataMap ) +{ + Assert( origData.Count() == origIndices.Count() ); + + const int nOrigDataCount = origData.Count(); + + T_t *pMirrorData = reinterpret_cast< T_t * >( alloca( nOrigDataCount * sizeof( T_t ) ) ); + int *pMirrorIndices = reinterpret_cast< int * >( alloca( nOrigDataCount * sizeof( int ) ) ); + + int nMirrorDataCount = 0; + for ( int i = 0; i < nOrigDataCount; ++i ) + { + if ( dataMap[ origIndices[ i ] ] != origIndices[ i ] ) + { + pMirrorData[ nMirrorDataCount ] = origData[ i ]; + MirrorData( pMirrorData[ nMirrorDataCount ], axis ); + pMirrorIndices[ nMirrorDataCount ] = dataMap[ origIndices[ i ] ]; + ++nMirrorDataCount; + } + } + + const DmAttributeType_t dmAttributeType = ArrayTypeToValueType( origData.GetAttribute()->GetType() ); + + pDelta->AddVertexData( fieldIndex, nMirrorDataCount ); + pDelta->SetVertexData( fieldIndex, nOrigDataCount, nMirrorDataCount, dmAttributeType, pMirrorData ); + pDelta->SetVertexIndices( fieldIndex, nOrigDataCount, nMirrorDataCount, pMirrorIndices ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::MirrorDelta( + CDmeVertexDeltaData *pDelta, + int axis, + const CUtlVector< int > &posMirrorMap, + const CUtlVector< int > &normalMirrorMap, + const CUtlVector< int > &uvMirrorMap ) +{ + if ( !pDelta || axis < kXAxis || axis > kZAxis ) + return false; + + const int posFieldIndex = pDelta->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + const int normalFieldIndex = pDelta->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + const int uvFieldIndex = pDelta->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + + const int nFields = pDelta->FieldCount(); + for ( int i = 0; i < nFields; ++i ) + { + CDmAttribute *pDeltaData( pDelta->GetVertexData( i ) ); + const CUtlVector< int > &deltaIndices( pDelta->GetVertexIndexData( i ) ); + + switch ( pDeltaData->GetType() ) + { + case AT_VECTOR2_ARRAY: + if ( i == uvFieldIndex ) + { + MirrorDeltaData( pDelta, i, axis % 2, CDmrArrayConst< Vector2D >( pDeltaData ), deltaIndices, uvMirrorMap ); + continue; + } + break; + case AT_VECTOR3_ARRAY: + if ( i == posFieldIndex ) + { + MirrorDeltaData( pDelta, i, axis, CDmrArrayConst< Vector >( pDeltaData ), deltaIndices, posMirrorMap ); + continue; + } + else if ( i == normalFieldIndex ) + { + MirrorDeltaData( pDelta, i, axis, CDmrArrayConst< Vector >( pDeltaData ), deltaIndices, normalMirrorMap ); + continue; + } + break; + default: + break; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Finds all materials bound to the mesh and replaces ones which match the +// source name with the destination name +//----------------------------------------------------------------------------- +bool CDmMeshUtils::RemapMaterial( CDmeMesh *pMesh, const CUtlString &src, const CUtlString &dst ) +{ + bool retVal = false; + + char srcName[ MAX_PATH ]; + char matName[ MAX_PATH ]; + char dstName[ MAX_PATH ]; + + Q_StripExtension( src.Get(), srcName, sizeof( srcName ) ); + Q_FixSlashes( srcName, '/' ); + + Q_strncpy( dstName, dst.Get(), sizeof( dstName ) ); + Q_FixSlashes( dstName, '/' ); + + const int nFaceSets = pMesh->FaceSetCount(); + for ( int i = 0; i < nFaceSets; ++i ) + { + CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( i ); + if ( !pFaceSet ) + continue; + + CDmeMaterial *pMaterial = pFaceSet->GetMaterial(); + if ( !pMaterial ) + continue; + + const char *pMaterialName = pMaterial->GetMaterialName(); + Q_StripExtension( pMaterialName, matName, sizeof( matName ) ); + Q_FixSlashes( matName, '/' ); + + // TODO: Regular expressions or at least glob style matching would be cool + if ( !Q_stricmp( srcName, matName ) ) + { + pMaterial->SetMaterial( dstName ); + pMaterial->SetName( dstName ); + retVal = true; + } + } + + return retVal; +} + + +//----------------------------------------------------------------------------- +// Replaces the nth material found with the specified material name +//----------------------------------------------------------------------------- +bool CDmMeshUtils::RemapMaterial( CDmeMesh *pMesh, const int nMaterialIndex, const CUtlString &dst ) +{ + const int nFaceSets = pMesh->FaceSetCount(); + if ( nMaterialIndex >= nFaceSets ) + return false; + + CDmeFaceSet *pFaceSet = pMesh->GetFaceSet( nMaterialIndex ); + if ( !pFaceSet ) + return false; + + CDmeMaterial *pMaterial = pFaceSet->GetMaterial(); + if ( !pMaterial ) + return false; + + pMaterial->SetMaterial( dst ); + pMaterial->SetName( dst ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Finds the "socket" on which to base the mesh merge +// This is defined as the vertices along the two meshes +// Returns the index into srcBorderEdgesList of the edge list that is found +// -1 if not found +//----------------------------------------------------------------------------- +int CDmMeshUtils::FindMergeSocket( + const CUtlVector< CUtlVector< CDmMeshComp::CEdge * > > &srcBorderEdgesList, + CDmeMesh *pDstMesh ) +{ + CDmMeshComp dstComp( pDstMesh ); + + const CUtlVector< CDmMeshComp::CEdge * > &edgeList = dstComp.m_edges; + + for ( int i = srcBorderEdgesList.Count() - 1; i >= 0; --i ) + { + const CUtlVector< CDmMeshComp::CEdge * > &srcBorderEdges = srcBorderEdgesList[ i ]; + + int nEdgeMatch = 0; + + for ( int j = 0; j != edgeList.Count(); j++ ) + { + const CDmMeshComp::CEdge &e = *edgeList[ j ]; + + for ( int k = srcBorderEdges.Count() - 1; k >= 0; --k ) + { + if ( e == *srcBorderEdges[ k ] ) + { + ++nEdgeMatch; + break; + } + } + } + + if ( nEdgeMatch == srcBorderEdges.Count() ) + { + return i; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Merge by finding the two meshes in the scene which are joined at a socket +// A socket being defined as a group of border edges that match exactly +// between two meshes +//----------------------------------------------------------------------------- +bool CDmMeshUtils::Merge( CDmeMesh *pSrcMesh, CDmElement *pRoot ) +{ + CDmMeshComp srcComp( pSrcMesh ); + + CUtlVector< CUtlVector< CDmMeshComp::CEdge * > > srcBorderEdgesList; + if ( srcComp.GetBorderEdges( srcBorderEdgesList ) == 0 ) + return false; + + CDmeMesh *pDstMesh = NULL; + + // Find each mesh under pRoot + CDmeDag *pModel = pRoot->GetValueElement< CDmeDag >( "model" ); + if ( !pModel ) + return false; + + CUtlStack< CDmeDag * > traverseStack; + traverseStack.Push( pModel ); + + CDmeDag *pDag; + CDmeMesh *pMesh; + + Vector srcCenter; + float srcRadius; + + Vector dstCenter; + float dstRadius; + + float sqDist = FLT_MAX; + pSrcMesh->GetBoundingSphere( srcCenter, srcRadius ); + + int nEdgeListIndex = -1; + + while ( traverseStack.Count() ) + { + traverseStack.Pop( pDag ); + if ( !pDag ) + continue; + + // Push all children onto stack in reverse order + for ( int nChildIndex = pDag->GetChildCount() - 1; nChildIndex >= 0; --nChildIndex ) + { + traverseStack.Push( pDag->GetChild( nChildIndex ) ); + } + + // See if there's a mesh associated with this dag + pMesh = CastElement< CDmeMesh >( pDag->GetShape() ); + if ( !pMesh ) + continue; + + int eli = FindMergeSocket( srcBorderEdgesList, pMesh ); + if ( eli < 0 ) + continue; + + pMesh->GetBoundingSphere( dstCenter, dstRadius ); + dstRadius = dstCenter.DistToSqr( srcCenter ); + + if ( dstRadius < sqDist ) + { + sqDist = dstRadius; + pDstMesh = pMesh; + nEdgeListIndex = eli; + } + } + + if ( pDstMesh ) + { + return Merge( srcComp, srcBorderEdgesList[ nEdgeListIndex ], pDstMesh ); + } + + Msg( "Error: Merge() - No Merge Socket Found - i.e. A Set Of Border Edges On The Source Model That Are Found On The Merge Model" ); + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void AppendData( + const CDmrArrayConst< T_t > &srcData, + CDmrArray< T_t > &dstData, + const matrix3x4_t *pMat = NULL ) +{ + const int nSrcCount = srcData.Count(); + const int nDstCount = dstData.Count(); + + dstData.AddMultipleToTail( nSrcCount ); + dstData.SetMultiple( nDstCount, nSrcCount, srcData.Base() ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template <> +void AppendData( + const CDmrArrayConst< Vector > &srcData, + CDmrArray< Vector > &dstData, + const matrix3x4_t *pMat ) +{ + const int nSrcCount = srcData.Count(); + const int nDstCount = dstData.Count(); + + dstData.AddMultipleToTail( nSrcCount ); + + if ( pMat ) + { + Vector v; + for ( int i = 0; i < nSrcCount; ++i ) + { + v = srcData.Get( i ); + VectorTransform( srcData.Get( i ), *pMat, v ); + dstData.Set( nDstCount + i, v ); + } + } + else + { + dstData.SetMultiple( nDstCount, nSrcCount, srcData.Base() ); + } +} + + +//----------------------------------------------------------------------------- +// Merge data from a base state on one DmeMesh into another DmeMesh +// Preserve positions and normals by transforming them with the +// positionMatrix & normalMatrix +// +// Return the number of new vertices in the mesh +//----------------------------------------------------------------------------- +int MergeBaseState( + CDmeVertexData *pSrcBase, + CDmeVertexData *pDstBase, + const matrix3x4_t &pMat, + const matrix3x4_t &nMat, + int nSkinningJointIndex, + int &nPositionOffset, + int &nNormalOffset, + int &nWrinkleOffset ) +{ + int nRetVal = -1; + + const int nSrcPositionIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + const int nSrcNormalIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + const int nSrcWrinkleIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_WRINKLE ); + const int nSrcJointWeightsIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); + const int nSrcJointIndicesIndex = pSrcBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); + + const int nDstJointWeightsIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); + const int nDstJointIndicesIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); + + // Handle skinning the new mesh data to a single joint if the destination mesh + // is already skinned. If the destination mesh is skinned but there is no + // specific joint specified to skin to, the first joint is used and a warning issued + + if ( nDstJointWeightsIndex >= 0 && nDstJointIndicesIndex >= 0 ) + { + if ( nSkinningJointIndex < 0 ) + { + Msg( "Warning: Destination mesh is skinned but no valid joint specified to skin to, using first joint\n" ); + nSkinningJointIndex = 0; + } + + const int nJointCount = pDstBase->JointCount(); + + CDmrGenericArray srcPos( pSrcBase->GetVertexData( nSrcPositionIndex ) ); + const int nSrcPosCount = srcPos.Count(); + + CDmrArray< float > dstWeights( pDstBase->GetVertexData( nDstJointWeightsIndex ) ); + CDmrArray< int > dstIndices( pDstBase->GetVertexData( nDstJointIndicesIndex ) ); + + const int nDstCount = dstWeights.Count(); + Assert( nDstCount == dstIndices.Count() ); + + dstWeights.AddMultipleToTail( nSrcPosCount * nJointCount ); + dstIndices.AddMultipleToTail( nSrcPosCount * nJointCount ); + + // Since there can be more than 1 joint per vertex, specify 1 + // for the first joint and 0 for the rest but use the same joint + const int nEnd = nDstCount + nSrcPosCount * nJointCount; + for ( int i = nDstCount; i < nEnd; i += nJointCount ) + { + dstWeights.Set( i, 1.0f ); + dstIndices.Set( i, nSkinningJointIndex ); + } + + for ( int i = 1; i < nJointCount; ++i ) + { + for ( int j = nDstCount + i; j < nEnd; j += nJointCount ) + { + dstWeights.Set( j, 0.0f ); + dstIndices.Set( j, nSkinningJointIndex ); + } + } + } + + // Handling merging all fields that match + int nIndexPadCount = -1; + + for ( int i = 0; i < pSrcBase->FieldCount(); ++i ) + { + bool bMerged = false; + + for ( int j = 0; j < pDstBase->FieldCount(); ++j ) + { + if ( i == nSrcJointWeightsIndex || i == nSrcJointIndicesIndex || Q_strcmp( pSrcBase->FieldName( i ), pDstBase->FieldName( j ) ) ) + continue; + + bMerged = true; + + CDmAttribute *pSrcData = pSrcBase->GetVertexData( i ); + CDmAttribute *pDstData = pDstBase->GetVertexData( j ); + + const int nOffset = CDmrGenericArray( pDstData ).Count(); + + switch ( pSrcData->GetType() ) + { + case AT_FLOAT_ARRAY: + AppendData( CDmrArrayConst< float >( pSrcData ), CDmrArray< float >( pDstData ) ); + break; + case AT_VECTOR2_ARRAY: + AppendData( CDmrArrayConst< Vector2D >( pSrcData ), CDmrArray< Vector2D >( pDstData ) ); + break; + case AT_VECTOR3_ARRAY: + if ( i == nSrcPositionIndex ) + { + AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ), &pMat ); + } + else if ( i == nSrcNormalIndex ) + { + AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ), &nMat ); + } + else + { + AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ) ); + } + break; + case AT_VECTOR4_ARRAY: + AppendData( CDmrArrayConst< Vector4D >( pSrcData ), CDmrArray< Vector4D >( pDstData ) ); + break; + case AT_QUATERNION_ARRAY: + AppendData( CDmrArrayConst< Quaternion >( pSrcData ), CDmrArray< Quaternion >( pDstData ) ); + break; + case AT_COLOR_ARRAY: + AppendData( CDmrArrayConst< Color >( pSrcData ), CDmrArray< Color >( pDstData ) ); + break; + default: + Assert( 0 ); + break; + } + + CDmrArray< int > srcIndices( pSrcBase->GetIndexData( i ) ); + CDmrArray< int > dstIndices( pDstBase->GetIndexData( j ) ); + + const int nSrcIndexCount = srcIndices.Count(); + const int nDstIndexCount = dstIndices.Count(); + + if ( nRetVal < 0 ) + { + nRetVal = nDstIndexCount; + } + Assert( nRetVal == nDstIndexCount ); + + dstIndices.AddMultipleToTail( nSrcIndexCount ); + if ( nIndexPadCount < 0 ) + { + nIndexPadCount = nSrcIndexCount; + } + Assert( nIndexPadCount == nSrcIndexCount ); + + for ( int k = 0; k < nSrcIndexCount; ++k ) + { + dstIndices.Set( nDstIndexCount + k, srcIndices.Get( k ) + nOffset ); + } + + if ( i == nSrcPositionIndex ) + { + nPositionOffset = nOffset; + } + else if ( i == nSrcNormalIndex ) + { + nNormalOffset = nOffset; + } + else if ( i == nSrcWrinkleIndex ) + { + nWrinkleOffset = nOffset; + } + } + + if ( !bMerged ) + { + Msg( "Warning: Not merging base data %s\n", pSrcBase->FieldName( i ) ); + } + } + + const int nDstSpeedIndex = pDstBase->FindFieldIndex( CDmeVertexData::FIELD_MORPH_SPEED ); + + // Handle all fields on the destination mesh that weren't on the source mesh + for ( int i = 0; i < pDstBase->FieldCount(); ++i ) + { + bool bFound = false; + + if ( i == nDstJointWeightsIndex || i == nDstJointIndicesIndex ) + continue; + + for ( int j = 0; j < pSrcBase->FieldCount(); ++j ) + { + if ( Q_strcmp( pDstBase->FieldName( i ), pSrcBase->FieldName( j ) ) ) + continue; + + bFound = true; + break; + } + + if ( !bFound ) + { + int nDstIndex = -1; + + if ( i == nDstSpeedIndex ) + { + // Pad data with a 1 + nDstIndex = CDmrArray< float >( pDstBase->GetVertexData( i ) ).AddToTail( 1.0f ); + } + else + { + // Pad data with a 0 + nDstIndex = CDmrGenericArray( pDstBase->GetVertexData( i ) ).AddToTail(); + } + + // Pad data indices with index to that extra data value + CDmrArray< int > dstIndices( pDstBase->GetIndexData( i ) ); + const int nStart = dstIndices.Count(); + const int nEnd = dstIndices.Count() + nIndexPadCount; + dstIndices.AddMultipleToTail( nIndexPadCount ); + for ( int k = nStart; k < nEnd; ++k ) + { + dstIndices.Set( k, nDstIndex ); + } + } + } + + pDstBase->Resolve(); + + return nRetVal; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void MergeDeltaState( CDmeMesh *pDmeMesh, CDmeVertexDeltaData *pSrcDelta, CDmeVertexDeltaData *pDstDelta, int &nPositionOffset, int &nNormalOffset, int &nWrinkleOffset ) +{ + if ( !pDstDelta ) + { + // No destination delta... copy it + pDstDelta = pDmeMesh->FindOrCreateDeltaState( pSrcDelta->GetName() ); + if ( !pDstDelta ) + return; + } + + for ( int i = 0; i < pSrcDelta->FieldCount(); ++i ) + { + bool bFound = false; + + for ( int j = 0; j < pDstDelta->FieldCount(); ++j ) + { + if ( Q_strcmp( pSrcDelta->FieldName( i ), pDstDelta->FieldName( j ) ) ) + continue; + + bFound = true; + break; + } + + if ( !bFound ) + { + // Make an empty one, data will be added below + CDmAttribute *pSrcData = pSrcDelta->GetVertexData( i ); + pDstDelta->CreateField( pSrcDelta->FieldName( i ), pSrcData->GetType() ); + } + } + + const int nSrcPositionIndex = pSrcDelta->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + const int nSrcNormalIndex = pSrcDelta->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + const int nSrcWrinkleIndex = pSrcDelta->FindFieldIndex( CDmeVertexData::FIELD_WRINKLE ); + + for ( int i = 0; i < pSrcDelta->FieldCount(); ++i ) + { + int nOffset = 0; + + if ( i == nSrcPositionIndex ) + { + nOffset = nPositionOffset; + } + else if ( i == nSrcNormalIndex ) + { + nOffset = nNormalOffset; + } + else if ( i == nSrcWrinkleIndex ) + { + nOffset = nWrinkleOffset; + } + + if ( nOffset < 0 ) + { + nOffset = 0; + } + + for ( int j = 0; j < pDstDelta->FieldCount(); ++j ) + { + if ( Q_strcmp( pSrcDelta->FieldName( i ), pDstDelta->FieldName( j ) ) ) + continue; + + CDmAttribute *pSrcData = pSrcDelta->GetVertexData( i ); + CDmAttribute *pDstData = pDstDelta->GetVertexData( j ); + + switch ( pSrcData->GetType() ) + { + case AT_FLOAT_ARRAY: + AppendData( CDmrArrayConst< float >( pSrcData ), CDmrArray< float >( pDstData ) ); + break; + case AT_VECTOR2_ARRAY: + AppendData( CDmrArrayConst< Vector2D >( pSrcData ), CDmrArray< Vector2D >( pDstData ) ); + break; + case AT_VECTOR3_ARRAY: + AppendData( CDmrArrayConst< Vector >( pSrcData ), CDmrArray< Vector >( pDstData ) ); + break; + case AT_VECTOR4_ARRAY: + AppendData( CDmrArrayConst< Vector4D >( pSrcData ), CDmrArray< Vector4D >( pDstData ) ); + break; + case AT_QUATERNION_ARRAY: + AppendData( CDmrArrayConst< Quaternion >( pSrcData ), CDmrArray< Quaternion >( pDstData ) ); + break; + case AT_COLOR_ARRAY: + AppendData( CDmrArrayConst< Color >( pSrcData ), CDmrArray< Color >( pDstData ) ); + break; + default: + Assert( 0 ); + break; + } + + CDmrArray< int > srcIndices( pSrcDelta->GetIndexData( i ) ); + CDmrArray< int > dstIndices( pDstDelta->GetIndexData( j ) ); + + const int nSrcIndexCount = srcIndices.Count(); + const int nDstIndexCount = dstIndices.Count(); + + dstIndices.AddMultipleToTail( nSrcIndexCount ); + + for ( int k = 0; k < nSrcIndexCount; ++k ) + { + dstIndices.Set( nDstIndexCount + k, srcIndices.Get( k ) + nOffset ); + } + + break; + } + } + + // TODO: Centralize all of the '_' for corrector business... + const char *pszDeltaName = pDstDelta->GetName(); + if ( strchr( pszDeltaName, '_' ) ) + return; // No controls for deltas with '_''s + + if ( !pDmeMesh ) + return; + + CDmeCombinationOperator *pDmeCombo = FindReferringElement< CDmeCombinationOperator >( pDmeMesh, "targets" ); + if ( !pDmeCombo ) + return; + + if ( pDmeCombo->HasRawControl( pszDeltaName ) ) + return; + + pDmeCombo->FindOrCreateControl( pDstDelta->GetName(), false, true ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void GetAbsTransform( CDmeDag *pDmeDag, matrix3x4_t &m ) +{ + matrix3x4_t mParentAbsTransform; + pDmeDag->GetParentWorldMatrix( mParentAbsTransform ); + + matrix3x4_t mLocal; + CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); + if ( pDmeTransform ) + { + pDmeTransform->GetTransform( mLocal ); + } + else + { + SetIdentityMatrix( mLocal ); + } + + ConcatTransforms( mParentAbsTransform, mLocal, m ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::Merge( CDmeMesh *pSrcMesh, CDmeMesh *pDstMesh, int nSkinningJointIndex ) +{ + if ( !pSrcMesh || !pDstMesh ) + return false; + + CDmeDag *pSrcDag = FindReferringElement< CDmeDag >( pSrcMesh, "shape", true ); + CDmeDag *pDstDag = FindReferringElement< CDmeDag >( pDstMesh, "shape", true ); + + if ( !pSrcDag || !pDstDag ) + return false; + + matrix3x4_t nMat; + GetAbsTransform( pSrcDag, nMat ); + matrix3x4_t pMat; + GetAbsTransform( pDstDag, pMat ); + matrix3x4_t dMatInv; + + MatrixInvert( pMat, dMatInv ); + MatrixMultiply( dMatInv, nMat, pMat ); + MatrixInverseTranspose( pMat, nMat ); + + int nPositionOffset = -1; + int nNormalOffset = -1; + int nWrinkleOffset = -1; + + int nVertexOffset = -1; + + for ( int i = 0; i < pSrcMesh->BaseStateCount(); ++i ) + { + CDmeVertexData *pSrcBase = pSrcMesh->GetBaseState( i ); + bool bMerged = false; + + for ( int j = 0; j < pDstMesh->BaseStateCount(); ++j ) + { + CDmeVertexData *pDstBase = pDstMesh->GetBaseState( j ); + + if ( Q_strcmp( pSrcBase->GetName(), pDstBase->GetName() ) ) + continue; + + bMerged = true; + const int nTmpVertexOffset = MergeBaseState( pSrcBase, pDstBase, pMat, nMat, nSkinningJointIndex, nPositionOffset, nNormalOffset, nWrinkleOffset ); + if ( nVertexOffset < 0 ) + { + nVertexOffset = nTmpVertexOffset; + } + + Assert( nVertexOffset == nTmpVertexOffset ); + } + + if ( !bMerged ) + { + Msg( "Error: Merge( %s, %s ) - Can't Find Base State %s On %s\n", pSrcMesh->GetName(), pDstMesh->GetName(), pSrcBase->GetName(), pDstMesh->GetName() ); + } + } + + // Merge Face Sets + + int nFaceSetIndex; + + for ( int i = 0; i < pSrcMesh->FaceSetCount(); ++i ) + { + CDmeFaceSet *pFaceSet = pSrcMesh->GetFaceSet( i )->Copy(); + pFaceSet->SetFileId( pDstMesh->GetFileId(), TD_DEEP ); + const int nFaceSetIndexCount = pFaceSet->NumIndices(); + for ( int j = 0; j < nFaceSetIndexCount; ++j ) + { + nFaceSetIndex = pFaceSet->GetIndex( j ); + if ( nFaceSetIndex >= 0 ) + { + pFaceSet->SetIndex( j, nFaceSetIndex + nVertexOffset ); + } + } + pDstMesh->AddFaceSet( pFaceSet ); + } + + // Merge Deltas + + for ( int i = 0; i < pSrcMesh->DeltaStateCount(); ++i ) + { + CDmeVertexDeltaData *pSrcDelta = pSrcMesh->GetDeltaState( i ); + CDmeVertexDeltaData *pDstDelta = pDstMesh->FindDeltaState( pSrcDelta->GetName() ); + MergeDeltaState( pDstMesh, pSrcDelta, pDstDelta, nPositionOffset, nNormalOffset, nWrinkleOffset ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +struct VertexWeightMap_s +{ + struct VertexWeight_s + { + int m_vertexDataIndex; // Index into the CDmeVertexData data (only used for joint weights & indices) + const CUtlVector< int > *m_pVertexIndices; // Index into the CDmeVertexData vertex indices + float m_vertexWeight; + }; + + int m_nVertexWeights; + VertexWeight_s m_vertexWeights[ 5 ]; +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CopyJointWeights( + CDmeVertexData *pSrcData, + CDmeVertexData *pDstData, + const CUtlVector< VertexWeightMap_s > &vertexWeightMap ) +{ + const int nJointCount = pSrcData->GetValue< int >( "jointCount" ); + + const FieldIndex_t nSrcJointWeightsField = pSrcData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); + const FieldIndex_t nSrcJointIndicesField = pSrcData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); + + if ( nJointCount <= 0 || nSrcJointWeightsField < 0 || nSrcJointIndicesField < 0 ) + return false; + + const CUtlVector< float > &srcJointWeights = CDmrArrayConst< float >( pSrcData->GetVertexData( nSrcJointWeightsField ) ).Get(); + const float *const pSrcJointWeights = srcJointWeights.Base(); + + const CUtlVector< int > &srcJointIndices = CDmrArrayConst< int >( pSrcData->GetVertexData( nSrcJointIndicesField ) ).Get(); + const int *const pSrcJointIndices = srcJointIndices.Base(); + + FieldIndex_t nDstJointWeightsField; + FieldIndex_t nDstJointIndicesField; + + pDstData->CreateJointWeightsAndIndices( nJointCount, &nDstJointWeightsField, &nDstJointIndicesField ); + + const int nDstCount = vertexWeightMap.Count(); + + float *pDstJointWeights = reinterpret_cast< float * >( alloca( nDstCount * nJointCount * sizeof( float ) ) ); + memset( pDstJointWeights, 0, nDstCount * nJointCount ); + + int *pDstJointIndices = reinterpret_cast< int * >( alloca( nDstCount * nJointCount * sizeof( int ) ) ); + memset( pDstJointIndices, 0, nDstCount * nJointCount ); + + for ( int i = 0; i < nDstCount; ++i ) + { + const VertexWeightMap_s &vertexWeight = vertexWeightMap[ i ]; + const int nVertexWeights = vertexWeight.m_nVertexWeights; + + if ( nVertexWeights > 0 ) + { + // TODO: Find the best weights to use! For now, use the first one + int nMatchIndex = vertexWeight.m_vertexWeights[ 0 ].m_vertexDataIndex; + + memcpy( pDstJointWeights + i * nJointCount, pSrcJointWeights + nMatchIndex * nJointCount, nJointCount * sizeof( float ) ); + memcpy( pDstJointIndices + i * nJointCount, pSrcJointIndices + nMatchIndex * nJointCount, nJointCount * sizeof( int ) ); + } + } + + pDstData->AddVertexData( nDstJointIndicesField, nDstCount * nJointCount ); + pDstData->SetVertexData( nDstJointIndicesField, 0, nDstCount * nJointCount, AT_INT, pDstJointIndices ); + + pDstData->AddVertexData( nDstJointWeightsField, nDstCount * nJointCount ); + pDstData->SetVertexData( nDstJointWeightsField, 0, nDstCount * nJointCount, AT_FLOAT, pDstJointWeights ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Replaces the DstMesh with the SrcMesh +//----------------------------------------------------------------------------- +CDmeMesh *ReplaceMesh( + CDmeMesh *pSrcMesh, + CDmeMesh *pDstMesh ) +{ + if ( !pSrcMesh || !pDstMesh ) + return NULL; + + CDmeDag *pSrcDag = pSrcMesh->GetParent(); + CDmeDag *pDstDag = pDstMesh->GetParent(); + + if ( !pSrcDag || !pDstDag ) + return NULL; + + // Fix up the transform + matrix3x4_t inclusiveMat; + matrix3x4_t localMat; + + pDstDag->GetShapeToWorldTransform( inclusiveMat ); + pDstDag->GetTransform()->GetTransform( localMat ); + + matrix3x4_t inverseMat; + MatrixInvert( localMat, inverseMat ); + + matrix3x4_t exclusiveMat; + MatrixMultiply( inclusiveMat, inverseMat, exclusiveMat ); + MatrixInvert( exclusiveMat, inverseMat ); + + pSrcDag->GetShapeToWorldTransform( inclusiveMat ); + MatrixMultiply( inverseMat, inclusiveMat, localMat ); + + pDstDag->GetTransform()->SetTransform( localMat ); + + // Duplicate the mesh + CDmeMesh *pNewMesh = pSrcMesh->Copy(); + pNewMesh->SetFileId( pDstMesh->GetFileId(), TD_DEEP ); + + // A bit of cleanup + pNewMesh->RemoveAttribute( "selection" ); + pNewMesh->SetCurrentBaseState( "bind" ); + pNewMesh->DeleteBaseState( "__dmxEdit_work" ); + + // Replace the DstMesh with the SrcMesh + pDstDag->SetShape( pNewMesh ); + + // Replace the combination operators, if applicable + CDmeCombinationOperator *pSrcComboOp = FindReferringElement< CDmeCombinationOperator >( pSrcMesh, "targets" ); + if ( pSrcComboOp ) + { + CDmeCombinationOperator *pDstComboOp = FindReferringElement< CDmeCombinationOperator >( pDstMesh, "targets" ); + CDmElement *pDstRoot = NULL; + if ( pDstComboOp ) + { + // Find the root the easy way + pDstRoot = FindReferringElement< CDmElement >( pDstComboOp, "combinationOperator" ); + + // Delete the old busted combination operator + g_pDataModel->DestroyElement( pDstComboOp->GetHandle() ); + } + else + { + // Find the root the hard way + CDmeDag *pDmeDag = pDstDag; + for ( ;; ) + { + // Walk backwards via "children" attribute + CDmeDag *pNextDag = FindReferringElement< CDmeDag >( pDmeDag, "children" ); + if ( pNextDag ) + { + pDmeDag = pNextDag; + } + else + { + // Can't find anyone referring to this via "children" so, hopefully it's the DmeModel referred to by "model" + pDstRoot = FindReferringElement< CDmElement >( pDmeDag, "model" ); + break; + } + } + } + + if ( pDstRoot ) + { + // Install the shiny new combination operator + CDmeCombinationOperator *pNewComboOp = pSrcComboOp->Copy(); + pNewComboOp->SetFileId( pDstRoot->GetFileId(), TD_DEEP ); + pDstRoot->SetValue( "combinationOperator", pNewComboOp ); + pNewComboOp->RemoveAllTargets(); + pNewComboOp->AddTarget( pNewMesh ); + pNewComboOp->GenerateWrinkleDeltas( false ); + + } + } + + return pNewMesh; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template < class T_t > +void CopyFieldData( + const CDmrArrayConst< T_t > &srcData, + const CUtlVector< int > &srcIndices, + CDmeVertexData *pDstVertexData, + FieldIndex_t dstFieldIndex, + const CUtlVector< VertexWeightMap_s > &vertexWeightMap ) +{ + const int nDstData = vertexWeightMap.Count(); + + T_t sum; + + T_t *pDstData = reinterpret_cast< T_t * >( alloca( nDstData * sizeof( T_t ) ) ); + + for ( int i = 0; i < nDstData; ++i ) + { + CDmAttributeInfo< T_t >::SetDefaultValue( pDstData[ i ] ); + + const VertexWeightMap_s &vertexWeight = vertexWeightMap[ i ]; + for ( int j = 0; j < vertexWeight.m_nVertexWeights; ++j ) + { + const VertexWeightMap_s::VertexWeight_s &vWeight = vertexWeight.m_vertexWeights[ j ]; + + CDmAttributeInfo< T_t >::SetDefaultValue( sum ); + + const CUtlVector< int > &vertexList = *vWeight.m_pVertexIndices; + for ( int k = 0; k < vertexList.Count(); ++k ) + { + sum += srcData[ srcIndices[ vertexList[ k ] ] ]; + } + sum /= static_cast< float >( vertexList.Count() ); + + pDstData[ i ] += sum * vWeight.m_vertexWeight; + } + } + + const DmAttributeType_t dmAttributeType = ArrayTypeToValueType( srcData.GetAttribute()->GetType() ); + CDmrArray< T_t > dstData( pDstVertexData->GetVertexData( dstFieldIndex ) ); + dstData.EnsureCount( nDstData ); + pDstVertexData->SetVertexData( dstFieldIndex, 0, nDstData, dmAttributeType, pDstData ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CopyField( + CDmeVertexData::StandardFields_t field, + CDmeVertexData *pSrcData, + CDmeVertexData *pDstData, + const CUtlVector< VertexWeightMap_s > &vertexWeightMap ) +{ + FieldIndex_t srcFieldIndex = pSrcData->FindFieldIndex( field ); + if ( srcFieldIndex < 0 ) + return false; + + FieldIndex_t dstFieldIndex = pDstData->CreateField( field ); + if ( dstFieldIndex < 0 ) + return false; + + CDmAttribute *pSrcVertexData = pSrcData->GetVertexData( srcFieldIndex ); + const CUtlVector< int > &srcIndices = pSrcData->GetVertexIndexData( srcFieldIndex ); + + // Everything on dst has to be indexed the same as position + const CUtlVector< int > &dstPosIndices = pDstData->GetVertexIndexData( CDmeVertexData::FIELD_POSITION ); + CDmrArray< int > dstIndices( pDstData->GetIndexData( dstFieldIndex ) ); + dstIndices.EnsureCount( dstPosIndices.Count() ); + pDstData->SetVertexIndices( dstFieldIndex, 0, dstPosIndices.Count(), dstPosIndices.Base() ); + + + switch ( pSrcVertexData->GetType() ) + { + case AT_FLOAT_ARRAY: + CopyFieldData( CDmrArrayConst< float >( pSrcVertexData ), srcIndices, pDstData, dstFieldIndex, vertexWeightMap ); + break; + case AT_VECTOR2_ARRAY: + CopyFieldData( CDmrArrayConst< Vector2D >( pSrcVertexData ), srcIndices, pDstData, dstFieldIndex, vertexWeightMap ); + break; + case AT_VECTOR3_ARRAY: + CopyFieldData( CDmrArrayConst< Vector >( pSrcVertexData ), srcIndices, pDstData, dstFieldIndex, vertexWeightMap ); + break; + default: + break; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::Merge( + CDmMeshComp &srcComp, + const CUtlVector< CDmMeshComp::CEdge * > &edgeList, + CDmeMesh *pDstMesh ) +{ + CDmeMesh *pSrcMesh = srcComp.m_pMesh; + if ( !pSrcMesh || !pDstMesh ) + return false; + + CDmeVertexData *pSrcData = pSrcMesh->FindBaseState( "bind" ); + CDmeVertexData *pDstData = pDstMesh->FindBaseState( "bind" ); + + if ( !pSrcData || !pDstData ) + return false; + + const CUtlVector< Vector > &srcPosData = pSrcData->GetPositionData(); + const int nSrcCount = srcPosData.Count(); + + const CUtlVector< Vector > &dstPosData = pDstData->GetPositionData(); + const int nDstCount = dstPosData.Count(); + + if ( nSrcCount <= 0 || nDstCount <= 0 ) + return false; + + CUtlVector< VertexWeightMap_s > vertexWeightMap; + vertexWeightMap.SetSize( nSrcCount ); + + for ( int i = 0; i < nSrcCount; ++i ) + { + int nClosestIndex = -1; + float closest = FLT_MAX; + + VertexWeightMap_s &vertexWeight = vertexWeightMap[ i ]; + vertexWeight.m_nVertexWeights = 0; + + const Vector &vSrc = srcPosData[ i ]; + + for ( int j = 0; j < nDstCount; ++j ) + { + const Vector &vDst = dstPosData[ j ]; + if ( vSrc.DistToSqr( vDst ) < FLT_EPSILON * 10.0f ) + { + vertexWeight.m_nVertexWeights = 1; + vertexWeight.m_vertexWeights[ 0 ].m_vertexDataIndex = j; + vertexWeight.m_vertexWeights[ 0 ].m_vertexWeight = 1.0f; + vertexWeight.m_vertexWeights[ 0 ].m_pVertexIndices = &pDstData->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, j ); + break; + } + + float distance = vSrc.DistToSqr( vDst ); + if ( distance < closest ) + { + closest = distance; + nClosestIndex = j; + } + } + + if ( vertexWeight.m_nVertexWeights == 0 ) + { + Warning( "Warning: Merge() - No Match For Src Vertex: %f %f %f, Using Closest: %f %f %f\n", + vSrc.x, vSrc.y, vSrc.z, + dstPosData[ nClosestIndex ].x, dstPosData[ nClosestIndex ].y, dstPosData[ nClosestIndex ].z ); + + // TODO: Loop through and find up to n closest vertices by position + + vertexWeight.m_nVertexWeights = 1; + vertexWeight.m_vertexWeights[ 0 ].m_vertexDataIndex = nClosestIndex; + vertexWeight.m_vertexWeights[ 0 ].m_vertexWeight = 1.0f; + vertexWeight.m_vertexWeights[ 0 ].m_pVertexIndices = &pDstData->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, nClosestIndex ); + + // Assert( vertexWeight.m_nVertexWeights ); + // return false; + } + } + + CDmeMesh *pNewMesh = ReplaceMesh( pSrcMesh, pDstMesh ); + if ( !pNewMesh ) + { + Error( "Error: Merge() - Couldn't Replace Mesh %s With %s\n", pDstMesh->GetName(), pSrcMesh->GetName() ); + return false; + } + + CDmeVertexData *pNewData = pNewMesh->FindBaseState( "bind" ); + if ( pNewData ) + { + CopyJointWeights( pDstData, pNewData, vertexWeightMap ); + + CopyField( CDmeVertexData::FIELD_BALANCE, pDstData, pNewData, vertexWeightMap ); + + CopyField( CDmeVertexData::FIELD_MORPH_SPEED, pDstData, pNewData, vertexWeightMap ); + + if ( pNewData->FindFieldIndex( CDmeVertexData::FIELD_MORPH_SPEED ) >= 0 ) + { + CDmeCombinationOperator *pComboOp( FindReferringElement< CDmeCombinationOperator >( pNewMesh, "targets" ) ); + if ( pComboOp ) + { + pComboOp->UsingLaggedData( true ); + } + } + } + + // Destroy the old busted mesh + g_pDataModel->DestroyElement( pDstMesh->GetHandle() ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Returns a guaranteed unique DmFileId_t +//----------------------------------------------------------------------------- +DmFileId_t CreateUniqueFileId() +{ + DmFileId_t fileId = DMFILEID_INVALID; + + UniqueId_t uniqueId; + char fileIdBuf[ MAX_PATH ]; + + do + { + CreateUniqueId( &uniqueId ); + UniqueIdToString( uniqueId, fileIdBuf, sizeof( fileIdBuf ) ); + + fileId = g_pDataModel->GetFileId( fileIdBuf ); + } while( fileId != DMFILEID_INVALID ); + + return g_pDataModel->FindOrCreateFileId( fileIdBuf ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CreateExpressionFile( const char *pExpressionFile, const CUtlVector< CUtlString > *pPurgeAllButThese, CDmeCombinationOperator *pComboOp, CDmePresetGroup *pPresetGroup ) +{ + if ( !pPresetGroup ) + return false; + + Assert( pExpressionFile && pComboOp ); + + const int nControlCount = pComboOp->GetControlCount(); + + const CDmaElementArray< CDmePreset > &presets = pPresetGroup->GetPresets(); + const int nPresetsCount = presets.Count(); + + if ( nControlCount <= 0 || nPresetsCount <= 0 ) + return false; + + char expName[ MAX_PATH ]; + Q_FileBase( pExpressionFile, expName, sizeof( expName ) ); + + CDmePresetGroup *pDstPresetGroup = CreateElement< CDmePresetGroup >( expName, CreateUniqueFileId() ); + if ( !pDstPresetGroup ) + return false; + + for ( int i = 0; i < nPresetsCount; ++i ) + { + CDmePreset *pPreset = presets[ i ]; + const char *pPresetName = pPreset->GetName(); + CDmePreset *pDstPreset = pDstPresetGroup->FindOrAddPreset( pPresetName ); + + const CDmaElementArray< CDmElement > &controlValues = pPreset->GetControlValues(); + const int nControlValueCount = controlValues.Count(); + + for ( int j = 0; j < nControlCount; ++j ) + { + // Figure out if this preset is used + bool bFound = false; // Used for two things + const char *pControlName = pComboOp->GetControlName( j ); + + if ( pPurgeAllButThese ) + { + for ( int k = 0; k < pPurgeAllButThese->Count(); ++k ) + { + if ( !Q_strcmp( pControlName, pPurgeAllButThese->Element( k ).Get() ) ) + { + bFound = true; + break; + } + } + } + + if ( !bFound && pPresetGroup->FindPreset( pControlName ) ) + bFound = true; + + if ( !bFound ) + continue; + + CDmElement *pDstControlValue = NULL; + + const bool bStereo = pComboOp->IsStereoControl( j ); + const bool bMulti = pComboOp->IsMultiControl( j ); + + if ( !Q_strcmp( pControlName, pPresetName ) ) + { + pDstControlValue = pDstPreset->FindOrAddControlValue( pControlName ); + pDstControlValue->SetValue( "value", 1.0f ); + + // These shouldn't really happen because these are presets which were made + // into deltas so they are never stereo nor multi-controls + if ( bStereo ) + { + pDstControlValue->SetValue( "balance", 0.5f ); + } + if ( bStereo ) + { + pDstControlValue->SetValue( "multilevel", 0.5f ); + } + continue; + } + + for ( int k = 0; k < nControlValueCount; ++k ) + { + CDmElement *pControlPreset = controlValues[ k ]; + + if ( !Q_strcmp( pControlName, pControlPreset->GetName() ) ) + { + pDstControlValue = pDstPreset->FindOrAddControlValue( pControlName ); + pDstControlValue->SetValue( "value", pControlPreset->GetValue( "value", 0.0f ) ); + if ( bStereo ) + { + pDstControlValue->SetValue( "balance", pControlPreset->GetValue( "balance", 0.5f ) ); + } + if ( bMulti ) + { + pDstControlValue->SetValue( "multilevel", pControlPreset->GetValue( "multilevel", 0.5f ) ); + } + break; + } + } + + if ( !pDstControlValue ) + { + pDstControlValue = pDstPreset->FindOrAddControlValue( pControlName ); + pDstControlValue->SetValue( "value", pComboOp->GetControlDefaultValue( j ) ); + + if ( bStereo ) + { + pDstControlValue->SetValue( "balance", 0.5f ); + } + if ( bMulti ) + { + pDstControlValue->SetValue( "multilevel", 0.5f ); + } + } + } + } + + char buf[ MAX_PATH ]; + char buf1[ MAX_PATH ]; + Q_strncpy( buf, pExpressionFile, sizeof( buf ) ); + Q_SetExtension( buf, ".txt", sizeof( buf ) ); + Q_ExtractFilePath( buf, buf1, sizeof( buf1 ) ); + Q_FixSlashes( buf1 ); + g_pFullFileSystem->CreateDirHierarchy( buf1 ); + + if ( !g_p4factory->AccessFile( buf )->Edit() ) + { + g_p4factory->AccessFile( buf )->Add(); + } + + pDstPresetGroup->ExportToTXT( buf, NULL, pComboOp ); + + Q_SetExtension( buf, ".vfe", sizeof( buf ) ); + Q_ExtractFilePath( buf, buf1, sizeof( buf1 ) ); + Q_FixSlashes( buf1 ); + g_pFullFileSystem->CreateDirHierarchy( buf1 ); + + if ( !g_p4factory->AccessFile( buf )->Edit() ) + { + g_p4factory->AccessFile( buf )->Add(); + } + + pDstPresetGroup->ExportToVFE( buf, NULL, pComboOp ); + + g_pDataModel->UnloadFile( pDstPresetGroup->GetFileId() ); + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::CreateDeltasFromPresets( + CDmeMesh *pMesh, + CDmeVertexData *pPassedDst, + const CUtlStringMap< CUtlString > &presetExpressionMap, + bool bPurge, + const CUtlVector< CUtlString > *pPurgeAllButThese /*= NULL */ ) +{ + if ( !pMesh ) + return false; + + CDisableUndoScopeGuard sgDisableUndo; + + CUtlStringMap< CDmePreset * > presetMap; + CUtlStringMap< CUtlString > conflictingNames; + + CDmeVertexData *pDst = pPassedDst ? pPassedDst : pMesh->GetCurrentBaseState(); + CDmeVertexData *pBind = pMesh->FindBaseState( "bind" ); + if ( !pDst || !pBind || pDst == pBind ) + return false; + + CDmeCombinationOperator *pComboOp = FindReferringElement< CDmeCombinationOperator >( pMesh, "targets" ); + if ( !pComboOp ) + return false; + + const bool bSavedUsingLagged = pComboOp->IsUsingLaggedData(); + + CUtlVector< CDmePresetGroup * > presetGroups; + + for ( int i = 0; i < presetExpressionMap.GetNumStrings(); ++i ) + { + const char *pPresetFilename = presetExpressionMap.String( i ); + + // Load the preset file + CDmElement *pRoot = NULL; + g_p4factory->AccessFile( pPresetFilename )->Add(); + g_pDataModel->RestoreFromFile( pPresetFilename, NULL, NULL, &pRoot ); + CDmePresetGroup *pPresetGroup = CastElement< CDmePresetGroup >( pRoot ); + + presetGroups.AddToTail( pPresetGroup ); + + if ( !pPresetGroup ) + continue; + + CreateDeltasFromPresetGroup( pPresetGroup, pComboOp, pPurgeAllButThese, pMesh, pDst, conflictingNames, presetMap ); + } + + if ( bPurge ) + { + PurgeUnreferencedDeltas( pMesh, presetMap, pPurgeAllButThese, pComboOp ); + } + + for ( int i = 0; i < presetMap.GetNumStrings(); ++i ) + { + const char *pPresetName = presetMap[ i ]->GetName(); + const int nControlIndex = pComboOp->FindControlIndex( pPresetName ); + if ( nControlIndex < 0 ) + { + pComboOp->FindOrCreateControl( pPresetName, false, true ); + } + else + { + bool bFound = false; + + if ( bPurge ) + { + pComboOp->RemoveAllRawControls( nControlIndex ); + } + else + { + const int nRawControls = pComboOp->GetRawControlCount( nControlIndex ); + for ( int j = 0; j < nRawControls; ++j ) + { + if ( !Q_strcmp( pComboOp->GetRawControlName( nControlIndex, j ), pPresetName ) ) + { + bFound = true; + break; + } + } + } + + if ( !bFound ) + { + pComboOp->AddRawControl( nControlIndex, pPresetName ); + } + } + } + + pComboOp->UsingLaggedData( bSavedUsingLagged ); + pComboOp->SetToDefault(); + + for ( int i = 0; i < presetExpressionMap.GetNumStrings(); ++i ) + { + const CUtlString &expressionFile = presetExpressionMap[ i ]; + if ( expressionFile.IsEmpty() ) + continue; + + CreateExpressionFile( expressionFile.Get(), pPurgeAllButThese, pComboOp, presetGroups[ i ] ); + } + + for ( int i = 0; i < presetGroups.Count(); ++i ) + { + CDmePresetGroup *pPresetGroup = presetGroups[ i ]; + if ( !pPresetGroup ) + continue; + + g_pDataModel->UnloadFile( pPresetGroup->GetFileId() ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Removes any deltas from the specified mesh which are not referred to by +// any rule of the combination operator driving the mesh +//----------------------------------------------------------------------------- +bool CDmMeshUtils::PurgeUnusedDeltas( CDmeMesh *pMesh ) +{ + // Disable for now + // This code will delete all corrective delta states, i.e. deltas named A_B + return true; + + if ( !pMesh ) + return false; + + CDmeCombinationOperator *pCombo = FindReferringElement< CDmeCombinationOperator >( pMesh, "targets" ); + if ( !pCombo ) + return false; + + const int nControlCount = pCombo->GetControlCount(); + + CUtlVector< CDmeMesh::DeltaComputation_t > compList; + pMesh->ComputeDependentDeltaStateList( compList ); + const int nDeltaCount = compList.Count(); + + Assert( nDeltaCount == pMesh->DeltaStateCount() ); + + CUtlVector< bool > deltasToKeep; + deltasToKeep.EnsureCount( nDeltaCount ); + memset( deltasToKeep.Base(), 0, sizeof( bool ) * nDeltaCount ); + + for ( int i = 0; i < nControlCount; ++i ) + { + const int nRawControlCount = pCombo->GetRawControlCount( i ); + for ( int j = 0; j < nRawControlCount; ++j ) + { + const int nDeltaIndex = pMesh->FindDeltaStateIndex( pCombo->GetRawControlName( i, j ) ); + const CDmeMesh::DeltaComputation_t &deltaComp = compList[ nDeltaIndex ]; + deltasToKeep[ deltaComp.m_nDeltaIndex ] = true; + for ( int k = 0; k < deltaComp.m_DependentDeltas.Count(); ++k ) + { + deltasToKeep[ deltaComp.m_DependentDeltas[ k ] ] = true; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshUtils::CreateWrinkleDeltaFromBaseState( + CDmeVertexDeltaData *pDelta, + float flScale /* = 1.0f */, + WrinkleOp wrinkleOp /* = kReplace */, + CDmeMesh *pPassedMesh /* = NULL */, + CDmeVertexData *pPassedBind /* = NULL */, + CDmeVertexData *pPassedCurrent /* = NULL */ ) +{ + CDmeVertexData *pBind = pPassedBind ? pPassedBind : pPassedMesh ? pPassedMesh->GetBindBaseState() : NULL; + CDmeVertexData *pCurr = pPassedCurrent ? pPassedCurrent : pPassedMesh ? pPassedMesh->GetCurrentBaseState() : NULL; + + const CDmeMesh *pMesh = pPassedMesh ? pPassedMesh : pBind ? FindReferringElement< CDmeMesh >( pBind, "baseStates" ) : NULL; + const CDmeMesh *pBindMesh = pBind ? FindReferringElement< CDmeMesh >( pBind, "baseStates" ) : NULL; + const CDmeMesh *pCurrMesh = pCurr ? FindReferringElement< CDmeMesh >( pCurr, "baseStates", false ) : NULL; + const CDmeMesh *pDeltaMesh = pDelta ? FindReferringElement< CDmeMesh >( pDelta, "deltaStates" ) : NULL; + + if ( !pDelta || !pBind || !pCurr || pBind == pCurr || !pMesh || pMesh != pBindMesh || pMesh != pCurrMesh || pMesh != pDeltaMesh ) + { + return false; + } + + const FieldIndex_t nBindPosIndex = pBind->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + const FieldIndex_t nBindTexIndex = pBind->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + const FieldIndex_t nCurrPosIndex = pCurr->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + + if ( nBindPosIndex < 0 || nBindTexIndex < 0 || nCurrPosIndex < 0 ) + return false; + + const CUtlVector< Vector > &bindPos = CDmrArrayConst< Vector >( pBind->GetVertexData( nBindPosIndex ) ).Get(); + const CUtlVector< Vector > &currPos = CDmrArrayConst< Vector >( pCurr->GetVertexData( nCurrPosIndex ) ).Get(); + const CUtlVector< int > &baseTexCoordIndices = pBind->GetVertexIndexData( nBindTexIndex ); + + const int nPosCount = bindPos.Count(); + if ( nPosCount != currPos.Count() ) + return false; + + const CDmrArrayConst< Vector2D > texData( pBind->GetVertexData( nBindTexIndex ) ); + const int nBaseTexCoordCount = texData.Count(); + + FieldIndex_t nWrinkleIndex = pDelta->FindFieldIndex( CDmeVertexDeltaData::FIELD_WRINKLE ); + if ( nWrinkleIndex < 0 ) + { + nWrinkleIndex = pDelta->CreateField( CDmeVertexDeltaData::FIELD_WRINKLE ); + } + + float *pOldWrinkleData = NULL; + + if ( wrinkleOp == kAdd ) + { + // Copy the old wrinkle data + CDmAttribute *pWrinkleDeltaAttr = pDelta->GetVertexData( nWrinkleIndex ); + if ( pWrinkleDeltaAttr ) + { + CDmrArrayConst< float > wrinkleDeltaArray( pWrinkleDeltaAttr ); + if ( wrinkleDeltaArray.Count() ) + { + const CUtlVector< int > &wrinkleDeltaIndices = pDelta->GetVertexIndexData( nWrinkleIndex ); + Assert( wrinkleDeltaIndices.Count() == wrinkleDeltaArray.Count() ); + + pOldWrinkleData = reinterpret_cast< float * >( alloca( nBaseTexCoordCount * sizeof( float ) ) ); + memset( pOldWrinkleData, 0, nBaseTexCoordCount * sizeof( float ) ); + + for ( int i = 0; i < wrinkleDeltaIndices.Count(); ++i ) + { + if ( i < nPosCount ) + { + *( pOldWrinkleData + wrinkleDeltaIndices[i]) = wrinkleDeltaArray[ i ]; + } + } + } + } + } + + pDelta->RemoveAllVertexData( nWrinkleIndex ); + if ( flScale == 0.0f && wrinkleOp != kAdd ) + return true; + + float flMaxDeflection = 0.0f; + int *pWrinkleIndices = reinterpret_cast< int * >( alloca( nPosCount * sizeof( int ) ) ); + float *pWrinkleDelta = reinterpret_cast< float * >( alloca( nPosCount * sizeof( float ) ) ); + int nWrinkleCount = 0; + + float flDelta; + Vector v; + + if ( pOldWrinkleData ) + { + for ( int i = 0; i < nPosCount; ++i ) + { + v = bindPos[ i ] - currPos[ i ]; + + // Figure out the texture indices for this position index + const CUtlVector< int > &baseVerts = pBind->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, i ); + + for ( int j = 0; j < baseVerts.Count(); ++j ) + { + // See if we have a delta for this texcoord... + const int nTexCoordIndex = baseTexCoordIndices[ baseVerts[ j ] ]; + + if ( fabs( pOldWrinkleData[ nTexCoordIndex ] ) > 0.0001 || fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) + { + flDelta = v.Length(); + if ( flDelta > flMaxDeflection ) + { + flMaxDeflection = flDelta; + } + pWrinkleDelta[ nWrinkleCount ] = flDelta; + pWrinkleIndices[ nWrinkleCount ] = i; + ++nWrinkleCount; + break; + } + } + } + } + else + { + for ( int i = 0; i < nPosCount; ++i ) + { + v = bindPos[ i ] - currPos[ i ]; + if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) + { + flDelta = v.Length(); + if ( flDelta > flMaxDeflection ) + { + flMaxDeflection = flDelta; + } + pWrinkleDelta[ nWrinkleCount ] = flDelta; + pWrinkleIndices[ nWrinkleCount ] = i; + ++nWrinkleCount; + } + } + } + + if ( flMaxDeflection == 0.0f ) + return true; + + const double scaledInverseMaxDeflection = static_cast< double >( flScale ) / static_cast< double >( flMaxDeflection ); + + const int nBufSize = ( ( nBaseTexCoordCount + 7 ) >> 3 ); + unsigned char * const pUsedBits = reinterpret_cast< unsigned char* >( alloca( nBufSize * sizeof( unsigned char ) ) ); + memset( pUsedBits, 0, nBufSize ); + + for ( int i = 0; i < nWrinkleCount; ++i ) + { + float flWrinkleDelta = static_cast< float >( static_cast< double >( pWrinkleDelta[ i ] ) * scaledInverseMaxDeflection ); + + Assert( fabs( flWrinkleDelta ) <= fabs( flScale ) ); + + // NOTE: This will produce bad behavior in cases where two positions share the + // same texcoord, which shouldn't theoretically happen. + const CUtlVector< int > &baseVerts = pBind->FindVertexIndicesFromDataIndex( CDmeVertexData::FIELD_POSITION, pWrinkleIndices[ i ] ); + const int nBaseVertCount = baseVerts.Count(); + for ( int j = 0; j < nBaseVertCount; ++j ) + { + // See if we have a delta for this texcoord... + int nTexCoordIndex = baseTexCoordIndices[ baseVerts[j] ]; + if ( pUsedBits[ nTexCoordIndex >> 3 ] & ( 1 << ( nTexCoordIndex & 0x7 ) ) ) + continue; + + pUsedBits[ nTexCoordIndex >> 3 ] |= 1 << ( nTexCoordIndex & 0x7 ); + + if ( pOldWrinkleData ) + { + flWrinkleDelta += pOldWrinkleData[ nTexCoordIndex ]; + } + + int nDeltaIndex = pDelta->AddVertexData( nWrinkleIndex, 1 ); + pDelta->SetVertexIndices( nWrinkleIndex, nDeltaIndex, 1, &nTexCoordIndex ); + pDelta->SetVertexData( nWrinkleIndex, nDeltaIndex, 1, AT_FLOAT, &flWrinkleDelta ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmMeshFaceIt::CDmMeshFaceIt( const CDmeMesh *pMesh, const CDmeVertexData *pVertexData /* = NULL */ ) +{ + Reset( pMesh, pVertexData ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshFaceIt::Reset( const CDmeMesh *pMesh, const CDmeVertexData *pVertexData /* = NULL */ ) +{ + m_nFaceIndex = 0; + + if ( pMesh ) + { + m_pMesh = pMesh; + m_pVertexData = pVertexData ? pVertexData : m_pVertexData ? m_pVertexData : m_pMesh->GetCurrentBaseState(); + + m_nFaceSetCount = 0; + m_nFaceSetIndex = 0; + + m_pFaceSet = NULL; + + m_nFaceSetIndexCount = 0; + m_nFaceSetIndexIndex = 0; + + m_nFaceCount = 0; + + // Get number of face sets in current mesh + m_nFaceSetCount = m_pMesh->FaceSetCount(); + if ( m_nFaceSetCount <= 0 ) + return false; + + // Get number of faces in current mesh + for ( m_nFaceSetIndex = 0; m_nFaceSetIndex < m_nFaceSetCount; ++m_nFaceSetIndex ) + { + const CDmeFaceSet *pFaceSet = m_pMesh->GetFaceSet( m_nFaceSetIndex ); + m_nFaceCount += pFaceSet->GetFaceCount(); + } + } + else if ( !m_pMesh ) + { + return false; + } + + // Set indices to point to first index of first face of first face set, accounting for + // NULL face sets and NULL faces + for ( m_nFaceSetIndex = 0; m_nFaceSetIndex < m_nFaceSetCount; ++m_nFaceSetIndex ) + { + if ( SetFaceSet() ) + return true; + } + + // All face sets were empty or full of nothing but -1's + Assert( m_nFaceSetIndex == m_nFaceSetCount ); + Assert( m_nFaceCount == 0 ); + + m_pFaceSet = NULL; + + m_nFaceSetIndexCount = 0; + m_nFaceSetIndexIndex = 0; + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmMeshFaceIt::Count() const +{ + return m_nFaceCount; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmMeshFaceIt::VertexCount() const +{ + if ( IsDone() ) + return 0; + + return m_pFaceSet->GetNextPolygonVertexCount( m_nFaceSetIndexIndex ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshFaceIt::IsDone() const +{ + if ( m_nFaceIndex < m_nFaceCount ) + { + Assert( m_nFaceSetIndex < m_nFaceSetCount ); + Assert( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ); + } + else + { + Assert( m_nFaceSetIndex >= m_nFaceSetCount ); + Assert( m_nFaceSetIndexIndex >= m_nFaceSetIndexCount ); + } + + return m_nFaceIndex >= m_nFaceCount; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshFaceIt::Next() +{ + // Set indices to point to first index of first face of first face set, accounting for + // NULL face sets and NULL faces + + while ( m_nFaceSetIndex < m_nFaceSetCount ) + { + // Skip to next -1 face delimiter + while ( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ) + { + if ( m_pFaceSet->GetIndex( m_nFaceSetIndexIndex ) >= 0 ) + break; + + ++m_nFaceSetIndexIndex; + } + + // Skip to next face index + while ( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ) + { + if ( m_pFaceSet->GetIndex( m_nFaceSetIndexIndex ) < 0 ) + break; + + ++m_nFaceSetIndexIndex; + } + + if ( m_nFaceSetIndexIndex < m_nFaceSetIndexCount ) + { + ++m_nFaceIndex; + Assert( m_nFaceIndex < m_nFaceCount ); + return true; + } + + // Must increment the face set + ++m_nFaceSetIndex; + SetFaceSet(); + } + + // At the end of the iteration + Assert( IsDone() ); + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshFaceIt::SetFaceSet() +{ + if ( !m_pMesh ) + { + m_pFaceSet = NULL; + m_nFaceSetIndexCount = 0; + m_nFaceSetIndexIndex = 0; + + return false; + } + + if ( m_nFaceSetIndex >= m_nFaceSetCount ) + { + m_pFaceSet = NULL; + m_nFaceSetIndexCount = 0; + m_nFaceSetIndexIndex = 0; + + return false; + } + + m_pFaceSet = m_pMesh->GetFaceSet( m_nFaceSetIndex ); + m_nFaceSetIndexCount = m_pFaceSet->NumIndices(); + m_nFaceSetIndexIndex = 0; + + // Skip to the first valid face index + for ( m_nFaceSetIndexIndex = 0; m_nFaceSetIndexIndex < m_nFaceSetIndexCount; ++m_nFaceSetIndexIndex ) + { + if ( m_pFaceSet->GetIndex( m_nFaceSetIndex ) >= 0 ) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshFaceIt::GetVertexIndices( int *pIndices, int nIndices ) const +{ + if ( IsDone() || nIndices != VertexCount() ) + { + memset( pIndices, 0, nIndices * sizeof( int ) ); + return false; + } + + int vertexIndex; + + for ( int i = m_nFaceSetIndexIndex; i < m_nFaceSetIndexCount; ++i ) + { + vertexIndex = m_pFaceSet->GetIndex( i ); + if ( vertexIndex < 0 ) + { + Assert( i == m_nFaceSetIndexIndex + VertexCount() ); + return true; + } + + Assert( i < m_nFaceSetIndexIndex + VertexCount() ); + + *pIndices = vertexIndex; + ++pIndices; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmMeshFaceIt::GetVertexIndices( CUtlVector< int > &vertexIndices ) const +{ + vertexIndices.SetCount( VertexCount() ); + + if ( IsDone() ) + { + memset( vertexIndices.Base(), 0, vertexIndices.Count() * sizeof( int ) ); + return false; + } + + return GetVertexIndices( vertexIndices.Base(), vertexIndices.Count() ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmMeshFaceIt::GetVertexIndex( int nFaceRelativeVertexIndex ) const +{ + if ( IsDone() ) + return -1; + + const int nVertexCount = VertexCount(); + if ( nVertexCount <= 0 || nFaceRelativeVertexIndex < 0 || nFaceRelativeVertexIndex >= nVertexCount ) + return -1; + + int *pVertexIndices = reinterpret_cast< int * >( alloca( nVertexCount * sizeof( int ) ) ); + if ( !GetVertexIndices( pVertexIndices, nVertexCount ) ) + return -1; + + return pVertexIndices[ nFaceRelativeVertexIndex ]; +} + + +//----------------------------------------------------------------------------- +// Copied from dmeanimationset.cpp, remove this function +// after further integrations +//----------------------------------------------------------------------------- +ControlIndex_t FindComboOpControlIndexForAnimSetControl( CDmeCombinationOperator *pComboOp, const char *pControlName, bool *pIsMulti /*= NULL*/ ) +{ + const char *pMultiControlBaseName = pControlName ? StringAfterPrefix( pControlName, "multi_" ) : NULL; + if ( pIsMulti ) + { + *pIsMulti = pMultiControlBaseName != NULL; + } + + if ( !pComboOp || !pControlName ) + return -1; + + ControlIndex_t index = pComboOp->FindControlIndex( pControlName ); + if ( index >= 0 ) + return index; + + if ( !pMultiControlBaseName ) + return -1; + + index = pComboOp->FindControlIndex( pMultiControlBaseName ); + if ( index < 0 ) + return -1; + + Assert( pComboOp->IsMultiControl( index ) ); + + return index; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmMeshUtils::CreateDeltasFromPresetGroup( + CDmePresetGroup *pPresetGroup, + CDmeCombinationOperator * pComboOp, + const CUtlVector< CUtlString > *pPurgeAllButThese, + CDmeMesh *pMesh, + CDmeVertexData *pDst, + CUtlStringMap< CUtlString > &conflictingNames, + CUtlStringMap< CDmePreset * > &presetMap ) +{ + const CDmaElementArray< CDmePreset > &presets = pPresetGroup->GetPresets(); + const int nPresetsCount = presets.Count(); + + if ( nPresetsCount <= 0 ) + return; + + for ( int i = 0; i < nPresetsCount; ++i ) + { + pComboOp->SetToBase(); + CDmePreset *pPreset = presets[ i ]; + + CDmaElementArray< CDmElement > &controlValues = pPreset->GetControlValues(); + const int nControlValues = controlValues.Count(); + for ( int j = 0; j < nControlValues; ++j ) + { + CDmElement *pControlPreset = controlValues[ j ]; + + const ControlIndex_t nControlIndex = pComboOp->FindControlIndex( pControlPreset->GetName() ); + if ( nControlIndex < 0 ) + continue; + + bool bSkip = false; + + if ( pPurgeAllButThese ) + { + for ( int k = 0; k < pPurgeAllButThese->Count(); ++k ) + { + if ( !Q_strcmp( pControlPreset->GetName(), pPurgeAllButThese->Element( k ).Get() ) ) + { + bSkip = true; + } + } + } + + if ( bSkip ) + continue; + + if ( pComboOp->IsStereoControl( nControlIndex ) ) + { + pComboOp->SetControlValue( + nControlIndex, + pControlPreset->GetValue< float >( "value", 0.0 ), + pControlPreset->GetValue< float >( "balance", 0.5 ) ); + } + else + { + pComboOp->SetControlValue( + nControlIndex, + pControlPreset->GetValue< float >( "value", 0.0 ) ); + } + + if ( pComboOp->IsMultiControl( nControlIndex ) ) + { + pComboOp->SetMultiControlLevel( + nControlIndex, + pControlPreset->GetValue< float >( "multilevel", 0.5 ) ); + } + } + + // Pass the control data from the DmeCombinationOperator into the mesh + pComboOp->Resolve(); + pComboOp->Operate(); + + pMesh->Resolve(); + pMesh->SetBaseStateToDeltas( pDst ); + + CUtlString presetName = pPreset->GetName(); + + // Look for any conflicting pre-existing names + for ( int presetSuffix = 1; pComboOp->FindControlIndex( presetName ) >= 0 || pMesh->FindDeltaState( presetName ) != NULL || conflictingNames.Defined( presetName ) || presetMap.Defined( presetName ); ++presetSuffix ) + { + presetName = pPreset->GetName(); + presetName += presetSuffix; + } + + if ( Q_strcmp( pPreset->GetName(), presetName ) ) + { + // Had to rename preset... save name for later renaming back + conflictingNames[ presetName ] = pPreset->GetName(); + } + + presetMap[ presetName ] = pPreset; + + pMesh->ModifyOrCreateDeltaStateFromBaseState( presetName, pDst, true ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmMeshUtils::PurgeUnreferencedDeltas( CDmeMesh *pMesh, CUtlStringMap< CDmePreset * > &presetMap, const CUtlVector< CUtlString > *pPurgeAllButThese, CDmeCombinationOperator *pComboOp ) +{ + // Loop because deleting changes indexing + bool bDeleted = false; + do + { + bDeleted = false; + for ( int i = 0; i < pMesh->DeltaStateCount(); ++i ) + { + const char *pDeltaStateName = pMesh->GetDeltaState( i )->GetName(); + + if ( presetMap.Defined( pDeltaStateName ) ) + continue; + + bool bDelete = true; + + if ( pPurgeAllButThese ) + { + for ( int j = 0; j < pPurgeAllButThese->Count(); ++j ) + { + if ( !Q_strcmp( pDeltaStateName, pPurgeAllButThese->Element( j ).Get() ) ) + { + bDelete = false; + break; + } + + const ControlIndex_t nControlIndex = pComboOp->FindControlIndex( pPurgeAllButThese->Element( j ) ); + if ( nControlIndex < 0 ) + continue; + + for ( int k = 0; k < pComboOp->GetRawControlCount( nControlIndex ); ++k ) + { + if ( !Q_strcmp( pDeltaStateName, pComboOp->GetRawControlName( nControlIndex, k ) ) ) + { + bDelete = false; + break; + } + } + } + } + + if ( bDelete ) + { + pMesh->DeleteDeltaState( pDeltaStateName ); + bDeleted = true; + break; + } + } + } while( bDeleted ); + + // Loop because deleting changes indexing + do + { + bDeleted = false; + for ( int i = 0; i < pComboOp->GetControlCount(); ++i ) + { + const char *pControlName = pComboOp->GetControlName( i ); + + if ( presetMap.Defined( pControlName ) ) + continue; + + bool bDelete = true; + + if ( pPurgeAllButThese ) + { + for ( int j = 0; j < pPurgeAllButThese->Count(); ++j ) + { + if ( !Q_strcmp( pControlName, pPurgeAllButThese->Element( j ) ) ) + { + bDelete = false; + break; + } + } + } + + if ( bDelete ) + { + pComboOp->RemoveControl( pControlName ); + bDeleted = true; + break; + } + } + } while( bDeleted ); + + // Rename any that can be renamed... which should be all of them + for ( int i = 0; i < presetMap.GetNumStrings(); ++i ) + { + const char *pPresetName = presetMap.String( i ); + CDmePreset *pPreset = presetMap[ i ]; + + if ( Q_strcmp( pPreset->GetName(), pPresetName ) ) + { + const ControlIndex_t nOrigIndex = pComboOp->FindControlIndex( pPreset->GetName() ); + const ControlIndex_t nRenamedIndex = pComboOp->FindControlIndex( pPresetName ); + CDmeVertexDeltaData *pOrigDelta = pMesh->FindDeltaState( pPreset->GetName() ); + CDmeVertexDeltaData *pRenamedDelta = pMesh->FindDeltaState( pPresetName ); + + if ( nOrigIndex < 0 && nRenamedIndex >= 0 && pOrigDelta == NULL && pRenamedDelta != NULL ) + { + pComboOp->RemoveControl( pPresetName ); + pRenamedDelta->SetName( pPreset->GetName() ); + } + } + } +} diff --git a/movieobjects/dmobjserializer.cpp b/movieobjects/dmobjserializer.cpp new file mode 100644 index 0000000..e8c3a83 --- /dev/null +++ b/movieobjects/dmobjserializer.cpp @@ -0,0 +1,1464 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Serialize and Unserialize Wavefront OBJ <-> DME Data +// +//============================================================================= + + +// Valve includes +#include "tier1/characterset.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmemesh.h" +#include "movieobjects/dmefaceset.h" +#include "movieobjects/dmematerial.h" +#include "movieobjects/dmobjserializer.h" +#include "movieobjects/dmecombinationoperator.h" +#include "movieobjects/dmemodel.h" +#include "filesystem.h" +#include "tier2/tier2.h" +#include "tier1/UtlStringMap.h" +#include "mathlib/mathlib.h" + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CFaceSetData +{ +public: + void Clear(); + + inline CUtlVector< int > *GetFaceSetIndices( const char *pFaceSetName ) + { + return &m_faceSetIndices[ pFaceSetName ]; + } + + void AddToMesh( CDmeMesh *pMesh ); +protected: + + CUtlStringMap< CUtlVector< int > > m_faceSetIndices; +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CFaceSetData::Clear() +{ + m_faceSetIndices.Clear(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CFaceSetData::AddToMesh( CDmeMesh *pMesh ) +{ + const int nFaceSets( m_faceSetIndices.GetNumStrings() ); + for ( int i( 0 ); i < nFaceSets; ++i ) + { + const char *pName( m_faceSetIndices.String( i ) ); + CUtlVector< int > &faceSetIndices( m_faceSetIndices[ pName ] ); + + if ( faceSetIndices.Count() ) + { + CDmeFaceSet *pFaceSet = CreateElement< CDmeFaceSet >( pName, pMesh->GetFileId() ); + + CDmeMaterial *pMaterial = CreateElement< CDmeMaterial >( pName, pMesh->GetFileId() ); + pMaterial->SetMaterial( pName ); + + pFaceSet->AddIndices( faceSetIndices.Count() ); + pFaceSet->SetIndices( 0, faceSetIndices.Count(), faceSetIndices.Base() ); + + pFaceSet->SetMaterial( pMaterial ); + + pMesh->AddFaceSet( pFaceSet ); + } + } + + Clear(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CVertexData +{ +public: + void Clear(); + + inline void AddPosition( const Vector &p ) { m_positions.AddToTail( p ); } + + inline void AddPositionIndex( int i ) { m_pIndices.AddToTail( i ); } + + inline void AddNormal( const Vector &n ) { m_normals.AddToTail( n ); } + + inline void AddNormalIndex( int i ) { m_nIndices.AddToTail( i ); } + + inline void AddUV( const Vector2D &uv ) { AddUniqueValue( uv, m_uvs, m_uvIndexMap, FLT_EPSILON * 0.1f ); } + + inline void AddUVIndex( int i ) { Assert( i < m_uvIndexMap.Count() ); m_uvIndices.AddToTail( m_uvIndexMap[ i ] ); } + + inline int VertexCount() const { return m_pIndices.Count(); } + + CDmeVertexDataBase *AddToMesh( CDmeMesh *pMesh, bool bAbsolute, const char *pName, bool bDelta ); + +protected: + template < class T_t > void AddUniqueValue( + const T_t &v, + CUtlVector< T_t > &vs, + CUtlVector< int > &map, + float flThresh = FLT_EPSILON ) + { + const int nVs( vs.Count() ); + for ( int i( 0 ); i < nVs; ++i ) + { + if ( v.DistToSqr( vs[ i ] ) < flThresh ) + { + map.AddToTail( i ); + return; + } + } + + map.AddToTail( vs.Count() ); + vs.AddToTail( v ); + } + + CDmeVertexDataBase *Add( CDmeMesh *pMesh, const char *pName = "bind" ); + + CDmeVertexDeltaData *AddDelta( CDmeMesh *pMesh, bool bAbsolute, const char *pName = "bind" ); + + CUtlVector< Vector > m_positions; + CUtlVector< int > m_pIndices; + + CUtlVector< Vector > m_normals; + CUtlVector< int > m_nIndices; + + CUtlVector< Vector2D > m_uvs; + CUtlVector< int > m_uvIndexMap; + CUtlVector< int > m_uvIndices; +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVertexData::Clear() +{ + m_positions.RemoveAll(); + m_pIndices.RemoveAll(); + + m_normals.RemoveAll(); + m_nIndices.RemoveAll(); + + m_uvs.RemoveAll(); + m_uvIndexMap.RemoveAll(); + m_uvIndices.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeVertexDataBase *CVertexData::AddToMesh( CDmeMesh *pMesh, bool bAbsolute, const char *pName, bool delta ) +{ + if ( delta ) + return AddDelta( pMesh, bAbsolute, pName ); + + return Add( pMesh, pName ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeVertexDataBase *CVertexData::Add( CDmeMesh *pMesh, const char *pName ) +{ + CDmeVertexDataBase *pVertexData( NULL ); + + if ( m_positions.Count() && m_pIndices.Count() ) + { + { + CDmeVertexData *pBaseVertexData = pMesh->FindOrCreateBaseState( pName ); + pMesh->SetCurrentBaseState( pName ); + pBaseVertexData->AddVertexIndices( m_pIndices.Count() ); + pVertexData = pBaseVertexData; + } + + pVertexData->FlipVCoordinate( true ); + + const FieldIndex_t pIndex( pVertexData->CreateField( CDmeVertexData::FIELD_POSITION ) ); + pVertexData->AddVertexData( pIndex, m_positions.Count() ); + pVertexData->SetVertexData( pIndex, 0, m_positions.Count(), AT_VECTOR3, m_positions.Base() ); + pVertexData->SetVertexIndices( pIndex, 0, m_pIndices.Count(), m_pIndices.Base() ); + + if ( pVertexData && m_normals.Count() && m_nIndices.Count() ) + { + Assert( m_pIndices.Count() == m_nIndices.Count() ); + + const FieldIndex_t nIndex( pVertexData->CreateField( CDmeVertexData::FIELD_NORMAL ) ); + pVertexData->AddVertexData( nIndex, m_normals.Count() ); + pVertexData->SetVertexData( nIndex, 0, m_normals.Count(), AT_VECTOR3, m_normals.Base() ); + pVertexData->SetVertexIndices( nIndex, 0, m_nIndices.Count(), m_nIndices.Base() ); + } + + if ( pVertexData && m_uvs.Count() && m_uvIndices.Count() ) + { + Assert( m_pIndices.Count() == m_uvIndices.Count() ); + + const FieldIndex_t uvIndex( pVertexData->CreateField( CDmeVertexData::FIELD_TEXCOORD ) ); + pVertexData->AddVertexData( uvIndex, m_uvs.Count() ); + pVertexData->SetVertexData( uvIndex, 0, m_uvs.Count(), AT_VECTOR2, m_uvs.Base() ); + pVertexData->SetVertexIndices( uvIndex, 0, m_uvIndices.Count(), m_uvIndices.Base() ); + } + } + + return pVertexData; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeVertexDeltaData *CVertexData::AddDelta( CDmeMesh *pMesh, bool bAbsolute, const char *pName ) +{ + CDmeVertexDeltaData *pDelta( NULL ); + + if ( m_positions.Count() ) + { + CDmeVertexData *pBind = pMesh->FindBaseState( "bind" ); + if ( pBind == NULL ) + return NULL; + + const FieldIndex_t pBindIndex( pBind->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); + + if ( pBindIndex < 0 ) + return NULL; + + CDmrArrayConst< Vector > pBindData( pBind->GetVertexData( pBindIndex ) ); + + const int pCount( m_positions.Count() ); + if ( pBindData.Count() != pCount ) + return NULL; + + for ( int i( 0 ); i < pCount; ++i ) + { + m_positions[ i ] -= pBindData[ i ]; + } + + int *pIndices = reinterpret_cast< int * >( alloca( pCount * sizeof( int ) ) ); + int nNonZero( 0 ); + for ( int i( 0 ); i < pCount; ++i ) + { + const Vector &v( m_positions[ i ] ); + // Kind of a magic number but it's because of 16 bit compression of the delta values + if ( fabs( v.x ) >= ( 1 / 4096.0f ) || fabs( v.y ) >= ( 1 / 4096.0f ) || fabs( v.z ) >= ( 1 / 4096.0f ) ) + { + m_positions[ nNonZero ] = v; + pIndices[ nNonZero ] = i; + ++nNonZero; + } + } + + pDelta = pMesh->FindOrCreateDeltaState( pName ); + pDelta->FlipVCoordinate( true ); + pDelta->SetValue( "corrected", !bAbsolute ); + + const FieldIndex_t pIndex( pDelta->CreateField( CDmeVertexData::FIELD_POSITION ) ); + pDelta->AddVertexData( pIndex, nNonZero ); + pDelta->SetVertexData( pIndex, 0, nNonZero, AT_VECTOR3, m_positions.Base() ); + pDelta->SetVertexIndices( pIndex, 0, nNonZero, pIndices ); + + const FieldIndex_t nBindNormalIndex = pBind->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + if ( nBindNormalIndex >= 0 ) + { + CDmrArrayConst< Vector > bindNormalData( pBind->GetVertexData( nBindNormalIndex ) ); + const int nNormalCount = m_normals.Count(); + if ( bindNormalData.Count() == nNormalCount ) + { + for ( int i = 0; i < nNormalCount; ++i ) + { + m_normals[ i ] -= bindNormalData[ i ]; + } + + int *pNormalIndices = reinterpret_cast< int * >( stackalloc( nNormalCount * sizeof( int ) ) ); + int nNormalDeltaCount = 0; + for ( int i = 0; i < nNormalCount; ++i ) + { + const Vector &n = m_normals[ i ]; + // Kind of a magic number but it's because of 16 bit compression of the delta values + if ( fabs( n.x ) >= ( 1 / 4096.0f ) || fabs( n.y ) >= ( 1 / 4096.0f ) || fabs( n.z ) >= ( 1 / 4096.0f ) ) + { + m_normals[ nNormalDeltaCount ] = n; + pNormalIndices[ nNormalDeltaCount ] = i; + ++nNormalDeltaCount; + } + } + } + } + } + + return pDelta; +} + + +//----------------------------------------------------------------------------- +// Convert from DME -> OBJ +//----------------------------------------------------------------------------- +bool CDmObjSerializer::Serialize( CUtlBuffer &buf, CDmElement *pRoot ) +{ + return false; // For now +} + + +//----------------------------------------------------------------------------- +// Convert from OBJ -> DME +//----------------------------------------------------------------------------- +bool CDmObjSerializer::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) +{ + *ppRoot = ReadOBJ( buf, fileid, "bind" ); + return *ppRoot != NULL; +} + + +//----------------------------------------------------------------------------- +// Convert from OBJ -> DME +// If mesh is not NULL, the OBJ is added as a delta state to the mesh +//----------------------------------------------------------------------------- +CDmElement *CDmObjSerializer::ReadOBJ( + const char *pFilename, + CDmeMesh **ppCreatedMesh, + bool bLoadAllDeltas /* = true */, + bool bAbsolute /* = true */ ) +{ + char filename[ MAX_PATH ]; + Q_strncpy( filename, pFilename, sizeof( filename ) ); + Q_FixSlashes( filename ); + + CUtlBuffer utlBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + if ( !g_pFullFileSystem->ReadFile( filename, NULL, utlBuf ) ) + return NULL; + + char baseFile[ MAX_PATH ]; + Q_FileBase( filename, baseFile, sizeof( baseFile ) ); + + CDmeMesh *pMesh( NULL ); + + DmFileId_t nFileId = g_pDataModel->FindOrCreateFileId( pFilename ); + + CDmElement *pRoot = ReadOBJ( utlBuf, nFileId, baseFile, filename, NULL, &pMesh, bAbsolute ); + if ( pRoot && pMesh ) + { + if ( ppCreatedMesh ) + { + *ppCreatedMesh = pMesh; + } + + CDmeCombinationOperator *pCombo( NULL ); + + // Check if there are deltas in the directory with the same prefix + // But only if the rest of the file is <prefix>=<suffix>.obj or is <prefix>_zero.obj + + char *pSuffix = Q_strrchr( baseFile, '=' ); + if ( !pSuffix || !*pSuffix ) + { + pSuffix = Q_strrchr( baseFile, '_' ); + if ( !pSuffix || !*pSuffix ) + return pRoot; + + if ( Q_stricmp( pSuffix, "_zero" ) ) + return pRoot; + } + + char findGlob[ MAX_PATH ]; + Q_strncpy( findGlob, baseFile, sizeof( findGlob ) ); + pSuffix = findGlob + ( pSuffix - baseFile ); + *( pSuffix + 0 ) = '_'; // Just in case it was <prefix>=<suffix>.obj + *( pSuffix + 1 ) = '*'; + *( pSuffix + 2 ) = '.'; + Q_strncpy( pSuffix + 3, Q_GetFileExtension( filename ), sizeof( findGlob ) - ( pSuffix - findGlob + 3 ) ); + + char path[ MAX_PATH ]; + Q_ExtractFilePath( filename, path, sizeof( path ) ); + + m_objDirectory = path; + + char findPath[ MAX_PATH ]; + Q_ComposeFileName( path, findGlob, findPath, sizeof( findPath ) ); + + FileFindHandle_t hFind; + + char deltaFile[ MAX_PATH ]; + char deltaPath[ MAX_PATH ]; + + for ( const char *pFindFile( g_pFullFileSystem->FindFirst( findPath, &hFind ) ); pFindFile && *pFindFile; pFindFile = g_pFullFileSystem->FindNext( hFind ) ) + { + Q_FileBase( pFindFile, deltaFile, sizeof( deltaFile ) ); + + if ( Q_stricmp( baseFile, deltaFile ) ) + { + Q_ComposeFileName( path, pFindFile, deltaPath, sizeof( deltaPath ) ); + + if ( !g_pFullFileSystem->FileExists( deltaPath ) ) + continue; + + char *pControlName = strchr( deltaFile, '_' ); + if ( pControlName && *( pControlName + 1 ) ) + { + ++pControlName; + char *pDeltaName( pControlName ); + for ( char *pPlus( strchr( pDeltaName, '+' ) ); pPlus; pPlus = strchr( pPlus, '+' ) ) + { + *pPlus = '_'; + } + } + + if ( !strchr( pControlName, '_' ) ) + { + if ( pCombo == NULL ) + { + pCombo = CreateElement< CDmeCombinationOperator >( "combinationOperator", pRoot->GetFileId() ); + pRoot->SetValue( "combinationOperator", pCombo ); + } + } + + DeltaInfo_t &deltaInfo = m_deltas[ pControlName ]; + deltaInfo.m_filename = pFindFile; + deltaInfo.m_pMesh = pMesh; + deltaInfo.m_pComboOp = pCombo; + + if ( bLoadAllDeltas ) + { + GetDelta( pControlName, bAbsolute ); + } + } + } + + g_pFullFileSystem->FindClose( hFind ); + + if ( pCombo ) + { + pCombo->AddTarget( pMesh ); + pMesh->ComputeAllCorrectedPositionsFromActualPositions(); + } + } + + return pRoot; +} + + +//----------------------------------------------------------------------------- +// Common function both ReadOBJ & Unserialize can call +//----------------------------------------------------------------------------- +CDmElement *CDmObjSerializer::ReadOBJ( CUtlBuffer &buf, + DmFileId_t dmFileId, + const char *pName, + const char *pFilename /* = NULL */, + CDmeMesh *pBaseMesh /* = NULL */, + CDmeMesh **ppCreatedMesh /* = NULL */, + bool bAbsolute /* = true */ ) +{ + CDmElement *pRoot( NULL ); + CDmeModel *pModel( NULL ); + + if ( !pBaseMesh ) + { + pRoot = CreateElement< CDmElement >( "root", dmFileId ); + pModel = CreateElement< CDmeModel >( "model", dmFileId ); + + pRoot->SetValue( "skeleton", pModel ); + pRoot->SetValue( "model", pModel ); + } + + m_mtlLib.RemoveAll(); + + char tmpBuf0[ 4096 ]; + char tmpBuf1[ 4096 ]; + + characterset_t breakSet; + CharacterSetBuild( &breakSet, "/\\" ); + + const char *pBuf; + + Vector p; + Vector2D uv; + + CVertexData vertexData; + CFaceSetData faceSetData; + + CUtlString groupName; + + CDmeDag *pDmeDag( NULL ); + CDmeMesh *pDmeMesh( NULL ); + CUtlVector< int > *pFaceIndices( NULL ); + + while ( buf.IsValid() ) + { + buf.GetLine( tmpBuf0, sizeof( tmpBuf0 ) ); + + pBuf = SkipSpace( tmpBuf0 ); + + if ( sscanf( tmpBuf0, "v %f %f %f", &p.x, &p.y, &p.z ) == 3 ) + { + if ( pDmeDag ) + { + vertexData.AddToMesh( pDmeMesh, bAbsolute, "bind", false ); + faceSetData.AddToMesh( pDmeMesh ); + + pDmeDag = NULL; + pDmeMesh = NULL; + pFaceIndices = NULL; + } + + vertexData.AddPosition( p ); + + continue; + } + + if ( sscanf( pBuf, "vn %f %f %f", &p.x, &p.y, &p.z ) == 3 ) + { + vertexData.AddNormal( p ); + continue; + } + + if ( !pBaseMesh ) + { + if ( sscanf( pBuf, "vt %f %f", &uv.x, &uv.y ) == 2 ) + { + vertexData.AddUV( uv ); + continue; + } + + if ( pFilename && sscanf( pBuf, "mtllib %4096s", tmpBuf1 ) == 1 ) + { + CUtlString mtlLib( tmpBuf1 ); + + Q_strncpy( tmpBuf0, pFilename, sizeof( tmpBuf0 ) ); + Q_FixSlashes( tmpBuf0 ); + Q_StripFilename( tmpBuf0 ); + + char mtlLibPath[ MAX_PATH ]; + + Q_ComposeFileName( tmpBuf0, tmpBuf1, mtlLibPath, sizeof( mtlLibPath ) ); + CUtlBuffer utlBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + if ( g_pFullFileSystem->ReadFile( mtlLibPath, NULL, utlBuf ) ) + { + ParseMtlLib( utlBuf ); + } + + continue; + } + + if ( sscanf( pBuf, "usemtl %4096s", tmpBuf1 ) == 1 ) + { + // Remove any 'SG' suffix from the material + const uint sLen = Q_strlen( tmpBuf1 ); + if ( sLen && !Q_strcmp( tmpBuf1 + sLen - 2, "SG" ) ) + { + tmpBuf1[ sLen - 2 ] = '\0'; + } + + const char *pTexture( FindMtlEntry( tmpBuf1 ) ); + if ( pTexture ) + { + pFaceIndices = faceSetData.GetFaceSetIndices( pTexture ); + } + else + { + pFaceIndices = faceSetData.GetFaceSetIndices( tmpBuf1 ); + } + + continue; + } + + if ( sscanf( pBuf, "g %4096s", tmpBuf1 ) == 1 ) + { + groupName = tmpBuf1; + if ( pFaceIndices == NULL ) + { + pFaceIndices = faceSetData.GetFaceSetIndices( tmpBuf1 ); + } + continue; + } + if ( *pBuf == 'f' && ( *( pBuf + 1 ) == ' ' || *( pBuf + 1 ) == '\t' ) ) + { + if ( pDmeDag == NULL ) + { + pDmeDag = CreateElement< CDmeDag >( pName ? pName : ( groupName.IsEmpty() ? "obj" : groupName.Get() ), pRoot->GetFileId() ); + Assert( pDmeDag ); + pDmeMesh = CreateElement< CDmeMesh >( pName ? pName : ( groupName.IsEmpty() ? "obj" : groupName.Get() ), pRoot->GetFileId() ); + if ( ppCreatedMesh && *ppCreatedMesh == NULL ) + { + // Only the first mesh created... + *ppCreatedMesh = pDmeMesh; + } + pDmeDag->SetShape( pDmeMesh ); + if ( pModel ) + { + pModel->AddJoint( pDmeDag ); + pModel->AddChild( pDmeDag ); + } + } + + if ( pFaceIndices == NULL ) + { + pFaceIndices = faceSetData.GetFaceSetIndices( "facetSet" ); + } + + int v; + int t; + int n; + + pBuf = SkipSpace( pBuf + 1 ); + int nLen = Q_strlen( pBuf ); + + CUtlBuffer bufParse( pBuf, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + + while ( bufParse.IsValid() ) + { + if ( !ParseVertex( bufParse, breakSet, v, t, n ) ) + break; + + pFaceIndices->AddToTail( vertexData.VertexCount() ); + if ( v > 0 ) + { + vertexData.AddPositionIndex( v - 1 ); + } + + if ( n > 0 ) + { + vertexData.AddNormalIndex( n - 1 ); + } + + if ( t > 0 ) + { + vertexData.AddUVIndex( t - 1 ); + } + } + + pFaceIndices->AddToTail( -1 ); + continue; + } + } + } + + CDmeVertexDataBase *pVertexData( NULL ); + + if ( pBaseMesh ) + { + pVertexData = vertexData.AddToMesh( pBaseMesh, bAbsolute, pName, true ); + } + else + { + pVertexData = vertexData.AddToMesh( pDmeMesh, bAbsolute, "bind", false ); + faceSetData.AddToMesh( pDmeMesh ); + } + + if ( pModel ) + { + pModel->CaptureJointsToBaseState( "bind" ); + } + + if ( pBaseMesh ) + return pVertexData; + + return pRoot; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmObjSerializer::OutputVectors( + CUtlBuffer &b, + const char *pPrefix, + const CUtlVector< Vector > &vData, + const matrix3x4_t &matrix ) +{ + Vector v; + + const int nv( vData.Count() ); + + for ( int i( 0 ); i < nv; ++i ) + { + VectorTransform( vData[ i ], matrix, v ); + b << pPrefix << v << "\n"; + } + + return nv; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDmObjSerializer::OutputVectors( + CUtlBuffer &b, + const char *pPrefix, + const CUtlVector< Vector2D > &vData ) +{ + Vector v; + + const int nv( vData.Count() ); + + for ( int i( 0 ); i < nv; ++i ) + { + b << pPrefix << vData[ i ] << "\n"; + } + + return nv; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmObjSerializer::MeshToObj( + CUtlBuffer &b, + const matrix3x4_t &parentWorldMatrix, + CDmeMesh *pMesh, + const char *pDeltaName, + bool absolute ) +{ + CUtlVector< CDmeMesh::DeltaComputation_t > compList; + + if ( pDeltaName ) + { + pMesh->ComputeDependentDeltaStateList( compList ); + } + + const int nCompList( compList.Count() ); + + CDmeVertexData *pBase( pMesh->FindBaseState( "bind" ) ); + if ( !pBase ) + return; + + const FieldIndex_t nPosIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_POSITION ) ); + if ( nPosIndex < 0 ) + return; + + int nPositionCount = 0; + int nTextureCount = 0; + int nNormalCount = 0; + + b << "g " << pMesh->GetName() << "\n"; + + CDmrArrayConst< Vector > pArray( pBase->GetVertexData( nPosIndex ) ); + const CUtlVector< int > *ppIndices( &pBase->GetVertexIndexData( nPosIndex ) ); + const CUtlVector< Vector > &pConstData( pArray.Get() ); + + if ( nCompList ) + { + CUtlVector< Vector > pData; + pData.CopyArray( pConstData.Base(), pConstData.Count() ); + + if ( absolute ) + { + for ( int i ( 0 ); i < nCompList; ++i ) + { + CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) ); + if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) ) + continue; + + b << "# Delta: " << pTmpDeltaState->GetName() << "\n"; + pMesh->AddDelta( pTmpDeltaState, pData.Base(), pData.Count(), CDmeVertexData::FIELD_POSITION, 1.0f ); + + const CUtlVector< int > &depDeltas( compList[ i ].m_DependentDeltas ); + const int nDepDeltas( depDeltas.Count() ); + for ( int j( 0 ); j < nDepDeltas; ++j ) + { + pTmpDeltaState = pMesh->GetDeltaState( depDeltas[ j ] ); + b << "# Dependent Delta: " << pTmpDeltaState->GetName() << "\n"; + pMesh->AddDelta( pTmpDeltaState, pData.Base(), pData.Count(), CDmeVertexData::FIELD_POSITION, 1.0f ); + } + } + } + else + { + for ( int i ( 0 ); i < nCompList; ++i ) + { + CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) ); + if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) ) + continue; + + b << "# Delta: " << pTmpDeltaState->GetName() << "\n"; + pMesh->AddDelta( pTmpDeltaState, pData.Base(), pData.Count(), CDmeVertexData::FIELD_POSITION, 1.0f ); + } + } + + nPositionCount = OutputVectors( b, "v ", pData, parentWorldMatrix ); + } + else + { + nPositionCount = OutputVectors( b, "v ", pConstData, parentWorldMatrix ); + } + + const CUtlVector< int > *puvIndices( NULL ); + const FieldIndex_t uvIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ) ); + if ( uvIndex >= 0 ) + { + CDmrArrayConst< Vector2D > uvArray( pBase->GetVertexData( uvIndex ) ); + const CUtlVector< Vector2D > &uvData( uvArray.Get() ); + puvIndices = &pBase->GetVertexIndexData( uvIndex ); + + nTextureCount = OutputVectors( b, "vt ", uvData ); + } + + const CUtlVector< int > *pnIndices( NULL ); + const FieldIndex_t nIndex( pBase->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ) ); + if ( nIndex >= 0 ) + { + matrix3x4_t normalMatrix; + MatrixInverseTranspose( parentWorldMatrix, normalMatrix ); + + CDmrArrayConst< Vector > nArray( pBase->GetVertexData( nIndex ) ); + const CUtlVector< Vector > &nConstData( nArray.Get() ); + pnIndices = &pBase->GetVertexIndexData( nIndex ); + + if ( nCompList ) + { + CUtlVector< Vector > nData; + nData.CopyArray( nConstData.Base(), nConstData.Count() ); + + if ( absolute ) + { + for ( int i ( 0 ); i < nCompList; ++i ) + { + CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) ); + if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) ) + continue; + + b << "# Delta: " << pTmpDeltaState->GetName() << "\n"; + pMesh->AddDelta( pTmpDeltaState, nData.Base(), nData.Count(), CDmeVertexData::FIELD_NORMAL, 1.0f ); + + const CUtlVector< int > &depDeltas( compList[ i ].m_DependentDeltas ); + const int nDepDeltas( depDeltas.Count() ); + for ( int j( 0 ); j < nDepDeltas; ++j ) + { + pTmpDeltaState = pMesh->GetDeltaState( depDeltas[ j ] ); + b << "# Dependent Delta: " << pTmpDeltaState->GetName() << "\n"; + pMesh->AddDelta( pTmpDeltaState, nData.Base(), nData.Count(), CDmeVertexData::FIELD_NORMAL, 1.0f ); + } + } + } + else + { + for ( int i ( 0 ); i < nCompList; ++i ) + { + CDmeVertexDeltaData *pTmpDeltaState( pMesh->GetDeltaState( compList[ i ].m_nDeltaIndex ) ); + if ( Q_strcmp( pTmpDeltaState->GetName(), pDeltaName ) ) + continue; + + b << "# Delta: " << pTmpDeltaState->GetName() << "\n"; + pMesh->AddDelta( pTmpDeltaState, nData.Base(), nData.Count(), CDmeVertexData::FIELD_NORMAL, 1.0f ); + } + } + + nNormalCount = OutputVectors( b, "vn ", nData, normalMatrix ); + } + else + { + nNormalCount = OutputVectors( b, "vn ", nConstData, normalMatrix ); + } + } + + const int pCount( ppIndices->Count() ); + const int uvCount( puvIndices ? puvIndices->Count() : 0 ); + const int nCount( pnIndices ? pnIndices->Count() : 0 ); + + const int nFaceSets( pMesh->FaceSetCount() ); + for ( int i= 0 ; i < nFaceSets; ++i ) + { + CDmeFaceSet *pFaceSet( pMesh->GetFaceSet( i ) ); + CDmeMaterial *pMaterial( pFaceSet->GetMaterial() ); + if ( pMaterial ) + { + b << "usemtl " << pMaterial->GetMaterialName() << "\n"; + } + + const int nIndices( pFaceSet->NumIndices() ); + const int *pnFaceSetIndex( pFaceSet->GetIndices() ); + const int *const pEnd( pnFaceSetIndex + nIndices ); + int fIndex; + + const char *const pFaceStart( "f " ); + const char *const pFaceNext( " " ); + const char *pFaceSep( pFaceStart ); + + if ( pCount == uvCount && pCount == nCount ) + { + const CUtlVector< int > &pIndices( *ppIndices ); + const CUtlVector< int > &uvIndices( *puvIndices ); + const CUtlVector< int > &nvIndices( *pnIndices ); + + while ( pnFaceSetIndex < pEnd ) + { + fIndex = *pnFaceSetIndex++; + if ( fIndex < 0 ) + { + b << "\n"; + pFaceSep = pFaceStart; + continue; + } + + b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << '/' << ( uvIndices[ fIndex ] + m_nTextureOffset ) << '/' << ( nvIndices[ fIndex ] + m_nNormalOffset ); + pFaceSep = pFaceNext; + } + } + else if ( pCount == uvCount ) + { + const CUtlVector< int > &pIndices( *ppIndices ); + const CUtlVector< int > &uvIndices( *puvIndices ); + + while ( pnFaceSetIndex < pEnd ) + { + fIndex = *pnFaceSetIndex++; + if ( fIndex < 0 ) + { + b << "\n"; + pFaceSep = pFaceStart; + continue; + } + + b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << '/' << ( uvIndices[ fIndex ] + m_nTextureOffset ) << '/'; + pFaceSep = pFaceNext; + } + } + else if ( pCount == nCount ) + { + const CUtlVector< int > &pIndices( *ppIndices ); + const CUtlVector< int > &nvIndices( *pnIndices ); + + while ( pnFaceSetIndex < pEnd ) + { + fIndex = *pnFaceSetIndex++; + if ( fIndex < 0 ) + { + b << "\n"; + pFaceSep = pFaceStart; + continue; + } + + b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << "//" << ( nvIndices[ fIndex ] + m_nNormalOffset ); + pFaceSep = pFaceNext; + } + } + else + { + const CUtlVector< int > &pIndices( *ppIndices ); + + while ( pnFaceSetIndex < pEnd ) + { + fIndex = *pnFaceSetIndex++; + if ( fIndex < 0 ) + { + b << "\n"; + pFaceSep = pFaceStart; + continue; + } + + b << pFaceSep << ( pIndices[ fIndex ] + m_nPositionOffset ) << "//"; + pFaceSep = pFaceNext; + } + } + } + + m_nPositionOffset += nPositionCount; + m_nTextureOffset += nTextureCount; + m_nNormalOffset += nNormalCount; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmObjSerializer::DagToObj( + CUtlBuffer &b, + const matrix3x4_t &parentWorldMatrix, + CDmeDag *pDag, + const char *pDeltaName, + bool absolute ) +{ + matrix3x4_t inclusiveMatrix; + pDag->GetTransform()->GetTransform( inclusiveMatrix ); + + ConcatTransforms( parentWorldMatrix, inclusiveMatrix, inclusiveMatrix ); + + CDmeMesh *pMesh( CastElement< CDmeMesh >( pDag->GetShape() ) ); + if ( pMesh ) + { + MeshToObj( b, inclusiveMatrix, pMesh, pDeltaName, absolute ); + } + + const int nChildren( pDag->GetChildCount() ); + for ( int i( 0 ); i < nChildren; ++i ) + { + DagToObj( b, inclusiveMatrix, pDag->GetChild( i ), pDeltaName, absolute ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmObjSerializer::FindDeltaMeshes( CDmeDag *pDag, CUtlVector< CDmeMesh * > &meshes ) +{ + CDmeMesh *pMesh( CastElement< CDmeMesh >( pDag->GetShape() ) ); + if ( pMesh && pMesh->DeltaStateCount() ) + { + meshes.AddToTail( pMesh ); + } + + const int nChildren( pDag->GetChildCount() ); + for ( int i( 0 ); i < nChildren; ++i ) + { + FindDeltaMeshes( pDag->GetChild( i ), meshes ); + } +} + + +//----------------------------------------------------------------------------- +// Convert from OBJ -> DME +//----------------------------------------------------------------------------- +bool CDmObjSerializer::WriteOBJ( const char *pFilename, CDmElement *pRoot, bool bWriteOBJs, const char *pDeltaName, bool absolute ) +{ + CDmeDag *pModel = pRoot->GetValueElement< CDmeDag >( "model" ); + + if ( !pModel ) + return false; + + matrix3x4_t identityMatrix; + SetIdentityMatrix( identityMatrix ); + + if ( !pDeltaName ) + { + CUtlBuffer b( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + b << "# OBJ\n"; + b << "#\n"; + + m_nPositionOffset = 1; // OBJs start indexing at 1 + m_nTextureOffset = 1; + m_nNormalOffset = 1; + + DagToObj( b, identityMatrix, pModel, pDeltaName, absolute ); + + g_pFullFileSystem->WriteFile( pFilename, NULL, b ); + + // Filesystem is silly + // On WIN32 filesystem changes all of the characters to lowercase grrrr..... + rename( pFilename, pFilename ); + } + + if ( !bWriteOBJs ) + return true; + + CUtlVector< CDmeMesh * > deltaMeshes; + FindDeltaMeshes( pModel, deltaMeshes ); + + if ( deltaMeshes.Count() ) + { + char base[ MAX_PATH ]; + Q_FileBase( pFilename, base, sizeof( base ) ); + + char path[ MAX_PATH ]; + Q_ExtractFilePath( pFilename, path, sizeof( path ) ); + + char *pSuffix = strchr( base, '=' ); + if ( !pSuffix ) + { + pSuffix = strchr( base, '_' ); + } + + if ( pSuffix ) + { + *( pSuffix + 0 ) = '_'; + *( pSuffix + 1 ) = '\0'; + } + + char filename[ MAX_PATH ]; + + const int nDeltaMeshes( deltaMeshes.Count() ); + for ( int i( 0 ); i < nDeltaMeshes; ++i ) + { + CDmeMesh *pDeltaMesh( deltaMeshes[ i ] ); + const int nDeltas( pDeltaMesh->DeltaStateCount() ); + for ( int j( 0 ); j < nDeltas; ++j ) + { + CDmeVertexDeltaData *pDelta( pDeltaMesh->GetDeltaState( j ) ); + + if ( !pDeltaName || !Q_strcmp( pDeltaName, pDelta->GetName() ) ) + { + CUtlBuffer b( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + b << "# Delta OBJ: " << pDelta->GetName() << "\n"; + b << "#\n"; + + Q_strncpy( filename, pDelta->GetName(), sizeof( filename ) ); + // Change _ to + + const char *const pEnd( filename + sizeof( filename ) ); + for ( char *pChar = filename; *pChar && pChar < pEnd; ++pChar ) + { + if ( *pChar == '_' ) + { + *pChar = '+'; + } + } + + CUtlString deltaFile( base ); + deltaFile += filename; + deltaFile += ".obj"; + + m_nPositionOffset = 1; // OBJs use 1 based indexes + m_nTextureOffset = 1; + m_nNormalOffset = 1; + + DagToObj( b, identityMatrix, pModel, pDelta->GetName(), absolute ); + + Q_ComposeFileName( path, deltaFile.Get(), filename, sizeof( filename ) ); + g_pFullFileSystem->WriteFile( filename, NULL, b ); + // On WIN32 filesystem changes all of the characters to lowercase grrrr..... + rename( filename, filename ); + } + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmObjSerializer::ParseMtlLib( CUtlBuffer &buf ) +{ + char tmpBuf0[ 4096 ]; + + int nCurrentMtl = -1; + while ( buf.IsValid() ) + { + buf.GetLine( tmpBuf0, sizeof(tmpBuf0) ); + + if ( StringHasPrefix( tmpBuf0, "newmtl " ) ) + { + char mtlName[1024]; + if ( sscanf( tmpBuf0, "newmtl %s", mtlName ) == 1 ) + { + // Remove any 'SG' suffix from the material + const uint sLen = Q_strlen( mtlName ); + if ( sLen > 2 && !Q_strcmp( mtlName + sLen - 2, "SG" ) ) + { + mtlName[ sLen - 2 ] = '\0'; + } + + nCurrentMtl = m_mtlLib.AddToTail( ); + m_mtlLib[nCurrentMtl].m_MtlName = mtlName; + m_mtlLib[nCurrentMtl].m_TgaName = mtlName; + } + continue; + } + + if ( StringHasPrefix( tmpBuf0, "map_Kd " ) ) + { + if ( nCurrentMtl < 0 ) + continue; + + char tgaPath[MAX_PATH]; + char tgaName[1024]; + if ( sscanf( tmpBuf0, "map_Kd %s", tgaPath ) == 1 ) + { + // Try a cheesy hack - look for /materialsrc/ and set the material name off the entire path minus extension + Q_strncpy( tmpBuf0, tgaPath, sizeof( tmpBuf0 ) ); + Q_FixSlashes( tmpBuf0, '/' ); + const char *pMaterialSrc = Q_strstr( tmpBuf0, "/materialsrc/" ); + if ( pMaterialSrc ) + { + pMaterialSrc += Q_strlen( "/materialsrc/" ); + Q_StripExtension( pMaterialSrc, tgaName, sizeof( tgaName) ); + m_mtlLib[ nCurrentMtl ].m_TgaName = tgaName; + } + else + { + Q_FileBase( tgaPath, tgaName, sizeof(tgaName) ); + m_mtlLib[nCurrentMtl].m_TgaName = tgaName; + } + } + continue; + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDmObjSerializer::FindMtlEntry( const char *pTgaName ) +{ + int nCount = m_mtlLib.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( !Q_stricmp( m_mtlLib[i].m_MtlName, pTgaName ) ) + return m_mtlLib[i].m_TgaName; + } + return pTgaName; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmObjSerializer::ParseVertex( CUtlBuffer& bufParse, characterset_t &breakSet, int &v, int &t, int &n ) +{ + char cmd[1024]; + int nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false ); + if ( nLen <= 0 ) + return false; + + v = atoi( cmd ); + n = 0; + t = 0; + + char c = *(char*)bufParse.PeekGet(); + bool bHasTexCoord = IN_CHARACTERSET( breakSet, c ) != 0; + bool bHasNormal = false; + if ( bHasTexCoord ) + { + // Snag the '/' + nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false ); + Assert( nLen == 1 ); + + c = *(char*)bufParse.PeekGet(); + if ( !IN_CHARACTERSET( breakSet, c ) ) + { + nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false ); + Assert( nLen > 0 ); + t = atoi( cmd ); + + c = *(char*)bufParse.PeekGet(); + bHasNormal = IN_CHARACTERSET( breakSet, c ) != 0; + } + else + { + bHasNormal = true; + bHasTexCoord = false; + } + + if ( bHasNormal ) + { + // Snag the '/' + nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false ); + Assert( nLen == 1 ); + + nLen = bufParse.ParseToken( &breakSet, cmd, sizeof(cmd), false ); + Assert( nLen > 0 ); + n = atoi( cmd ); + } + } + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDmObjSerializer::SkipSpace( + const char *pBuf ) +{ + while ( *pBuf == ' ' || *pBuf == '\t' ) + ++pBuf; + + return pBuf; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeVertexDeltaData *CDmObjSerializer::GetDelta( const char *pDeltaName, bool bAbsolute ) +{ + if ( !m_deltas.Defined( pDeltaName ) ) + return NULL; + + DeltaInfo_t &deltaInfo( m_deltas[ pDeltaName ] ); + + if ( deltaInfo.m_pDeltaData ) + return deltaInfo.m_pDeltaData; + + if ( !LoadDependentDeltas( pDeltaName ) ) + return NULL; + + CUtlBuffer utlBuf; + + char deltaPath[ MAX_PATH ]; + Q_ComposeFileName( m_objDirectory, deltaInfo.m_filename, deltaPath, sizeof( deltaPath ) ); + Q_FixSlashes( deltaPath ); + + if ( !g_pFullFileSystem->ReadFile( deltaPath, NULL, utlBuf ) ) + return NULL; + + if ( deltaInfo.m_pComboOp && !strchr( pDeltaName, '_' ) ) + { + deltaInfo.m_pComboOp->FindOrCreateControl( pDeltaName, false, true ); + } + + deltaInfo.m_pDeltaData = CastElement< CDmeVertexDeltaData >( ReadOBJ( utlBuf, deltaInfo.m_pMesh->GetFileId(), pDeltaName, deltaPath, deltaInfo.m_pMesh, NULL, bAbsolute ) ); + + return deltaInfo.m_pDeltaData; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmObjSerializer::LoadDependentDeltas( const char *pDeltaName ) +{ + // TODO: Load Dependent Deltas + return true; +} + + +//----------------------------------------------------------------------------- +// Counts the number of _'s in a string +//----------------------------------------------------------------------------- +int ComputeDimensionality( const char *pDeltaName ) +{ + const char *pUnderBar = pDeltaName; + int nDimensions = 0; + + while ( pUnderBar ) + { + ++nDimensions; + pUnderBar = strchr( pUnderBar, '_' ); + if ( pUnderBar ) + { + ++pUnderBar; + } + } + + return nDimensions; +} + +/* +//----------------------------------------------------------------------------- +// Generates a sorted list in order of dimensionality of the delta states +// NOTE: This assumes a naming scheme where delta state names have _ that separate control names +//----------------------------------------------------------------------------- +void CDmObjSerializer::ComputeDeltaStateComputationList( CUtlVector< CUtlVector< int > > &dependentDeltaList ) +{ + // Do all combinations in order of dimensionality, lowest dimension first + + for ( int i = 0; i < nDeltas; ++i ) + { + compList[i].m_nDeltaIndex = i; + compList[i].m_nDimensionality = ComputeDeltaStateDimensionality( i ); + } + qsort( compList.Base(), nCount, sizeof(DeltaComputation_t), DeltaStateLessFunc ); +} + + + +{ + CUtlVector< CUtlString > atomicControls; + deltaStateUsage.SetCount( nCount ); + + // Build a list of atomic controls + int nCurrentDelta; + for ( nCurrentDelta = 0; nCurrentDelta < nCount; ++nCurrentDelta ) + { + if ( pInfo[nCurrentDelta].m_nDimensionality != 1 ) + break; + int j = atomicControls.AddToTail( GetDeltaState( pInfo[nCurrentDelta].m_nDeltaIndex )->GetName() ); + deltaStateUsage[ nCurrentDelta ].AddToTail( j ); + } + + for ( ; nCurrentDelta < nCount; ++nCurrentDelta ) + { + CDmeVertexDeltaData *pDeltaState = GetDeltaState( pInfo[nCurrentDelta].m_nDeltaIndex ); + int nLen = Q_strlen( pDeltaState->GetName() ); + char *pTempBuf = (char*)_alloca( nLen + 1 ); + memcpy( pTempBuf, pDeltaState->GetName(), nLen+1 ); + char *pNext; + for ( char *pUnderBar = pTempBuf; pUnderBar; pUnderBar = pNext ) + { + pNext = strchr( pUnderBar, '_' ); + if ( pNext ) + { + *pNext = 0; + ++pNext; + } + + // Find this name in the list of strings + int j; + int nControlCount = atomicControls.Count(); + for ( j = 0; j < nControlCount; ++j ) + { + if ( !Q_stricmp( pUnderBar, atomicControls[j] ) ) + break; + } + if ( j == nControlCount ) + { + j = atomicControls.AddToTail( pUnderBar ); + } + deltaStateUsage[ nCurrentDelta ].AddToTail( j ); + } + deltaStateUsage[ nCurrentDelta ].Sort( DeltaStateUsageLessFunc ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmObjSerializer::ComputeDependentUsage( CUtlVector< CUtlVector< int > > &deltaUsage ) +{ + const int nDeltas = m_deltas.GetNumStrings(); + compList.EnsureCount( nDeltas ); + + CUtlVector< CUtlVector< int > > deltaStateUsage; + const int nCount( compList.Count() ); + BuildAtomicControlLists( nCount, compList.Base(), deltaStateUsage ); + + // Now build up a list of dependent delta states based on usage + // NOTE: Usage is sorted in ascending order. + for ( int i = 1; i < nCount; ++i ) + { + int nUsageCount1 = deltaStateUsage[i].Count(); + for ( int j = 0; j < i; ++j ) + { + // At the point they have the same dimensionality, no more need to check + if ( compList[j].m_nDimensionality == compList[i].m_nDimensionality ) + break; + + int ii = 0; + bool bSubsetFound = true; + int nUsageCount2 = deltaStateUsage[j].Count(); + for ( int ji = 0; ji < nUsageCount2; ++ji ) + { + for ( bSubsetFound = false; ii < nUsageCount1; ++ii ) + { + if ( deltaStateUsage[j][ji] == deltaStateUsage[i][ii] ) + { + ++ii; + bSubsetFound = true; + break; + } + + if ( deltaStateUsage[j][ji] < deltaStateUsage[i][ii] ) + break; + } + + if ( !bSubsetFound ) + break; + } + + if ( bSubsetFound ) + { + compList[i].m_DependentDeltas.AddToTail( compList[j].m_nDeltaIndex ); + } + } + } +} +*/
\ No newline at end of file diff --git a/movieobjects/dmsmdserializer.cpp b/movieobjects/dmsmdserializer.cpp new file mode 100644 index 0000000..f1299cc --- /dev/null +++ b/movieobjects/dmsmdserializer.cpp @@ -0,0 +1,1706 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Read SMD and create DMX data +// +//============================================================================= + + +// Because we use STL +#pragma warning( disable: 4530 ) + + +// Standard includes +#include <io.h> +#include <algorithm> +#include <deque> +#include <fstream> +#include <list> +#include <map> +#include <string> + + +// Valve includes +#include "movieobjects/dmeanimationlist.h" +#include "movieobjects/dmechannel.h" +#include "movieobjects/dmedag.h" +#include "movieobjects/dmemesh.h" +#include "movieobjects/dmefaceset.h" +#include "movieobjects/dmematerial.h" +#include "movieobjects/dmemodel.h" +#include "movieobjects/dmsmdserializer.h" +#include "filesystem.h" +#include "tier1/characterset.h" +#include "tier1/fmtstr.h" +#include "tier2/tier2.h" +#include "mathlib/mathlib.h" + + +// Last include +#include "tier0/memdbgon.h" + + +//============================================================================= +// +// CDmSmdSerializer +// +//============================================================================= +//----------------------------------------------------------------------------- +// Convert from SMD -> DME +//----------------------------------------------------------------------------- +bool CDmSmdSerializer::Unserialize( + CUtlBuffer &utlBuf, + const char * /* pszEncodingName */, + int /* nEncodingVersion */, + const char * /* pszSourceFormatName */, + int /* nSourceFormatVersion */, + DmFileId_t nDmFileId, + DmConflictResolution_t /* nDmConflictResolution */, + CDmElement **ppDmRoot ) +{ + if ( !ppDmRoot ) + return false; + + const char *pszFilename = g_pDataModel->GetFileName( nDmFileId ); + + if ( pszFilename ) + { + char szFilename[ MAX_PATH ]; + V_strncpy( szFilename, pszFilename, ARRAYSIZE( szFilename ) ); + V_FixSlashes( szFilename ); + + *ppDmRoot = ReadSMD( utlBuf, nDmFileId, szFilename, NULL ); + } + else + { + *ppDmRoot = ReadSMD( utlBuf, nDmFileId, "utlBuffer", NULL ); + } + + return *ppDmRoot != NULL; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmElement *CDmSmdSerializer::ReadSMD( + const char *pszFilename, + CDmeMesh **ppDmeMeshCreated /* = NULL */ ) +{ + char szFilename[ MAX_PATH ]; + V_strncpy( szFilename, pszFilename, ARRAYSIZE( szFilename ) ); + V_FixSlashes( szFilename ); + + CUtlBuffer utlBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + if ( !g_pFullFileSystem->ReadFile( szFilename, NULL, utlBuf ) ) + return NULL; + + DmFileId_t nDmFileId = g_pDataModel->FindOrCreateFileId( pszFilename ); + + return ReadSMD( utlBuf, nDmFileId, szFilename, ppDmeMeshCreated ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmSmdSerializer::SetUpAxis( CDmSmdSerializer::Axis_t nUpAxis ) +{ + m_nUpAxis = clamp( nUpAxis, X_AXIS, Z_AXIS ); + + switch ( m_nUpAxis ) + { + case X_AXIS: // X Up + AngleMatrix( RadianEuler( -M_PI / 2.0, M_PI / 2.0, 0.0 ), m_mAdj ); + MatrixInverseTranspose( m_mAdj, m_mAdjNormal ); + break; + case Y_AXIS: // Y Up + SetIdentityMatrix( m_mAdj ); + SetIdentityMatrix( m_mAdjNormal ); + break; + case Z_AXIS: + default: + AngleMatrix( RadianEuler( -M_PI / 2.0, 0.0, 0.0 ), m_mAdj ); + MatrixInverseTranspose( m_mAdj, m_mAdjNormal ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Tests whether the passed buffer is an all whitespace line or not +//----------------------------------------------------------------------------- +static bool ParserIsBlankLine( const char *pszBuf ) +{ + for ( const char *pChar = pszBuf; *pChar; ++pChar ) + { + if ( !V_isspace( static_cast< unsigned char >( *pChar ) ) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Skips over whitespace +//----------------------------------------------------------------------------- +static const char *ParserSkipSpace( const char *pszBuf ) +{ + // Skip to first non-whitespace character + for ( ;; ) + { + if ( !V_isspace( static_cast< unsigned char >( *pszBuf ) ) ) + break; + + ++pszBuf; + } + + return pszBuf; +} + + +//----------------------------------------------------------------------------- +// Returns true the specified buffer is a comment line +// meaning that it starts with // (with optional white space preceding the //) +//----------------------------------------------------------------------------- +static bool IsCommentLine( const char *pszBuf ) +{ + pszBuf = ParserSkipSpace( pszBuf ); + + if ( *pszBuf && *pszBuf == '/' && *( pszBuf + 1 ) == '/' ) + { + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool ParserHandleVersion( const char *pszBuf, int *pnVersion = NULL ) +{ + pszBuf = ParserSkipSpace( pszBuf ); + + if ( !( *pszBuf && V_strnicmp( pszBuf, "version", 7 ) == 0 ) ) + return false; + + if ( pnVersion ) + { + *pnVersion = strtol( pszBuf + 7, NULL, 0 ); // Skip past "version" + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool ParserHandleSectionStart( const char *pszBuf, const char *pszSectionId ) +{ + pszBuf = ParserSkipSpace( pszBuf ); + + if ( ! *pszBuf ) + return false; + + const int nCmd = V_stricmp( pszBuf, pszSectionId ); + if ( !nCmd ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool ParserHandleTime( const char *pszBuf, int &nTime ) +{ + if ( sscanf( pszBuf, "time %d", &nTime ) == 1 ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Strip trailing CR/NL +//----------------------------------------------------------------------------- +static void Chomp( char *pszBuf ) +{ + char *pChar = pszBuf + V_strlen( pszBuf ) - 1; + + while ( pChar >= pszBuf && ( *pChar == '\n' || *pChar == '\r' ) ) + { + *pChar = '\0'; + } +} + + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void GetLine( CUtlBuffer &utlBuf, char *pszBuf, int nMaxLen ) +{ + utlBuf.GetLine( pszBuf, nMaxLen ); + Chomp( pszBuf ); +} + + +//============================================================================= +// +//============================================================================= +class CQcData +{ +public: + CQcData() + : m_nUpAxis( CDmSmdSerializer::Z_AXIS ) + , m_scale( 1.0f ) + {} + + bool ParseQc( const CUtlString &smdPath, const CUtlString &qcPath ); + + bool GetQcData( const CUtlString &smdPath ); + + CDmSmdSerializer::Axis_t m_nUpAxis; + float m_scale; + std::list< std::string > m_cdmaterials; +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool HandleQcHints( + const char *pBuf, + CQcData &qcData ) +{ + if ( !IsCommentLine( pBuf ) ) + return false; + + char key[ 512 ]; + key[0] = '\0'; + + char val[ 512 ]; + val[0] = '\0'; + + if ( sscanf( pBuf, "// %511s=%511s", key, val ) == 2 ) + { + if ( Q_stricmp( key, "UPAXIS" ) == 0 ) + { + if ( strpbrk( val, "xX" ) ) + { + qcData.m_nUpAxis = CDmSmdSerializer::X_AXIS; + } + else if ( strpbrk( val, "yY" ) ) + { + qcData.m_nUpAxis = CDmSmdSerializer::Y_AXIS; + } + else if ( strpbrk( val, "zZ" ) ) + { + qcData.m_nUpAxis = CDmSmdSerializer::Z_AXIS; + } + } + } + + key[ ARRAYSIZE(key) - 1 ] = '\0'; + val[ ARRAYSIZE(val) - 1 ] = '\0'; + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void Tokenize( CUtlVector< CUtlString > &tokens, const char *pszBuf ) +{ + tokens.RemoveAll(); + CUtlString strBuf( pszBuf ); + + for ( char *pszToken = strtok( (char *)strBuf.Get(), " \t\n" ); pszToken; pszToken = strtok( NULL, " \t\n" ) ) + { + tokens.AddToTail( pszToken ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmSmdSerializer::ParserGetNodeName( const char *pszBuf, CUtlString &sName ) const +{ + sName = ParserSkipSpace( pszBuf ); + FixNodeName( sName ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDmSmdSerializer::ParserHandleSkeletonLine( + const char *pszBuf, + CUtlString &sName, + int &nId, + int &nParentId ) const +{ + const char *pszOrigBuf = pszBuf; + + pszBuf = ParserSkipSpace( pszBuf ); + + char szTmpBuf[ 512 ]; + if ( sscanf( pszBuf, "%d \"%[^\"]\" %d", &nId, szTmpBuf, &nParentId ) == 3 ) + { + ParserGetNodeName( szTmpBuf, sName ); + return true; + } + + Warning( "Warning! Ignoring malformed skeleton line: %s\n", pszOrigBuf ); + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static CUtlString PathJoin( const char *pszStr1, const char *pszStr2 ) +{ + char szPath[MAX_PATH]; + V_ComposeFileName( pszStr1, pszStr2, szPath, sizeof( szPath ) ); + return CUtlString( szPath ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CQcData::ParseQc( + const CUtlString &smdPath, + const CUtlString &qcPath ) +{ + bool bRetVal = false; + + if ( _access( qcPath.Get(), 04 ) == 0 ) + { + try + { + std::string buf; + std::ifstream ifs( qcPath.Get() ); + + CUtlVector< CUtlString > tokens; + + while ( std::getline( ifs, buf ) ) + { + Tokenize( tokens, buf.c_str() ); + + if ( tokens.Count() < 1 ) + continue; + + if ( !V_stricmp( tokens[0], "$upaxis" ) ) + { + if ( strchr( tokens[1].Get(), 'y' ) || strchr( tokens[1].Get(), 'Y' ) ) + { + m_nUpAxis = CDmSmdSerializer::Y_AXIS; + } + else if ( strchr( tokens[1].Get(), 'x' ) || strchr( tokens[1].Get(), 'X' ) ) + { + m_nUpAxis = CDmSmdSerializer::X_AXIS; + } + else + { + m_nUpAxis = CDmSmdSerializer::Z_AXIS; + } + } + else if ( !V_stricmp( tokens[0], "$scale" ) ) + { + m_scale = strtod( tokens[1].Get(), NULL ); + } + else if ( !V_stricmp( tokens[0], "$cdmaterials" ) ) + { + m_cdmaterials.push_back( tokens[1].Get() ); + } + } + + bRetVal = true; + } + catch ( ... ) + { + } + } + + if ( m_cdmaterials.empty() ) + { + // If m_cdmaterials is empty, then put the relative smd path onto the cdmaterials path + + char szBuf0[MAX_PATH]; + V_strncpy( szBuf0, smdPath.Get(), ARRAYSIZE( szBuf0 ) ); + V_StripFilename( szBuf0 ); + V_FixSlashes( szBuf0, '/' ); + + CUtlVector< char *, CUtlMemory< char *, int > > sPathArray; + V_SplitString( szBuf0, "/", sPathArray ); + + CUtlString sRelSmdPath; + + bool bJoin = false; + for ( int i = 0; i < sPathArray.Count(); ++i ) + { + if ( !bJoin && !V_stricmp( sPathArray[i], "models" ) ) + { + bJoin = true; + } + + if ( bJoin ) + { + sRelSmdPath = PathJoin( sRelSmdPath.Get(), sPathArray[i] ); + } + } + sRelSmdPath.FixSlashes( '/' ); + + m_cdmaterials.push_back( sRelSmdPath.Get() ); + } + + return bRetVal; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CQcData::GetQcData( + const CUtlString &smdPath ) +{ + try + { + // Look for same thing named with a .qc extension + char szBuf0[MAX_PATH]; + char szBuf1[MAX_PATH]; + + V_strncpy( szBuf0, smdPath.Get(), ARRAYSIZE( szBuf0 ) ); + V_SetExtension( szBuf0, ".qc", ARRAYSIZE( szBuf0 ) ); + if ( _access( szBuf0, 04 ) == 0 ) + return ParseQc( smdPath, szBuf0 ); + + // Remove "_reference" if found + char *pszRef = V_stristr( szBuf0, "_reference.qc" ); + if ( pszRef ) + { + *pszRef = '\0'; + + V_SetExtension( szBuf0, ".qc", ARRAYSIZE( szBuf0 ) ); + if ( _access( szBuf0, 04 ) == 0 ) + return ParseQc( smdPath, szBuf0 ); + } + else + { + // Add _reference + + V_SetExtension( szBuf0, "", ARRAYSIZE( szBuf0 ) ); + V_strcat( szBuf0, "_reference", ARRAYSIZE( szBuf0 ) ); + V_SetExtension( szBuf0, ".qc", ARRAYSIZE( szBuf0 ) ); + if ( _access( szBuf0, 04 ) == 0 ) + return ParseQc( smdPath, szBuf0 ); + } + + // Look for any *.qc file in the same directory as the smd that contains the smd pathname + V_strncpy( szBuf0, smdPath.Get(), ARRAYSIZE( szBuf0 ) ); + V_FixSlashes( szBuf0 ); + + V_FileBase( szBuf0, szBuf1, ARRAYSIZE( szBuf1 ) ); + CUtlString sFileBase0( "\"" ); + sFileBase0 += szBuf1; + sFileBase0 += "\""; + + V_SetExtension( szBuf1, ".smd", ARRAYSIZE( szBuf1 ) ); + CUtlString sFileBase1( "\"" ); + sFileBase1 += szBuf1; + sFileBase1 += "\""; + + V_ExtractFilePath( szBuf0, szBuf1, ARRAYSIZE( szBuf1 ) ); + CUtlString sFilePath = szBuf1; + + if ( sFileBase0.Length() > 0 && sFilePath.Length() > 0 ) + { + struct _finddata_t qcFile; + long hFile; + + CUtlVector< CUtlString > tokens; + + /* Find first .qc file in current directory */ + + CUtlString sQcGlob = sFilePath; + sQcGlob += "*.qc"; + + if ( ( hFile = _findfirst( sQcGlob.Get(), &qcFile ) ) != -1L ) + { + /* Find the rest of the .qc files */ + do { + CUtlString sQcFile = sFilePath; + sQcFile += qcFile.name; + + std::ifstream ifs( sQcFile.Get() ); + std::string buf; + + while ( std::getline( ifs, buf ) ) + { + if ( V_stristr( buf.c_str(), sFileBase0.Get() ) || V_stristr( buf.c_str(), sFileBase1.Get() ) ) + { + _findclose( hFile ); + return ParseQc( smdPath, sQcFile ); + } + } + } while( _findnext( hFile, &qcFile ) == 0 ); + + _findclose( hFile ); + } + } + } + catch ( const std::exception &e ) + { + Error( "Exception: %s\n", e.what() ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#define MAX_WEIGHTS_PER_VERTEX 3 + + +//============================================================================= +// +//============================================================================= +class CVertexWeight +{ +public: + int m_nBoneIndex; + float m_flWeight; + + CVertexWeight() + : m_nBoneIndex( -1 ) + , m_flWeight( 0.0f ) + {} + + inline void Reset() + { + m_nBoneIndex = -1; + m_flWeight = 0.0f; + } + + inline bool operator==( const CVertexWeight &rhs ) const + { + return m_nBoneIndex == rhs.m_nBoneIndex && m_flWeight == rhs.m_flWeight; + } + + inline bool operator!=( const CVertexWeight &rhs ) const + { + return m_nBoneIndex != rhs.m_nBoneIndex || m_flWeight != rhs.m_flWeight; + } +}; + + +//============================================================================= +// +//============================================================================= +class CVertex +{ +public: + Vector m_vPosition; + Vector m_vNormal; + Vector2D m_vUV; + uint m_nWeights; + CVertexWeight m_vertexWeights[ MAX_WEIGHTS_PER_VERTEX ]; + + CVertex() + { + Reset(); + } + + inline bool operator==( const CVertex &rhs ) const + { + if ( m_vPosition != rhs.m_vPosition || + m_vNormal != rhs.m_vNormal || + m_vUV != rhs.m_vUV || + m_nWeights != rhs.m_nWeights ) + return false; + + for ( uint i = 0; i != m_nWeights; ++i ) + { + if ( m_vertexWeights[ i ] != rhs.m_vertexWeights[ i ] ) + return false; + } + + return true; + } + + inline void Reset() + { + m_nWeights = 0; + for ( int i = 0; i < ARRAYSIZE( m_vertexWeights ); ++i ) + { + m_vertexWeights[i].Reset(); + } + } +}; + + +//============================================================================= +// +//============================================================================= +class CTriangle +{ +public: + uint m_nVertices; + CVertex m_vertices[ 3 ]; + + CTriangle() + { + Reset(); + } + + bool Valid() const; + + inline void Reset() + { + m_nVertices = 0; + m_vertices[ 0U ].Reset(); + m_vertices[ 1U ].Reset(); + m_vertices[ 2U ].Reset(); + } +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CTriangle::Valid() const +{ + // A triangle is valid it it has three vertices and all three vertices are unique + + if ( m_nVertices != 3U ) + return false; + + if ( m_vertices[0] == m_vertices[1] ) + return false; + + if ( m_vertices[1] == m_vertices[2] ) + return false; + + if ( m_vertices[2] == m_vertices[0] ) + return false; + + return true; +} + + +//============================================================================= +// +//============================================================================= +class CShadingGroup +{ +public: + std::string m_materialPath; + std::map< int, std::deque< int > > m_componentListMap; +}; + + +//============================================================================= +// +//============================================================================= +enum ParserState_t +{ + kUnknown, + kPreamble, + kGeneral, + kNodes, + kSkeleton, + kTriangles +}; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool ParserAddJoint( + CDmSmdSerializer::SmdJointMap_t &smdJointMap, + int nId, + const char *pszName, + int nParentId, + const char *pszFilename, + int nLineNumber ) +{ + if ( nId < 0 ) + { + Error( "Error! %s(%d) : node error : invalid node id %d, must be >= 0: Line '%d \"%s\" %d'\n", + pszFilename, nLineNumber, nId, nId, pszName, nParentId ); + return false; + } + + if ( smdJointMap.IsValidIndex( smdJointMap.Find( nId ) ) ) + { + Error( "Error! %s(%d) : node error : node id %d already defined\n", + pszFilename, nLineNumber, nId ); + return false; + } + + CDmSmdSerializer::SmdJoint_t &smdJoint = smdJointMap.Element( smdJointMap.Insert( nId ) ); + + smdJoint.m_nId = nId; + smdJoint.m_sName = pszName; + smdJoint.m_nParentId = MAX( -1, nParentId ); + smdJoint.m_nLineNumber = nLineNumber; + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static bool ParserCreateJoint( + const CDmSmdSerializer::SmdJointMap_t &smdJointMap, + CDmeModel *pDmeModel, + CDmeChannelsClip *pDmeChannelsClip, + CDmSmdSerializer::SmdJoint_t *pSmdJoint, + const char *pszFilename ) +{ + const CUtlString &sName = pSmdJoint->m_sName; + const int nId = pSmdJoint->m_nId; + const int nParentId = pSmdJoint->m_nParentId; + const int nLineNumber = pSmdJoint->m_nLineNumber; + + CDmeJoint *pDmeJoint = CreateElement< CDmeJoint >( sName.Get(), pDmeModel->GetFileId() ); + + if ( !pDmeJoint ) + { + Error( "%s(%d) : node error : can't create joint %d(%s)\n", + pszFilename, nLineNumber, nId, sName.Get() ); + return false; + } + + if ( nParentId < 0 ) + { + if ( !pDmeModel ) + { + Error( "%s(%d) : node error : No DmeModel passed for root joint %d(%s)\n", + pszFilename, nLineNumber, nId, sName.Get() ); + DestroyElement( pDmeJoint ); + + return false; + } + + pDmeModel->AddChild( pDmeJoint ); + CDmAttribute *pRootJointAttr = pDmeJoint->AddAttribute( "__rootJoint", AT_BOOL ); + pRootJointAttr->AddFlag( FATTRIB_DONTSAVE ); + pRootJointAttr->SetValue( true ); + } + else + { + if ( nParentId == nId ) + { + Error( "%s(%d) : node error : joint %d(%s) is its own parent\n", + pszFilename, nLineNumber, nId, sName.Get() ); + DestroyElement( pDmeJoint ); + + return false; + } + + const int nParentIdIndex = smdJointMap.Find( nParentId ); + + if ( !smdJointMap.IsValidIndex( nParentIdIndex ) ) + { + Error( "%s(%d) : node error : joint %d(%s) has invalid parentId (%d)\n", + pszFilename, nLineNumber, nId, sName.Get(), nParentId ); + DestroyElement( pDmeJoint ); + + return false; + } + + CDmeDag *pDmeDagParent = smdJointMap.Element( nParentIdIndex ).m_pDmeDag; + if ( pDmeDagParent ) + { + pDmeDagParent->AddChild( pDmeJoint ); + } + else + { + Error( "%s(%d) : node error : joint %d(%s) has invalid parentId (%d)\n", + pszFilename, nLineNumber, nId, sName.Get(), nParentId ); + } + } + + if ( pDmeChannelsClip ) + { + CDmeTransform *pDmeTransform = pDmeJoint->GetTransform(); + + CDmeChannel *pDmePosChannel = CreateElement< CDmeChannel >( CFmtStr( "%s_p", pDmeTransform->GetName() ).Access(), pDmeChannelsClip->GetFileId() ); + pDmePosChannel->SetMode( CM_PLAY ); + pDmePosChannel->SetOutput( pDmeTransform, "position" ); + CDmeVector3Log *pDmePosLog = pDmePosChannel->CreateLog< Vector >(); + pDmePosLog->SetValueThreshold( 1.0e-6 ); + pDmeChannelsClip->m_Channels.AddToTail( pDmePosChannel ); + + CDmAttribute *pPosLogAttr = pDmeJoint->AddAttribute( "__posLog", AT_ELEMENT ); + pPosLogAttr->AddFlag( FATTRIB_DONTSAVE ); + pPosLogAttr->SetValue( pDmePosLog ); + + CDmeChannel *pDmeRotChannel = CreateElement< CDmeChannel >( CFmtStr( "%s_o", pDmeTransform->GetName() ).Access(), pDmeChannelsClip->GetFileId() ); + pDmeRotChannel->SetMode( CM_PLAY ); + pDmeRotChannel->SetOutput( pDmeTransform, "orientation" ); + CDmeQuaternionLog *pDmeRotLog = pDmeRotChannel->CreateLog< Quaternion >(); + pDmeRotLog->SetValueThreshold( 1.0e-6 ); + pDmeChannelsClip->m_Channels.AddToTail( pDmeRotChannel ); + + CDmAttribute *pRotLogAttr = pDmeJoint->AddAttribute( "__rotLog", AT_ELEMENT ); + pRotLogAttr->AddFlag( FATTRIB_DONTSAVE ); + pRotLogAttr->SetValue( pDmeRotLog ); + } + + pSmdJoint->m_pDmeDag = pDmeJoint; + pDmeModel->AddJoint( pDmeJoint ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Used by ParserAddJoints, sorts joints by parent index +//----------------------------------------------------------------------------- +static bool SmdJointLessFunc( const CDmSmdSerializer::SmdJoint_t *pLhs, const CDmSmdSerializer::SmdJoint_t *pRhs ) +{ + // try to preserve joints without parents in their original order as much as possible + if ( pLhs->m_nParentId < 0 ) + return pLhs->m_nId < pRhs->m_nId; + + return pLhs->m_nParentId < pRhs->m_nParentId; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void ParserCreateJoints( + CDmSmdSerializer::SmdJointMap_t &smdJointMap, + CDmeModel *pDmeModel, + CDmeChannelsClip *pDmeChannelsClip, + const char *pszFilename ) +{ + // Sort the joints in order of parent, pointers only temporary and no elements + // added/removed from map while pointers exist, so pointers are stable + CUtlVector< CDmSmdSerializer::SmdJoint_t * > smdJointList; + FOR_EACH_MAP_FAST( smdJointMap, nSmdJointMapIndex ) + { + smdJointList.AddToTail( &smdJointMap.Element( nSmdJointMapIndex ) ); + } + + std::stable_sort( smdJointList.Base(), smdJointList.Base() + smdJointList.Count(), SmdJointLessFunc ); + + for ( int i = 0; i < smdJointList.Count(); ++i ) + { + CDmSmdSerializer::SmdJoint_t *pSmdJoint = smdJointList[i]; + pSmdJoint->m_nActualId = i; + + ParserCreateJoint( smdJointMap, pDmeModel, pDmeChannelsClip, pSmdJoint, pszFilename ); + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmSmdSerializer::ParserSetJoint( + const SmdJointMap_t &smdJointMap, + int nFrame, + int nId, + const Vector &vPosition, + const RadianEuler &eRadianEulerXYZ, + const char *pszFilename, + int nLineNumber ) +{ + const SmdJointMap_t::IndexType_t nIdIndex = smdJointMap.Find( nId ); + if ( !smdJointMap.IsValidIndex( nIdIndex ) ) + { + Error( "%s(%d) : skeleton error : can't find joint %d\n", + pszFilename, nLineNumber, nId ); + return; + } + + CDmeDag *pDmeDag = smdJointMap.Element( nIdIndex ).m_pDmeDag; + if ( !pDmeDag ) + { + Error( "%s(%d) : skeleton error : no dmedag created for can't find joint %d (%s)\n", + pszFilename, nLineNumber, nId, smdJointMap.Element( nIdIndex ).m_sName.Get() ); + return; + } + + const Quaternion qOrientation = eRadianEulerXYZ; + + if ( pDmeDag->GetValue( "__rootJoint", false ) && GetUpAxis() != CDmSmdSerializer::Y_AXIS ) + { + matrix3x4_t mPre; + matrix3x4_t mPost; + AngleMatrix( qOrientation, vPosition, mPre ); + ConcatTransforms( m_mAdj, mPre, mPost ); + pDmeDag->GetTransform()->SetTransform( mPost ); + } + else + { + pDmeDag->GetTransform()->SetPosition( vPosition ); + pDmeDag->GetTransform()->SetOrientation( qOrientation ); + } + + if ( m_bOptAnimation ) + { + const DmeTime_t tCurrent( static_cast< float >( nFrame ), DmeFramerate_t( m_flFrameRate ) ); + + CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); + + CDmeVector3Log *pDmePosLog = pDmeDag->GetValueElement< CDmeVector3Log >( "__posLog" ); + pDmePosLog->SetKey( tCurrent, pDmeTransform->GetPosition() ); + + CDmeQuaternionLog *pDmeRotLog = pDmeDag->GetValueElement< CDmeQuaternionLog >( "__rotLog" ); + pDmeRotLog->SetKey( tCurrent, pDmeTransform->GetOrientation() ); + } +} + +//----------------------------------------------------------------------------- +// Returns -1 if invalid +//----------------------------------------------------------------------------- +static int GetActualId( const CDmSmdSerializer::SmdJointMap_t &smdJointMap, int nId ) +{ + const CDmSmdSerializer::SmdJointMap_t::IndexType_t nIdIndex = smdJointMap.Find( nId ); + + if ( !smdJointMap.IsValidIndex( nIdIndex ) ) + { + Error( "Error! Invalid node id %d looked up\n", nId ); + return -1; + } + + return smdJointMap.Element( nIdIndex ).m_nActualId; +} + + +//----------------------------------------------------------------------------- +// Used by HandleVertexWeights, sorts vertex weights by weight +//----------------------------------------------------------------------------- +static int VertexWeightLessFunc( const void *pLhs, const void *pRhs ) +{ + const CVertexWeight *pVertexWeightL = reinterpret_cast< const CVertexWeight * >( pLhs ); + const CVertexWeight *pVertexWeightR = reinterpret_cast< const CVertexWeight * >( pRhs ); + + if ( pVertexWeightL->m_nBoneIndex < 0 ) + { + if ( pVertexWeightR->m_nBoneIndex < 0 ) + { + return 0; + } + else + { + return 1; + } + } + else if ( pVertexWeightR->m_nBoneIndex < 0 ) + { + return -1; + } + + if ( pVertexWeightL->m_flWeight > pVertexWeightR->m_flWeight ) + { + return -1; + } + else if ( pVertexWeightL->m_flWeight < pVertexWeightR->m_flWeight ) + { + return 1; + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void HandleVertexWeights( + const CDmSmdSerializer::SmdJointMap_t &smdJointMap, + int nFallbackBoneIndexId, + CVertex &vertex, + const char *pszLine, + const char *pszFilename, + int nLineNumber ) +{ + for ( int i = 0; i < ARRAYSIZE( vertex.m_vertexWeights ); ++i ) + { + vertex.m_vertexWeights[i].Reset(); + } + + CUtlVector< CVertexWeight > tmpVertexWeights; + + CUtlVector< CUtlString > tokens; + Tokenize( tokens, pszLine ); + + const float flEps = 1.0e-6; + + uint nTokenEnd = tokens.Count(); + if ( nTokenEnd > 10 ) + { + int nId = -1; + float flWeight = 0.0f; + for ( uint i = 10; i < nTokenEnd; ++i ) + { + nId = strtol( tokens[ i ].Get(), NULL, 0 ); + ++i; + flWeight = strtod( tokens[ i ].Get(), NULL ); + + if ( nId < 0 || flWeight < flEps ) + continue; + + const int nActualId = GetActualId( smdJointMap, nId ); + + if ( nActualId < 0 ) + { + Error( "%s(%d) : triangle error : ignoring unknown joint id(%d) with actual id(%d) for vertex weight\n", + pszFilename, nLineNumber, nId, nActualId ); + continue; + } + + CVertexWeight &tmpVertexWeight = tmpVertexWeights[ tmpVertexWeights.AddToTail() ]; + tmpVertexWeight.m_nBoneIndex = nActualId; + tmpVertexWeight.m_flWeight = flWeight; + } + + // Sort vertex weights inversely by weight + qsort( tmpVertexWeights.Base(), tmpVertexWeights.Count(), sizeof( CVertexWeight ), VertexWeightLessFunc ); + + // Take at most the top ARRAYSIZE( vertex.m_vertexWeights ) from tmpVertexWeights + // Figure out actual weight count and total weight + + float flTotalWeight = 0.0f; + vertex.m_nWeights = 0; + + for ( int i = 0; i < tmpVertexWeights.Count() && i < ARRAYSIZE( vertex.m_vertexWeights ); ++i ) + { + CVertexWeight &vertexWeight( vertex.m_vertexWeights[ i ] ); + vertexWeight = tmpVertexWeights[i]; + flTotalWeight += vertexWeight.m_flWeight; + vertex.m_nWeights += 1; + } + + // If there are valid user specified weights then renormalize + if ( vertex.m_nWeights > 0 && flTotalWeight > flEps ) + { + for ( uint i = 0; i != ARRAYSIZE( vertex.m_vertexWeights ); ++i ) + { + CVertexWeight &vertexWeight( vertex.m_vertexWeights[ i ] ); + if ( vertexWeight.m_nBoneIndex >= 0 ) + { + vertexWeight.m_flWeight /= flTotalWeight; + } + } + } + else + { + // No valid user weights, assign to fallback bone with weight of 1 + vertex.m_vertexWeights[ 0 ].m_nBoneIndex = GetActualId( smdJointMap, nFallbackBoneIndexId ); + if ( vertex.m_vertexWeights[0].m_nBoneIndex < 0 ) + { + // Vertex is not weighted, can't find fallback bone, this is invalid + vertex.m_nWeights = 0; + vertex.m_vertexWeights[0].m_nBoneIndex = -1; + vertex.m_vertexWeights[0].m_flWeight = 0.0f; + } + else + { + vertex.m_nWeights = 1; + vertex.m_vertexWeights[0].m_flWeight = 1.0f; + } + + // Assign rest of weights to -1, 0.0f + for ( uint i = 1; i < ARRAYSIZE( vertex.m_vertexWeights ); ++i ) + { + vertex.m_vertexWeights[i].m_nBoneIndex = -1; + vertex.m_vertexWeights[i].m_flWeight = 0.0f; + } + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static CDmeMesh *CreateDmeMesh( + CDmeModel *pDmeModel, + const char *pszFilename, + int nLineNumber ) +{ + char szFileBase[MAX_PATH]; + szFileBase[0] = '\0'; + + const char *pszMeshName = "mesh"; + + if ( pszFilename ) + { + V_FileBase( pszFilename, szFileBase, ARRAYSIZE( szFileBase ) ); + + if ( V_strlen( szFileBase ) > 0 ) + { + pszMeshName = szFileBase; + } + } + + CDmeDag *pDmeDag = CreateElement< CDmeDag >( pszMeshName, pDmeModel->GetFileId() ); + if ( !pDmeDag ) + return NULL; + + pDmeModel->AddChild( pDmeDag ); + + CUtlString sMeshName = pDmeDag->GetName(); + sMeshName += "Shape"; + + CDmeMesh *pDmeMesh = CreateElement< CDmeMesh >( sMeshName.Get(), pDmeDag->GetFileId() ); + if ( !pDmeMesh ) + return NULL; + + CDmeVertexData *pDmeVertexData = pDmeMesh->FindOrCreateBaseState( "bind" ); + pDmeMesh->SetCurrentBaseState( "bind" ); + + pDmeVertexData->FlipVCoordinate( true ); + pDmeVertexData->CreateField( CDmeVertexData::FIELD_POSITION ); + pDmeVertexData->CreateField( CDmeVertexData::FIELD_NORMAL ); + pDmeVertexData->CreateField( CDmeVertexData::FIELD_TEXCOORD ); + FieldIndex_t nJointWeightField; + FieldIndex_t nJointIndexField; + pDmeVertexData->CreateJointWeightsAndIndices( MAX_WEIGHTS_PER_VERTEX, &nJointWeightField, &nJointIndexField ); + + pDmeDag->SetShape( pDmeMesh ); + + pDmeVertexData->Resolve(); + pDmeMesh->Resolve(); + pDmeDag->Resolve(); + + return pDmeMesh; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static CDmeFaceSet *FindOrCreateFaceSet( + CDmeMesh *pDmeMesh, + const CUtlString &sMaterial, + const char *pszFilename, + int nLineNumber ) +{ + // Could cache in a hashed map or something... + for ( int i = 0; i < pDmeMesh->FaceSetCount(); ++i ) + { + CDmeFaceSet *pDmeFaceSet = pDmeMesh->GetFaceSet( i ); + if ( !pDmeFaceSet ) + continue; + + CDmeMaterial *pDmeMaterial = pDmeFaceSet->GetMaterial(); + if ( !pDmeMaterial ) + continue; + + if ( !V_strcmp( pDmeMaterial->GetMaterialName(), sMaterial.Get() ) ) + return pDmeFaceSet; + } + + char szFaceSetName[ MAX_PATH ]; + V_FileBase( sMaterial.Get(), szFaceSetName, ARRAYSIZE( szFaceSetName ) ); + + CDmeFaceSet *pDmeFaceSet = CreateElement< CDmeFaceSet >( szFaceSetName, pDmeMesh->GetFileId() ); + Assert( pDmeFaceSet ); + + CDmeMaterial *pDmeMaterial = CreateElement< CDmeMaterial >( szFaceSetName, pDmeMesh->GetFileId() ); + Assert( pDmeMaterial ); + + pDmeMaterial->SetMaterial( sMaterial.Get() ); + pDmeFaceSet->SetMaterial( pDmeMaterial ); + + pDmeMesh->AddFaceSet( pDmeFaceSet ); + + return pDmeFaceSet; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void CreatePolygon( + CDmeMesh *pDmeMesh, + const CUtlString &sMaterial, + CTriangle &triangle, + const char *pszFilename, + int nLineNumber ) +{ + if ( !triangle.Valid() || !pDmeMesh ) + return; + + CDmeVertexData *pDmeVertexData = pDmeMesh->GetBindBaseState(); + Assert( pDmeVertexData ); + + FieldIndex_t nPositionField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_POSITION ); + CDmrArray< Vector > positionData = pDmeVertexData->GetVertexData( nPositionField ); + + FieldIndex_t nNormalField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_NORMAL ); + CDmrArray< Vector > normalData = pDmeVertexData->GetVertexData( nNormalField ); + + FieldIndex_t nUvField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_TEXCOORD ); + CDmrArray< Vector2D > uvData = pDmeVertexData->GetVertexData( nUvField ); + + FieldIndex_t nJointWeightField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_WEIGHTS ); + CDmrArray< float > jointWeightData = pDmeVertexData->GetVertexData( nJointWeightField ); + + FieldIndex_t nJointIndexField = pDmeVertexData->FindFieldIndex( CDmeVertexData::FIELD_JOINT_INDICES ); + CDmrArray< int > jointIndexData = pDmeVertexData->GetVertexData( nJointIndexField ); + + CDmeFaceSet *pDmeFaceSet = FindOrCreateFaceSet( pDmeMesh, sMaterial, pszFilename, nLineNumber ); + if ( !pDmeFaceSet ) + { + Error( "%s(%d) : mesh error : couldn't find or create DmeFace set for material \"%s\" on mesh \"%s\"\n", + pszFilename, nLineNumber, sMaterial.Get(), pDmeMesh->GetName() ); + return; + } + + for ( uint i = 0; i < 3; ++i ) + { + const int nNewVertexIndex = pDmeVertexData->AddVertexIndices( 1 ); + + CVertex &cVertex = triangle.m_vertices[i]; + + { + // TODO: Make two positions if they are skinned differently + int nPositionIndex = positionData.Find( cVertex.m_vPosition ); + if ( !positionData.IsValidIndex( nPositionIndex ) ) + { + nPositionIndex = positionData.AddToTail( cVertex.m_vPosition ); + + for ( int j = 0; j < ARRAYSIZE( cVertex.m_vertexWeights ); ++j ) + { + jointWeightData.AddToTail( cVertex.m_vertexWeights[j].m_flWeight ); + jointIndexData.AddToTail( cVertex.m_vertexWeights[j].m_nBoneIndex ); + } + } + + pDmeVertexData->SetVertexIndices( nPositionField, nNewVertexIndex, 1, &nPositionIndex ); + + const int nFaceSetIndex = pDmeFaceSet->AddIndices( 1 ); + pDmeFaceSet->SetIndices( nFaceSetIndex, 1, const_cast< int * >( &nNewVertexIndex ) ); + + } + + { + int nNormalIndex = normalData.Find( cVertex.m_vNormal ); + if ( !normalData.IsValidIndex( nNormalIndex ) ) + { + nNormalIndex = normalData.AddToTail( cVertex.m_vNormal ); + } + + pDmeVertexData->SetVertexIndices( nNormalField, nNewVertexIndex, 1, &nNormalIndex ); + } + + + { + int nUvIndex = uvData.Find( cVertex.m_vUV ); + if ( !uvData.IsValidIndex( nUvIndex ) ) + { + nUvIndex = uvData.AddToTail( cVertex.m_vUV ); + } + + pDmeVertexData->SetVertexIndices( nUvField, nNewVertexIndex, 1, &nUvIndex ); + } + + } + + static const int nFaceDelimiter = -1; + const int nFaceSetIndex = pDmeFaceSet->AddIndices( 1 ); + pDmeFaceSet->SetIndices( nFaceSetIndex, 1, const_cast< int * >( &nFaceDelimiter ) ); + + triangle.Reset(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static CDmeFaceSet *FindOrAddFaceSet( + CDmeMesh *pDmeMesh, + CUtlMap< CUtlString, CDmeFaceSet * > &faceListMap, + const char *pszMaterialPath ) +{ + CUtlMap< CUtlString, CDmeFaceSet * >::IndexType_t nIndex = faceListMap.Find( CUtlString( pszMaterialPath ) ); + + if ( faceListMap.IsValidIndex( nIndex ) ) + return faceListMap.Element( nIndex ); + + CDmeFaceSet *pDmeFaceSet = CreateElement< CDmeFaceSet >( pszMaterialPath, pDmeMesh->GetFileId() ); + CDmeMaterial *pDmeMaterial = CreateElement< CDmeMaterial >( pszMaterialPath, pDmeMesh->GetFileId() ); + Assert( pDmeFaceSet && pDmeMaterial ); + pDmeMaterial->SetMaterial( pszMaterialPath ); + pDmeMesh->AddFaceSet( pDmeFaceSet ); + + return faceListMap.Element( faceListMap.Insert( CUtlString( pszMaterialPath ), pDmeFaceSet ) ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDmeChannelsClip *FindOrCreateChannelsClip( CDmElement *pDmeRoot, const char *pszAnimationName ) +{ + CDmeAnimationList *pDmeAnimationList = pDmeRoot->GetValueElement< CDmeAnimationList >( "animationList" ); + CDmeChannelsClip *pDmeChannelsClip = NULL; + + if ( pDmeAnimationList ) + { + pDmeChannelsClip = pDmeAnimationList->GetAnimation( 0 ); + } + else + { + CDmeModel *pDmeModel = pDmeRoot->GetValueElement< CDmeModel >( "skeleton" ); + + pDmeAnimationList = CreateElement< CDmeAnimationList >( pszAnimationName, pDmeRoot->GetFileId() ); + pDmeChannelsClip = CreateElement< CDmeChannelsClip >( pszAnimationName, pDmeRoot->GetFileId() ); + pDmeAnimationList->AddAnimation( pDmeChannelsClip ); + pDmeRoot->SetValue( "animationList", pDmeAnimationList ); + pDmeModel->SetValue( "animationList", pDmeAnimationList ); + } + + return pDmeChannelsClip; +} + +//----------------------------------------------------------------------------- +// Common function both ReadSMD & Unserialize can call +//----------------------------------------------------------------------------- +CDmElement *CDmSmdSerializer::ReadSMD( + CUtlBuffer &utlBuf, + DmFileId_t nDmFileId, + const char *pszFilename, + CDmeMesh **ppDmeMeshCreated ) +{ + CDmElement *pDmeRoot = CreateElement< CDmElement >( "root", nDmFileId ); + CDmeModel *pDmeModel = CreateElement< CDmeModel >( "model", nDmFileId ); + + char szAnimationName[ MAX_PATH ] = ""; + + if ( pszFilename ) + { + V_FileBase( pszFilename, szAnimationName, ARRAYSIZE( szAnimationName ) ); + } + else + { + pszFilename = "unknown"; + } + + if ( V_strlen( szAnimationName ) > 0 ) + { + pDmeModel->SetName( szAnimationName ); + } + else + { + V_strcpy_safe( szAnimationName, "anim" ); + } + + pDmeRoot->SetValue( "skeleton", pDmeModel ); + + if ( !m_bOptAnimation ) + { + // Don't set root.model if animation only, studiomdl can't handle it + pDmeRoot->SetValue( "model", pDmeModel ); + } + + SmdJointMap_t smdJointMap( CDefOps< int >::LessFunc ); + + CUtlString sMaterial; + + CTriangle triangle; + + uint nLineNumber = 0; + uint nBadPreambleCount = 0; + ParserState_t nParserState = kPreamble; + int nFrame = 0; + + CDmeChannelsClip *pDmeChannelsClip = NULL; + + if ( m_bOptAnimation ) + { + pDmeChannelsClip = FindOrCreateChannelsClip( pDmeRoot, szAnimationName ); + } + + bool bStartTimeSet = false; + + CQcData qcData; + + CDmeMesh *pDmeMesh = NULL; + + char szLine[ 4096 ]; + + while ( utlBuf.IsValid() ) + { + GetLine( utlBuf, szLine, ARRAYSIZE( szLine ) ); + ++nLineNumber; + + const char *pszLine = ParserSkipSpace( szLine ); + + if ( ParserIsBlankLine( pszLine ) ) + continue; + + if ( nParserState == kPreamble ) + { + if ( HandleQcHints( pszLine, qcData ) ) + continue; + + if ( ParserHandleVersion( pszLine ) ) + { + if ( qcData.m_nUpAxis != m_nUpAxis ) + { + Warning( "Importer UpAxis (%d) is different from UpAxis in SMD data (%d), using value found in SMD/QC\n", m_nUpAxis, qcData.m_nUpAxis ); + } + + SetUpAxis( qcData.m_nUpAxis ); + nParserState = kGeneral; + continue; + } + + Error( "%s(%d) : preamble error : expecting comment: \"%s\"\n", + pszFilename, nLineNumber, szLine ); + + ++nBadPreambleCount; + if ( nBadPreambleCount > 10 ) + { + Error( "%s(%d) : preamble error : too many errors, not an SMD file, aborting\n", + pszFilename, nLineNumber ); + break; + } + } + else if ( nParserState == kGeneral ) + { + if ( ParserHandleSectionStart( pszLine, "nodes" ) ) + { + nParserState = kNodes; + } + else if ( ParserHandleSectionStart( pszLine, "skeleton" ) ) + { + nParserState = kSkeleton; + } + else if ( ParserHandleSectionStart( pszLine, "triangles" ) ) + { + nParserState = kTriangles; + } + } + else if ( nParserState == kNodes ) + { + if ( ParserHandleSectionStart( pszLine, "end" ) ) + { + ParserCreateJoints( smdJointMap, pDmeModel, pDmeChannelsClip, pszFilename ); + + nParserState = kGeneral; + continue; + } + + CUtlString sName; + int nId; + int nParentId; + + if ( !m_bOptImportSkeleton || !ParserHandleSkeletonLine( pszLine, sName, nId, nParentId ) ) + continue; + + if ( !ParserAddJoint( smdJointMap, nId, sName, nParentId, pszFilename, nLineNumber ) ) + continue; + } + else if ( nParserState == kSkeleton ) + { + if ( ParserHandleSectionStart( pszLine, "end" ) ) + { + nParserState = kGeneral; + continue; + } + + if ( m_bOptAnimation && ParserHandleTime( pszLine, nFrame ) ) + { + if ( !bStartTimeSet ) + { + pDmeChannelsClip->SetStartTime( DmeTime_t( static_cast< float >( nFrame ), DmeFramerate_t( m_flFrameRate ) ) ); + bStartTimeSet = true; + } + continue; + } + + int nId; + Vector vPosition; + RadianEuler eRadianEulerXYZ; + + if ( sscanf( pszLine, "%d %f %f %f %f %f %f", &nId, &vPosition.x, &vPosition.y, &vPosition.z, &eRadianEulerXYZ.x, &eRadianEulerXYZ.y, &eRadianEulerXYZ.z ) == 7 ) + { + if ( !m_bOptImportSkeleton ) + continue; + + ParserSetJoint( smdJointMap, nFrame, nId, vPosition, eRadianEulerXYZ, pszFilename, nLineNumber ); + } + } + else if ( nParserState == kTriangles ) + { + if ( ParserHandleSectionStart( pszLine, "end" ) ) + { + triangle.Reset(); + nParserState = kGeneral; + continue; + } + + int nFallbackSkinJoint; + Vector vPosition; + Vector vNormal; + Vector2D vUV; + char szShadingGroup[ ARRAYSIZE( szLine ) ]; + + if ( sscanf( pszLine, "%d %f %f %f %f %f %f %f %f", + &nFallbackSkinJoint, + &vPosition.x, &vPosition.y, &vPosition.z, + &vNormal.x, &vNormal.y, &vNormal.z, &vUV.x, &vUV.y ) == 9 ) + { + if ( triangle.m_nVertices >= 3 ) + { + Error( "%s(%d) : triangle error : Too many vertices in triangle\n", + pszFilename, nLineNumber ); + continue; + } + + CVertex &vertex( triangle.m_vertices[ triangle.m_nVertices++ ] ); + vertex.m_vUV = vUV; + + if ( GetUpAxis() != CDmSmdSerializer::Y_AXIS ) + { + VectorTransform( vPosition, m_mAdj, vertex.m_vPosition ); + VectorTransform( vNormal, m_mAdjNormal, vertex.m_vNormal ); + } + else + { + vertex.m_vPosition = vPosition; + vertex.m_vNormal = vNormal; + } + + if ( m_bOptImportSkeleton ) + { + HandleVertexWeights( smdJointMap, nFallbackSkinJoint, vertex, pszLine, pszFilename, nLineNumber ); + } + + if ( !pDmeMesh ) + { + pDmeMesh = CreateDmeMesh( pDmeModel, pszFilename, nLineNumber ); + if ( !pDmeMesh ) + { + Error( "%s(%d) : Couldn't create DmeMesh\n", + pszFilename, nLineNumber ); + break; + } + } + + CreatePolygon( pDmeMesh, sMaterial, triangle, pszFilename, nLineNumber ); + } + else if ( sscanf( pszLine, "%1024s", szShadingGroup ) == 1 ) + { + char pszMaterialPath[ ARRAYSIZE( szShadingGroup ) ]; + V_StripExtension( szShadingGroup, pszMaterialPath, ARRAYSIZE( pszMaterialPath ) ); + + sMaterial = pszMaterialPath; + + triangle.Reset(); + } + else + { + Error( "%s(%d) : triangle error : unexpected data in triangles\n", + pszFilename, nLineNumber ); + triangle.Reset(); + } + } + else + { + Error( "%s(%d) : parser error : unknown parser state\n", + pszFilename, nLineNumber ); + nParserState = kGeneral; + } + } + + pDmeModel->CaptureJointsToBaseState( "bind" ); + + if ( pDmeChannelsClip ) + { + // Reset skeleton values to the first sample + FOR_EACH_MAP_FAST( smdJointMap, nJointMapIndex ) + { + CDmeDag *pDmeDag = smdJointMap.Element( nJointMapIndex ).m_pDmeDag; + if ( pDmeDag ) + { + CDmeTransform *pDmeTransform = pDmeDag->GetTransform(); + + CDmeVector3Log *pDmePosLog = pDmeDag->GetValueElement< CDmeVector3Log >( "__posLog" ); + pDmeTransform->SetPosition( pDmePosLog->GetKeyValue( 0 ) ); + + CDmeQuaternionLog *pDmeRotLog = pDmeDag->GetValueElement< CDmeQuaternionLog >( "__rotLog" ); + pDmeTransform->SetOrientation( pDmeRotLog->GetKeyValue( 0 ) ); + } + } + + pDmeChannelsClip->SetDuration( DmeTime_t( nFrame, DmeFramerate_t( m_flFrameRate ) ) - pDmeChannelsClip->GetStartTime() ); + } + + return pDmeRoot; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmSmdSerializer::FixNodeName( CUtlString &sName ) const +{ + char szTmpBuf0[ MAX_PATH ]; + char szTmpBuf1[ MAX_PATH ]; + + V_strncpy( szTmpBuf0, sName.Get(), ARRAYSIZE( szTmpBuf0 ) ); + + // Strip trailing quotes + char *pszName = szTmpBuf0 + sName.Length() - 1; + while ( pszName >= szTmpBuf0 && *pszName && *pszName == '"' ) + { + *pszName = '\0'; + --pszName; + } + + // Strip leading quotes + pszName = szTmpBuf0; + while ( *pszName && *pszName == '"') + { + ++pszName; + } + + if ( m_bOptAutoStripPrefix ) + { + // Get the string before the '.' + V_FileBase( pszName, szTmpBuf1, ARRAYSIZE( szTmpBuf1 ) ); + V_strncpy( szTmpBuf0, szTmpBuf1, ARRAYSIZE( szTmpBuf0 ) ); + pszName = szTmpBuf0; + } + + if ( !m_sNodeDelPrefix.IsEmpty() && StringHasPrefix( pszName, m_sNodeDelPrefix.Get() ) ) + { + pszName = const_cast< char * >( StringAfterPrefix( pszName, m_sNodeDelPrefix.Get() ) ); + } + + if ( m_sNodeAddPrefix.IsEmpty() ) + { + sName = pszName; + } + else + { + sName = m_sNodeAddPrefix; + sName += pszName; + } +}
\ No newline at end of file diff --git a/movieobjects/dmx_to_vcd.cpp b/movieobjects/dmx_to_vcd.cpp new file mode 100644 index 0000000..a0037bc --- /dev/null +++ b/movieobjects/dmx_to_vcd.cpp @@ -0,0 +1,1052 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/dmx_to_vcd.h" +#include "movieobjects/movieobjects.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "choreoevent.h" +#include "iscenetokenprocessor.h" +#include "characterset.h" + + +bool ConvertEventToDmx( CChoreoEvent *event, CDmeChannelsClip *clip ) +{ + clip->SetName( event->GetName() ); + clip->SetValue( "eventtype", CChoreoEvent::NameForType( event->GetType() ) ); + + CDmeTimeFrame *tf = clip->GetTimeFrame(); + Assert( tf ); + if ( tf ) + { + tf->SetStartTime( DmeTime_t( event->GetStartTime() ) ); + tf->SetDuration( DmeTime_t( event->GetDuration() ) ); + } + + clip->SetValue( "parameters", event->GetParameters() ); + clip->SetValue( "parameters2", event->GetParameters2() ); + + // event_ramp is a channel under the event's channels clip + CDmrElementArray<> array( clip, "channels" ); + Assert( array.IsValid() ); + if ( array.IsValid() ) + { + CDmeChannel *channel = CreateElement< CDmeChannel >( "event_ramp", clip->GetFileId() ); + array.AddToTail( channel ); + + // Fill in values..., just log for now + channel->CreateLog( AT_FLOAT ); + CDmeTypedLog<float> *ramp = static_cast< CDmeTypedLog<float> * >( channel->GetLog() ); + if ( ramp ) + { + CDmeFloatCurveInfo *pCurveInfo = CreateElement< CDmeFloatCurveInfo >( "floatcurveinfo", clip->GetFileId() ); + + pCurveInfo->SetDefaultCurveType( MAKE_CURVE_TYPE( INTERPOLATE_CATMULL_ROM_NORMALIZE, INTERPOLATE_CATMULL_ROM_NORMALIZE ) ); + pCurveInfo->SetRightEdgeTime( DmeTime_t( event->GetDuration() ) ); + + pCurveInfo->SetUseEdgeInfo( true ); + pCurveInfo->SetDefaultEdgeZeroValue( 0.0f ); + // Left edge + pCurveInfo->SetEdgeInfo( 0, + event->GetRamp()->IsEdgeActive( true ), + event->GetRamp()->GetEdgeZeroValue( true ), + event->GetRamp()->GetEdgeCurveType( true ) ); + // Right edge + pCurveInfo->SetEdgeInfo( 1, + event->GetRamp()->IsEdgeActive( false ), + event->GetRamp()->GetEdgeZeroValue( false ), + event->GetRamp()->GetEdgeCurveType( false ) ); + + ramp->SetCurveInfo( pCurveInfo ); + + int rampCount = event->GetRampCount(); + for ( int j = 0; j < rampCount; ++j ) + { + CExpressionSample *sample = event->GetRamp( j ); + ramp->SetKey( DmeTime_t( sample->time ), sample->value, sample->GetCurveType() ); + } + } + } + + clip->SetValue< float >( "pitch", event->GetPitch() ); + clip->SetValue< float >( "yaw", event->GetYaw() ); + + clip->SetValue< bool >( "resumecondition", event->IsResumeCondition() ); + clip->SetValue< bool >( "lockbodyfacing", event->IsLockBodyFacing() ); + clip->SetValue< float >( "distancetotarget", event->GetDistanceToTarget() ); + clip->SetValue< bool >( "fixedlength", event->IsFixedLength() ); + clip->SetValue< bool >( "forceshortmovement", event->GetForceShortMovement() ); + clip->SetValue< bool >( "active", event->GetActive() ); + + // Relative tags + if ( event->GetNumRelativeTags() > 0 ) + { + CDmElement *tags = CreateElement< CDmElement >( "relative_tags", clip->GetFileId() ); + if ( tags ) + { + clip->SetValue< CDmElement >( "tags", tags ); + + // Now create arrays for the name and percentages + CDmrStringArray names( tags, "tagname", true ); + CDmrArray<float> percentages( tags, "tagpercentage", true ); + Assert( names.IsValid() && percentages.IsValid() ); + + for ( int t = 0; t < event->GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *rt = event->GetRelativeTag( t ); + Assert( rt ); + + names.AddToTail( rt->GetName() ); + percentages.AddToTail( rt->GetPercentage() ); + } + } + } + + // Timing tags + if ( event->GetNumTimingTags() > 0 ) + { + CDmElement *tags = CreateElement< CDmElement >( "timing_tags", clip->GetFileId() ); + if ( tags ) + { + clip->SetValue< CDmElement >( "flextimingtags", tags ); + + // Now create arrays for the name and percentages + CDmrStringArray names( tags, "tagname", true ); + CDmrArray<float> percentages( tags, "tagpercentage", true ); + CDmrArray<bool> lockstates( tags, "lockedstate", true ); + Assert( names.IsValid() && percentages.IsValid() && lockstates.IsValid() ); + + for ( int t = 0; t < event->GetNumTimingTags(); t++ ) + { + CFlexTimingTag *tt = event->GetTimingTag( t ); + Assert( tt ); + + names.AddToTail( tt->GetName() ); + percentages.AddToTail( tt->GetPercentage() ); + lockstates.AddToTail( tt->GetLocked() ); + } + } + } + + // Abs tags + int tagtype; + for ( tagtype = 0; tagtype < CChoreoEvent::NUM_ABS_TAG_TYPES; tagtype++ ) + { + if ( event->GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)tagtype ) > 0 ) + { + char sz[ 512 ]; + + Q_snprintf( sz, sizeof( sz ), "absolutetags %s", CChoreoEvent::NameForAbsoluteTagType( (CChoreoEvent::AbsTagType)tagtype ) ); + + CDmElement *tags = CreateElement< CDmElement >( sz, clip->GetFileId() ); + if ( tags ) + { + clip->SetValue< CDmElement >( sz, tags ); + + // Now create arrays for the name and percentages + CDmrStringArray names( tags, "tagname", true ); + CDmrArray<float> percentages( tags, "tagpercentage", true ); + Assert( names.IsValid() && percentages.IsValid() ); + for ( int t = 0; t < event->GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)tagtype ); t++ ) + { + CEventAbsoluteTag *abstag = event->GetAbsoluteTag( (CChoreoEvent::AbsTagType)tagtype, t ); + Assert( abstag ); + + names.AddToTail( abstag->GetName() ); + percentages.AddToTail( abstag->GetPercentage() ); + } + } + } + } + + // IsUsingRelativeTag + if ( event->IsUsingRelativeTag() ) + { + CDmElement *relativeTag = CreateElement< CDmElement >( "relative_tag", clip->GetFileId() ); + if ( relativeTag ) + { + clip->SetValue< CDmElement >( "relative_tag", relativeTag ); + + relativeTag->SetValue( "tagname", event->GetRelativeTagName() ); + relativeTag->SetValue( "tagwav", event->GetRelativeWavName() ); + } + } + + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + CDmElement *speakProperties = CreateElement< CDmElement >( "SPEAK", clip->GetFileId() ); + if ( speakProperties ) + { + clip->SetValue< CDmElement >( "SPEAK", speakProperties ); + + speakProperties->SetValue( "closedcaption_type", CChoreoEvent::NameForCCType( event->GetCloseCaptionType() ) ); + speakProperties->SetValue( "closedcaption_token", event->GetCloseCaptionToken() ); + bool usingCombined = ( event->GetCloseCaptionType() != CChoreoEvent::CC_DISABLED && event->IsUsingCombinedFile() ); + speakProperties->SetValue< bool >( "closedcaption_usingcombinedfile", usingCombined ); + speakProperties->SetValue< bool >( "closedcaption_combinedusesgender", event->IsCombinedUsingGenderToken() ); + speakProperties->SetValue< bool >( "closedcaption_noattenuate", event->IsSuppressingCaptionAttenuation() ); + } + } + break; + case CChoreoEvent::GESTURE: + { + CDmElement *gestureProperties = CreateElement< CDmElement >( "GESTURE", clip->GetFileId() ); + if ( gestureProperties ) + { + clip->SetValue< CDmElement >( "GESTURE", gestureProperties ); + + float duration; + event->GetGestureSequenceDuration( duration ); + gestureProperties->SetValue< float >( "sequenceduration", duration ); + } + } + break; + case CChoreoEvent::FLEXANIMATION: + { + // Save flex animation tracks as channels + CDmrElementArray<> array( clip, "channels" ); + Assert( array.IsValid() ); + if ( array.IsValid() ) + { + // Now add a DmeChannel for each flex controller + int numTracks = event->GetNumFlexAnimationTracks(); + for ( int i = 0 ; i < numTracks; ++i ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + + CDmeChannel *channel = CreateElement< CDmeChannel >( track->GetFlexControllerName(), clip->GetFileId() ); + array.AddToTail( channel ); + + channel->SetValue< bool >( "flexchannel", true ); + + channel->SetValue< bool >( "disabled", !track->IsTrackActive() ); + channel->SetValue< bool >( "combo", track->IsComboType() ); + + channel->SetValue< float >( "rangemin", track->GetMin() ); + channel->SetValue< float >( "rangemax", track->GetMax() ); + + channel->SetValue< bool >( "isbalancechannel", false ); + + // Fill in values..., just log for now + channel->CreateLog( AT_FLOAT ); + CDmeTypedLog<float> *log = static_cast< CDmeTypedLog<float> * >( channel->GetLog() ); + if ( log ) + { + CDmeFloatCurveInfo *pCurveInfo = CreateElement< CDmeFloatCurveInfo >( "floatcurveinfo", clip->GetFileId() ); + + pCurveInfo->SetDefaultCurveType( MAKE_CURVE_TYPE( INTERPOLATE_CATMULL_ROM_NORMALIZEX, INTERPOLATE_CATMULL_ROM_NORMALIZEX ) ); + pCurveInfo->SetRightEdgeTime( DmeTime_t( event->GetDuration() ) ); + + pCurveInfo->SetUseEdgeInfo( true ); + pCurveInfo->SetDefaultEdgeZeroValue( 0.0f ); + // Left edge + pCurveInfo->SetEdgeInfo( 0, + track->IsEdgeActive( true ), + track->GetEdgeZeroValue( true ), + track->GetEdgeCurveType( true ) ); + // Right edge + pCurveInfo->SetEdgeInfo( 1, + track->IsEdgeActive( false ), + track->GetEdgeZeroValue( false ), + track->GetEdgeCurveType( false ) ); + + log->SetCurveInfo( pCurveInfo ); + + // Now set up edge properties + + int sampleCount = track->GetNumSamples(); + for ( int j = 0; j < sampleCount; ++j ) + { + CExpressionSample *sample = track->GetSample( j ); + log->SetKey( DmeTime_t( sample->time ), sample->value, sample->GetCurveType() ); + } + } + + // Right out the stereo "balance" curve + if ( track->IsComboType() ) + { + char balanceChannelName[ 512 ]; + Q_snprintf( balanceChannelName, sizeof( balanceChannelName ), "%s_balance", track->GetFlexControllerName() ); + + CDmeChannel *balanceChannel = CreateElement< CDmeChannel >( balanceChannelName, clip->GetFileId() ); + array.AddToTail( balanceChannel ); + + channel->SetValue( "balanceChannel", balanceChannel ); + + balanceChannel->SetValue< bool >( "flexchannel", true ); + + balanceChannel->SetValue< bool >( "disabled", !track->IsTrackActive() ); + balanceChannel->SetValue< bool >( "isbalancechannel", true ); + + balanceChannel->CreateLog( AT_FLOAT ); + CDmeTypedLog< float > *balance = static_cast< CDmeTypedLog< float > * >( balanceChannel->GetLog() ); + if ( balance ) + { + CDmeFloatCurveInfo *pCurveInfo = CreateElement< CDmeFloatCurveInfo >( "floatcurveinfo", clip->GetFileId() ); + + pCurveInfo->SetDefaultCurveType( MAKE_CURVE_TYPE( INTERPOLATE_CATMULL_ROM_NORMALIZEX, INTERPOLATE_CATMULL_ROM_NORMALIZEX ) ); + pCurveInfo->SetRightEdgeTime( DmeTime_t( event->GetDuration() ) ); + + pCurveInfo->SetUseEdgeInfo( false ); + pCurveInfo->SetDefaultEdgeZeroValue( 0.5f ); + + /* + // Don't need to support edge properties for balance curves? + // Left edge + pCurveInfo->SetEdgeInfo( 0, + track->IsEdgeActive( true ), + track->GetEdgeZeroValue( true ), + track->GetEdgeCurveType( true ) ); + // Right edge + pCurveInfo->SetEdgeInfo( 1, + track->IsEdgeActive( false ), + track->GetEdgeZeroValue( false ), + track->GetEdgeCurveType( false ) ); + */ + + balance->SetCurveInfo( pCurveInfo ); + + // Now set up edge properties + + int sampleCount = track->GetNumSamples( 1 ); + for ( int j = 0; j < sampleCount; ++j ) + { + CExpressionSample *sample = track->GetSample( j, 1 ); + balance->SetKey( DmeTime_t( sample->time ), sample->value, sample->GetCurveType() ); + } + } + } + } + } + } + break; + case CChoreoEvent::LOOP: + { + CDmElement *loopProperties = CreateElement< CDmElement >( "LOOP", clip->GetFileId() ); + if ( loopProperties ) + { + clip->SetValue< CDmElement >( "LOOP", loopProperties ); + + loopProperties->SetValue< int >( "loopcount", event->GetLoopCount() ); + } + } + break; + } + + return true; +} + +bool ConvertSceneToDmx( CChoreoScene *scene, CDmeFilmClip *dmx ) +{ + bool bret = true; + + dmx->SetName( scene->GetFilename() ); + + CDmeTimeFrame *tf = dmx->GetTimeFrame(); + Assert( tf ); + if ( tf ) + { + tf->SetDuration( DmeTime_t( scene->FindStopTime() ) ); + } + + CDmElement *scaleSettings = CreateElement< CDmElement >( "scalesettings", dmx->GetFileId() ); + Assert( scaleSettings ); + CDmAttribute *scaleAttribute = dmx->AddAttributeElement< CDmElement >( "scalesettings" ); + Assert( scaleAttribute ); + scaleAttribute->SetValue( scaleSettings->GetHandle() ); + if ( scaleSettings ) + { + for ( int i = scene->TimeZoomFirst(); i != scene->TimeZoomInvalid(); i = scene->TimeZoomNext( i ) ) + { + const char *name = scene->TimeZoomName( i ); + int value = scene->GetTimeZoom( name ); + + scaleSettings->SetValue< int >( name, value ); + } + } + + CDmeTrackGroup *pTrackGroup = dmx->FindOrAddTrackGroup( VCD_SCENE_RAMP_TRACK_GROUP_NAME ); + CDmeTrack *track = pTrackGroup->FindOrAddTrack( VCD_SCENE_RAMP_TRACK_GROUP_NAME, DMECLIP_CHANNEL ); + Assert( track ); + + // Set a CDmeChannel for the scene_ramp + CDmeChannelsClip *pClip = CreateElement< CDmeChannelsClip >( VCD_SCENE_RAMP_TRACK_GROUP_NAME, dmx->GetFileId() ); + Assert( pClip ); + track->AddClip( pClip ); + + int rampCount = scene->GetSceneRampCount(); + if ( rampCount > 0 ) + { + // scene_ramp is a channels + CDmrElementArray<> array( pClip, "channels" ); + Assert( array.IsValid() ); + if ( array.IsValid() ) + { + CDmeChannel *channel = CreateElement< CDmeChannel >( VCD_SCENE_RAMP_TRACK_GROUP_NAME, dmx->GetFileId() ); + array.AddToTail( channel ); + + // Fill in values..., just log for now + channel->CreateLog( AT_FLOAT ); + CDmeTypedLog<float> *ramp = static_cast< CDmeTypedLog<float> * >( channel->GetLog() ); + if ( ramp ) + { + CDmeFloatCurveInfo *pCurveInfo = CreateElement< CDmeFloatCurveInfo >( "floatcurveinfo", dmx->GetFileId() ); + + pCurveInfo->SetDefaultCurveType( MAKE_CURVE_TYPE( INTERPOLATE_CATMULL_ROM_NORMALIZE, INTERPOLATE_CATMULL_ROM_NORMALIZE ) ); + pCurveInfo->SetRightEdgeTime( DmeTime_t( scene->FindStopTime() ) ); + + pCurveInfo->SetUseEdgeInfo( true ); + pCurveInfo->SetDefaultEdgeZeroValue( 0.0f ); + // Left edge + pCurveInfo->SetEdgeInfo( 0, + scene->GetSceneRamp()->IsEdgeActive( true ), + scene->GetSceneRamp()->GetEdgeZeroValue( true ), + scene->GetSceneRamp()->GetEdgeCurveType( true ) ); + // Right edge + pCurveInfo->SetEdgeInfo( 1, + scene->GetSceneRamp()->IsEdgeActive( false ), + scene->GetSceneRamp()->GetEdgeZeroValue( false ), + scene->GetSceneRamp()->GetEdgeCurveType( false ) ); + + ramp->SetCurveInfo( pCurveInfo ); + + for ( int j = 0; j < rampCount; ++j ) + { + CExpressionSample *sample = scene->GetSceneRamp( j ); + ramp->SetKey( DmeTime_t( sample->time ), sample->value, sample->GetCurveType() ); + } + } + } + } + + // Walk the actors and channels + int numActors = scene->GetNumActors(); + for ( int actor = 0; actor < numActors; ++actor ) + { + CChoreoActor *pActor = scene->GetActor( actor ); + Assert( pActor ); + if ( !pActor ) + continue; + + CDmeTrackGroup *pTrackGroup = dmx->FindOrAddTrackGroup( pActor->GetName() ); + Assert( pTrackGroup ); + + pTrackGroup->SetValue< bool >( "isActor", true ); + pTrackGroup->SetValue< bool >( "actorDisabled", !pActor->GetActive() ); + pTrackGroup->SetValue( "actorModel", pActor->GetFacePoserModelName() ); + + int numChannels = pActor->GetNumChannels(); + for ( int channel = 0; channel < numChannels; ++channel ) + { + CChoreoChannel *pChannel = pActor->GetChannel( channel ); + Assert( pChannel ); + if ( !pChannel ) + continue; + + const char *channelName = pChannel->GetName(); + CDmeTrack *track = pTrackGroup->FindOrAddTrack( channelName, DMECLIP_CHANNEL ); + Assert( track ); + if ( !track ) + continue; + + track->SetMute( !pChannel->GetActive() ); + + int numEvents = pChannel->GetNumEvents(); + for ( int event = 0; event < numEvents; ++event ) + { + CChoreoEvent *pEvent = pChannel->GetEvent( event ); + Assert( pEvent ); + if ( !pEvent ) + continue; + + // Set up event + CDmeChannelsClip *pClip = CreateElement< CDmeChannelsClip >( "", dmx->GetFileId() ); + Assert( pClip ); + track->AddClip( pClip ); + + // Fill in data + bool success = ConvertEventToDmx( pEvent, pClip ); + if ( !success ) + { + bret = false; + Assert( 0 ); + break; + } + } + } + } + + pTrackGroup = dmx->FindOrAddTrackGroup( VCD_GLOBAL_EVENTS_TRACK_GROUP_NAME ); + Assert( pTrackGroup ); + + track = pTrackGroup->FindOrAddTrack( VCD_GLOBAL_EVENTS_TRACK_GROUP_NAME, DMECLIP_CHANNEL ); + Assert( track ); + + // Now add global events + int numEvents = scene->GetNumEvents(); + for ( int event = 0; event < numEvents; ++event ) + { + CChoreoEvent *pEvent = scene->GetEvent( event ); + if ( !pEvent || pEvent->GetActor() ) + continue; + + // Set up event + CDmeChannelsClip *pClip = CreateElement< CDmeChannelsClip >( "", dmx->GetFileId() ); + Assert( pClip ); + + track->AddClip( pClip ); + + // Fill in data + bool success = ConvertEventToDmx( pEvent, pClip ); + if ( !success ) + { + bret = false; + Assert( 0 ); + break; + } + } + + dmx->SetValue( "associated_bsp", scene->GetMapname() ); + dmx->SetValue< float >( "fps", scene->GetSceneFPS() ); + dmx->SetValue< bool >( "snap", scene->IsUsingFrameSnap() ); + + return bret; +} + +void EnsureActorAndChannelForTrack( CChoreoScene *scene, CDmeTrackGroup *pActor, CDmeTrack *pChannel ) +{ + const char *actorName = pActor->GetName(); + const char *channelName = pChannel->GetName(); + + CChoreoActor *a = scene->FindActor( actorName ); + if ( !a ) + { + a = scene->AllocActor(); + Assert( a ); + a->SetName( actorName ); + a->SetActive( !pActor->GetValue< bool >( "actorDisabled" ) ); + a->SetFacePoserModelName( pActor->GetValueString( "actorModel" ) ); + } + + CChoreoChannel *c = a->FindChannel( channelName ); + if ( !c ) + { + c = scene->AllocChannel(); + Assert( c ); + c->SetName( channelName ); + c->SetActor( a ); + c->SetActive( !pChannel->IsMute() ); + a->AddChannel( c ); + } +} + +template< class T > +T *FindAttributeInArray( const CDmrElementArray<> &array, const char *elementName ) +{ + int c = array.Count(); + for ( int i = 0; i < c; ++i ) + { + T *element = CastElement< T >( array[ i ] ); + if ( !element ) + continue; + + if ( !Q_stricmp( element->GetName(), elementName ) ) + return element; + } + + return NULL; +} + +bool ConvertDmxToEvent( CChoreoScene *scene, CDmeTrackGroup *pActor, CDmeTrack *pChannel, CDmeChannelsClip *clip, bool globalEvent ) +{ + bool bret = true; + + // Allocate choreo event + CChoreoEvent *event = scene->AllocEvent(); + Assert( event ); + if ( !event ) + { + bret = false; + return bret; + } + + event->SetName( clip->GetName() ); + event->SetType( CChoreoEvent::TypeForName( clip->GetValueString( "eventtype" ) ) ); + + if ( !globalEvent ) + { + EnsureActorAndChannelForTrack( scene, pActor, pChannel ); + + const char *actorName = pActor->GetName(); + const char *channelName = pChannel->GetName(); + + CChoreoActor *a = scene->FindActor( actorName ); + Assert( a ); + CChoreoChannel *c = a->FindChannel( channelName ); + Assert( c ); + + if ( !a || !c ) + { + bret = false; + return bret; + } + + event->SetActor( a ); + event->SetChannel( c ); + c->AddEvent( event ); + } + + // Set timeframe info + CDmeTimeFrame *tf = clip->GetTimeFrame(); + Assert( tf ); + if ( tf ) + { + event->SetStartTime( tf->GetStartTime().GetSeconds() ); + float duration = tf->GetDuration().GetSeconds(); + if ( duration <= 0.0f ) + { + event->SetEndTime( -1.0f ); + } + else + { + event->SetEndTime( event->GetStartTime() + duration ); + } + } + + event->SetParameters( clip->GetValueString( "parameters" ) ); + event->SetParameters2( clip->GetValueString( "parameters2" ) ); + + const CDmrElementArray<> array( clip, "channels" ); + Assert( array.IsValid() ); + if ( array.IsValid() ) + { + int c = array.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CDmeChannel *channel = CastElement< CDmeChannel >( array[i] ); + if ( !channel || Q_stricmp( channel->GetName(), "event_ramp" ) ) + continue; + + CDmeTypedLog< float > *ramp = static_cast< CDmeTypedLog< float > * >( channel->GetLog() ); + if ( !ramp ) + continue; + + bool active[ 2 ]; + float value[ 2 ]; + int curveType[ 2 ]; + + ramp->GetEdgeInfo( 0, active[ 0 ], value[ 0 ], curveType[ 0 ] ); + ramp->GetEdgeInfo( 1, active[ 1 ], value[ 1 ], curveType[ 1 ] ); + + event->GetRamp()->SetEdgeActive( true, active[ 0 ] ); + event->GetRamp()->SetEdgeActive( false, active[ 1 ] ); + + event->GetRamp()->SetEdgeInfo( true, curveType[ 0 ], value[ 0 ] ); + event->GetRamp()->SetEdgeInfo( false, curveType[ 1 ], value[ 1 ] ); + + int rampCount = ramp->GetKeyCount(); + for ( int j = 0; j < rampCount; ++j ) + { + CExpressionSample *sample = event->AddRamp( ramp->GetKeyTime( j ).GetSeconds(), ramp->GetKeyValue( j ), false ); + sample->SetCurveType( ramp->GetKeyCurveType( j ) ); + } + } + } + + event->SetPitch( clip->GetValue< float >( "pitch" ) ); + event->SetYaw( clip->GetValue< float >( "yaw" ) ); + + event->SetResumeCondition( clip->GetValue< bool >( "resumecondition" ) ); + event->SetLockBodyFacing( clip->GetValue< bool >( "lockbodyfacing" ) ); + event->SetDistanceToTarget( clip->GetValue< float >( "distancetotarget" ) ); + event->SetFixedLength( clip->GetValue< bool >( "fixedlength" ) ); + event->SetForceShortMovement( clip->GetValue< bool >( "forceshortmovement" ) ); + event->SetActive( clip->GetValue< bool >( "active" ) ); + + if ( clip->HasAttribute( "tags" ) ) + { + CDmElement *tags = clip->GetValueElement< CDmElement >( "tags" ); + if ( tags ) + { + // Now create arrays for the name and percentages + const CDmrStringArray names( tags, "tagname" ); + const CDmrArray<float> percentages( tags, "tagpercentage" ); + Assert( names.IsValid() && percentages.IsValid() ); + + Assert( names.Count() == percentages.Count() ); + for ( int t = 0; t < names.Count(); t++ ) + { + event->AddRelativeTag( names[t], percentages[t] ); + } + } + } + + if ( clip->HasAttribute( "timing_tags" ) ) + { + CDmElement *tags = clip->GetValueElement< CDmElement >( "timing_tags" ); + if ( tags ) + { + // Now create arrays for the name and percentages + const CDmrStringArray names( tags, "tagname" ); + const CDmrArray<float> percentages( tags, "tagpercentage" ); + const CDmrArray<bool> lockstates( tags, "lockedstate" ); + + Assert( names.IsValid() && percentages.IsValid() && lockstates.IsValid() ); + Assert( names.Count() == percentages.Count() && names.Count() == lockstates.Count() ); + + for ( int t = 0; t < names.Count(); t++ ) + { + event->AddTimingTag( names[ t ], percentages[ t ], lockstates[ t ] ); + } + } + } + + + // Abs tags + int tagtype; + for ( tagtype = 0; tagtype < CChoreoEvent::NUM_ABS_TAG_TYPES; tagtype++ ) + { + char sz[ 512 ]; + Q_snprintf( sz, sizeof( sz ), "absolutetags %s", CChoreoEvent::NameForAbsoluteTagType( (CChoreoEvent::AbsTagType)tagtype ) ); + + if ( clip->HasAttribute( sz ) ) + { + CDmElement *tags = clip->GetValueElement< CDmElement >( sz ); + if ( tags ) + { + // Now create arrays for the name and percentages + const CDmrStringArray names( tags, "tagname" ); + const CDmrArray<float> percentages( tags, "tagpercentage" ); + + Assert( names.IsValid() && percentages.IsValid() ); + Assert( names.Count() == percentages.Count() ); + + for ( int t = 0; t < names.Count(); t++ ) + { + event->AddAbsoluteTag( (CChoreoEvent::AbsTagType)tagtype, names[ t ], percentages[ t ] ); + } + } + } + } + + if ( clip->HasAttribute( "relative_tag" ) ) + { + CDmElement *relativeTag = clip->GetValueElement< CDmElement >( "relative_tag" ); + if ( relativeTag ) + { + event->SetUsingRelativeTag + ( + true, + relativeTag->GetValueString( "tagname" ), + relativeTag->GetValueString( "tagwav" ) + ); + } + } + + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + CDmElement *speakProperties = NULL; + if ( clip->HasAttribute( "SPEAK" ) ) + { + speakProperties = clip->GetValueElement< CDmElement >( "SPEAK" ); + } + if ( speakProperties ) + { + event->SetCloseCaptionType( CChoreoEvent::CCTypeForName( speakProperties->GetValueString( "closedcaption_type" ) ) ); + event->SetCloseCaptionToken( speakProperties->GetValueString( "closedcaption_token" ) ); + event->SetUsingCombinedFile( speakProperties->GetValue< bool >( "closedcaption_usingcombinedfile" ) ); + event->SetCombinedUsingGenderToken( speakProperties->GetValue< bool >( "closedcaption_combinedusesgender" ) ); + event->SetSuppressingCaptionAttenuation( speakProperties->GetValue< bool >( "closedcaption_noattenuate" ) ); + } + } + break; + case CChoreoEvent::GESTURE: + { + CDmElement *gestureProperties = NULL; + if ( clip->HasAttribute( "GESTURE" ) ) + { + gestureProperties = clip->GetValueElement< CDmElement >( "GESTURE" ); + } + if ( gestureProperties ) + { + if ( Q_stricmp( clip->GetName(), "NULL" ) ) + { + event->SetGestureSequenceDuration( gestureProperties->GetValue< float >( "sequenceduration" ) ); + } + } + } + break; + case CChoreoEvent::FLEXANIMATION: + { + // Save flex animation tracks as channels + const CDmrElementArray<> array( clip, "channels" ); + if ( array.IsValid() ) + { + // Now add a DmeChannel for each flex controller + int numTracks = array.Count(); + for ( int i = 0 ; i < numTracks; ++i ) + { + CDmeChannel *channel = CastElement< CDmeChannel >( array[ i ] ); + Assert( channel ); + if ( !channel ) + { + bret = false; + break; + } + + if ( !channel->HasAttribute( "flexchannel" ) ) + continue; + + // Skip all helper channels, only care about flexchannels + if ( !channel->GetValue< bool >( "flexchannel" ) ) + continue; + + // Skip the balance channels, we'll pull their data below + if ( channel->GetValue< bool >( "isbalancechannel" ) ) + continue; + + Assert( !Q_stristr( channel->GetName(), "_balance" ) ); + + CFlexAnimationTrack *track = event->AddTrack( channel->GetName() ); + Assert( track ); + + if ( !track ) + { + bret = false; + break; + } + + track->SetTrackActive( !channel->GetValue< bool >( "disabled" ) ); + track->SetComboType( channel->GetValue< bool >( "combo" ) ); + + track->SetMin( channel->GetValue< float >( "rangemin" ) ); + track->SetMax( channel->GetValue< float >( "rangemax" ) ); + + CDmeTypedLog<float> *log = static_cast< CDmeTypedLog<float> * >( channel->GetLog() ); + if ( log ) + { + bool active[ 2 ]; + float value[ 2 ]; + int curveType[ 2 ]; + + log->GetEdgeInfo( 0, active[ 0 ], value[ 0 ], curveType[ 0 ] ); + log->GetEdgeInfo( 1, active[ 1 ], value[ 1 ], curveType[ 1 ] ); + + track->SetEdgeActive( true, active[ 0 ] ); + track->SetEdgeActive( false, active[ 1 ] ); + + track->SetEdgeInfo( true, curveType[ 0 ], value[ 0 ] ); + track->SetEdgeInfo( false, curveType[ 1 ], value[ 1 ] ); + + int sampleCount = log->GetKeyCount(); + for ( int j = 0; j < sampleCount; ++j ) + { + int curveType = log->GetKeyCurveType( j ); + float value = log->GetKeyValue( j ); + DmeTime_t time = log->GetKeyTime( j ); + + CExpressionSample *sample = track->AddSample( time.GetSeconds(), value, 0 ); + sample->SetCurveType( curveType ); + } + } + + // Right out the stereo "balance" curve + if ( track->IsComboType() ) + { + char balanceChannelName[ 512 ]; + Q_snprintf( balanceChannelName, sizeof( balanceChannelName ), "%s_balance", track->GetFlexControllerName() ); + + // Find the balance data + CDmeChannel *balanceChannel = FindAttributeInArray< CDmeChannel >( array, balanceChannelName ); + if ( balanceChannel ) + { + CDmeTypedLog< float > *balance = static_cast< CDmeTypedLog< float > * >( balanceChannel->GetLog() ); + if ( balance ) + { + // Now set up edge properties + int sampleCount = balance->GetKeyCount(); + for ( int j = 0; j < sampleCount; ++j ) + { + int curveType = balance->GetKeyCurveType( j ); + float value = balance->GetKeyValue( j ); + DmeTime_t time = balance->GetKeyTime( j ); + + CExpressionSample *sample = track->AddSample( time.GetSeconds(), value, 1 ); + sample->SetCurveType( curveType ); + } + } + else + { + char msg[ 512 ]; + Q_snprintf( msg, sizeof( msg ), "Error: Missing balance channel for combo flex track (%s) in (%s)\n", track->GetFlexControllerName(), event->GetName() ); + Warning( msg ); + } + } + } + } + } + } + break; + case CChoreoEvent::LOOP: + { + CDmElement *loopProperties = NULL; + if ( clip->HasAttribute( "LOOP" ) ) + { + loopProperties = clip->GetValueElement< CDmElement >( "LOOP" ); + } + if ( loopProperties ) + { + event->SetLoopCount( loopProperties->GetValue< int >( "loopcount" ) ); + } + } + break; + } + + return true; +} + +bool ConvertDmxToScene( CDmeFilmClip *dmx, CChoreoScene *scene ) +{ + bool bret = true; + + // This should have been created correctly already + // Assert( !Q_stricmp( scene->GetFilename(), dmx->GetName() ) ); + + CDmElement *scaleSettings = dmx->GetValueElement< CDmElement >( "scalesettings" ); + Assert( scaleSettings ); + if ( scaleSettings ) + { + CDmAttribute *setting = scaleSettings->FirstAttribute(); + for ( ; setting ; setting = setting->NextAttribute() ) + { + if ( setting->GetType() != AT_INT ) + continue; + + scene->SetTimeZoom( setting->GetName(), setting->GetValue<int>() ); + } + } + + // Deal with the scene ramp + CDmeTrackGroup *pTrackGroup = dmx->FindTrackGroup( VCD_SCENE_RAMP_TRACK_GROUP_NAME ); + if ( pTrackGroup ) + { + CDmeTrack *track = pTrackGroup->FindTrack( VCD_SCENE_RAMP_TRACK_GROUP_NAME ); + if ( track ) + { + CDmeChannelsClip *pClip = CastElement< CDmeChannelsClip >( track->FindNamedClip( VCD_SCENE_RAMP_TRACK_GROUP_NAME ) ); + if ( pClip ) + { + // scene_ramp should be the first subchannel + const CDmrElementArray<> array( pClip, "channels" ); + Assert( array.IsValid() ); + if ( array.IsValid() && ( array.Count() > 0 ) ) + { + CDmeChannel *channel = CastElement< CDmeChannel >( array[0] ); + if ( channel ) + { + CDmeTypedLog< float > *ramp = static_cast< CDmeTypedLog< float > * >( channel->GetLog() ); + if ( ramp ) + { + bool active[ 2 ]; + float value[ 2 ]; + int curveType[ 2 ]; + + ramp->GetEdgeInfo( 0, active[ 0 ], value[ 0 ], curveType[ 0 ] ); + ramp->GetEdgeInfo( 1, active[ 1 ], value[ 1 ], curveType[ 1 ] ); + + scene->GetSceneRamp()->SetEdgeActive( true, active[ 0 ] ); + scene->GetSceneRamp()->SetEdgeActive( false, active[ 1 ] ); + + scene->GetSceneRamp()->SetEdgeInfo( true, curveType[ 0 ], value[ 0 ] ); + scene->GetSceneRamp()->SetEdgeInfo( false, curveType[ 1 ], value[ 1 ] ); + + int rampCount = ramp->GetKeyCount(); + for ( int j = 0; j < rampCount; ++j ) + { + CExpressionSample *sample = scene->AddSceneRamp( ramp->GetKeyTime( j ).GetSeconds(), ramp->GetKeyValue( j ), false ); + sample->SetCurveType( ramp->GetKeyCurveType( j ) ); + } + } + } + } + } + } + } + + // Deal with global events + pTrackGroup = dmx->FindTrackGroup( VCD_GLOBAL_EVENTS_TRACK_GROUP_NAME ); + if ( pTrackGroup ) + { + CDmeTrack *pTrack = pTrackGroup->FindTrack( VCD_GLOBAL_EVENTS_TRACK_GROUP_NAME ); + if ( pTrack ) + { + // Now add global events + int numEvents = pTrack->GetClipCount(); + for ( int event = 0; event < numEvents; ++event ) + { + CDmeChannelsClip *pClip = CastElement< CDmeChannelsClip >( pTrack->GetClip( event ) ); + if ( !pClip ) + continue; + + bool success = ConvertDmxToEvent( scene, pTrackGroup, pTrack, pClip, true ); + if ( !success ) + { + bret = false; + Assert( 0 ); + break; + } + } + } + } + + int nNumTrackGroups = dmx->GetTrackGroupCount(); + for ( int i = 0; i < nNumTrackGroups; ++i ) + { + CDmeTrackGroup *pTrackGroup = dmx->GetTrackGroup( i ); + if ( !pTrackGroup->GetValue< bool >( "isActor" ) ) + continue; + + // Walk the track groups + int nNumTracks = pTrackGroup->GetTrackCount(); + for ( int j = 0 ; j < nNumTracks; ++j ) + { + CDmeTrack *pTrack = pTrackGroup->GetTrack( j ); + + EnsureActorAndChannelForTrack( scene, pTrackGroup, pTrack ); + + int numEvents = pTrack->GetClipCount(); + for ( int clip = 0; clip < numEvents; ++clip ) + { + CDmeChannelsClip *pClip = CastElement< CDmeChannelsClip >( pTrack->GetClip( clip ) ); + if ( !pClip ) + continue; + + bool success = ConvertDmxToEvent( scene, pTrackGroup, pTrack, pClip, false ); + if ( !success ) + { + bret = false; + Assert( 0 ); + break; + } + } + } + } + + scene->SetMapname( dmx->GetValueString( "associated_bsp" ) ); + scene->SetSceneFPS( dmx->GetValue< float >( "fps" ) ); + scene->SetUsingFrameSnap( dmx->GetValue< bool >( "snap" ) ); + + return bret; +}
\ No newline at end of file diff --git a/movieobjects/importintovcd.cpp b/movieobjects/importintovcd.cpp new file mode 100644 index 0000000..9b88605 --- /dev/null +++ b/movieobjects/importintovcd.cpp @@ -0,0 +1,863 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "movieobjects/importintovcd.h" +#include "movieobjects/movieobjects.h" +#include "tier3/scenetokenprocessor.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "choreoevent.h" +#include "tier2/p4helpers.h" +#include "tier1/utlbuffer.h" +#include "tier3/tier3.h" +#include "datacache/imdlcache.h" +#include "filesystem.h" +#include "studio.h" + + +//----------------------------------------------------------------------------- +// Helper wrapper class for log layers (necessary to avoid movieobjects dependence) +//----------------------------------------------------------------------------- +class CDmeLogLayerHelper +{ +public: + CDmeLogLayerHelper( CDmElement *pLogLayer, int nDefaultCurveType ); + + // Finds a key + int FindKey( int nTime ) const; + + // Gets a value at a particular time + float GetValue( int nTime ) const; + + // Inserts keys + void AddToTail( int nTime, float flValue, int nCurveType ); + void InsertAfter( int nAfter, int nTime, float flValue, int nCurveType ); + int InsertKey( int nTime, float flValue, int nCurveType ); + + // Simplifies the curve + void Simplify( float flThreshhold ); + + void SetCurveType( int nKey, int nCurveType ); + + // Total simplified points + static int TotalRemovedPoints(); + +private: + void CurveSimplify_R( float flThreshold, int nStartPoint, int nEndPoint, CDmeLogLayerHelper *pDest ); + + // Computes the total error + float ComputeTotalError( CDmeLogLayerHelper *pDest, int nStartPoint, int nEndPoint ); + + // Select the best fit curve type + void ChooseBestCurveType( int nKey, int nStartPoint, int nEndPoint, CDmeLogLayerHelper *pDest ); + + // Compute first + second derivatives of data + void ComputeDerivates( float *pSlope, float *pAccel, int nPoint, CDmeLogLayerHelper *pDest ); + + CDmElement *m_pLogLayer; + CDmrArray<int> m_times; + CDmrArray<float> m_values; + CDmrArray<int> m_curvetypes; + int m_nDefaultCurveType; + + static int s_nTotalRemovedPoints; +}; + + +//----------------------------------------------------------------------------- +// Total simplified points +//----------------------------------------------------------------------------- +int CDmeLogLayerHelper::s_nTotalRemovedPoints = 0; +int CDmeLogLayerHelper::TotalRemovedPoints() +{ + return s_nTotalRemovedPoints; +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDmeLogLayerHelper::CDmeLogLayerHelper( CDmElement *pLogLayer, int nDefaultCurveType ) : + m_pLogLayer( pLogLayer ), m_times( pLogLayer, "times", true ), + m_values( pLogLayer, "values", true ), m_curvetypes( pLogLayer, "curvetypes", true ) +{ + m_nDefaultCurveType = nDefaultCurveType; +} + + +//----------------------------------------------------------------------------- +// Inserts keys +//----------------------------------------------------------------------------- +void CDmeLogLayerHelper::AddToTail( int nTime, float flValue, int nCurveType ) +{ + m_times.AddToTail( nTime ); + m_values.AddToTail( flValue ); + m_curvetypes.AddToTail( nCurveType ); +} + +void CDmeLogLayerHelper::InsertAfter( int nAfter, int nTime, float flValue, int nCurveType ) +{ + int nBefore = nAfter + 1; + m_times.InsertBefore( nBefore, nTime ); + m_values.InsertBefore( nBefore, flValue ); + m_curvetypes.InsertBefore( nBefore, nCurveType ); +} + +int CDmeLogLayerHelper::InsertKey( int nTime, float flValue, int nCurveType ) +{ + int nAfter = FindKey( nTime ); + InsertAfter( nAfter, nTime, flValue, nCurveType ); + return nAfter + 1; +} + +void CDmeLogLayerHelper::SetCurveType( int nKey, int nCurveType ) +{ + m_curvetypes.Set( nKey, nCurveType ); +} + + +//----------------------------------------------------------------------------- +// Finds a key +//----------------------------------------------------------------------------- +int CDmeLogLayerHelper::FindKey( int nTime ) const +{ + int tn = m_times.Count(); + for ( int ti = tn - 1; ti >= 0; --ti ) + { + if ( nTime >= m_times[ ti ] ) + return ti; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Gets a value at a particular time +//----------------------------------------------------------------------------- +float CDmeLogLayerHelper::GetValue( int nTime ) const +{ + int tc = m_times.Count(); + + Assert( m_values.Count() == tc ); + + int ti = FindKey( nTime ); + if ( ti < 0 ) + { + if ( tc > 0 ) + return m_values[ 0 ]; + return 0.0f; + } + + // Early out if we're at the end + if ( ti >= tc - 1 ) + return m_values[ ti ]; + + // Figure out the lerp factor + int nDummy, nInterpolationType; + int nCurveType = m_curvetypes.Count() ? m_curvetypes[ti] : m_nDefaultCurveType; + Interpolator_CurveInterpolatorsForType( nCurveType, nInterpolationType, nDummy ); + + Vector vecOutput; + Vector vecArg1( 0.0f, m_values[ti], 0.0f ); + Vector vecArg2( 1.0f, m_values[ti+1], 0.0f ); + float t = (float)( nTime - m_times[ti] ) / (float)( m_times[ti+1] - m_times[ti] ); + Interpolator_CurveInterpolate( nInterpolationType, vecArg1, vecArg1, vecArg2, vecArg2, t, vecOutput ); + return vecOutput.y; +} + + +//----------------------------------------------------------------------------- +// Computes the total error +//----------------------------------------------------------------------------- +float CDmeLogLayerHelper::ComputeTotalError( CDmeLogLayerHelper *pDest, int nStartPoint, int nEndPoint ) +{ + float flTotalDistance = 0.0f; + + for ( int i = nStartPoint; i <= nEndPoint; ++i ) + { + float flCheck = m_values[i]; + float flCheck2 = pDest->GetValue( m_times[i] ); + float flDistance = fabs( flCheck2 - flCheck ); + flTotalDistance += flDistance; + } + + return flTotalDistance; +} + + +//----------------------------------------------------------------------------- +// Select the best fit curve type +//----------------------------------------------------------------------------- +static int s_nInterpTypes[] = +{ + INTERPOLATE_LINEAR_INTERP, + INTERPOLATE_EASE_INOUT, +// INTERPOLATE_EASE_IN, +// INTERPOLATE_EASE_OUT, +// INTERPOLATE_EXPONENTIAL_DECAY, +// INTERPOLATE_HOLD, + -1, +}; + +void CDmeLogLayerHelper::ChooseBestCurveType( int nKey, int nStartPoint, int nEndPoint, CDmeLogLayerHelper *pDest ) +{ + return; + + float flMinError = FLT_MAX; + int nBestInterpType = -1; + for ( int i = 0; s_nInterpTypes[i] >= 0; ++i ) + { + pDest->SetCurveType( nKey, MAKE_CURVE_TYPE( s_nInterpTypes[i], s_nInterpTypes[i] ) ); + float flError = ComputeTotalError( pDest, nStartPoint, nEndPoint ); + if ( flMinError > flError ) + { + nBestInterpType = s_nInterpTypes[i]; + flMinError = flError; + } + } + Assert( nBestInterpType >= 0 ); + pDest->SetCurveType( nKey, MAKE_CURVE_TYPE( nBestInterpType, nBestInterpType ) ); +} + + +//----------------------------------------------------------------------------- +// Compute first + second derivatives of data +//----------------------------------------------------------------------------- +void CDmeLogLayerHelper::ComputeDerivates( float *pSlope, float *pAccel, int nPoint, CDmeLogLayerHelper *pDest ) +{ + // Central difference, assume linear slope between points. + // Find neighboring point with minimum distance + bool bLeftEdge = ( nPoint == 0 ); + bool bRightEdge = ( nPoint == m_times.Count() - 1 ); + + int nTime = m_times[nPoint]; + int nPrevTime = ( !bLeftEdge ) ? m_times[ nPoint - 1 ] : nTime - 1000; + int nNextTime = ( !bRightEdge ) ? m_times[ nPoint + 1 ] : nTime + 1000; + float flPrevPoint, flNextPoint; + if ( nTime - nPrevTime < nNextTime - nTime ) + { + // prev point is closer + flPrevPoint = ( !bLeftEdge ) ? m_values[ nPoint - 1 ] : m_values[ nPoint ]; + nNextTime = nTime + ( nTime - nPrevTime ); + flNextPoint = GetValue( nNextTime ); + } + else + { + // next point is closer + flNextPoint = ( !bRightEdge ) ? m_values[ nPoint + 1 ] : m_values[ nPoint ]; + nPrevTime = nTime - ( nNextTime - nTime ); + flPrevPoint = GetValue( nPrevTime ); + } + + // Central difference: slope = ( vnext - vprev ) / ( tnext - tprev ); + // accel = ( vnext - 2 * vcurr + vprev ) / ( 0.5 * ( tnext - tprev ) )^2 + float flCurrPoint = m_values[nPoint]; + flPrevPoint -= pDest->GetValue( nPrevTime ); + flCurrPoint -= pDest->GetValue( nTime ); + flNextPoint -= pDest->GetValue( nNextTime ); + + float flDeltaTime = DMETIME_TO_SECONDS( nTime - nPrevTime ); + *pSlope = ( flNextPoint - flPrevPoint ) / ( 2.0f * flDeltaTime ); + *pAccel = ( flNextPoint - 2 * flCurrPoint + flPrevPoint ) / ( flDeltaTime * flDeltaTime ); +} + + + +//----------------------------------------------------------------------------- +// Implementation of Douglas-Peucker curve simplification routine +// (hacked to only care about error against original curve (sort of 1D) +//----------------------------------------------------------------------------- +void CDmeLogLayerHelper::CurveSimplify_R( float flThreshold, int nStartPoint, int nEndPoint, CDmeLogLayerHelper *pDest ) +{ + if ( nEndPoint <= nStartPoint + 1 ) + return; + + int nMaxPoint = nStartPoint; + float flMaxDistance = 0.0f; + + for ( int i = nStartPoint + 1 ; i < nEndPoint; ++i ) + { + float flCheck = m_values[i]; + float flCheck2 = pDest->GetValue( m_times[i] ); + float flDistance = fabs( flCheck2 - flCheck ); + + if ( flDistance < flMaxDistance ) + continue; + + nMaxPoint = i; + flMaxDistance = flDistance; + } + + /* + float flMaxAccel = 0.0f; + for ( int i = nStartPoint + 1 ; i < nEndPoint; ++i ) + { + float flSlope, flAccel; + ComputeDerivates( &flSlope, &flAccel, i, pDest ); + flAccel = fabs( flAccel ); + if ( flAccel < flMaxAccel ) + continue; + + nMaxPoint = i; + flMaxAccel = flAccel; + } + */ + + if ( flMaxDistance > flThreshold ) + { + int nKey = pDest->InsertKey( m_times[ nMaxPoint ], m_values[ nMaxPoint ], m_nDefaultCurveType ); + Assert( nKey != 0 ); + ChooseBestCurveType( nKey-1, nStartPoint, nMaxPoint, pDest ); + ChooseBestCurveType( nKey, nMaxPoint, nEndPoint, pDest ); + + CurveSimplify_R( flThreshold, nStartPoint, nMaxPoint, pDest ); + CurveSimplify_R( flThreshold, nMaxPoint, nEndPoint, pDest ); + } +} + + +//----------------------------------------------------------------------------- +// Simplifies the curve +//----------------------------------------------------------------------------- +void CDmeLogLayerHelper::Simplify( float flThreshhold ) +{ + int nFirstKey, nLastKey; + int nKeys = m_values.Count(); + if ( nKeys <= 1 ) + return; + + for ( nFirstKey = 1; nFirstKey < nKeys; ++nFirstKey ) + { + // FIXME: Should we use a tolerance check here? + if ( m_values[ nFirstKey ] != m_values[ nFirstKey - 1 ] ) + break; + } + --nFirstKey; + + for ( nLastKey = nKeys; --nLastKey >= 1; ) + { + // FIXME: Should we use a tolerance check here? + if ( m_values[ nLastKey ] != m_values[ nLastKey - 1 ] ) + break; + } + + if ( nLastKey <= nFirstKey ) + { + m_times.RemoveMultiple( 1, nKeys - 1 ); + m_values.RemoveMultiple( 1, nKeys - 1 ); + s_nTotalRemovedPoints += nKeys - 1; + return; + } + + CDmElement *pTemp = CreateElement< CDmElement >( "simplified" ); + CDmeLogLayerHelper destLayer( pTemp, m_nDefaultCurveType ); + + destLayer.AddToTail( m_times[nFirstKey], m_values[nFirstKey], m_nDefaultCurveType ); + destLayer.AddToTail( m_times[nLastKey], m_values[nLastKey], m_nDefaultCurveType ); + + // Recursively finds the point with the largest error from the "simplified curve" + // and subdivides the problem on both sides until the largest delta from the simplified + // curve is less than the tolerance + CurveSimplify_R( flThreshhold, nFirstKey, nLastKey, &destLayer ); + + m_times.CopyArray( destLayer.m_times.Base(), destLayer.m_times.Count() ); + m_values.CopyArray( destLayer.m_values.Base(), destLayer.m_values.Count() ); + m_curvetypes.CopyArray( destLayer.m_curvetypes.Base(), destLayer.m_curvetypes.Count() ); + + DestroyElement( pTemp ); + + s_nTotalRemovedPoints += nKeys - m_times.Count(); +} + + +//----------------------------------------------------------------------------- +// Finds or adds actors, channels +//----------------------------------------------------------------------------- +static CChoreoActor* FindOrAddActor( CChoreoScene *pScene, const char *pActorName, const char *pActorModel ) +{ + CChoreoActor *a = pScene->FindActor( pActorName ); + if ( !a ) + { + a = pScene->AllocActor(); + Assert( a ); + a->SetName( pActorName ); + a->SetActive( true ); + a->SetFacePoserModelName( pActorModel ); + } + return a; +} + + +//----------------------------------------------------------------------------- +// Finds animation events +//----------------------------------------------------------------------------- +static CChoreoEvent* FindOrAddAnimationEvent( CChoreoScene *pScene, CChoreoActor *pActor ) +{ + int nEventCount = pScene->GetNumEvents(); + for ( int i = 0; i < nEventCount; ++i ) + { + CChoreoEvent* pEvent = pScene->GetEvent(i); + if ( pEvent->GetActor() != pActor ) + continue; + + if ( pEvent->GetType() != CChoreoEvent::FLEXANIMATION ) + continue; + + return pEvent; + } + + // Allocate new channel + CChoreoChannel *pChannel = pScene->AllocChannel(); + pChannel->SetName( "imported_flex" ); + pChannel->SetActor( pActor ); + pChannel->SetActive( true ); + pActor->AddChannel( pChannel ); + + // Allocate choreo event + CChoreoEvent *pEvent = pScene->AllocEvent(); + pEvent->SetName( pActor->GetName() ); + pEvent->SetType( CChoreoEvent::FLEXANIMATION ); + pEvent->SetActor( pActor ); + pEvent->SetChannel( pChannel ); + pEvent->SetActive( true ); + pChannel->AddEvent( pEvent ); + + return pEvent; +} + + +//----------------------------------------------------------------------------- +// Finds sound events +//----------------------------------------------------------------------------- +static CChoreoEvent* FindOrAddSoundEvent( CChoreoScene *pScene, CChoreoActor *pActor, const char *pEventName ) +{ + int nEventCount = pScene->GetNumEvents(); + for ( int i = 0; i < nEventCount; ++i ) + { + CChoreoEvent* pEvent = pScene->GetEvent(i); + if ( pEvent->GetActor() != pActor ) + continue; + + if ( pEvent->GetType() != CChoreoEvent::SPEAK ) + continue; + + if ( Q_stricmp( pEvent->GetName(), pEventName ) ) + continue; + + return pEvent; + } + + // Allocate new channel + CChoreoChannel *pChannel = pScene->AllocChannel(); + pChannel->SetName( "imported sounds" ); + pChannel->SetActor( pActor ); + pChannel->SetActive( true ); + pActor->AddChannel( pChannel ); + + // Allocate sound event + CChoreoEvent *pEvent = pScene->AllocEvent(); + pEvent->SetName( pEventName ); + pEvent->SetType( CChoreoEvent::SPEAK ); + pEvent->SetActor( pActor ); + pEvent->SetChannel( pChannel ); + pEvent->SetActive( true ); + pChannel->AddEvent( pEvent ); + + return pEvent; +} + + +static CFlexAnimationTrack *FindOrCreateTrack( CChoreoEvent *pEvent, const char *pFlexControllerName ) +{ + CFlexAnimationTrack *pTrack = pEvent->FindTrack( pFlexControllerName ); + if ( pTrack ) + { + pTrack->Clear(); + } + else + { + pTrack = pEvent->AddTrack( pFlexControllerName ); + pTrack->SetTrackActive( true ); + } + + pTrack->SetMin( 0.0f ); + pTrack->SetMax( 1.0f ); + pTrack->SetInverted( false ); + + return pTrack; +} + + +//----------------------------------------------------------------------------- +// Returns flex controller ranges +//----------------------------------------------------------------------------- +void GetStereoFlexControllerRange( float *pMin, float *pMax, studiohdr_t *pStudioHdr, const char *pFlexName ) +{ + char pRightBuf[MAX_PATH]; + char pLeftBuf[MAX_PATH]; + Q_snprintf( pRightBuf, sizeof(pRightBuf), "right_%s", pFlexName ); + Q_snprintf( pLeftBuf, sizeof(pLeftBuf), "left_%s", pFlexName ); + + for ( LocalFlexController_t i = LocalFlexController_t(0); i < pStudioHdr->numflexcontrollers; ++i ) + { + mstudioflexcontroller_t *pFlex = pStudioHdr->pFlexcontroller( i ); + const char *pFlexControllerName = pFlex->pszName(); + if ( !Q_stricmp( pFlexControllerName, pFlexName ) ) + { + *pMin = pFlex->min; + *pMax = pFlex->max; + return; + } + + // FIXME: Probably want to get the left + right controller + find the min and max of each, but this is unnecessary. + if ( !Q_stricmp( pFlexControllerName, pRightBuf ) ) + { + *pMin = pFlex->min; + *pMax = pFlex->max; + return; + } + + } + *pMin = 0.0f; + *pMax = 1.0f; +} + + +void GetFlexControllerRange( float *pMin, float *pMax, studiohdr_t *pStudioHdr, const char *pFlexName ) +{ + for ( LocalFlexController_t i = LocalFlexController_t(0); i < pStudioHdr->numflexcontrollers; ++i ) + { + mstudioflexcontroller_t *pFlex = pStudioHdr->pFlexcontroller( i ); + const char *pFlexControllerName = pFlex->pszName(); + if ( !Q_stricmp( pFlexControllerName, pFlexName ) ) + { + *pMin = pFlex->min; + *pMax = pFlex->max; + return; + } + } + *pMin = 0.0f; + *pMax = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Imports samples into a track +//----------------------------------------------------------------------------- +void ImportSamplesIntoTrack( CFlexAnimationTrack *pTrack, CDmElement *pLog, int nSampleType, int nTimeOffset, const ImportVCDInfo_t& info ) +{ + CDmrArray<int> times( pLog, "times" ); + CDmrArray<float> values( pLog, "values" ); + + // Add the samples + int nSampleCount = times.Count(); + if ( nSampleCount == 0 ) + return; + + int nDefaultCurveType = MAKE_CURVE_TYPE( info.m_nInterpolationType, info.m_nInterpolationType ); + if ( info.m_flSimplificationThreshhold > 0.0f ) + { + CDmeLogLayerHelper helper( pLog, nDefaultCurveType ); + helper.Simplify( info.m_flSimplificationThreshhold ); + } + + CDmrArray<int> curveTypes( pLog, "curvetypes" ); + + nSampleCount = times.Count(); + bool bHasCurveTypeData = ( curveTypes.Count() > 0 ); + for ( int j = 0; j < nSampleCount; ++j ) + { + int nCurveType = bHasCurveTypeData ? curveTypes[j] : nDefaultCurveType; + float flValue = values[j]; + float flTime = DMETIME_TO_SECONDS( times[j] - nTimeOffset ); + + CExpressionSample *pSample = pTrack->AddSample( flTime, flValue, nSampleType ); + pSample->SetCurveType( nCurveType ); + } + + if ( nSampleType == 0 ) + { + pTrack->SetEdgeActive( true, true ); + pTrack->SetEdgeActive( false, true ); + + int nCurveType0, nCurveType1; + if ( bHasCurveTypeData ) + { + nCurveType0 = curveTypes[0]; + nCurveType1 = curveTypes[nSampleCount-1]; + } + else + { + nCurveType0 = nCurveType1 = nDefaultCurveType; + } + pTrack->SetEdgeInfo( true, nCurveType0, values[ 0 ] ); + pTrack->SetEdgeInfo( false, nCurveType1, values[ nSampleCount-1 ] ); + } +} + + +//----------------------------------------------------------------------------- +// Imports mono log data into a event, creates a new track if necessary +//----------------------------------------------------------------------------- +void ImportMonoLogDataIntoEvent( studiohdr_t *pStudioHdr, CChoreoEvent *pEvent, const char *pTrackName, CDmElement *pLog, int nTimeOffset, const ImportVCDInfo_t& info ) +{ + CDmrArray<int> times( pLog, "times" ); + if ( times.Count() == 0 ) + return; + + float flMin, flMax; + GetFlexControllerRange( &flMin, &flMax, pStudioHdr, pTrackName ); + + CFlexAnimationTrack *pTrack = FindOrCreateTrack( pEvent, pTrackName ); + pTrack->Clear(); + pTrack->SetComboType( false ); + pTrack->SetMin( flMin ); + pTrack->SetMax( flMax ); + ImportSamplesIntoTrack( pTrack, pLog, 0, nTimeOffset, info ); +} + + +//----------------------------------------------------------------------------- +// Imports stereo log data into a event, creates a new track if necessary +//----------------------------------------------------------------------------- +void ImportStereoLogDataIntoEvent( studiohdr_t *pStudioHdr, CChoreoEvent *pEvent, const char *pTrackName, CDmElement *pValueLog, CDmElement *pBalanceLog, int nTimeOffset, const ImportVCDInfo_t& info ) +{ + CDmrArray<int> valueTimes( pValueLog, "times" ); + CDmrArray<int> balanceTimes( pBalanceLog, "times" ); + if ( valueTimes.Count() == 0 && balanceTimes.Count() == 0 ) + return; + + float flMin, flMax; + GetStereoFlexControllerRange( &flMin, &flMax, pStudioHdr, pTrackName ); + + CFlexAnimationTrack *pTrack = FindOrCreateTrack( pEvent, pTrackName ); + pTrack->Clear(); + pTrack->SetComboType( true ); + pTrack->SetMin( flMin ); + pTrack->SetMax( flMax ); + ImportSamplesIntoTrack( pTrack, pValueLog, 0, nTimeOffset, info ); + ImportSamplesIntoTrack( pTrack, pBalanceLog, 1, nTimeOffset, info ); +} + + +//----------------------------------------------------------------------------- +// Compute track start, end time +//----------------------------------------------------------------------------- +static int ComputeEventTime( CDmElement *pRoot, CChoreoEvent *pEvent ) +{ + int nStartTime = INT_MAX; + int nEndTime = INT_MIN; + + // Iterate over all elements in the animations attribute; each one refers to a log. + CDmrElementArray<> animations( pRoot, "animations" ); + if ( !animations.IsValid() ) + return 0; + + int nCount = animations.Count(); + for( int i = 0; i < nCount; ++i ) + { + CDmElement *pLog = animations[i]; + if ( !pLog ) + continue; + + CDmrArray<int> times( pLog, "times" ); + int nSampleCount = times.Count(); + if ( nSampleCount == 0 ) + continue; + + if ( nStartTime > times[0] ) + { + nStartTime = times[0]; + } + if ( nEndTime < times[nSampleCount-1] ) + { + nEndTime = times[nSampleCount-1]; + } + } + + pEvent->SetStartTime( DMETIME_TO_SECONDS( nStartTime ) ); + pEvent->SetEndTime( DMETIME_TO_SECONDS( nEndTime ) ); + return nStartTime; +} + + +//----------------------------------------------------------------------------- +// Main entry point for importing animations +//----------------------------------------------------------------------------- +void ImportAnimations( CDmElement *pRoot, CChoreoScene *pChoreoScene, CChoreoActor *pActor, studiohdr_t *pStudioHdr, const ImportVCDInfo_t& info ) +{ + CChoreoEvent *pEvent = FindOrAddAnimationEvent( pChoreoScene, pActor ); + pEvent->SetDefaultCurveType( MAKE_CURVE_TYPE( info.m_nInterpolationType, info.m_nInterpolationType ) ); + int nTimeOffset = ComputeEventTime( pRoot, pEvent ); + + // Iterate over all elements in the animations attribute; each one refers to a log. + CDmrElementArray<> animations( pRoot, "animations" ); + if ( !animations.IsValid() ) + return; + int nCount = animations.Count(); + for( int i = 0; i < nCount; ++i ) + { + CDmElement *pLog = animations[i]; + if ( !pLog ) + continue; + + const char *pLogName = pLog->GetName(); + + // Balance is done at the same time as value + if ( StringHasPrefix( pLogName, "balance_" ) ) + continue; + + if ( StringHasPrefix( pLogName, "value_" ) ) + { + if ( i == nCount - 1 ) + continue; + + char pBalanceName[256]; + Q_snprintf( pBalanceName, sizeof(pBalanceName), "balance_%s", pLogName + 6 ); + CDmElement *pBalanceLog = animations[i+1]; + if ( !Q_stricmp( pBalanceName, pBalanceLog->GetName() ) ) + { + ++i; + } + else + { + pBalanceLog = NULL; + } + if ( pBalanceLog ) + { + ImportStereoLogDataIntoEvent( pStudioHdr, pEvent, pLogName + 6, pLog, pBalanceLog, nTimeOffset, info ); + } + } + else + { + ImportMonoLogDataIntoEvent( pStudioHdr, pEvent, pLogName, pLog, nTimeOffset, info ); + } + } +} + + +//----------------------------------------------------------------------------- +// Main entry point for importing sounds +//----------------------------------------------------------------------------- +void ImportSounds( CDmElement *pRoot, CChoreoScene *pChoreoScene, CChoreoActor *pActor, const ImportVCDInfo_t& info ) +{ + // Iterate over all element in the sound attribute; each one refers to a sound + CDmrElementArray<> sounds( pRoot, "sounds" ); + if ( !sounds.IsValid() ) + return; + int nCount = sounds.Count(); + for( int i = 0; i < nCount; ++i ) + { + CDmElement *pSound = sounds[i]; + if ( !pSound ) + continue; + + const char *pEventName = pSound->GetName(); + CChoreoEvent *pEvent = FindOrAddSoundEvent( pChoreoScene, pActor, pEventName ); + + int nStart = pSound->GetValue<int>( "start" ); + int nEnd = pSound->GetValue<int>( "end" ); + const char *pGameSound = pSound->GetValueString( "gamesound" ); + pEvent->SetStartTime( DMETIME_TO_SECONDS( nStart ) ); + pEvent->SetEndTime( DMETIME_TO_SECONDS( nEnd ) ); + pEvent->SetParameters( pGameSound ); + pEvent->SetCloseCaptionType( CChoreoEvent::CC_MASTER ); + } +} + + +//----------------------------------------------------------------------------- +// Main entry point for importing a .fac file into a .vcd file +//----------------------------------------------------------------------------- +bool ImportLogsIntoVCD( const char *pFacFullPath, CChoreoScene *pChoreoScene, const ImportVCDInfo_t& info ) +{ + CDmElement *pRoot; + DmFileId_t id = g_pDataModel->RestoreFromFile( pFacFullPath, NULL, NULL, &pRoot, CR_FORCE_COPY ); + if ( id == DMFILEID_INVALID ) + { + Warning( "Unable to load file %s\n", pFacFullPath ); + return false; + } + + pChoreoScene->IgnorePhonemes( info.m_bIgnorePhonemes ); + + // Create the actor in the scene + const char *pActorName = pRoot->GetName(); + const char *pActorModel = pRoot->GetValueString( "gamemodel" ); + + MDLHandle_t hMDL = g_pMDLCache->FindMDL( pActorModel ); + if ( hMDL == MDLHANDLE_INVALID ) + { + Warning( "vcdimport: Model %s doesn't exist!\n", pActorModel ); + return false; + } + + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( hMDL ); + if ( !pStudioHdr || g_pMDLCache->IsErrorModel( hMDL ) ) + { + Warning( "vcdimport: Model %s doesn't exist!\n", pActorModel ); + return false; + } + + CChoreoActor *pActor = FindOrAddActor( pChoreoScene, pActorName, pActorModel ); + + ImportAnimations( pRoot, pChoreoScene, pActor, pStudioHdr, info ); + ImportSounds( pRoot, pChoreoScene, pActor, info ); + + DestroyElement( pRoot, TD_DEEP ); + return true; +} + + +//----------------------------------------------------------------------------- +// Main entry point for importing a .fac file into a .vcd file +//----------------------------------------------------------------------------- +bool ImportLogsIntoVCD( const char *pFacFullPath, const char *pVCDInFullPath, const char *pVCDOutPath, const ImportVCDInfo_t& info ) +{ + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( pVCDInFullPath, NULL, buf ) ) + { + Warning( "Unable to load file %s\n", pVCDInFullPath ); + return false; + } + + SetTokenProcessorBuffer( (char *)buf.Base() ); + CChoreoScene *pScene = ChoreoLoadScene( pVCDInFullPath, NULL, GetTokenProcessor(), NULL ); + if ( !pScene ) + { + Warning( "Unable to parse file %s\n", pVCDInFullPath ); + return false; + } + + bool bOk = ImportLogsIntoVCD( pFacFullPath, pScene, info ); + if ( !bOk ) + return false; + + Msg( "Removed %d samples\n", CDmeLogLayerHelper::TotalRemovedPoints() ); + + char pTemp[MAX_PATH]; + if ( !Q_IsAbsolutePath( pVCDOutPath ) ) + { + g_pFullFileSystem->RelativePathToFullPath( pVCDOutPath, NULL, pTemp, sizeof(pTemp) ); + if ( !Q_IsAbsolutePath( pTemp ) ) + { + char pDir[MAX_PATH]; + if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) ) + { + Q_ComposeFileName( pDir, pVCDOutPath, pTemp, sizeof(pTemp) ); + pVCDOutPath = pTemp; + } + } + else + { + pVCDOutPath = pTemp; + } + } + + CP4AutoEditFile checkout( pVCDOutPath ); + return pScene->SaveToFile( pVCDOutPath ); +}
\ No newline at end of file diff --git a/movieobjects/movieobjects.vpc b/movieobjects/movieobjects.vpc new file mode 100644 index 0000000..90d28f8 --- /dev/null +++ b/movieobjects/movieobjects.vpc @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// MOVIEOBJECTS.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;MOVIEOBJECTS_LIB" + } +} + +$Project "Movieobjects" +{ + $Folder "Header Files" + { + $File "movieobjects_interfaces.h" + } + + $Folder "Source Files" + { + $File "dmeanimationlist.cpp" + $File "dmeanimationset.cpp" + $File "dmeattachment.cpp" + $File "dmebalancetostereocalculatoroperator.cpp" + $File "dmebookmark.cpp" + $File "dmecamera.cpp" + $File "dmechannel.cpp" + $File "dmeclip.cpp" + $File "dmecombinationoperator.cpp" + $File "dmedag.cpp" + $File "dmedccmakefile.cpp" + $File "dmeeditortypedictionary.cpp" + $File "dmeexpressionoperator.cpp" + $File "dmefaceset.cpp" + $File "dmegamemodel.cpp" + $File "dmegamemodelinput.cpp" + $File "dmeinput.cpp" + $File "dmejoint.cpp" + $File "dmekeyboardinput.cpp" + $File "dmelight.cpp" + $File "dmelog.cpp" + $File "dmemakefile.cpp" + $File "dmemakefileutils.cpp" + $File "dmematerial.cpp" + $File "dmematerialoverlayfxclip.cpp" + $File "dmemdl.cpp" + $File "dmemdlmakefile.cpp" + $File "dmemesh.cpp" + $File "dmemodel.cpp" + $File "dmemorphoperator.cpp" + $File "dmemouseinput.cpp" + $File "dmeoperator.cpp" + $File "dmepackoperators.cpp" + $File "dmeparticlesystemdefinition.cpp" + $File "dmephonememapping.cpp" + $File "dmeselection.cpp" + $File "dmeshape.cpp" + $File "dmesound.cpp" + $File "dmetimeframe.cpp" + $File "dmetimeselection.cpp" + $File "dmetrack.cpp" + $File "dmetrackgroup.cpp" + $File "dmetransform.cpp" + $File "dmetransforminput.cpp" + $File "dmetransformlist.cpp" + $File "dmetransformoperator.cpp" + $File "dmeunpackoperators.cpp" + $File "dmevertexdata.cpp" + $File "dmobjserializer.cpp" + $File "movieobjects_interfaces.cpp" + $File "$SRCDIR\common\movieobjects\timeutils.cpp" + $File "dmedrawsettings.cpp" + $File "dmmeshutils.cpp" + $File "dmmeshcomp.cpp" + $File "dmeeyeposition.cpp" + $File "dmeeyeball.cpp" + $File "dmmeshutils.cpp" + $File "dmsmdserializer.cpp" + } + + $Folder "Interface" + { + $File "$SRCDIR\public\movieobjects\dmeanimationlist.h" + $File "$SRCDIR\public\movieobjects\dmeanimationset.h" + $File "$SRCDIR\public\movieobjects\dmebalancetostereocalculatoroperator.h" + $File "$SRCDIR\public\movieobjects\dmebookmark.h" + $File "$SRCDIR\public\movieobjects\dmecamera.h" + $File "$SRCDIR\public\movieobjects\dmechannel.h" + $File "$SRCDIR\public\movieobjects\dmeclip.h" + $File "$SRCDIR\public\movieobjects\dmecombinationoperator.h" + $File "$SRCDIR\public\movieobjects\dmedag.h" + $File "$SRCDIR\public\movieobjects\dmedccmakefile.h" + $File "$SRCDIR\public\movieobjects\dmeexpressionoperator.h" + $File "$SRCDIR\public\movieobjects\dmefaceset.h" + $File "$SRCDIR\public\movieobjects\dmegamemodel.h" + $File "$SRCDIR\public\movieobjects\dmegamemodelinput.h" + $File "$SRCDIR\public\movieobjects\dmeinput.h" + $File "$SRCDIR\public\movieobjects\dmejoint.h" + $File "$SRCDIR\public\movieobjects\dmekeyboardinput.h" + $File "$SRCDIR\public\movieobjects\dmelight.h" + $File "$SRCDIR\public\movieobjects\dmelog.h" + $File "$SRCDIR\public\movieobjects\dmemakefile.h" + $File "$SRCDIR\public\movieobjects\dmemakefileutils.h" + $File "$SRCDIR\public\movieobjects\dmematerial.h" + $File "$SRCDIR\public\movieobjects\dmemdl.h" + $File "$SRCDIR\public\movieobjects\dmemdlmakefile.h" + $File "$SRCDIR\public\movieobjects\dmemesh.h" + $File "$SRCDIR\public\movieobjects\dmemodel.h" + $File "$SRCDIR\public\movieobjects\dmemorphoperator.h" + $File "$SRCDIR\public\movieobjects\dmemouseinput.h" + $File "$SRCDIR\public\movieobjects\dmeoperator.h" + $File "$SRCDIR\public\movieobjects\dmepackoperators.h" + $File "$SRCDIR\public\movieobjects\dmephonememapping.h" + $File "$SRCDIR\public\movieobjects\dmeselection.h" + $File "$SRCDIR\public\movieobjects\dmeshape.h" + $File "$SRCDIR\public\movieobjects\dmesound.h" + $File "$SRCDIR\public\movieobjects\dmetestmesh.h" + $File "$SRCDIR\public\movieobjects\dmetimeframe.h" + $File "$SRCDIR\public\movieobjects\dmetimeselection.h" + $File "$SRCDIR\public\movieobjects\dmetimeselectiontimes.h" + $File "$SRCDIR\public\movieobjects\dmetrack.h" + $File "$SRCDIR\public\movieobjects\dmetrackgroup.h" + $File "$SRCDIR\public\movieobjects\dmetransform.h" + $File "$SRCDIR\public\movieobjects\dmetransforminput.h" + $File "$SRCDIR\public\movieobjects\dmetransformlist.h" + $File "$SRCDIR\public\movieobjects\dmetransformoperator.h" + $File "$SRCDIR\public\movieobjects\dmeunpackoperators.h" + $File "$SRCDIR\public\movieobjects\dmevertexdata.h" + $File "$SRCDIR\public\movieobjects\dmobjserializer.h" + $File "$SRCDIR\public\movieobjects\idmemakefileutils.h" + $File "$SRCDIR\public\movieobjects\movieobjects.h" + $File "$SRCDIR\public\movieobjects\timeutils.h" + $File "$SRCDIR\public\movieobjects\dmeattachment.h" + $File "$SRCDIR\public\movieobjects\dmeeditortypedictionary.h" + $File "$SRCDIR\public\movieobjects\dmeparticlesystemdefinition.h" + $File "$SRCDIR\public\movieobjects\dmematerialoverlayfxclip.h" + $File "$SRCDIR\public\movieobjects\dmedrawsettings.h" + $File "$SRCDIR\public\movieobjects\dmmeshutils.h" + $File "$SRCDIR\public\movieobjects\dmmeshcomp.h" + $File "$SRCDIR\public\movieobjects\dmeeyeposition.h" + $File "$SRCDIR\public\movieobjects\dmeeyeball.h" + $File "$SRCDIR\public\movieobjects\dmmeshutils.h" + $File "$SRCDIR\public\movieobjects\dmsmdserializer.h" + } + + $Folder "external" + { + $File "$SRCDIR\public\bone_setup.cpp" + $File "$SRCDIR\public\collisionutils.cpp" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\phonemeconverter.cpp" + $File "$SRCDIR\public\phonemeconverter.h" + $File "$SRCDIR\public\studio.cpp" + $File "$SRCDIR\public\mathlib\vector.h" + } +} diff --git a/movieobjects/movieobjects_interfaces.cpp b/movieobjects/movieobjects_interfaces.cpp new file mode 100644 index 0000000..18211dc --- /dev/null +++ b/movieobjects/movieobjects_interfaces.cpp @@ -0,0 +1,8 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "movieobjects_interfaces.h" + +IGlobalFlexController *g_pGlobalFlexController = 0; diff --git a/movieobjects/movieobjects_interfaces.h b/movieobjects/movieobjects_interfaces.h new file mode 100644 index 0000000..1e92b7e --- /dev/null +++ b/movieobjects/movieobjects_interfaces.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef MOVIEOBJECTS_INTERFACE_H +#define MOVIEOBJECTS_INTERFACE_H + +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// typedefs that should be in platform.h +//----------------------------------------------------------------------------- +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; +typedef unsigned long ulong; + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IGlobalFlexController; + + +//----------------------------------------------------------------------------- +// Global interfaces used by the movieobjects library +//----------------------------------------------------------------------------- +extern IGlobalFlexController *g_pGlobalFlexController; + + +#endif // MOVIEOBJECTS_INTERFACE_H
\ No newline at end of file |