3282 lines
107 KiB
C++
3282 lines
107 KiB
C++
//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. =======
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================
|
|
|
|
#include "movieobjects/dmechannel.h"
|
|
#include "movieobjects/dmelog.h"
|
|
#include "movieobjects/dmeclip.h"
|
|
#include "movieobjects/dmetimeselection.h"
|
|
#include "movieobjects/dmetransformcontrol.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"
|
|
|
|
|
|
// Paste data utility function declarations
|
|
KeyValues *FindLayerInPasteData( const CUtlVector< KeyValues * > &list, CDmeLog *log );
|
|
static int FindSpanningLayerAndSetIntensity( DmeLog_TimeSelection_t &ts, LayerSelectionData_t *data );
|
|
void CopyPasteData( CUtlVector< KeyValues * > &dstList, const CUtlVector< KeyValues * > &srcList );
|
|
void DestroyPasteData( CUtlVector< KeyValues * > &list );
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// CRecordingLayer
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Default constructor
|
|
//-----------------------------------------------------------------------------
|
|
CRecordingLayer::CRecordingLayer()
|
|
: m_pPresetValuesDict( 0 )
|
|
, m_tHeadShotTime( DMETIME_INVALID )
|
|
, m_ProceduralType( 0 )
|
|
, m_OperationFlags( 0 )
|
|
, m_RandomSeed( 0 )
|
|
, m_flThreshold( 0 )
|
|
, m_flIntensity( 0 )
|
|
, m_RecordingMode( RECORD_PRESET )
|
|
, m_pUndoOperation( NULL )
|
|
, m_pfnAddChannelCallback( NULL )
|
|
, m_pfnFinishChannelCallback( NULL )
|
|
{
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Destructor, releases any clipboard data or log layer data that may be held
|
|
// by the recording layer.
|
|
//-----------------------------------------------------------------------------
|
|
CRecordingLayer::~CRecordingLayer()
|
|
{
|
|
DestroyPasteData( m_ClipboardData );
|
|
|
|
int nChannels = m_LayerChannels.Count();
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
LayerChannelInfo_t &info = m_LayerChannels[ iChannel ];
|
|
g_pDataModel->DestroyElement( info.m_hRawDataLayer );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Determine if the specified channel is in the list of modify
|
|
// channels, regardless of component flags.
|
|
//-----------------------------------------------------------------------------
|
|
bool ModifyChannel::IsChannelInList( const CUtlVector< ModifyChannel > &modifyList, CDmeChannel *pChannel )
|
|
{
|
|
int nChannels = modifyList.Count();
|
|
for ( int i = 0; i < nChannels; ++i )
|
|
{
|
|
if ( modifyList[ i ].m_pChannel == pChannel )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// CDmeChannelModificationLayer
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Default constructor
|
|
//-----------------------------------------------------------------------------
|
|
CDmeChannelModificationLayer::CDmeChannelModificationLayer()
|
|
: m_bVisible( true )
|
|
{
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Destructor
|
|
//-----------------------------------------------------------------------------
|
|
CDmeChannelModificationLayer::~CDmeChannelModificationLayer()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Add a channel to the modification layer if it is not already
|
|
// present, adding the channel to the modification layer causes a new log layer
|
|
// to be added to the channel to which modifications will be made.
|
|
// Input : pChannel - Pointer to the channel to be added.
|
|
// enableUndo - Flag indicating if undo should be enabled when adding
|
|
// the modification layer to the log of the channel.
|
|
// Output : Returns the index of the location in the modification layer's array
|
|
// of channels where the information for the channel was stored.
|
|
//-----------------------------------------------------------------------------
|
|
int CDmeChannelModificationLayer::AddChannel( CDmeChannel *pChannel, bool enableUndo )
|
|
{
|
|
// Make sure the channel is valid and that is has a valid log.
|
|
Assert( pChannel );
|
|
if ( pChannel == NULL )
|
|
return -1;
|
|
|
|
CDmeLog* pLog = pChannel->GetLog();
|
|
if ( pLog == NULL )
|
|
return -1;
|
|
|
|
// Check to see if the channel is already in the modification layer,
|
|
// if so just update the reference count of the channel and return.
|
|
int nChannels = m_ActiveChannels.Count();
|
|
int availableSlot = -1;
|
|
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
if ( m_ActiveChannels[ iChannel ].m_Channel == pChannel->GetHandle() )
|
|
{
|
|
++m_ActiveChannels[ iChannel ].m_RefCount;
|
|
return iChannel;
|
|
}
|
|
|
|
if ( ( availableSlot < 0 ) && ( m_ActiveChannels[ iChannel ].m_Channel.Get() == NULL ) )
|
|
{
|
|
Assert( m_ActiveChannels[ iChannel ].m_RefCount == 0 );
|
|
availableSlot = iChannel;
|
|
}
|
|
}
|
|
|
|
// Add the new channel to the modification layer's list in the
|
|
// available slot or at the end if there is not an available slot.
|
|
if ( availableSlot < 0 )
|
|
{
|
|
availableSlot = m_ActiveChannels.AddToTail();
|
|
}
|
|
m_ActiveChannels[ availableSlot ].m_Channel = pChannel->GetHandle();
|
|
m_ActiveChannels[ availableSlot ].m_RefCount = 1;
|
|
|
|
|
|
// Store the index of the current topmost layer, this is the layer which will be copied.
|
|
int sourceLayerIndex = pLog->GetTopmostLayer();
|
|
|
|
// If there is only one layer in the log add a new log layer, this is the layer upon which modifications
|
|
// within the modification layer will be applied to until the modification layer is completed.
|
|
if ( sourceLayerIndex == 0 )
|
|
{
|
|
pLog->GetLayer( sourceLayerIndex );
|
|
|
|
g_pDataModel->SetUndoEnabled( enableUndo );
|
|
CDmeLogLayer *pLayer = pLog->AddNewLayer();
|
|
g_pDataModel->SetUndoEnabled( false );
|
|
|
|
if ( ( pLayer ) && ( sourceLayerIndex >= 0 ) )
|
|
{
|
|
CDmeLogLayer *pSrcLayer = pLog->GetLayer( sourceLayerIndex );
|
|
|
|
if ( pSrcLayer )
|
|
{
|
|
pLayer->CopyLayer( pSrcLayer );
|
|
}
|
|
|
|
// The modification layer is always considered infinite so
|
|
// as long as it is active it will be used at any time.
|
|
pLayer->SetInfinite( true, true );
|
|
}
|
|
}
|
|
|
|
// Return the index of the location in the array where the channel was added.
|
|
return availableSlot;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Flatten the log layers of all of the active channels onto the base
|
|
// layer.
|
|
// Input : bSaveChanges - Flag indicating if the changes recorded on the
|
|
// modification layer should be applied to the base layer ( true ) or
|
|
// completely discarded ( false ).
|
|
// Input : bFlattenLayers - Flag indicating if the modification layers of the
|
|
// logs should be flattened onto the base layer of the logs. Ignored
|
|
// if bSaveChanges is false.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelModificationLayer::Finish( bool bSaveChanges, bool bFlattenLayers, bool bRunChannelCallbacks )
|
|
{
|
|
int nChannels = m_ActiveChannels.Count();
|
|
|
|
if ( ( bSaveChanges == false ) && ( bFlattenLayers == true ) )
|
|
{
|
|
// If not saving the changes just remove all by the layers from the log except the base layer.
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
CDmeChannel *pChannel = m_ActiveChannels[ iChannel ].m_Channel.Get();
|
|
|
|
if ( pChannel )
|
|
{
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
|
|
if ( pLog )
|
|
{
|
|
while ( pLog->GetNumLayers() > 1 )
|
|
{
|
|
pLog->RemoveLayerFromTail();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( bFlattenLayers )
|
|
{
|
|
// If requested flatten the logs of the active channels. This flattens the logs completely,
|
|
// so if there are any layers above the modification layer they will be flattened too.
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
CDmeChannel *pChannel = m_ActiveChannels[ iChannel ].m_Channel.Get();
|
|
|
|
if ( pChannel )
|
|
{
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
|
|
if ( pLog )
|
|
{
|
|
pLog->FlattenLayers( 0.0f, 0 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bRunChannelCallbacks )
|
|
{
|
|
int nLayers = m_RecordingLayerStack.Count();
|
|
for ( int iLayer = 0; iLayer < nLayers; ++iLayer )
|
|
{
|
|
CDmeChannelRecordingMgr::RunFinishCallbacksOnRecordingLayer( &m_RecordingLayerStack[ iLayer ] );
|
|
}
|
|
}
|
|
|
|
// Remove all recording layers
|
|
m_RecordingLayerStack.Purge();
|
|
|
|
// Remove all channels
|
|
m_ActiveChannels.Purge();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Add a recording layer to the modification layer.
|
|
// Output : Returns a pointer to the recording layer which was added.
|
|
//-----------------------------------------------------------------------------
|
|
CRecordingLayer* CDmeChannelModificationLayer::AddRecordingLayer()
|
|
{
|
|
int index = m_RecordingLayerStack.AddToTail();
|
|
return &m_RecordingLayerStack[ index ];
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Remove the last recording layer from the modification layer.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelModificationLayer::RemoveLastRecordingLayer()
|
|
{
|
|
// Iterate through the channels in the recording layer and update the reference count of the
|
|
// channel in the modification layer. If the channel is no longer referenced by any of the
|
|
// recording layers in the modification layer it will be removed from the modification layer.
|
|
CRecordingLayer &recLayer = m_RecordingLayerStack.Tail();
|
|
|
|
int nChannels = recLayer.m_LayerChannels.Count();
|
|
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
int index = recLayer.m_LayerChannels[ iChannel ].m_ModLayerIndex;
|
|
Assert( index >= 0 );
|
|
Assert( index < m_ActiveChannels.Count() );
|
|
|
|
if ( index >= 0 && index < m_ActiveChannels.Count() )
|
|
{
|
|
--m_ActiveChannels[ index ].m_RefCount;
|
|
|
|
if ( m_ActiveChannels[ index ].m_RefCount < 1 )
|
|
{
|
|
Assert( m_ActiveChannels[ index ].m_RefCount == 0 );
|
|
m_ActiveChannels[ index ].m_Channel = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_RecordingLayerStack.RemoveMultipleFromTail( 1 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Restore the modification log layer of each of the channels in the
|
|
// modification layer to their original state before any recording layers were
|
|
// applied by copying the base layer.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelModificationLayer::WipeChannelModifications()
|
|
{
|
|
int nChannels = m_ActiveChannels.Count();
|
|
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
CDmeChannel *pChannel = m_ActiveChannels[ iChannel ].m_Channel.Get();
|
|
|
|
if ( pChannel )
|
|
{
|
|
// Get the log from the channel
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( pLog == NULL )
|
|
continue;
|
|
|
|
// Get the number of layers, it must be at least two,
|
|
// the base layer and the modification layer.
|
|
int numLayers = pLog->GetNumLayers();
|
|
if ( numLayers < 2 )
|
|
{
|
|
Assert( numLayers >= 2 );
|
|
continue;
|
|
}
|
|
|
|
// Get the modification layer and the base layer and then copy the base layer into the
|
|
// modification layer, overwriting anything that is currently in the modification layer.
|
|
CDmeLogLayer *pModLayer = pLog->GetLayer( numLayers - 1 );
|
|
CDmeLogLayer *pBaseLayer = pLog->GetLayer( numLayers - 2 );
|
|
|
|
pModLayer->CopyLayer( pBaseLayer );
|
|
|
|
// Modification layer is always infinite, while
|
|
// it is active no layers beneath it matter.
|
|
pModLayer->SetInfinite( true, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the number of recording layers in the modification layer
|
|
//-----------------------------------------------------------------------------
|
|
int CDmeChannelModificationLayer::NumRecordingLayers() const
|
|
{
|
|
return m_RecordingLayerStack.Count();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get a reference to the specified recording layer
|
|
//-----------------------------------------------------------------------------
|
|
CRecordingLayer &CDmeChannelModificationLayer::GetRecordingLayer( int index )
|
|
{
|
|
return m_RecordingLayerStack[ index ];
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return if the modification layer visible to the user
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeChannelModificationLayer::IsVisible() const
|
|
{
|
|
return m_bVisible;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Apply the specified transform write mode all of the active channels in each
|
|
// of the recording layers
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelModificationLayer::UpdateTransformWriteMode( TransformWriteMode_t mode )
|
|
{
|
|
int nLayers = m_RecordingLayerStack.Count();
|
|
|
|
for ( int iLayer = 0; iLayer < nLayers; ++iLayer )
|
|
{
|
|
CRecordingLayer &layer = m_RecordingLayerStack[ iLayer ];
|
|
int nChannels = layer.m_LayerChannels.Count();
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = layer.m_LayerChannels[ iChannel ];
|
|
channelInfo.m_TransformWriteMode = mode;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CUndoAddRecoringLayer
|
|
//
|
|
// Undo operation for adding a recording layer to the modification layer,
|
|
// removes the last recording layer from the modification layer.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
class CUndoAddRecordingLayer : public CUndoElement
|
|
{
|
|
|
|
public:
|
|
|
|
CUndoAddRecordingLayer( const char *pUndoDesc, CDmeChannelRecordingMgr* pRecordingMgr )
|
|
: CUndoElement( pUndoDesc )
|
|
, m_pRecordingMgr( pRecordingMgr )
|
|
, m_pPresetValuesDict( NULL )
|
|
, m_tHeadShotTime( NULL )
|
|
, m_ProceduralType( PROCEDURAL_PRESET_NOT )
|
|
, m_OperationFlags( 0 )
|
|
, m_pfnAddChannelCallback( NULL )
|
|
, m_pfnFinishChannelCallback( NULL )
|
|
{
|
|
Assert( m_pRecordingMgr );
|
|
m_TimeSelection = m_pRecordingMgr->GetTimeSelection();
|
|
|
|
if ( pRecordingMgr )
|
|
{
|
|
m_bInModificationLayer = pRecordingMgr->IsModificationLayerActive();
|
|
}
|
|
}
|
|
|
|
~CUndoAddRecordingLayer()
|
|
{
|
|
// Destroy any paste data which was stored.
|
|
DestroyPasteData( m_ClipboardData );
|
|
|
|
// Destroy any log layer data
|
|
int nChannels = m_ChannelInfo.Count();
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
g_pDataModel->DestroyElement( m_ChannelInfo[ iChannel ].m_hRawDataLayer );
|
|
}
|
|
|
|
if ( m_pPresetValuesDict )
|
|
{
|
|
delete m_pPresetValuesDict;
|
|
}
|
|
}
|
|
|
|
void SaveRecordingLayerData( CRecordingLayer *pRecordingLayer )
|
|
{
|
|
Assert( pRecordingLayer );
|
|
|
|
// Save the operation type
|
|
m_pPresetValuesDict = CopyAttributeDict( pRecordingLayer->m_pPresetValuesDict );
|
|
m_tHeadShotTime = pRecordingLayer->m_tHeadShotTime;
|
|
m_ProceduralType = pRecordingLayer->m_ProceduralType;
|
|
m_OperationFlags = pRecordingLayer->m_OperationFlags;
|
|
m_pfnAddChannelCallback = pRecordingLayer->m_pfnAddChannelCallback;
|
|
m_pfnFinishChannelCallback = pRecordingLayer->m_pfnFinishChannelCallback;
|
|
|
|
// Save the operation time parameters
|
|
m_TimeSelection.m_flIntensity = pRecordingLayer->m_flIntensity;
|
|
m_TimeSelection.m_flThreshold = pRecordingLayer->m_flThreshold;
|
|
m_TimeSelection.SetRecordingMode( pRecordingLayer->m_RecordingMode );
|
|
|
|
// Store the channel information for all of the channels that are modified by the recording layer. This
|
|
// information is not required for the undo, which simply destroys the recording layer, but is required
|
|
// for the redo operation as it needs to add the channels to the recording layer when it re-creates it.
|
|
int nChannels = pRecordingLayer->m_LayerChannels.Count();
|
|
m_ChannelInfo.EnsureCount( nChannels );
|
|
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
ChannelInfo_t &dstInfo = m_ChannelInfo[ iChannel ];
|
|
LayerChannelInfo_t &srcInfo = pRecordingLayer->m_LayerChannels[ iChannel ];
|
|
|
|
dstInfo.m_Channel = srcInfo.m_Channel;
|
|
dstInfo.m_ComponentFlags = srcInfo.m_ComponentFlags;
|
|
dstInfo.m_pPresetValue = srcInfo.m_pPresetValue;
|
|
dstInfo.m_pPresetTimes = srcInfo.m_pPresetTimes;
|
|
dstInfo.m_pRoot = srcInfo.m_pRoot;
|
|
dstInfo.m_pShot = srcInfo.m_pShot;
|
|
|
|
// Copy the transform info
|
|
dstInfo.m_HeadPosition = srcInfo.m_HeadPosition;
|
|
dstInfo.m_TransformWriteMode = srcInfo.m_TransformWriteMode;
|
|
dstInfo.m_bManipulateInFalloff = srcInfo.m_bManipulateInFalloff;
|
|
dstInfo.m_Transform = srcInfo.m_Transform;
|
|
dstInfo.m_DeltaRotationLocal = srcInfo.m_DeltaRotationLocal;
|
|
dstInfo.m_DeltaRotationParent = srcInfo.m_DeltaRotationParent;
|
|
dstInfo.m_PivotPosition = srcInfo.m_PivotPosition;
|
|
|
|
|
|
// Copy the data of the to attribute
|
|
if ( srcInfo.m_ToAttrData.Size() > 0 )
|
|
{
|
|
dstInfo.m_ToAttrData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
|
|
srcInfo.m_ToAttrData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
dstInfo.m_ToAttrData.Put( srcInfo.m_ToAttrData.PeekGet( 0 ), srcInfo.m_ToAttrData.GetBytesRemaining() );
|
|
}
|
|
|
|
// Copy the data of the from attribute
|
|
if ( srcInfo.m_FromAttrData.Size() > 0 )
|
|
{
|
|
dstInfo.m_FromAttrData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
|
|
srcInfo.m_FromAttrData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
dstInfo.m_FromAttrData.Put( srcInfo.m_FromAttrData.PeekGet( 0 ), srcInfo.m_FromAttrData.GetBytesRemaining() );
|
|
}
|
|
|
|
// If the recording layer had a raw data layer for the channel make a copy of it.
|
|
if ( srcInfo.m_hRawDataLayer.Get() )
|
|
{
|
|
CDmeChannel *pChannel = srcInfo.m_Channel.Get();
|
|
if ( pChannel )
|
|
{
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( pLog )
|
|
{
|
|
dstInfo.m_hRawDataLayer = pLog->AddNewLayer();
|
|
dstInfo.m_hRawDataLayer->CopyLayer( srcInfo.m_hRawDataLayer );
|
|
pLog->RemoveLayerFromTail();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the paste data from the recording layer if it has stored it.
|
|
CopyPasteData( m_ClipboardData, pRecordingLayer->m_ClipboardData );
|
|
}
|
|
|
|
virtual void Undo()
|
|
{
|
|
CDmeChannelModificationLayer* pModificationLayer = m_pRecordingMgr->GetModificationLayer();
|
|
|
|
if ( pModificationLayer )
|
|
{
|
|
pModificationLayer->RemoveLastRecordingLayer();
|
|
|
|
// If the modification layer is now empty, destroy it completely.
|
|
if ( pModificationLayer->NumRecordingLayers() == 0 )
|
|
{
|
|
m_pRecordingMgr->FinishModificationLayer( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void Redo()
|
|
{
|
|
m_pRecordingMgr->SetInRedo( true );
|
|
|
|
// Create a new modification layer if needed.
|
|
m_pRecordingMgr->StartModificationLayer( &m_TimeSelection, m_bInModificationLayer );
|
|
|
|
// Add the recording layer to the modification layer.
|
|
m_pRecordingMgr->StartLayerRecording( "AddRecordingLayerRedo", m_pPresetValuesDict, m_tHeadShotTime, m_ProceduralType, m_OperationFlags, m_pfnAddChannelCallback, m_pfnFinishChannelCallback );
|
|
|
|
// Add the channels to the recording layer.
|
|
int nChannels = m_ChannelInfo.Count();
|
|
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
ChannelInfo_t &srcInfo = m_ChannelInfo[ iChannel ];
|
|
|
|
int nChannelIndex = m_pRecordingMgr->AddChannelToRecordingLayer( srcInfo.m_Channel.Get(), srcInfo.m_ComponentFlags, srcInfo.m_pRoot, srcInfo.m_pShot );
|
|
|
|
if ( nChannelIndex >= 0 )
|
|
{
|
|
LayerChannelInfo_t &dstInfo = m_pRecordingMgr->m_pActiveRecordingLayer->m_LayerChannels[ nChannelIndex ];
|
|
|
|
// Copy the transform info
|
|
dstInfo.m_HeadPosition = srcInfo.m_HeadPosition;
|
|
dstInfo.m_TransformWriteMode = srcInfo.m_TransformWriteMode;
|
|
dstInfo.m_bManipulateInFalloff = srcInfo.m_bManipulateInFalloff;
|
|
dstInfo.m_Transform = srcInfo.m_Transform;
|
|
dstInfo.m_DeltaRotationLocal = srcInfo.m_DeltaRotationLocal;
|
|
dstInfo.m_DeltaRotationParent = srcInfo.m_DeltaRotationParent;
|
|
dstInfo.m_PivotPosition = srcInfo.m_PivotPosition;
|
|
dstInfo.m_ComponentFlags = srcInfo.m_ComponentFlags;
|
|
dstInfo.m_pPresetValue = srcInfo.m_pPresetValue;
|
|
dstInfo.m_pPresetTimes = srcInfo.m_pPresetTimes;
|
|
|
|
// Copy the data of the to attribute
|
|
if ( srcInfo.m_ToAttrData.Size() > 0 )
|
|
{
|
|
dstInfo.m_ToAttrData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
|
|
srcInfo.m_ToAttrData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
dstInfo.m_ToAttrData.Put( srcInfo.m_ToAttrData.PeekGet( 0 ), srcInfo.m_ToAttrData.GetBytesRemaining() );
|
|
}
|
|
|
|
// Copy the data of the from attribute
|
|
if ( srcInfo.m_FromAttrData.Size() > 0 )
|
|
{
|
|
dstInfo.m_FromAttrData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
|
|
srcInfo.m_FromAttrData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
dstInfo.m_FromAttrData.Put( srcInfo.m_FromAttrData.PeekGet( 0 ), srcInfo.m_FromAttrData.GetBytesRemaining() );
|
|
}
|
|
|
|
// If there there is a raw data layer make a copy of it.
|
|
if ( srcInfo.m_hRawDataLayer.Get() )
|
|
{
|
|
CDmeChannel *pChannel = srcInfo.m_Channel.Get();
|
|
if ( pChannel )
|
|
{
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( pLog )
|
|
{
|
|
dstInfo.m_hRawDataLayer = pLog->AddNewLayer();
|
|
dstInfo.m_hRawDataLayer->CopyLayer( srcInfo.m_hRawDataLayer );
|
|
pLog->RemoveLayerFromTail();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore the clipboard data in the recording layer
|
|
CopyPasteData( m_pRecordingMgr->m_pActiveRecordingLayer->m_ClipboardData, m_ClipboardData );
|
|
|
|
m_pRecordingMgr->FinishLayerRecording( m_TimeSelection.m_flThreshold, true, false );
|
|
m_pRecordingMgr->SetModificationLayerDirty();
|
|
|
|
m_pRecordingMgr->SetInRedo( false );
|
|
}
|
|
|
|
private:
|
|
|
|
|
|
struct ChannelInfo_t
|
|
{
|
|
CDmeHandle< CDmeChannel > m_Channel;
|
|
CDmeHandle< CDmeLogLayer, HT_UNDO > m_hRawDataLayer;
|
|
CUtlBuffer m_ToAttrData;
|
|
CUtlBuffer m_FromAttrData;
|
|
DmeTime_t m_HeadPosition;
|
|
TransformWriteMode_t m_TransformWriteMode;
|
|
bool m_bManipulateInFalloff;
|
|
matrix3x4_t m_Transform;
|
|
Quaternion m_DeltaRotationLocal;
|
|
Quaternion m_DeltaRotationParent;
|
|
Vector m_PivotPosition;
|
|
LogComponents_t m_ComponentFlags;
|
|
const CDmAttribute *m_pPresetValue;
|
|
const CDmAttribute *m_pPresetTimes;
|
|
CDmeClip *m_pRoot;
|
|
CDmeClip *m_pShot;
|
|
};
|
|
|
|
CDmeChannelRecordingMgr *m_pRecordingMgr;
|
|
DmeLog_TimeSelection_t m_TimeSelection;
|
|
AttributeDict_t *m_pPresetValuesDict;
|
|
DmeTime_t m_tHeadShotTime;
|
|
int m_ProceduralType;
|
|
int m_OperationFlags;
|
|
bool m_bInModificationLayer;
|
|
CUtlVector< ChannelInfo_t > m_ChannelInfo;
|
|
CUtlVector< KeyValues * > m_ClipboardData;
|
|
FnRecordChannelCallback m_pfnAddChannelCallback;
|
|
FnRecordChannelCallback m_pfnFinishChannelCallback;
|
|
|
|
};
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CUndoFinishModificationLayer
|
|
//
|
|
// Undo operation for finishing a modification layer, stores all of the
|
|
// information required to reconstruct the modification layer, redo simply
|
|
// finishes the modification layer again. Used both for saving and not saving
|
|
// the changes in the modification layer when finishing. The implementation is
|
|
// simply to use the functionality of the CUndoAddRecordingLayer to store and
|
|
// re-create the recording layers of the modification layer.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
class CUndoFinishModificationLayer : public CUndoElement
|
|
{
|
|
|
|
public:
|
|
|
|
CUndoFinishModificationLayer( const char *pUndoDesc, CDmeChannelRecordingMgr* pRecordingMgr, bool saveChanges )
|
|
: CUndoElement( pUndoDesc )
|
|
, m_pRecordingMgr( pRecordingMgr )
|
|
, m_bSaveChanges( saveChanges )
|
|
{
|
|
Assert( m_pRecordingMgr );
|
|
|
|
if ( m_pRecordingMgr )
|
|
{
|
|
CDmeChannelModificationLayer* pModificationLayer = pRecordingMgr->GetModificationLayer();
|
|
|
|
if ( pModificationLayer )
|
|
{
|
|
// Save the data required to restore each of the recording layers.
|
|
int nLayers = pModificationLayer->NumRecordingLayers();
|
|
|
|
for ( int iLayer = 0; iLayer < nLayers; ++iLayer )
|
|
{
|
|
// Add a new undo operation for the recording layer
|
|
CUndoAddRecordingLayer *pUndoLayer = new CUndoAddRecordingLayer( "Undo Recoding Layer", m_pRecordingMgr );
|
|
m_RecordingLayers.AddToTail( pUndoLayer );
|
|
|
|
// Save the data from the recording layer in the new undo operation
|
|
CRecordingLayer &recordingLayer = pModificationLayer->GetRecordingLayer( iLayer );
|
|
pUndoLayer->SaveRecordingLayerData( &recordingLayer );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
~CUndoFinishModificationLayer()
|
|
{
|
|
int nLayers = m_RecordingLayers.Count();
|
|
|
|
for ( int iLayer = 0; iLayer < nLayers; ++iLayer )
|
|
{
|
|
delete m_RecordingLayers[ iLayer ];
|
|
m_RecordingLayers[ iLayer ] = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
virtual void Undo()
|
|
{
|
|
// Undoing a finish operation requires reconstructing each of the recording layers, so to undo
|
|
// the modification layer finish operation we actually redo all of the recording layer operations.
|
|
int nLayers = m_RecordingLayers.Count();
|
|
|
|
for ( int iLayer = 0; iLayer < nLayers; ++iLayer )
|
|
{
|
|
Assert( m_RecordingLayers[ iLayer ] );
|
|
|
|
if ( m_RecordingLayers[ iLayer ] )
|
|
{
|
|
m_RecordingLayers[ iLayer ]->Redo();
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void Redo()
|
|
{
|
|
// Redoing the modification layer finish is simply a matter of calling finish again, except
|
|
// this time undo will be disabled. Note we never flatten the layers here because flatten
|
|
// layers has its own redo and we do not remove layers when not saving changes.
|
|
m_pRecordingMgr->SetInRedo( true );
|
|
m_pRecordingMgr->FinishModificationLayer( m_bSaveChanges, false );
|
|
m_pRecordingMgr->SetInRedo( false );
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
CDmeChannelRecordingMgr *m_pRecordingMgr;
|
|
bool m_bSaveChanges;
|
|
CUtlVector< CUndoAddRecordingLayer* > m_RecordingLayers;
|
|
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CUndoSetTimeSelection
|
|
//
|
|
// Undo operation for changing setting the time selection. This correctly
|
|
// updates both the standard time selection and the base time selection.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
class CUndoSetTimeSelection : public CUndoElement
|
|
{
|
|
|
|
public:
|
|
|
|
CUndoSetTimeSelection( const char *pUndoDesc, CDmeChannelRecordingMgr* pRecordingMgr, const DmeLog_TimeSelection_t &newTS, const CUtlVector< TimeSelection_t > &oldBaseTS, const CUtlVector< TimeSelection_t > &newBaseTS )
|
|
: CUndoElement( pUndoDesc )
|
|
, m_pRecordingMgr( pRecordingMgr )
|
|
, m_newTimeSelection( newTS.m_nTimes )
|
|
, m_newLeftFalloffType( newTS.m_nFalloffInterpolatorTypes[ 0 ] )
|
|
, m_newRightFalloffType( newTS.m_nFalloffInterpolatorTypes[ 1 ] )
|
|
, m_newLeftInfinite( newTS.m_bInfinite[ 0 ] )
|
|
, m_newRightInfinite( newTS.m_bInfinite[ 1 ] )
|
|
{
|
|
if ( m_pRecordingMgr )
|
|
{
|
|
const DmeLog_TimeSelection_t &orignalTimeSelection = m_pRecordingMgr->GetTimeSelection();
|
|
m_originalTimeSelection = orignalTimeSelection.m_nTimes;
|
|
m_originalLeftFalloffType = orignalTimeSelection.m_nFalloffInterpolatorTypes[ 0 ];
|
|
m_originalRightFalloffType = orignalTimeSelection.m_nFalloffInterpolatorTypes[ 1 ];
|
|
m_originalLeftInfinite = orignalTimeSelection.m_bInfinite[ 0 ];
|
|
m_originalRightInfinite = orignalTimeSelection.m_bInfinite[ 1 ];
|
|
|
|
m_newBaseTimeSelectionList = newBaseTS;
|
|
m_originalBaseTimeSelectionList = oldBaseTS;
|
|
}
|
|
}
|
|
|
|
virtual void Undo()
|
|
{
|
|
if ( m_pRecordingMgr )
|
|
{
|
|
m_pRecordingMgr->UpdateTimeSelection( m_originalTimeSelection, m_originalBaseTimeSelectionList, m_originalLeftFalloffType, m_originalRightFalloffType, m_originalLeftInfinite, m_originalRightInfinite );
|
|
}
|
|
}
|
|
|
|
virtual void Redo()
|
|
{
|
|
if ( m_pRecordingMgr )
|
|
{
|
|
m_pRecordingMgr->UpdateTimeSelection( m_newTimeSelection, m_newBaseTimeSelectionList, m_newLeftFalloffType, m_newRightFalloffType, m_newLeftInfinite, m_newRightInfinite );
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
CDmeChannelRecordingMgr *m_pRecordingMgr;
|
|
|
|
CUtlVector< TimeSelection_t > m_newBaseTimeSelectionList;
|
|
TimeSelection_t m_newTimeSelection;
|
|
int m_newLeftFalloffType;
|
|
int m_newRightFalloffType;
|
|
bool m_newLeftInfinite;
|
|
bool m_newRightInfinite;
|
|
|
|
CUtlVector< TimeSelection_t > m_originalBaseTimeSelectionList;
|
|
TimeSelection_t m_originalTimeSelection;
|
|
int m_originalLeftFalloffType;
|
|
int m_originalRightFalloffType;
|
|
bool m_originalLeftInfinite;
|
|
bool m_originalRightInfinite;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// CDmeChannelRecordingMgr
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Globals
|
|
//-----------------------------------------------------------------------------
|
|
static CDmeChannelRecordingMgr s_ChannelRecordingMgr;
|
|
CDmeChannelRecordingMgr *g_pChannelRecordingMgr = &s_ChannelRecordingMgr;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CDmeChannelRecordingMgr::CDmeChannelRecordingMgr()
|
|
{
|
|
m_bSavedUndoState = false;
|
|
m_bUseTimeSelection = false;
|
|
m_bModificationLayerDirty = false;
|
|
m_bModificationProcessing = false;
|
|
m_bWantsToFinish = false;
|
|
m_bFinishFlattenLayers = false;
|
|
m_bModificationLayerEnabled = true;
|
|
m_bInRedo = false;
|
|
|
|
m_pActiveRecordingLayer = NULL;
|
|
m_pModificationLayer = NULL;
|
|
|
|
m_nProceduralType = PROCEDURAL_PRESET_NOT;
|
|
m_pRevealTarget = NULL;
|
|
m_RandomSeed = 0;
|
|
m_TransformWriteMode = TRANSFORM_WRITE_MODE_TRANSFORM;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Start a new recording layer, may not be done when there is already
|
|
// and active recording layer.
|
|
// Input : pUndoRedoDesc - String to be used as the description for the undo
|
|
// and redo operations of the recording layer.
|
|
// Input : operationType - Specification of the general operation which the
|
|
// recording layer will perform.
|
|
// Input : proceduralType - If the recording operation is a procedural
|
|
// operation this may specify the specific type of procedural operation.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::StartLayerRecording( const char * const pUndoRedoDesc, AttributeDict_t *pPresetValuesDict /*= NULL*/, DmeTime_t tHeadShotTime /*= DMETIME_INVALID*/, int proceduralType /*=PROCEDURAL_PRESET_NOT*/, int nFlags /* = 0 */, FnRecordChannelCallback pfnAddChannel /*= NULL*/, FnRecordChannelCallback pfnFinishChannel /*= NULL*/ )
|
|
{
|
|
g_pDataModel->StartUndo( pUndoRedoDesc, pUndoRedoDesc );
|
|
m_bSavedUndoState = g_pDataModel->IsUndoEnabled();
|
|
|
|
Assert( m_pActiveRecordingLayer == NULL );
|
|
|
|
if ( m_pModificationLayer )
|
|
{
|
|
m_pActiveRecordingLayer = m_pModificationLayer->AddRecordingLayer();
|
|
}
|
|
else
|
|
{
|
|
m_pActiveRecordingLayer = new CRecordingLayer;
|
|
}
|
|
|
|
m_pActiveRecordingLayer->m_pPresetValuesDict = CopyAttributeDict( pPresetValuesDict );
|
|
m_pActiveRecordingLayer->m_tHeadShotTime = tHeadShotTime;
|
|
m_pActiveRecordingLayer->m_ProceduralType = proceduralType;
|
|
m_pActiveRecordingLayer->m_OperationFlags = nFlags;
|
|
m_pActiveRecordingLayer->m_pfnAddChannelCallback = pfnAddChannel;
|
|
m_pActiveRecordingLayer->m_pfnFinishChannelCallback = pfnFinishChannel;
|
|
m_pActiveRecordingLayer->m_OriginalTimes = m_TimeSelection.m_nTimes;
|
|
m_pActiveRecordingLayer->m_BaseTimes = m_TimeSelection.m_nTimes;
|
|
|
|
|
|
if ( m_bSavedUndoState == true )
|
|
{
|
|
CUndoAddRecordingLayer *pUndo = new CUndoAddRecordingLayer( "AddRecordingLayer", this );
|
|
g_pDataModel->AddUndoElement( pUndo );
|
|
m_pActiveRecordingLayer->m_pUndoOperation = pUndo;
|
|
}
|
|
|
|
g_pDataModel->SetUndoEnabled( false );
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Complete the current recording layer, there must be an active
|
|
// recording layer.
|
|
// Input : flThreshold - threshold value used in flattening the log layers from
|
|
// the recording layer on to their proceeding log layers.
|
|
// Input : bFlattenLayers - Flag indicating if the log layers resulting from
|
|
// the record operation should be flattened on to the proceeding layer.
|
|
// Input : bAllowFinishModification - Flag indicating if the recording layer is
|
|
// allowed to finish the modification layer. Most recording operations
|
|
// will not finish the modification layer, but some such as the drop
|
|
// operation will finish the modification layer if this flag is true.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::FinishLayerRecording( float flThreshhold, bool bFlattenLayers /*=true*/, bool bAllowFinishModification /*= true*/ )
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
bool finishModification = false;
|
|
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
m_pActiveRecordingLayer->m_flThreshold = flThreshhold;
|
|
m_pActiveRecordingLayer->m_flIntensity = m_TimeSelection.m_flIntensity;
|
|
m_pActiveRecordingLayer->m_RecordingMode = m_TimeSelection.GetRecordingMode();
|
|
m_pActiveRecordingLayer->m_RandomSeed = m_RandomSeed;
|
|
|
|
if ( m_pActiveRecordingLayer->m_ProceduralType == PROCEDURAL_PRESET_NOT )
|
|
{
|
|
m_pActiveRecordingLayer->m_ProceduralType = GetProceduralType();
|
|
}
|
|
|
|
// Set the flag to for the modification layer to be completed if the operation is to drop the layer.
|
|
if ( bAllowFinishModification && ( m_pActiveRecordingLayer->m_ProceduralType == PROCEDURAL_PRESET_DROP_LAYER ) )
|
|
{
|
|
finishModification = true;
|
|
}
|
|
|
|
// If the recording layer is a paste operation that is part of a modification layer it will need store the paste
|
|
// data so that the operation can be correctly executed even if the contents of the clipboard have been changed.
|
|
bool pasteOperation = ( m_pActiveRecordingLayer->m_ProceduralType == PROCEDURAL_PRESET_PASTE );
|
|
if ( pasteOperation && ( m_pModificationLayer != NULL ) )
|
|
{
|
|
if ( m_pActiveRecordingLayer->m_ClipboardData.Count() == 0)
|
|
{
|
|
CUtlVector< KeyValues * > clipBoardList;
|
|
g_pDataModel->GetClipboardData( clipBoardList );
|
|
CopyClipboardDataForRecordingLayer( clipBoardList );
|
|
}
|
|
}
|
|
|
|
// If time is advancing add an additional layer which will hold
|
|
// the original recorded values not filtered by the time selection.
|
|
if ( IsTimeAdvancing() && IsModificationLayerActive() )
|
|
{
|
|
int c = m_pActiveRecordingLayer->m_LayerChannels.Count();
|
|
for ( int i = 0 ; i < c; ++i )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = m_pActiveRecordingLayer->m_LayerChannels[ i ];
|
|
|
|
if ( channelInfo.m_hRawDataLayer.Get() == NULL )
|
|
{
|
|
CDmeChannel *pChannel = channelInfo.m_Channel.Get();
|
|
if ( !pChannel )
|
|
continue;
|
|
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
Assert( pLog );
|
|
if ( !pLog )
|
|
continue;
|
|
|
|
CDmeLogLayer *pTopLayer = pLog->GetLayer( pLog->GetTopmostLayer() );
|
|
channelInfo.m_hRawDataLayer = pLog->AddNewLayer();
|
|
channelInfo.m_hRawDataLayer->CopyLayer( pTopLayer );
|
|
pLog->RemoveLayerFromTail();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the information from the recording layer that will be required to reconstruct the recording
|
|
// layer for the redo operation which was not available when the recording layer was created.
|
|
if ( m_pActiveRecordingLayer->m_pUndoOperation )
|
|
{
|
|
m_pActiveRecordingLayer->m_pUndoOperation->SaveRecordingLayerData( m_pActiveRecordingLayer );
|
|
}
|
|
|
|
// Finalize the recording for all channels in the layer and detach the channels from the
|
|
// recording layer. The channels will no longer know where they are within the recording
|
|
// layer, but the recording layer will retain the list of channels it operated on.
|
|
RemoveAllChannelsFromRecordingLayer( m_pActiveRecordingLayer, false );
|
|
|
|
|
|
g_pDataModel->SetUndoEnabled( m_bSavedUndoState );
|
|
|
|
if ( bFlattenLayers )
|
|
{
|
|
FlattenLayers( m_pActiveRecordingLayer );
|
|
}
|
|
|
|
// If there is no modification layer then the recording layer was allocated and should be destroyed.
|
|
if ( m_pModificationLayer == NULL )
|
|
{
|
|
if ( !m_bInRedo )
|
|
{
|
|
RunFinishCallbacksOnRecordingLayer( m_pActiveRecordingLayer );
|
|
}
|
|
delete m_pActiveRecordingLayer;
|
|
}
|
|
|
|
if ( finishModification == false )
|
|
{
|
|
g_pDataModel->FinishUndo();
|
|
}
|
|
|
|
}
|
|
|
|
m_pActiveRecordingLayer = NULL;
|
|
m_nProceduralType = PROCEDURAL_PRESET_NOT;
|
|
m_pRevealTarget = NULL;
|
|
m_PasteTarget.RemoveAll();
|
|
|
|
|
|
// If the finish modification layer flag was set, finish the modification layer. Some
|
|
// recording operations such as the drop preset force the completion of the layer.
|
|
if ( finishModification )
|
|
{
|
|
FinishModificationLayer();
|
|
g_pDataModel->FinishUndo();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Cancel the current recording layer, losing all of its changes.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::CancelLayerRecording()
|
|
{
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
RunFinishCallbacksOnRecordingLayer( m_pActiveRecordingLayer );
|
|
|
|
// Detach all of the channels from the recording layer and and delete the associate log layers.
|
|
RemoveAllChannelsFromRecordingLayer( m_pActiveRecordingLayer, !m_bSavedUndoState );
|
|
|
|
if ( m_bSavedUndoState )
|
|
{
|
|
m_pActiveRecordingLayer = NULL;
|
|
g_pDataModel->SetUndoEnabled( m_bSavedUndoState );
|
|
g_pDataModel->AbortUndoableOperation();
|
|
}
|
|
else
|
|
{
|
|
if ( m_pModificationLayer )
|
|
{
|
|
m_pActiveRecordingLayer = NULL;
|
|
|
|
CDisableUndoScopeGuard undosg;
|
|
|
|
// If the modification layer is now empty, destroy it completely.
|
|
if ( m_pModificationLayer->NumRecordingLayers() < 2 )
|
|
{
|
|
FinishModificationLayer( false );
|
|
}
|
|
else
|
|
{
|
|
// Remove the recording layer from the modification layer
|
|
m_pModificationLayer->RemoveLastRecordingLayer();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete m_pActiveRecordingLayer;
|
|
m_pActiveRecordingLayer = NULL;
|
|
}
|
|
}
|
|
|
|
m_nProceduralType = PROCEDURAL_PRESET_NOT;
|
|
m_pRevealTarget = NULL;
|
|
m_PasteTarget.RemoveAll();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Creates a modification layer on which all further editing will
|
|
// take place until EndModificationLayer() is called. If there is already an
|
|
// active modification layer then the time selection of the active modification
|
|
// layer will be updated, but a new modification layer will not be started.
|
|
// Input : pTimeSelection - pointer to the active time selection which the
|
|
// modification layer is to apply to.
|
|
// Input : createLayer - flag indicating if a modification layer should be
|
|
// created, if false the function may be used to update the current
|
|
// time selection.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::StartModificationLayer( const DmeLog_TimeSelection_t *pTimeSelection, bool createLayer /*= true*/ )
|
|
{
|
|
// If the modification layer has already been started just update the time parameters.
|
|
if ( m_pModificationLayer != NULL )
|
|
{
|
|
if ( pTimeSelection )
|
|
{
|
|
m_TimeSelection = *pTimeSelection;
|
|
m_TimeSelection.ResetTimeAdvancing();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Create the modification layer unless create flag is false.
|
|
if ( createLayer && m_bModificationLayerEnabled )
|
|
{
|
|
m_pModificationLayer = new CDmeChannelModificationLayer();
|
|
}
|
|
|
|
// Set the time selection to provided time selection or reset the time selection
|
|
// to the default values and disable its use if no time selection was provided.
|
|
if ( pTimeSelection )
|
|
{
|
|
m_TimeSelection = *pTimeSelection;
|
|
m_bUseTimeSelection = true;
|
|
}
|
|
else
|
|
{
|
|
m_TimeSelection = DmeLog_TimeSelection_t();
|
|
m_bUseTimeSelection = false;
|
|
}
|
|
m_TimeSelection.ResetTimeAdvancing();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Complete the current modification layer, applying the modifications
|
|
// to the base log layers of the effected channels and destroying all of the
|
|
// recording layer data contained within the modification layer.
|
|
// Input : bSaveChanges - flag indicating if the changes within the
|
|
// modification layer should be applied or discarded.
|
|
// Input : bFlattenLayers - flag indicating if the log layers of the
|
|
// modification should be flattened onto the base layer.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::FinishModificationLayer( bool bSaveChanges, bool bFlattenLayers )
|
|
{
|
|
// Should never call finish modification layer while there is an active
|
|
// recording layer. FinishLayerRecording() should be called first.
|
|
Assert( m_pActiveRecordingLayer == NULL );
|
|
|
|
if ( m_pActiveRecordingLayer != NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the modification layer has already been finished
|
|
// is is already waiting to finish, ignore the request.
|
|
if ( IsModificationLayerActive() == false )
|
|
{
|
|
m_bUseTimeSelection = false;
|
|
m_TimeSelection.ResetTimeAdvancing();
|
|
return;
|
|
}
|
|
|
|
// Make sure the modification layer is not dirty. If it is set the flag which specifies the
|
|
// modification layer should be finished once the processing is complete. If we are not
|
|
// going to save the changes ignore the fact that the layer is dirty and just finish it.
|
|
if ( m_bModificationLayerDirty && bSaveChanges )
|
|
{
|
|
// If this assert is hit finish modification layer has been called twice without the
|
|
// modification layer being processed or the flags have not been properly cleared.
|
|
Assert( m_bWantsToFinish == false );
|
|
m_bWantsToFinish = true;
|
|
m_bFinishFlattenLayers = bFlattenLayers;
|
|
}
|
|
else
|
|
{
|
|
if ( g_pDataModel->IsUndoEnabled() )
|
|
{
|
|
g_pDataModel->StartUndo( "Finish Modification Layer", "Finish Modification Layer" );
|
|
CUndoFinishModificationLayer *pUndo = new CUndoFinishModificationLayer( "FinishModificationLayer", this, bSaveChanges );
|
|
g_pDataModel->AddUndoElement( pUndo );
|
|
}
|
|
|
|
// Destroy the modification layer and the recording layers it contains.
|
|
if ( m_pModificationLayer )
|
|
{
|
|
// Complete the modification layer and flatten the channel log layers if requested.
|
|
m_pModificationLayer->Finish( bSaveChanges, bFlattenLayers, !m_bInRedo );
|
|
|
|
delete m_pModificationLayer;
|
|
m_pModificationLayer = NULL;
|
|
}
|
|
|
|
// Complete the undo operation for the modification layer
|
|
if ( g_pDataModel->IsUndoEnabled() )
|
|
{
|
|
g_pDataModel->FinishUndo();
|
|
}
|
|
|
|
// Reset the time selection information
|
|
m_bUseTimeSelection = false;
|
|
m_TimeSelection.ResetTimeAdvancing();
|
|
|
|
// Clear the finish flags
|
|
m_bWantsToFinish = false;
|
|
m_bFinishFlattenLayers = false;
|
|
m_bModificationLayerDirty = false;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Enable or disable use of the modification layer.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::EnableModificationLayer( bool enable )
|
|
{
|
|
if ( enable )
|
|
{
|
|
m_bModificationLayerEnabled = true;
|
|
}
|
|
else
|
|
{
|
|
// Finish any active modification layer before disabling.
|
|
FinishModificationLayer();
|
|
m_bModificationLayerEnabled = false;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the current time selection as a CDmeTimeSelection element
|
|
// Output : timeSelection - reference to the time selection to be returned with
|
|
// the current time selection values.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::GetTimeSelection( CDmeTimeSelection &timeSelection ) const
|
|
{
|
|
timeSelection.SetThreshold( m_TimeSelection.m_flThreshold );
|
|
timeSelection.SetCurrent( m_TimeSelection.m_nTimes );
|
|
timeSelection.SetFalloffInterpolatorType( 0, m_TimeSelection.m_nFalloffInterpolatorTypes[ 0 ] );
|
|
timeSelection.SetFalloffInterpolatorType( 1, m_TimeSelection.m_nFalloffInterpolatorTypes[ 1 ] );
|
|
timeSelection.SetResampleInterval( m_TimeSelection.m_nResampleInterval );
|
|
|
|
if ( m_TimeSelection.m_bInfinite[ 0 ] )
|
|
{
|
|
timeSelection.SetInfinite( 0 );
|
|
}
|
|
|
|
if ( m_TimeSelection.m_bInfinite[ 1 ] )
|
|
{
|
|
timeSelection.SetInfinite( 1 );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the current time selection
|
|
// Output : Returns a reference to the current time selection structure
|
|
//-----------------------------------------------------------------------------
|
|
const DmeLog_TimeSelection_t &CDmeChannelRecordingMgr::GetTimeSelection() const
|
|
{
|
|
return m_TimeSelection;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Set the time selection for the modification layer and re-apply all
|
|
// recording layers that are on the current modification layer with the new
|
|
// time selection. Must be done with active modification layer, but without an
|
|
// active recording layer.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::SetTimeSelection( const DmeLog_TimeSelection_t &newTS, bool bUpdateBaseTimes )
|
|
{
|
|
// Can only set the time selection with the modification layer active but without any active recording layers.
|
|
if ( ( m_pModificationLayer == NULL ) || ( m_pActiveRecordingLayer != NULL ) )
|
|
{
|
|
Assert( m_pModificationLayer != NULL );
|
|
Assert( m_pActiveRecordingLayer == NULL );
|
|
return;
|
|
}
|
|
|
|
|
|
int nNumRecordingLayers = m_pModificationLayer->NumRecordingLayers();
|
|
CUtlVector< TimeSelection_t > newBaseTimeList;
|
|
CUtlVector< TimeSelection_t > oldBaseTimeList;
|
|
newBaseTimeList.SetCount( nNumRecordingLayers );
|
|
oldBaseTimeList.SetCount( nNumRecordingLayers );
|
|
|
|
for ( int iRecordingLayer = 0; iRecordingLayer < nNumRecordingLayers; ++iRecordingLayer )
|
|
{
|
|
CRecordingLayer &recordingLayer = m_pModificationLayer->GetRecordingLayer( iRecordingLayer );
|
|
TimeSelection_t newBaseTimes = recordingLayer.m_BaseTimes;
|
|
|
|
Assert( newBaseTimes[ TS_LEFT_HOLD ] >= newBaseTimes[ TS_LEFT_FALLOFF ] );
|
|
Assert( newBaseTimes[ TS_RIGHT_HOLD ] >= newBaseTimes[ TS_LEFT_HOLD ] );
|
|
Assert( newBaseTimes[ TS_RIGHT_FALLOFF ] >= newBaseTimes[ TS_RIGHT_HOLD ] );
|
|
|
|
// Update the base times by the same amount the time selection is to be changed.
|
|
if ( bUpdateBaseTimes )
|
|
{
|
|
// As long as the base times are being updated, the ratio of the duration of each section of the base time selection compared to
|
|
// the visible time selection should remain the same. Additionally movements of the hold region should operate such the source sample
|
|
// data appears static. This means for a change applied to the visible hold region, the change applied to the base time selection
|
|
// needs to be multiplied by the hold region ration. Although it would be desirable for the falloff regions to behave this way, if
|
|
// both the falloff time and the hold times are being modified this is not possible, so the hold region takes precedence.
|
|
DmeTime_t baseLeftTime = newBaseTimes[ TS_LEFT_HOLD ] - newBaseTimes[ TS_LEFT_FALLOFF ];
|
|
DmeTime_t baseHoldTime = newBaseTimes[ TS_RIGHT_HOLD ] - newBaseTimes[ TS_LEFT_HOLD ];
|
|
DmeTime_t baseRightTime = newBaseTimes[ TS_RIGHT_FALLOFF ] - newBaseTimes[ TS_RIGHT_HOLD ];
|
|
|
|
DmeTime_t leftTime = m_TimeSelection.m_nTimes[ TS_LEFT_HOLD ] - m_TimeSelection.m_nTimes[ TS_LEFT_FALLOFF ];
|
|
DmeTime_t holdTime = m_TimeSelection.m_nTimes[ TS_RIGHT_HOLD ] - m_TimeSelection.m_nTimes[ TS_LEFT_HOLD ];
|
|
DmeTime_t rightTime = m_TimeSelection.m_nTimes[ TS_RIGHT_FALLOFF ] - m_TimeSelection.m_nTimes[ TS_RIGHT_HOLD ];
|
|
|
|
float flLeftRatio = ( ( leftTime > DMETIME_ZERO ) && ( baseLeftTime > DMETIME_ZERO ) ) ? ( baseLeftTime / leftTime ) : 1.0f;
|
|
float flHoldRatio = ( ( holdTime > DMETIME_ZERO ) && ( baseHoldTime > DMETIME_ZERO ) ) ? ( baseHoldTime / holdTime ) : 1.0f;
|
|
float flRightRatio = ( ( rightTime > DMETIME_ZERO ) && ( baseRightTime > DMETIME_ZERO ) ) ? ( baseRightTime / rightTime ) : 1.0f;
|
|
|
|
// Apply the ratio adjusted delta time to the left and right hold times
|
|
DmeTime_t leftHoldDT = newTS.m_nTimes[ TS_LEFT_HOLD ] - m_TimeSelection.m_nTimes[ TS_LEFT_HOLD ];
|
|
DmeTime_t rightHoldDT = newTS.m_nTimes[ TS_RIGHT_HOLD ] - m_TimeSelection.m_nTimes[ TS_RIGHT_HOLD ];
|
|
newBaseTimes[ TS_LEFT_HOLD ] += ( leftHoldDT * flHoldRatio );
|
|
newBaseTimes[ TS_RIGHT_HOLD ] += ( rightHoldDT * flHoldRatio );
|
|
|
|
// Update the falloff times so that the ratios are preserved.
|
|
newBaseTimes[ TS_LEFT_FALLOFF ] = newBaseTimes[ TS_LEFT_HOLD ] - ( ( newTS.m_nTimes[ TS_LEFT_HOLD ] - newTS.m_nTimes[ TS_LEFT_FALLOFF ] ) * flLeftRatio );
|
|
newBaseTimes[ TS_RIGHT_FALLOFF ] = newBaseTimes[ TS_RIGHT_HOLD ] + ( ( newTS.m_nTimes[ TS_RIGHT_FALLOFF ] - newTS.m_nTimes[ TS_RIGHT_HOLD ] ) * flRightRatio );
|
|
|
|
// Make sure the time selection times remain in order.
|
|
newBaseTimes[ TS_LEFT_FALLOFF ] = MIN( newBaseTimes[ TS_LEFT_FALLOFF ], newBaseTimes[ TS_LEFT_HOLD ] - DMETIME_MINDELTA );
|
|
newBaseTimes[ TS_RIGHT_HOLD ] = MAX( newBaseTimes[ TS_RIGHT_HOLD ], newBaseTimes[ TS_LEFT_HOLD ] );
|
|
newBaseTimes[ TS_RIGHT_FALLOFF ] = MAX( newBaseTimes[ TS_RIGHT_FALLOFF ], newBaseTimes[ TS_RIGHT_HOLD ] + DMETIME_MINDELTA );
|
|
}
|
|
|
|
oldBaseTimeList[ iRecordingLayer ] = recordingLayer.m_BaseTimes;
|
|
newBaseTimeList[ iRecordingLayer ] = newBaseTimes;
|
|
recordingLayer.m_BaseTimes = newBaseTimes;
|
|
}
|
|
|
|
if ( g_pDataModel->IsUndoEnabled() )
|
|
{
|
|
CUndoSetTimeSelection *pUndo = new CUndoSetTimeSelection( "SetTimeSelection", this, newTS, oldBaseTimeList, newBaseTimeList );
|
|
g_pDataModel->AddUndoElement( pUndo );
|
|
}
|
|
|
|
// Set the new time selection for which the effects of the recording layers are to be applied.
|
|
m_TimeSelection.m_nTimes = newTS.m_nTimes;
|
|
m_TimeSelection.m_nFalloffInterpolatorTypes[ 0 ] = newTS.m_nFalloffInterpolatorTypes[ 0 ];
|
|
m_TimeSelection.m_nFalloffInterpolatorTypes[ 1 ] = newTS.m_nFalloffInterpolatorTypes[ 1 ];
|
|
m_TimeSelection.m_bInfinite[ 0 ] = newTS.m_bInfinite[ 0 ];
|
|
m_TimeSelection.m_bInfinite[ 1 ] = newTS.m_bInfinite[ 1 ];
|
|
|
|
m_bUseTimeSelection = true;
|
|
m_bModificationLayerDirty = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Update the time selection state, this is used by undo
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::UpdateTimeSelection( const TimeSelection_t &timeSelection, const CUtlVector< TimeSelection_t > &baseTimeSelectionList, int leftFalloff, int rightFalloff, bool bLeftInfinite, bool bRightInfinite )
|
|
{
|
|
m_TimeSelection.m_nTimes = timeSelection;
|
|
m_TimeSelection.m_nFalloffInterpolatorTypes[ 0 ] = leftFalloff;
|
|
m_TimeSelection.m_nFalloffInterpolatorTypes[ 1 ] = rightFalloff;
|
|
m_TimeSelection.m_bInfinite[ 0 ] = bLeftInfinite;
|
|
m_TimeSelection.m_bInfinite[ 1 ] = bRightInfinite;
|
|
|
|
m_bUseTimeSelection = true;
|
|
m_bModificationLayerDirty = true;
|
|
|
|
int nNumRecordingLayers = m_pModificationLayer->NumRecordingLayers();
|
|
Assert( baseTimeSelectionList.Count() == nNumRecordingLayers );
|
|
|
|
if ( baseTimeSelectionList.Count() == nNumRecordingLayers )
|
|
{
|
|
for ( int iRecordingLayer = 0; iRecordingLayer < nNumRecordingLayers; ++iRecordingLayer )
|
|
{
|
|
CRecordingLayer &recordingLayer = m_pModificationLayer->GetRecordingLayer( iRecordingLayer );
|
|
recordingLayer.m_BaseTimes = baseTimeSelectionList[ iRecordingLayer ];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Start or continue processing of the recording layers within the
|
|
// modification layer.
|
|
// In/Out : recordingLayer - Input with the index of the recording layer to
|
|
// process, returns the index of the next recording layer to be
|
|
// processed.
|
|
// Output : proceduralType - returns the specific procedural preset type for
|
|
// procedural recording operations.
|
|
// Output : operationFlags - returns flags providing information about the
|
|
// operation of the recording layer.
|
|
// Output : randomSeed - returns the random seed used by the recording layer
|
|
// for procedural operations which depend on random values.
|
|
// Output : flIntensity - returns the intensity parameter of the recording layer
|
|
// which was processed.
|
|
// Output : Returns true if recording layer index is valid and the recording
|
|
// layer was processed, returns false if the recording layer index was
|
|
// out of range, which occurs when processing of all layers has been
|
|
// completed.
|
|
//-----------------------------------------------------------------------------
|
|
CRecordingLayer *CDmeChannelRecordingMgr::ProcessModificationLayer( int &recordingLayer )
|
|
{
|
|
// Do nothing if the modification layer does not require an update.
|
|
if ( !m_bModificationLayerDirty )
|
|
return NULL;
|
|
|
|
// Obviously the modification layer must exist in order to process it.
|
|
if ( m_pModificationLayer == NULL )
|
|
{
|
|
Assert( m_pModificationLayer );
|
|
return NULL;
|
|
}
|
|
|
|
// If all of the recording layers have been processed clear the dirty
|
|
// flag and return false to indicate no further processing is required.
|
|
if ( recordingLayer >= m_pModificationLayer->NumRecordingLayers() )
|
|
{
|
|
m_bModificationLayerDirty = false;
|
|
m_bModificationProcessing = false;
|
|
|
|
// If the flag specifying that the modification layer should be finished is set, call
|
|
// the finish function to flatten the layers and complete the modification layer.
|
|
if ( m_bWantsToFinish )
|
|
{
|
|
m_bWantsToFinish = false;
|
|
FinishModificationLayer( m_bFinishFlattenLayers );
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Perform initialization actions
|
|
if ( recordingLayer == 0 )
|
|
{
|
|
// Set the flag to indicate that the modification layer is currently being processed.
|
|
m_bModificationProcessing = true;
|
|
|
|
// Restore the modification log layer of each of the channels in the modification layer to their
|
|
// original state before any recording layers were applied by copying the base layer.
|
|
m_pModificationLayer->WipeChannelModifications();
|
|
}
|
|
|
|
// Get the current recording layer and perform the initial update for applying it to the modification layer.
|
|
CRecordingLayer &recLayer = m_pModificationLayer->GetRecordingLayer( recordingLayer++ );
|
|
ApplyRecordingLayer( recLayer );
|
|
|
|
return &recLayer;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Complete the modification layer processing of the current recording
|
|
// layer.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::CompleteModificationProcessing()
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
// Record the channel operations if the operation mode requires it. Paste does not
|
|
// as the values are applied to the channel outside of the record mechanism.
|
|
if ( m_pActiveRecordingLayer->m_ProceduralType != PROCEDURAL_PRESET_PASTE )
|
|
{
|
|
int c = m_pActiveRecordingLayer->m_LayerChannels.Count();
|
|
|
|
for ( int i = 0 ; i < c; ++i )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = m_pActiveRecordingLayer->m_LayerChannels[ i ];
|
|
|
|
CDmeChannel *pChannel = channelInfo.m_Channel.Get();
|
|
|
|
if ( pChannel )
|
|
{
|
|
pChannel->Operate();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finish the record operation by detaching the channels from the record
|
|
// layer, returning them to play mode, and flattening the newly created layer.
|
|
RemoveAllChannelsFromRecordingLayer( m_pActiveRecordingLayer, false );
|
|
FlattenLayers( m_pActiveRecordingLayer );
|
|
|
|
// Reset the active recording layer to NULL since the recording
|
|
// layer being applied is not actually being recorded.
|
|
m_pActiveRecordingLayer = NULL;
|
|
m_nProceduralType = PROCEDURAL_PRESET_NOT;
|
|
m_pRevealTarget = NULL;
|
|
m_PasteTarget.RemoveAll();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Apply the effects of the recording layer to its channels with the
|
|
// active time selection.
|
|
// Input : recordingLayer - Reference to the recording layer to apply.
|
|
// Output : Returns the procedural type of the recording layer.
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeChannelRecordingMgr::ApplyRecordingLayer( CRecordingLayer &recordingLayer )
|
|
{
|
|
// An existing recording layer cannot be applied to the the modification
|
|
// layer while there is currently an active recording layer.
|
|
if ( m_pActiveRecordingLayer != NULL )
|
|
{
|
|
Assert( m_pActiveRecordingLayer == NULL );
|
|
return false;
|
|
}
|
|
|
|
// Temporarily set the recording layer being re-applied as the active recording layer, this
|
|
// is done because the channel Operate() function assumes an active recording channel.
|
|
m_pActiveRecordingLayer = &recordingLayer;
|
|
m_nProceduralType = recordingLayer.m_ProceduralType;
|
|
m_RandomSeed = recordingLayer.m_RandomSeed;
|
|
m_TimeSelection.m_flIntensity = recordingLayer.m_flIntensity;
|
|
m_TimeSelection.SetRecordingMode( recordingLayer.m_RecordingMode );
|
|
|
|
// Iterate through the channels in the recording layer, add a new layer to
|
|
// the log of each channel and put the channel in recording mode so that
|
|
// the operation of the layer can be recorded into the log.
|
|
int c = recordingLayer.m_LayerChannels.Count();
|
|
|
|
for ( int i = 0 ; i < c; ++i )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = recordingLayer.m_LayerChannels[ i ];
|
|
|
|
CDmeChannel *pChannel = channelInfo.m_Channel.Get();
|
|
if ( !pChannel )
|
|
continue;
|
|
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
Assert( pLog );
|
|
if ( !pLog )
|
|
continue;
|
|
|
|
|
|
// Add the new layer to the log and assign the channel
|
|
// its location within the current recoding layer.
|
|
CDmeLogLayer* pNewLayer = pLog->AddNewLayer();
|
|
if ( pNewLayer == NULL )
|
|
continue;
|
|
|
|
pNewLayer->SetInfinite( m_TimeSelection.m_bInfinite[ 0 ], m_TimeSelection.m_bInfinite[ 1 ] );
|
|
pChannel->SetRecordLayerIndex( i );
|
|
|
|
// If the recording operation was an attribute over time, use the stored recorded
|
|
// log data to re-apply the recording operation for the current time frame.
|
|
if ( channelInfo.m_hRawDataLayer.Get() )
|
|
{
|
|
DmeLog_TimeSelection_t localTimeSelection = m_TimeSelection;
|
|
localTimeSelection.m_flIntensity = 1.0f;
|
|
for ( int i = 0; i < TS_TIME_COUNT; ++i )
|
|
{
|
|
localTimeSelection.m_nTimes[i] = channelInfo.m_ClipStack.ToChildMediaTime( localTimeSelection.m_nTimes[i], false );
|
|
}
|
|
|
|
pLog->BlendLayersUsingTimeSelection( pLog->GetLayer( channelInfo.m_BaseLayer ), channelInfo.m_hRawDataLayer, pNewLayer, localTimeSelection, true, true, true, DMETIME_ZERO );
|
|
|
|
// Do not put the channel into record mode.
|
|
continue;
|
|
}
|
|
|
|
|
|
if ( recordingLayer.m_ProceduralType == PROCEDURAL_PRESET_NOT )
|
|
{
|
|
// If the recording layer's operation was not a procedural operation, restore the value of
|
|
// the source attribute to the recording value before re-recording with the new time frame.
|
|
CDmAttribute *pFromAttr = pChannel->GetFromAttribute();
|
|
|
|
if ( pFromAttr )
|
|
{
|
|
channelInfo.m_FromAttrData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
|
|
if ( IsArrayType( pFromAttr->GetType() ) )
|
|
{
|
|
pFromAttr->UnserializeElement( pChannel->GetFromArrayIndex(), channelInfo.m_FromAttrData );
|
|
}
|
|
else
|
|
{
|
|
pFromAttr->Unserialize( channelInfo.m_FromAttrData );
|
|
}
|
|
}
|
|
|
|
CDmAttribute *pToAttr = pChannel->GetToAttribute();
|
|
|
|
if ( pToAttr )
|
|
{
|
|
channelInfo.m_ToAttrData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
|
|
if ( IsArrayType( pToAttr->GetType() ) )
|
|
{
|
|
pToAttr->UnserializeElement( pChannel->GetToArrayIndex(), channelInfo.m_ToAttrData );
|
|
}
|
|
else
|
|
{
|
|
pToAttr->Unserialize( channelInfo.m_ToAttrData );
|
|
}
|
|
}
|
|
|
|
// Restore the transform, delta rotation, and pivot values in the control to the values used in the original operation.
|
|
CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pChannel->GetFromElement() );
|
|
if ( pTransformControl )
|
|
{
|
|
pTransformControl->SetManipulationTransform( channelInfo.m_Transform );
|
|
pTransformControl->SetManipulationRotationLocal( channelInfo.m_DeltaRotationLocal );
|
|
pTransformControl->SetManipulationRotationParent( channelInfo.m_DeltaRotationParent );
|
|
pTransformControl->SetManipulationPivot( channelInfo.m_PivotPosition );
|
|
}
|
|
}
|
|
|
|
// Record the operation.
|
|
pChannel->SetMode( CM_RECORD );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Store the data of the provided attribute in the channel's recording
|
|
// data.
|
|
// Input : nChannelIndex - Index of the channel within the recording layer's
|
|
// array of active channels.
|
|
// Input : fromAttr - flag indicating if the data of the from attribute is to
|
|
// be stored ( true ) or the data of the to attribute is to be stored
|
|
// ( false ).
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::StoreChannelAttributeData( int nChannelIndex, bool fromAttr )
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
LayerChannelInfo_t& info = m_pActiveRecordingLayer->m_LayerChannels[ nChannelIndex ];
|
|
CDmeChannel *pChannel = info.m_Channel.Get();
|
|
|
|
// The data from the provided attribute may be stored in either the to or from attribute
|
|
// data for the channel, select the appropriate data buffer based on selection flag.
|
|
CUtlBuffer *pBuffer = fromAttr ? &info.m_FromAttrData : &info.m_ToAttrData;
|
|
CDmAttribute *pAttr = fromAttr ? pChannel->GetFromAttribute() : pChannel->GetToAttribute();
|
|
|
|
if ( pAttr )
|
|
{
|
|
// Clear the buffer, we only want to store a single value.
|
|
pBuffer->Clear();
|
|
|
|
// If storing the to attribute data, make sure it is current and has the
|
|
// actual value of the log, not the control default value if the log is empty.
|
|
if ( !fromAttr )
|
|
{
|
|
pChannel->Play( true );
|
|
}
|
|
|
|
if ( IsArrayType( pAttr->GetType() ) )
|
|
{
|
|
int nArrayIndex = fromAttr ? pChannel->GetFromArrayIndex() : pChannel->GetToArrayIndex();
|
|
pAttr->SerializeElement( nArrayIndex, *pBuffer );
|
|
}
|
|
else
|
|
{
|
|
pAttr->Serialize( *pBuffer );
|
|
}
|
|
}
|
|
|
|
// If saving the from attribute, save the transform, delta rotation and pivot information.
|
|
if ( fromAttr )
|
|
{
|
|
CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pChannel->GetFromElement() );
|
|
if ( pTransformControl )
|
|
{
|
|
pTransformControl->GetManipulationTransform( info.m_Transform );
|
|
pTransformControl->GetManipulationRotationLocal( info.m_DeltaRotationLocal );
|
|
pTransformControl->GetManipulationRotationParent( info.m_DeltaRotationParent );
|
|
pTransformControl->GetManipulationPivot( info.m_PivotPosition );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Adds a channel to the recording layer
|
|
// Input : pChannel - pointer to the channel to be added to the recording
|
|
// layer.
|
|
// Input : componentFlags - flags specifying which components of the channel
|
|
// are to be recorded.
|
|
// Input : pRoot - pointer to the current root clip ( movie )
|
|
// Input : pShot - pointer to the current shot
|
|
// Output : Returns - Index of the location in the recording layer's array of
|
|
// channel information to which the new channel was assigned.
|
|
//-----------------------------------------------------------------------------
|
|
int CDmeChannelRecordingMgr::AddChannelToRecordingLayer( CDmeChannel *pChannel, LogComponents_t componentFlags, CDmeClip *pRoot, CDmeClip *pShot )
|
|
{
|
|
Assert( pChannel );
|
|
if ( pChannel == NULL )
|
|
return -1;
|
|
|
|
// The channel must not already be in an active recording layer.
|
|
Assert( pChannel->GetRecordLayerIndex() == -1 );
|
|
|
|
// There must be an active recording layer before channels may be added.
|
|
Assert( m_pActiveRecordingLayer );
|
|
if ( m_pActiveRecordingLayer == NULL )
|
|
return -1;
|
|
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( !pLog )
|
|
return -1;
|
|
|
|
// If specified call the add channel callback before adding the channel to the recording layer.
|
|
if ( !m_bInRedo && ( m_pActiveRecordingLayer->m_pfnAddChannelCallback ) )
|
|
{
|
|
CEnableUndoScopeGuard undosg;
|
|
m_pActiveRecordingLayer->m_pfnAddChannelCallback( pChannel );
|
|
}
|
|
|
|
int nRecordLayerIndex = m_pActiveRecordingLayer->m_LayerChannels.AddToTail();
|
|
LayerChannelInfo_t& info = m_pActiveRecordingLayer->m_LayerChannels[nRecordLayerIndex];
|
|
info.m_Channel = pChannel;
|
|
info.m_pRoot = pRoot;
|
|
info.m_pShot = pShot;
|
|
info.m_ComponentFlags = componentFlags;
|
|
info.m_HeadPosition = pChannel->GetCurrentTime();
|
|
info.m_TransformWriteMode = m_TransformWriteMode;
|
|
info.m_bManipulateInFalloff = m_TimeSelection.m_bManipulateInFalloff;
|
|
|
|
if ( !m_bInRedo && pLog->IsEmpty() )
|
|
{
|
|
CEnableUndoScopeGuard undosg;
|
|
pLog->SetKey( pChannel->GetCurrentTime(), pChannel->GetFromAttribute(), pChannel->GetFromArrayIndex() );
|
|
}
|
|
|
|
// Add the channel to the modification layer, which results in a new layer being
|
|
// added to the log on which subsequent modifications to the log will take place.
|
|
if ( m_pModificationLayer )
|
|
{
|
|
info.m_ModLayerIndex = m_pModificationLayer->AddChannel( pChannel, m_bSavedUndoState );
|
|
}
|
|
|
|
// Store the index of the current top most layer, this is the modification layer
|
|
// which was just added. When flatting the recording layer it will be flattened
|
|
// only to the modification layer, not all the way to the base layer of the log.
|
|
info.m_BaseLayer = pLog->GetTopmostLayer();
|
|
|
|
if ( pRoot )
|
|
{
|
|
if ( !pChannel->BuildClipStack( &info.m_ClipStack, pRoot, pShot ) )
|
|
{
|
|
m_pActiveRecordingLayer->m_LayerChannels.Remove( nRecordLayerIndex );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Store the index of the the channel in the recording layer for obtaining
|
|
// access to the channel info in the recording layer from the channel.
|
|
pChannel->SetRecordLayerIndex( nRecordLayerIndex );
|
|
|
|
// Store the value of the To attribute before recording starts
|
|
// to use as a reference value when re-applying the operation.
|
|
StoreChannelAttributeData( nRecordLayerIndex, false );
|
|
|
|
bool bWasUndoEnabled = false;
|
|
if ( m_bSavedUndoState )
|
|
{
|
|
bWasUndoEnabled = g_pDataModel->IsUndoEnabled();
|
|
g_pDataModel->SetUndoEnabled( true );
|
|
}
|
|
|
|
CDmeLogLayer *pNewLayer = pLog->AddNewLayer();
|
|
if ( pNewLayer )
|
|
{
|
|
pNewLayer->SetInfinite( m_TimeSelection.m_bInfinite[ 0 ], m_TimeSelection.m_bInfinite[ 1 ] );
|
|
}
|
|
|
|
if ( m_bSavedUndoState )
|
|
{
|
|
g_pDataModel->SetUndoEnabled( bWasUndoEnabled );
|
|
}
|
|
|
|
pChannel->SetMode( CM_RECORD );
|
|
|
|
return nRecordLayerIndex;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Explicitly set the clipboard data for the active recording layer
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::CopyClipboardDataForRecordingLayer( const CUtlVector< KeyValues * > &keyValuesList )
|
|
{
|
|
if ( m_pActiveRecordingLayer == NULL )
|
|
return;
|
|
|
|
// Destroy any existing clipboard data
|
|
DestroyPasteData( m_pActiveRecordingLayer->m_ClipboardData );
|
|
|
|
// Copy the new clipboard data
|
|
CopyPasteData( m_pActiveRecordingLayer->m_ClipboardData, keyValuesList );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes all channels from the recording layer
|
|
// Input : pRecordingLayer - Pointer to the recording layer from which all the
|
|
// channel information is to be removed.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::RemoveAllChannelsFromRecordingLayer( CRecordingLayer *pRecordingLayer, bool destroyLogLayers )
|
|
{
|
|
Assert( pRecordingLayer );
|
|
if ( pRecordingLayer == NULL )
|
|
return;
|
|
|
|
int c = pRecordingLayer->m_LayerChannels.Count();
|
|
for ( int i = 0 ; i < c; ++i )
|
|
{
|
|
CDmeChannel *pChannel = pRecordingLayer->m_LayerChannels[ i ].m_Channel.Get();
|
|
if ( !pChannel )
|
|
continue;
|
|
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( pLog )
|
|
{
|
|
if ( IsUsingTimeSelection() )
|
|
{
|
|
// Computes local times for the time selection
|
|
DmeLog_TimeSelection_t timeSelection;
|
|
GetLocalTimeSelection( timeSelection, pChannel->GetRecordLayerIndex() );
|
|
pLog->FinishTimeSelection( pChannel->GetCurrentTime(), timeSelection );
|
|
}
|
|
|
|
// Release recording layer from the log.
|
|
if ( destroyLogLayers )
|
|
{
|
|
pLog->RemoveLayerFromTail();
|
|
}
|
|
}
|
|
pChannel->SetRecordLayerIndex( -1 );
|
|
pChannel->SetMode( CM_PLAY );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Flattens recorded layers into the base layer
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::FlattenLayers( CRecordingLayer *pRecordingLayer )
|
|
{
|
|
Assert( pRecordingLayer );
|
|
if ( pRecordingLayer == NULL )
|
|
return;
|
|
|
|
int nFlags = 0;
|
|
if ( IsUsingDetachedTimeSelection() && IsTimeAdvancing() )
|
|
{
|
|
nFlags |= CDmeLog::FLATTEN_NODISCONTINUITY_FIXUP;
|
|
}
|
|
|
|
int c = pRecordingLayer->m_LayerChannels.Count();
|
|
for ( int i = 0 ; i < c; ++i )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = pRecordingLayer->m_LayerChannels[ i ];
|
|
|
|
CDmeChannel *pChannel = channelInfo.m_Channel.Get();
|
|
if ( !pChannel )
|
|
continue;
|
|
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
Assert( pLog );
|
|
if ( !pLog )
|
|
continue;
|
|
|
|
if ( channelInfo.m_BaseLayer < 0 )
|
|
continue;
|
|
|
|
pLog->FlattenLayers( pRecordingLayer->m_flThreshold, nFlags, channelInfo.m_BaseLayer );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Calls the callback for all channels in the specified recording layer
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::RunFinishCallbacksOnRecordingLayer( CRecordingLayer *pRecordingLayer )
|
|
{
|
|
if ( pRecordingLayer == NULL )
|
|
return;
|
|
|
|
if ( pRecordingLayer->m_pfnFinishChannelCallback == NULL )
|
|
return;
|
|
|
|
int nNumChannels = pRecordingLayer->m_LayerChannels.Count();
|
|
for ( int iChannel = 0; iChannel < nNumChannels; ++iChannel )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = pRecordingLayer->m_LayerChannels[ iChannel ];
|
|
CDmeChannel *pChannel = channelInfo.m_Channel.Get();
|
|
if ( pChannel == NULL )
|
|
continue;
|
|
|
|
pRecordingLayer->m_pfnFinishChannelCallback( pChannel );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Used to iterate over all channels currently being recorded
|
|
//-----------------------------------------------------------------------------
|
|
int CDmeChannelRecordingMgr::GetLayerRecordingChannelCount()
|
|
{
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
return m_pActiveRecordingLayer->m_LayerChannels.Count();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
CDmeChannel* CDmeChannelRecordingMgr::GetLayerRecordingChannel( int nIndex )
|
|
{
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
Assert( nIndex < m_pActiveRecordingLayer->m_LayerChannels.Count() );
|
|
if ( nIndex < m_pActiveRecordingLayer->m_LayerChannels.Count() )
|
|
{
|
|
return m_pActiveRecordingLayer->m_LayerChannels[ nIndex ].m_Channel.Get();
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes time selection info in log time for a particular recorded channel
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::GetLocalTimeSelection( DmeLog_TimeSelection_t& selection, int nIndex )
|
|
{
|
|
Assert( m_bUseTimeSelection );
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
LayerChannelInfo_t& info = m_pActiveRecordingLayer->m_LayerChannels[nIndex];
|
|
CDmeChannel *pChannel = info.m_Channel.Get();
|
|
Assert( pChannel );
|
|
if ( pChannel == NULL )
|
|
return;
|
|
|
|
|
|
selection = m_TimeSelection;
|
|
for ( int i = 0; i < TS_TIME_COUNT; ++i )
|
|
{
|
|
selection.m_nTimes[i] = info.m_ClipStack.ToChildMediaTime( selection.m_nTimes[i], false );
|
|
}
|
|
selection.m_pPresetValue = info.m_pPresetValue;
|
|
selection.m_pPresetTimes = info.m_pPresetTimes;
|
|
selection.m_tHeadPosition = info.m_HeadPosition;
|
|
selection.m_TransformWriteMode = info.m_TransformWriteMode;
|
|
selection.m_nComponentFlags = info.m_ComponentFlags;
|
|
selection.m_bManipulateInFalloff = info.m_bManipulateInFalloff;
|
|
|
|
|
|
// If the time selection is marked as infinite, make sure it extents past the ends of the log
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( pLog && selection.m_bInfinite[ 0 ] )
|
|
{
|
|
selection.m_nTimes[ TS_LEFT_HOLD ] = MIN( pLog->GetBeginTime() , selection.m_nTimes[ TS_LEFT_HOLD ] );
|
|
selection.m_nTimes[ TS_LEFT_FALLOFF ] = selection.m_nTimes[ TS_LEFT_HOLD ];
|
|
}
|
|
|
|
if ( pLog && selection.m_bInfinite[ 1 ] )
|
|
{
|
|
selection.m_nTimes[ TS_RIGHT_HOLD ] = MAX( pLog->GetEndTime(), selection.m_nTimes[ TS_RIGHT_HOLD ] );
|
|
selection.m_nTimes[ TS_RIGHT_FALLOFF ] = selection.m_nTimes[ TS_RIGHT_HOLD ];
|
|
}
|
|
|
|
// If the modification layer is currently being processed set the pointer to the channel's to attribute
|
|
// to be used as the old head position, this is required because the value of the head position now may
|
|
// not be what it was when the recording first took place.
|
|
if ( m_bModificationProcessing )
|
|
{
|
|
CDmAttribute *pAttr = pChannel->GetToAttribute();
|
|
|
|
if ( pAttr )
|
|
{
|
|
selection.m_pOldHeadValue = pAttr;
|
|
if ( IsArrayType( pAttr->GetType() ) )
|
|
{
|
|
selection.m_OldHeadValueIndex = pChannel->GetToArrayIndex();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the index of the modification base layer for the specified channel
|
|
// within the current recording layer.
|
|
//-----------------------------------------------------------------------------
|
|
int CDmeChannelRecordingMgr::GetModificationBaseLayer( int nIndex )
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
LayerChannelInfo_t& info = m_pActiveRecordingLayer->m_LayerChannels[ nIndex ];
|
|
return info.m_BaseLayer;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Methods which control various aspects of recording
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::UpdateTimeAdvancing( bool bPaused, DmeTime_t tCurTime )
|
|
{
|
|
Assert( m_bUseTimeSelection );
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
if ( !bPaused && !m_TimeSelection.IsTimeAdvancing() && m_pActiveRecordingLayer )
|
|
{
|
|
m_TimeSelection.StartTimeAdvancing();
|
|
|
|
// blow away logs after curtime
|
|
int nCount = m_pActiveRecordingLayer->m_LayerChannels.Count();
|
|
for ( int i = 0; i < nCount; ++i )
|
|
{
|
|
LayerChannelInfo_t& info = m_pActiveRecordingLayer->m_LayerChannels[i];
|
|
DmeTime_t t = info.m_ClipStack.ToChildMediaTime( tCurTime, false );
|
|
info.m_Channel->GetLog()->RemoveKeys( t, DMETIME_MAXTIME );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Update the head positions of all of the channels in the active recording
|
|
// layer. The input time should be a global time which will be converted to the
|
|
// local time for each channel.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::UpdateRecordingChannelHeadPositions( DmeTime_t tCurTime )
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
int nChannels = m_pActiveRecordingLayer->m_LayerChannels.Count();
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
LayerChannelInfo_t &info = m_pActiveRecordingLayer->m_LayerChannels[ iChannel ];
|
|
DmeTime_t channelTime = info.m_ClipStack.ToChildMediaTime( tCurTime, false );
|
|
|
|
if ( info.m_HeadPosition != channelTime )
|
|
{
|
|
info.m_HeadPosition = channelTime;
|
|
|
|
CDmeChannel *pChannel = info.m_Channel.Get();
|
|
if ( pChannel )
|
|
{
|
|
pChannel->SetCurrentTime( channelTime );
|
|
}
|
|
|
|
// Update the stored To attribute value to match the value at the new head position.
|
|
StoreChannelAttributeData( iChannel, false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CDmeChannelRecordingMgr::UpdateRecordingTimeSelectionTimes( const DmeLog_TimeSelection_t& timeSelection )
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
|
|
m_TimeSelection.m_nTimes = timeSelection.m_nTimes;
|
|
|
|
for ( int i = 0; i < 2; ++i )
|
|
{
|
|
m_TimeSelection.m_nFalloffInterpolatorTypes[ i ] = timeSelection.m_nFalloffInterpolatorTypes[ i ];
|
|
m_TimeSelection.m_bInfinite[ i ] = timeSelection.m_bInfinite[ 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, const CDmAttribute *pPresetValue, const CDmAttribute *pPresetTimes )
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
if ( m_pActiveRecordingLayer == NULL )
|
|
return;
|
|
|
|
Assert( pChannel->GetRecordLayerIndex() != -1 );
|
|
if ( pChannel->GetRecordLayerIndex() == -1 )
|
|
return;
|
|
|
|
LayerChannelInfo_t &info = m_pActiveRecordingLayer->m_LayerChannels[ pChannel->GetRecordLayerIndex() ];
|
|
info.m_pPresetValue = pPresetValue;
|
|
info.m_pPresetTimes = pPresetTimes;
|
|
}
|
|
|
|
void CDmeChannelRecordingMgr::SetInRedo( bool bInRedo )
|
|
{
|
|
Assert( bInRedo != m_bInRedo );
|
|
m_bInRedo = bInRedo;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Methods to query aspects of recording
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeChannelRecordingMgr::IsUsingDetachedTimeSelection() const
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
return !m_TimeSelection.m_bAttachedMode;
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsTimeAdvancing() const
|
|
{
|
|
Assert( m_pActiveRecordingLayer );
|
|
return m_TimeSelection.IsTimeAdvancing();
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsUsingTimeSelection() const
|
|
{
|
|
return m_bUseTimeSelection;
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsRecordingLayerActive() const
|
|
{
|
|
return ( m_pActiveRecordingLayer != NULL );
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsModificationLayerActive() const
|
|
{
|
|
return ( ( m_pModificationLayer != NULL ) && ( m_bWantsToFinish == false ) );
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsModificationLayerVisible() const
|
|
{
|
|
if ( !IsModificationLayerActive() )
|
|
return false;
|
|
|
|
return m_pModificationLayer->IsVisible();
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsProcessingModifications() const
|
|
{
|
|
return m_bModificationProcessing;
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsModificationLayerEnabled() const
|
|
{
|
|
|
|
return m_bModificationLayerEnabled;
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::ShouldRecordUsingTimeSelection() const
|
|
{
|
|
return m_bUseTimeSelection;
|
|
}
|
|
|
|
bool CDmeChannelRecordingMgr::IsInRedo() const
|
|
{
|
|
return m_bInRedo;
|
|
}
|
|
|
|
|
|
void CDmeChannelRecordingMgr::SetProceduralTarget( int nProceduralMode, const CDmAttribute *pTarget )
|
|
{
|
|
m_nProceduralType = nProceduralMode;
|
|
m_pRevealTarget = pTarget;
|
|
m_PasteTarget.RemoveAll();
|
|
m_RandomSeed = 0;
|
|
}
|
|
|
|
void CDmeChannelRecordingMgr::SetProceduralTarget( int nProceduralMode, const CUtlVector< KeyValues * >& list, int randomSeed )
|
|
{
|
|
m_nProceduralType = nProceduralMode;
|
|
m_pRevealTarget = NULL;
|
|
m_PasteTarget.RemoveAll();
|
|
for ( int i = 0; i < list.Count(); ++i )
|
|
{
|
|
m_PasteTarget.AddToTail( list[ i ] );
|
|
}
|
|
m_RandomSeed = randomSeed;
|
|
}
|
|
|
|
int CDmeChannelRecordingMgr::GetProceduralType() const
|
|
{
|
|
return m_nProceduralType;
|
|
}
|
|
|
|
const CDmAttribute *CDmeChannelRecordingMgr::GetProceduralTarget() const
|
|
{
|
|
Assert( m_pRevealTarget );
|
|
return m_pRevealTarget;
|
|
}
|
|
|
|
const CUtlVector< KeyValues * > &CDmeChannelRecordingMgr::GetPasteTarget() const
|
|
{
|
|
return m_PasteTarget;
|
|
}
|
|
|
|
void CDmeChannelRecordingMgr::SetModificationLayerDirty()
|
|
{
|
|
if ( m_pModificationLayer )
|
|
{
|
|
m_bModificationLayerDirty = true;
|
|
}
|
|
}
|
|
|
|
CDmeChannelModificationLayer *CDmeChannelRecordingMgr::GetModificationLayer()
|
|
{
|
|
return m_pModificationLayer;
|
|
}
|
|
|
|
void CDmeChannelRecordingMgr::SetTransformWriteMode( TransformWriteMode_t mode )
|
|
{
|
|
m_TransformWriteMode = mode;
|
|
|
|
if ( m_pModificationLayer )
|
|
{
|
|
m_pModificationLayer->UpdateTransformWriteMode( mode );
|
|
SetModificationLayerDirty();
|
|
}
|
|
}
|
|
|
|
TransformWriteMode_t CDmeChannelRecordingMgr::GetTransformWriteMode() const
|
|
{
|
|
return m_TransformWriteMode;
|
|
}
|
|
|
|
|
|
void CDmeChannelRecordingMgr::UpdateActiveLayerManipulateInFalloff( bool bManipulateInFalloff )
|
|
{
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
int nChannels = m_pActiveRecordingLayer->m_LayerChannels.Count();
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
LayerChannelInfo_t &channelInfo = m_pActiveRecordingLayer->m_LayerChannels[ iChannel ];
|
|
channelInfo.m_bManipulateInFalloff = bManipulateInFalloff;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Copy the clipboard data for the paste operation
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannelRecordingMgr::GetPasteClipboardData( CUtlVector< KeyValues * > &list ) const
|
|
{
|
|
if ( m_pActiveRecordingLayer )
|
|
{
|
|
if ( m_pActiveRecordingLayer->m_ClipboardData.Count() > 0 )
|
|
{
|
|
int nKeys = m_pActiveRecordingLayer->m_ClipboardData.Count();
|
|
for ( int iKey = 0; iKey < nKeys; ++iKey )
|
|
{
|
|
list.AddToTail( m_pActiveRecordingLayer->m_ClipboardData[ iKey ] );
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_pDataModel->GetClipboardData( list );
|
|
}
|
|
|
|
DmeTime_t GetGlobalKeyTime( const DmeClipStack_t &clipStack, CDmeLog *pLog, int idx )
|
|
{
|
|
return pLog && idx < pLog->GetKeyCount() ? clipStack.FromChildMediaTime( pLog->GetKeyTime( idx ), false ) : DMETIME_MAXTIME;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Expose this class to the scene database
|
|
//-----------------------------------------------------------------------------
|
|
IMPLEMENT_ELEMENT_FACTORY( DmeChannel, CDmeChannel );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannel::OnConstruction()
|
|
{
|
|
m_nRecordLayerIndex = -1;
|
|
m_nNextCurveType = CURVE_DEFAULT;
|
|
|
|
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;
|
|
}
|
|
|
|
int CDmeChannel::GetRecordLayerIndex() const
|
|
{
|
|
return m_nRecordLayerIndex;
|
|
}
|
|
|
|
void CDmeChannel::SetRecordLayerIndex( int layerIndex )
|
|
{
|
|
// If this assert is hit it most likely means that the
|
|
// channel is a being assigned to two record layers at once.
|
|
Assert( ( layerIndex == -1 ) || ( m_nRecordLayerIndex == -1 ) );
|
|
m_nRecordLayerIndex = layerIndex;
|
|
}
|
|
|
|
|
|
void CDmeChannel::Play( bool useEmptyLog /*= false*/ )
|
|
{
|
|
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 ( !useEmptyLog )
|
|
{
|
|
if ( pLog->IsEmpty() && !pLog->HasDefaultValue() &&
|
|
GetFromAttribute() != NULL )
|
|
{
|
|
Pass();
|
|
return;
|
|
}
|
|
}
|
|
|
|
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_TimeState.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_TimeState.m_tCurrentTime;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Simple version. Only works if multiple active channels clips
|
|
// do not reference the same channels
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannel::SetCurrentTime( DmeTime_t time )
|
|
{
|
|
m_TimeState.m_tPreviousTime = m_TimeState.m_tCurrentTime;
|
|
m_TimeState.m_tCurrentTime = time;
|
|
m_TimeState.m_timeOutsideTimeframe = DMETIME_ZERO;
|
|
}
|
|
|
|
const TimeState_t &CDmeChannel::GetTimeState() const
|
|
{
|
|
return m_TimeState;
|
|
}
|
|
|
|
void CDmeChannel::SetTimeState( TimeState_t &TimeState )
|
|
{
|
|
m_TimeState = TimeState;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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_TimeState.m_tPreviousTime = m_TimeState.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_TimeState.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_TimeState.m_tCurrentTime = time;
|
|
m_TimeState.m_timeOutsideTimeframe = dt;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ClearTimeMetric marks m_timeOutsideTimeframe invalid (-inf is the worst possible match)
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeChannel::ClearTimeMetric()
|
|
{
|
|
m_TimeState.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;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Copy the paste data from the source key values list into the
|
|
// destination key values list. This makes a complete copy of all data so the
|
|
// destination can be safely referenced when the source has been modified or
|
|
// destroyed.
|
|
// Input : srcList - Reference to the source list of key values to be copied
|
|
// Output : dstList - Reference to the list to which the key values of the the
|
|
// source list are to be copied.
|
|
//-----------------------------------------------------------------------------
|
|
void CopyPasteData( CUtlVector< KeyValues * > &dstList, const CUtlVector< KeyValues * > &srcList )
|
|
{
|
|
// Assuming the list is empty, if not we could be improperly accumulating data.
|
|
Assert( dstList.Count() == 0 );
|
|
|
|
int nKeys = srcList.Count();
|
|
for ( int iKey = 0; iKey < nKeys; ++iKey )
|
|
{
|
|
KeyValues *pKeyValue = srcList[ iKey ];
|
|
|
|
// Skip null key values, but there should not be any.
|
|
if ( pKeyValue == NULL )
|
|
{
|
|
Assert( pKeyValue );
|
|
continue;
|
|
}
|
|
|
|
// Skip any unknown key value sets
|
|
if ( V_stricmp( pKeyValue->GetName(), "ControlLayers" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get the layer selection data from the key value, if there is no LayerData entry, then skip the key.
|
|
LayerSelectionData_t *pSrcLayerData = reinterpret_cast< LayerSelectionData_t * >( pKeyValue->GetPtr( "LayerData" ) );
|
|
if ( pSrcLayerData == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get the pointer to the log to which the layer data is for.
|
|
CDmeLog *pLog = pSrcLayerData->m_hLog.Get();
|
|
|
|
if ( pLog == NULL )
|
|
{
|
|
Assert( pLog );
|
|
continue;
|
|
}
|
|
|
|
// Create the new layer selection structure and create a new
|
|
// key value set for it and place it in the destination list.
|
|
LayerSelectionData_t *pDstLayerData = new LayerSelectionData_t;
|
|
KeyValues *pDstKeyValues = new KeyValues( "ControlLayers" );
|
|
dstList.AddToTail( pDstKeyValues );
|
|
|
|
// Add the layer data structure pointer to the key value set.
|
|
pDstKeyValues->SetPtr( "LayerData", reinterpret_cast< void * >( pDstLayerData ) );
|
|
|
|
// Copy the simple data element of the layer selection data
|
|
pDstLayerData->m_hChannel = pSrcLayerData->m_hChannel;
|
|
pDstLayerData->m_hOwner = pSrcLayerData->m_hOwner;
|
|
pDstLayerData->m_hShot = pSrcLayerData->m_hShot;
|
|
pDstLayerData->m_hLog = pSrcLayerData->m_hLog;
|
|
pDstLayerData->m_DataType = pSrcLayerData->m_DataType;
|
|
pDstLayerData->m_nTimes[ 0 ] = pSrcLayerData->m_nTimes[ 0 ];
|
|
pDstLayerData->m_nTimes[ 1 ] = pSrcLayerData->m_nTimes[ 1 ];
|
|
pDstLayerData->m_nTimes[ 2 ] = pSrcLayerData->m_nTimes[ 2 ];
|
|
pDstLayerData->m_nTimes[ 3 ] = pSrcLayerData->m_nTimes[ 3 ];
|
|
|
|
// Copy each of the log layers in the layer selection data.
|
|
int nLayers = pSrcLayerData->m_vecData.Count();
|
|
for ( int iLayer = 0; iLayer < nLayers; ++iLayer )
|
|
{
|
|
float srcFrac = pSrcLayerData->m_vecData[ iLayer ].m_flStartFraction;
|
|
CDmeLogLayer *pSrcLayer = pSrcLayerData->m_vecData[ iLayer ].m_hData.Get();
|
|
|
|
|
|
if ( pSrcLayer )
|
|
{
|
|
// Create a new layer and copy the source layer
|
|
CDmeLogLayer* pNewLayer = pLog->AddNewLayer();
|
|
pNewLayer->CopyLayer( pSrcLayer );
|
|
|
|
// Add the new layer to the layer selection data
|
|
LayerSelectionData_t::DataLayer_t dataLayer( srcFrac, pNewLayer );
|
|
pDstLayerData->m_vecData.AddToTail( dataLayer );
|
|
|
|
// Disconnect the new layer from log
|
|
pLog->RemoveLayerFromTail();
|
|
}
|
|
}
|
|
|
|
} // For iKey
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destroy the contents of the provided paste data list, freeing all
|
|
// associated resources.
|
|
// Input : list - Paste data key value list to be destroyed.
|
|
//-----------------------------------------------------------------------------
|
|
void DestroyPasteData( CUtlVector< KeyValues * > &list )
|
|
{
|
|
int c = list.Count();
|
|
for ( int i = 0; i < c; ++i )
|
|
{
|
|
KeyValues *kv = list[ i ];
|
|
Assert( kv );
|
|
if ( !kv )
|
|
continue;
|
|
|
|
LayerSelectionData_t *data = reinterpret_cast< LayerSelectionData_t * >( kv->GetPtr( "LayerData" ) );
|
|
if ( !data )
|
|
continue;
|
|
|
|
data->Release();
|
|
delete data;
|
|
}
|
|
}
|
|
|
|
|
|
void CDmeChannel::Record()
|
|
{
|
|
VPROF( "CDmeChannel::Record" );
|
|
|
|
CDmElement* pElement = GetFromElement();
|
|
CDmAttribute *pFromAttr = GetFromAttribute();
|
|
if ( pFromAttr == NULL )
|
|
return; // or clear out the log?
|
|
|
|
CDmeLog *pLog = GetLog();
|
|
DmeTime_t time = GetCurrentTime();
|
|
if ( m_TimeState.m_tPreviousTime == DMETIME_INVALID )
|
|
{
|
|
m_TimeState.m_tPreviousTime = time;
|
|
}
|
|
|
|
if ( g_pChannelRecordingMgr->ShouldRecordUsingTimeSelection() )
|
|
{
|
|
Assert( m_nRecordLayerIndex != -1 );
|
|
if ( m_nRecordLayerIndex == -1 )
|
|
return;
|
|
|
|
// Computes local times for the time selection
|
|
DmeLog_TimeSelection_t timeSelection;
|
|
g_pChannelRecordingMgr->GetLocalTimeSelection( timeSelection, m_nRecordLayerIndex );
|
|
|
|
int nType = g_pChannelRecordingMgr->GetProceduralType();
|
|
|
|
enum { PROCEDURAL_PRESET_ANIMATED = NUM_PROCEDURAL_PRESET_TYPES + 1 };
|
|
if ( nType == PROCEDURAL_PRESET_NOT && timeSelection.m_pPresetTimes )
|
|
{
|
|
nType = PROCEDURAL_PRESET_ANIMATED; // this is an animated preset, which is blended in like paste/spline/etc
|
|
}
|
|
|
|
switch ( nType )
|
|
{
|
|
default:
|
|
case PROCEDURAL_PRESET_NOT:
|
|
{
|
|
DmeLogTransformParams_t transformParams;
|
|
transformParams.m_nProceduralType = nType;
|
|
|
|
// If the element is a transform control, get the additional manipulation parameters from the
|
|
// transform control and store them in the transform parameters so they can be used by the log.
|
|
CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pElement );
|
|
|
|
if ( pTransformControl )
|
|
{
|
|
pTransformControl->GetManipulationTransform( transformParams.m_Transform );
|
|
pTransformControl->GetManipulationRotationLocal( transformParams.m_RotationLocal );
|
|
pTransformControl->GetManipulationRotationParent( transformParams.m_RotationParent );
|
|
pTransformControl->GetManipulationPivot( transformParams.m_Pivot );
|
|
|
|
CDmeChannel* pRotChannel = pTransformControl->GetOrientationChannel();
|
|
if ( pRotChannel )
|
|
{
|
|
CDmeLog* pLog = pRotChannel->GetLog();
|
|
if ( pLog && pLog->GetDataType() == AT_QUATERNION )
|
|
{
|
|
transformParams.m_pRotationLog = static_cast< CDmeTypedLog< Quaternion >* >( pLog );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Only transform controls can have manipulation in falloff
|
|
timeSelection.m_bManipulateInFalloff = false;
|
|
}
|
|
|
|
// Stamp the key at the current head position, re-sampling the current time selection if time is not moving or writing
|
|
// a key at the current time if time is moving. If the modification layer is active and the time is moving forward the
|
|
// key will be recorded without respect to the current time selection as the time selection will be applied later.
|
|
g_pChannelRecordingMgr->StoreChannelAttributeData( m_nRecordLayerIndex, true );
|
|
pLog->StampKeyAtHead( time, m_TimeState.m_tPreviousTime, timeSelection, transformParams, pFromAttr, m_fromIndex.Get(), !g_pChannelRecordingMgr->IsModificationLayerActive() );
|
|
}
|
|
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_DROP_LAYER:
|
|
{
|
|
int nLayers = pLog->GetNumLayers();
|
|
|
|
// There must be at least 3 layers in order for the drop to operate:
|
|
// the base layer, the modification layer and the active recording layer.
|
|
if ( nLayers >= 3 )
|
|
{
|
|
CDmeLogLayer* pRecLayer = pLog->GetLayer( nLayers - 1 );
|
|
CDmeLogLayer* pModLayer = pLog->GetLayer( nLayers - 2 );
|
|
CDmeLogLayer* pBaseLayer = pLog->GetLayer( nLayers - 3 );
|
|
|
|
pLog->BlendLayersUsingTimeSelection( pBaseLayer, pModLayer, pRecLayer, timeSelection, true, false, false, DMETIME_ZERO );
|
|
}
|
|
}
|
|
break;
|
|
case PROCEDURAL_PRESET_INOUT:
|
|
case PROCEDURAL_PRESET_JITTER:
|
|
case PROCEDURAL_PRESET_SMOOTH:
|
|
case PROCEDURAL_PRESET_SHARPEN:
|
|
case PROCEDURAL_PRESET_SOFTEN:
|
|
case PROCEDURAL_PRESET_STAGGER:
|
|
case PROCEDURAL_PRESET_HOLD:
|
|
case PROCEDURAL_PRESET_RELEASE:
|
|
case PROCEDURAL_PRESET_STEADY:
|
|
case PROCEDURAL_PRESET_PASTE:
|
|
case PROCEDURAL_PRESET_SPLINE:
|
|
case PROCEDURAL_PRESET_ANIMATED:
|
|
{
|
|
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 );
|
|
|
|
DmeLog_TimeSelection_t blendTimeSelction = timeSelection;
|
|
int iSourceLayer = FindSpanningLayerAndSetIntensity( blendTimeSelction, 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 ( IsPresetTimeOperation( nType ) ) // stagger and steady operate on sample times mostly
|
|
{
|
|
bool bFeatherBlendInFalloff = nType == PROCEDURAL_PRESET_STAGGER || nType == PROCEDURAL_PRESET_STEADY;
|
|
pLog->BlendTimesUsingTimeSelection( sourceLayer, targetLayer, outputLayer, blendTimeSelction, data->m_nTimes[ TS_LEFT_FALLOFF ], bFeatherBlendInFalloff );
|
|
}
|
|
else if ( iSourceLayer != 0 )
|
|
{
|
|
CDmeLogLayer *baseLayer = data->m_vecData[ 0 ].m_hData.Get();
|
|
pLog->BlendLayersUsingTimeSelection( baseLayer, sourceLayer, targetLayer, outputLayer, blendTimeSelction, false, data->m_nTimes[ TS_LEFT_FALLOFF ] );
|
|
}
|
|
else
|
|
{
|
|
pLog->BlendLayersUsingTimeSelection( sourceLayer, targetLayer, outputLayer, blendTimeSelction, false, true, true, data->m_nTimes[ TS_LEFT_FALLOFF ] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int bestLayer = pLog->GetTopmostLayer();
|
|
SegmentInterpolation_t interpSetting = (bestLayer == -1) ? SEGMENT_INTERPOLATE : pLog->GetLayer( bestLayer )->GetSegmentInterpolationSetting( m_TimeState.m_tPreviousTime, DMETIME_INVALID, false );
|
|
|
|
if( interpSetting == SEGMENT_INTERPOLATE ) //use existing behavior
|
|
{
|
|
if ( m_TimeState.m_tPreviousTime != time )
|
|
{
|
|
pLog->SetDuplicateKeyAtTime( m_TimeState.m_tPreviousTime );
|
|
}
|
|
pLog->SetKey( time, pFromAttr, m_fromIndex.Get(), SEGMENT_INTERPOLATE, m_nNextCurveType );
|
|
}
|
|
else
|
|
{
|
|
//attempt to preserve the non-interpolated segment as it is. I believe that this will also work for the general case above. But I'm not 100% sure at the moment. Best not to break existing behavior
|
|
if ( m_TimeState.m_tPreviousTime != time )
|
|
{
|
|
pLog->InsertKeyAtTime( m_TimeState.m_tPreviousTime );
|
|
}
|
|
pLog->SetKey( time, pFromAttr, m_fromIndex.Get(), pLog->GetLayer( bestLayer )->GetSegmentInterpolationSetting( time ), 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 ) const
|
|
{
|
|
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.GetClip( stack.GetClipCount() - 1 );
|
|
return NULL;
|
|
}
|
|
|
|
void CDmeChannel::ScaleSampleTimes( float scale )
|
|
{
|
|
m_log->ScaleSampleTimes( scale );
|
|
}
|
|
|
|
void CDmeChannel::ClearAndAddSampleAtTime( DmeTime_t time )
|
|
{
|
|
m_log->ClearAndAddSampleAtTime( time );
|
|
}
|
|
|
|
|
|
void RemapFloatLogValues( CDmeChannel *pChannel, float flBias, float flScale )
|
|
{
|
|
if ( !pChannel )
|
|
return;
|
|
|
|
CDmeLog *pLog = pChannel->GetLog();
|
|
if ( !pLog )
|
|
return;
|
|
|
|
CDmeTypedLogLayer< float > *pLogLayer = CastElement< CDmeTypedLogLayer< float > >( pLog->GetLayer( pLog->GetTopmostLayer() ) );
|
|
if ( !pLogLayer )
|
|
return;
|
|
|
|
int nKeys = pLogLayer->GetKeyCount();
|
|
for ( int i = 0; i < nKeys; ++i )
|
|
{
|
|
float flValue = pLogLayer->GetKeyValue( i );
|
|
pLogLayer->SetKeyValue( i, flValue * flScale + flBias );
|
|
}
|
|
}
|
|
|
|
CDmeChannel *FindChannelTargetingAttribute( CDmAttribute *pTargetAttr )
|
|
{
|
|
if ( !pTargetAttr )
|
|
return NULL;
|
|
|
|
CDmElement *pTarget = pTargetAttr->GetOwner();
|
|
if ( !pTarget )
|
|
return NULL;
|
|
|
|
for ( DmAttributeReferenceIterator_t it = g_pDataModel->FirstAttributeReferencingElement( pTarget->GetHandle() );
|
|
it != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID;
|
|
it = g_pDataModel->NextAttributeReferencingElement( it ) )
|
|
{
|
|
CDmAttribute *pAttr = g_pDataModel->GetAttribute( it );
|
|
Assert( pAttr );
|
|
CDmeChannel *pChannel = CastElement< CDmeChannel >( pAttr->GetOwner() );
|
|
if ( pChannel && pChannel->GetToAttribute() == pTargetAttr )
|
|
return pChannel;
|
|
}
|
|
return NULL;
|
|
}
|