// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Channels/MovieSceneChannel.h" #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "CoreTypes.h" #include "Curves/KeyHandle.h" #include "Math/Range.h" #include "Math/RangeBound.h" #include "Misc/AssertionMacros.h" #include "Misc/FrameNumber.h" #include "Misc/FrameTime.h" #include "Templates/UnrealTemplate.h" #include "Templates/UnrealTypeTraits.h" #include "UObject/Class.h" #include "UObject/ObjectMacros.h" #include "IMovieSceneRetimingInterface.h" #include "MovieSceneChannelData.generated.h" struct FFrameRate; struct FKeyDataOptimizationParams; namespace UE::MovieScene { template void OnRemapChannelKeyTime(const FMovieSceneChannel* Channel, const IRetimingInterface& Retimer, FFrameNumber PreviousTime, FFrameNumber CurrentTime, ValueType& InOutValue) {} } /** A map of key handles that is copyable, but does not copy data on copy */ USTRUCT() struct FMovieSceneKeyHandleMap : public FKeyHandleLookupTable { GENERATED_BODY() public: FMovieSceneKeyHandleMap() = default; FMovieSceneKeyHandleMap(const FMovieSceneKeyHandleMap& RHS){} FMovieSceneKeyHandleMap& operator=(const FMovieSceneKeyHandleMap& RHS) { Reset(); return *this; } }; template<> struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 { enum { WithSerializer = true, }; static constexpr EPropertyObjectReferenceType WithSerializerObjectReferences = EPropertyObjectReferenceType::None; }; namespace UE { namespace MovieScene { /** * Evaluate the specified time array by finding the two indices that are adjacent to the supplied time * * @param InTimes A sorted array of frame numbers * @param InTime The time to find within the array * @param OutIndex1 The first time in the array that's >= InTime, or INDEX_NONE if there are none * @param OutIndex2 OutIndex1 + 1 if it is a valid index in the array, INDEX_NONE otherwise */ MOVIESCENE_API void EvaluateTime(TArrayView InTimes, FFrameTime InTime, int32& OutIndex1, int32& OutIndex2); /** * Evaluate the specified time array by finding the two indices and an interpolation value that are adjacent to the supplied time * * @param InTimes A sorted array of frame numbers * @param InTime The time to find within the array * @param OutIndex1 The first time in the array that's >= InTime, or INDEX_NONE if there are none * @param OutIndex2 OutIndex1 + 1 if it is a valid index in the array, INDEX_NONE otherwise * @param OutInterp A value from 0.0 -> 1.0 specifying how a linear interpolation value from index 1 to index 2 */ MOVIESCENE_API void EvaluateTime(TArrayView InTimes, FFrameTime InTime, int32& OutIndex1, int32& OutIndex2, double& OutInterp); /** * Find the range of times that fall around PredicateTime +/- InTolerance up to a maximum * * @param InTimes A sorted array of frame numbers * @param PredicateTime The time around which to search * @param InTolerance The tolerance range to search around PredicateTime with * @param MaxNum A maximum number of times to find, starting with those closest to the predicate time * @param OutMin The earliest index that met the conditions of the search * @param OutMax The latest index that met the conditions of the search */ MOVIESCENE_API void FindRange(TArrayView InTimes, FFrameNumber PredicateTime, FFrameNumber InTolerance, int32 MaxNum, int32& OutMin, int32& OutMax); } // namespace MovieScene } // namespace UE /** * Base class channel data utility that provides a consistent interface to a sorted array of times and handles. * Complete access should be through TMovieSceneChannelData that allows mutation of the data */ struct FMovieSceneChannelData { /** * Read-only access to this channel's key times. */ FORCEINLINE TArrayView GetTimes() const { return *Times; } /** * Mutable access to this channel's key times. * @note: *Warning*: any usage *must* keep times sorted. Any reordering of times will not be reflected in the values array. */ FORCEINLINE TArrayView GetTimes() { return *Times; } /** * Retrieve a key handle for the specified key time index * * @param Index The index to retrieve * @return A key handle that identifies the key at the specified index, regardless of re-ordering */ MOVIESCENE_API FKeyHandle GetHandle(int32 Index); /** * Attempt to retrieve the index of key from its handle * * @param Handle The handle to retrieve * @return The index of the key, or INDEX_NONE */ MOVIESCENE_API int32 GetIndex(FKeyHandle Handle); /** * Attempt to find a key at a given time and tolerance * * @param InTime The time at which to search * @param InTolerance A tolerance of frame numbers to allow either side of the specified time * @return The index of the key closest to InTime and within InTolerance, or INDEX_NONE */ MOVIESCENE_API int32 FindKey(FFrameNumber InTime, FFrameNumber InTolerance = 0); /** * Find the range of keys that fall around InTime +/- InTolerance up to a maximum * * @param InTime The time around which to search * @param MaxNum A maximum number of times to find, starting with those closest to the predicate time * @param OutMin The earliest index that met the conditions of the search * @param OutMax The latest index that met the conditions of the search * @param InTolerance The tolerance range to search around PredicateTime with */ MOVIESCENE_API void FindKeys(FFrameNumber InTime, int32 MaxNum, int32& OutMinIndex, int32& OutMaxIndex, int32 InTolerance); /** * Compute the total time range of the channel data. * * @return The range of this channel data */ MOVIESCENE_API TRange GetTotalRange() const; /** * Convert the frame resolution of a movie scene channel by moving the key times to the equivalent frame time * * @param SourceRate The frame rate the channel is currently in * @param DestinationRate The new frame rate to convert the channel to */ UE_DEPRECATED(5.6, "Please use RemapTimes") MOVIESCENE_API void ChangeFrameResolution(FFrameRate SourceRate, FFrameRate DestinationRate); /** * Get all the keys in the given range. Resulting arrays must be the same size where indices correspond to both arrays. * * @param WithinRange The bounds to get keys for * @param OutKeyTimes Array to receive all key times within the given range * @param OutKeyHandles Array to receive all key handles within the given range */ MOVIESCENE_API void GetKeys(const TRange& WithinRange, TArray* OutKeyTimes, TArray* OutKeyHandles); /** * Get key times for a number of keys in the channel data * * @param InHandles Array of key handles that should have their times set * @param OutKeyTimes Array of times that should be set for each key handle. Must be exactly the size of InHandles */ MOVIESCENE_API void GetKeyTimes(TArrayView InHandles, TArrayView OutKeyTimes); /** * Offset the channel data by a given delta time * * @param DeltaTime The time to offset by */ MOVIESCENE_API void Offset(FFrameNumber DeltaTime); protected: /** * Constructor that takes a non-owning pointer to an array of times and a key handle map * * @param InTimes A pointer to an array that should be operated on by this class. Externally owned. * @param InKeyHandles A key handle map used for persistent, order independent identification of keys * @param InChannel A pointer to the owning channel. */ MOVIESCENE_API FMovieSceneChannelData(FMovieSceneChannel* InChannel, TArray* InTimes, FKeyHandleLookupTable* InKeyHandles); UE_DEPRECATED(5.5, "Constructor that takes an optional FMovieSceneChannel is now deprecated. FMovieSceneChannel is now required.") MOVIESCENE_API FMovieSceneChannelData(TArray* InTimes, FKeyHandleLookupTable* InKeyHandles, FMovieSceneChannel* InChannel = nullptr); /** * Move the key at index KeyIndex to a new time * * @return The index of the key in its new position */ MOVIESCENE_API int32 MoveKeyInternal(int32 KeyIndex, FFrameNumber InNewTime); /** * Add a new key at the specified time * * @return The index of the key in its new position */ MOVIESCENE_API int32 AddKeyInternal(FFrameNumber InTime); protected: /** Pointer to an external array of sorted times. Must be kept in sync with a corresponding value array. */ TArray* Times; /** Pointer to an external key handle map */ FKeyHandleLookupTable* KeyHandles; /** Optional Pointer to the owning FMovieSceneChannel, should be set if the add,move, and delete callbacks are needed */ FMovieSceneChannel* OwningChannel; }; /** * Templated channel data utility class that provides a consistent interface for interacting with a channel's keys and values. * Assumes that the supplied time and value arrays are already sorted ascendingly by time and are the same size. * This class will maintain those invariants throughout its lifetime. */ template struct TMovieSceneChannelData : FMovieSceneChannelData { typedef typename TCallTraits::ParamType ParamType; /** * Constructor that takes a non-owning pointer to an array of times and values, and a key handle map * * @param InTimes A pointer to an array of times that should be operated on by this class. Externally owned. * @param InValues A pointer to an array of values that should be operated on by this class. Externally owned. * @param InKeyHandles A key handle map used for persistent, order independent identification of keys * @param InChannel A option point to the owning channel, should be set if the move,add, delete delegates are utilizaed */ UE_DEPRECATED(5.5, "Constructor that takes an optional FMovieSceneChannel is now deprecated. FMovieSceneChannel is now required.") TMovieSceneChannelData(TArray* InTimes, TArray* InValues, FKeyHandleLookupTable* InKeyHandles, FMovieSceneChannel* InChannel = nullptr) : FMovieSceneChannelData(InChannel, InTimes, InKeyHandles), Values(InValues) { check(Times && Values); } /** * Constructor that takes a non-owning pointer to an array of times and values, and a key handle map * * @param InTimes A pointer to an array of times that should be operated on by this class. Externally owned. * @param InValues A pointer to an array of values that should be operated on by this class. Externally owned. * @param InChannel A pointer to the owning channel. * @param InKeyHandles A key handle map used for persistent, order independent identification of keys */ TMovieSceneChannelData(TArray* InTimes, TArray* InValues, FMovieSceneChannel* InChannel, FKeyHandleLookupTable* InKeyHandles) : FMovieSceneChannelData(InChannel, InTimes, InKeyHandles), Values(InValues) { check(Times && Values); } /** * Conversion to a constant version of this class */ operator TMovieSceneChannelData() { return TMovieSceneChannelData(Times, Values); } /** * Read-only access to this channel's values */ FORCEINLINE TArrayView GetValues() const { return *Values; } /** * Mutable access to this channel's values */ FORCEINLINE TArrayView GetValues() { return *Values; } /** * Add a new key at a given time * * @param InTime The time at which to add the new key * @param InValue The value of the new key * @return The index of the newly added key */ int32 AddKey(FFrameNumber InTime, ParamType InValue) { int32 KeyIndex = AddKeyInternal(InTime); Values->Insert(InValue, KeyIndex); if (OwningChannel && OwningChannel->OnKeyAddedEvent().IsBound()) { TArray Items; Items.Add(FKeyAddOrDeleteEventItem(KeyIndex, InTime)); OwningChannel->OnKeyAddedEvent().Broadcast(OwningChannel, Items); } return KeyIndex; } /** * Move the key at index KeyIndex to a new time * * @param KeyIndex The index of the key to move * @param NewTime The time to move the key to * @return The index of the key in its new position */ int32 MoveKey(int32 KeyIndex, FFrameNumber NewTime, bool bRemoveDuplicateKeys = false) { if (bRemoveDuplicateKeys) { FKeyHandle KeyHandle = GetHandle(KeyIndex); TArray KeysToRemove; TArray DummyTimes; GetKeys(TRange(NewTime, NewTime), &DummyTimes, &KeysToRemove); KeysToRemove.Remove(KeyHandle); if (KeysToRemove.Num() > 0) { DeleteKeys(KeysToRemove); } KeyIndex = GetIndex(KeyHandle); } if (KeyIndex == INDEX_NONE) { return KeyIndex; } int32 NewIndex = MoveKeyInternal(KeyIndex, NewTime); if (NewIndex != KeyIndex) { // We have to remove the key and re-add it in the right place // This could probably be done better by just shuffling up/down the items that need to move, without ever changing the size of the array ValueType OldValue = (*Values)[KeyIndex]; Values->RemoveAt(KeyIndex, EAllowShrinking::No); Values->Insert(OldValue, NewIndex); } return NewIndex; } /** * Move the key at index KeyIndex to a new time * * @param KeyIndex The index of the key to move * @param NewTime The time to move the key to * @return The index of the key in its new position */ int32 SetKeyTime(int32 KeyIndex, FFrameNumber InNewTime) { return MoveKey(KeyIndex, InNewTime); } /** * Remap the times of all the keys in this channel using an abstract retimer * * @param Retimer Custom retimer object that can convert from an old time to a new time */ void RemapTimes(const UE::MovieScene::IRetimingInterface& Retimer) { using namespace UE::MovieScene; const int32 Num = Times->Num(); if (Num == 0) { return; } bool bIsSorted = true; { int32 Index = 0; // Remap the first time { FFrameNumber& NewTime = (*Times)[Index]; FFrameNumber OldTime = NewTime; NewTime = Retimer.RemapTime(OldTime); OnRemapChannelKeyTime(OwningChannel, Retimer, OldTime, NewTime, (*Values)[Index]); } ++Index; // Remap others keeping track of whether we need resorting for ( ; Index < Num; ++Index) { FFrameNumber& NewTime = (*Times)[Index]; FFrameNumber OldTime = NewTime; FFrameNumber Previous = (*Times)[Index-1]; NewTime = Retimer.RemapTime(OldTime); bIsSorted &= NewTime >= Previous; OnRemapChannelKeyTime(OwningChannel, Retimer, OldTime, NewTime, (*Values)[Index]); } } if (!bIsSorted) { // Have to re-sort all the keys... TArray> Indices; for (int32 Index = 0; Index < Num; ++Index) { Indices.Add(Index); } Algo::Sort(Indices, [this](int32 A, int32 B){ return (*Times)[A] < (*Times)[B]; }); for (int32 Index = 0; Index < Num; ++Index) { KeyHandles->MoveHandle(Index, Indices[Index]); } // Move all the keys by repeatedly swapping until we get the correct index for (int32 Index = 0; Index < Num; ++Index) { while (Indices[Index] != Index) { const int32 DestIndex = Indices[Index]; Swap((*Times)[Index], (*Times)[DestIndex]); Swap((*Values)[Index], (*Values)[DestIndex]); Swap(Indices[Index], Indices[DestIndex]); } } } } /** * Remove the key at a given index * * @param KeyIndex The index of the key to remove */ void RemoveKey(int32 KeyIndex) { check(Times->IsValidIndex(KeyIndex)); if (OwningChannel && OwningChannel->OnKeyDeletedEvent().IsBound()) { const FFrameNumber Time = (*Times)[KeyIndex]; TArray Items; Items.Add(FKeyAddOrDeleteEventItem(KeyIndex, Time)); OwningChannel->OnKeyDeletedEvent().Broadcast(OwningChannel, Items); } Times->RemoveAt(KeyIndex, EAllowShrinking::No); Values->RemoveAt(KeyIndex, EAllowShrinking::No); if (KeyHandles) { KeyHandles->DeallocateHandle(KeyIndex); } } /** * Set the value of the key at InTime to InValue, adding a new key if necessary * * @param InTime The time at which to add the new key * @param InValue The value of the new key * @return The handle of the key */ FKeyHandle UpdateOrAddKey(FFrameNumber InTime, ParamType InValue) { int32 ExistingKey = FindKey(InTime); if (ExistingKey != INDEX_NONE) { (*Values)[ExistingKey] = InValue; } else { ExistingKey = AddKey(InTime, InValue); } return GetHandle(ExistingKey); } /** * Updates keys with these times in the channel. Will add or insert any missing keys, and replace values of existing keys. * This assumes that the InTimes array is sorted, as it performs a tape merge to efficiently update the keys. * * @param InTimes Times to update * @param InValues Values to update */ void UpdateOrAddKeys(const TArrayView InTimes, const TArrayView InValues) { check(InTimes.Num() == InValues.Num()); const int32 UpdateNum = InTimes.Num(); int32 CurrIdx = 0; int32 UpdateIdx = 0; while (true) { if (UpdateIdx == UpdateNum) { // No more keys to insert break; } if (CurrIdx == Times->Num()) { // Append the rest of the keys to the end Times->Append(InTimes.RightChop(UpdateIdx)); Values->Append(InValues.RightChop(UpdateIdx)); break; } if ((*Times)[CurrIdx] == InTimes[UpdateIdx]) { // Replace value at the associated time (*Values)[CurrIdx] = InValues[UpdateIdx]; CurrIdx++; UpdateIdx++; continue; } if ((*Times)[CurrIdx] > InTimes[UpdateIdx]) { // Insert new time value pair Times->Insert(InTimes[UpdateIdx], CurrIdx); Values->Insert(InValues[UpdateIdx], CurrIdx); CurrIdx++; UpdateIdx++; continue; } if ((*Times)[CurrIdx] < InTimes[UpdateIdx]) { // Not found insertion slot yet CurrIdx++; continue; } } KeyHandles->Reset(); for (int32 Index = 0; Index < Times->Num(); ++Index) { KeyHandles->AllocateHandle(Index); } } /** * Set key times for a number of keys in this channel data * * @param InHandles Array of key handles that should have their times set * @param InKeyTimes Array of new times for each handle of the above array */ void SetKeyTimes(TArrayView InHandles, TArrayView InKeyTimes) { check(InHandles.Num() == InKeyTimes.Num()); for (int32 Index = 0; Index < InHandles.Num(); ++Index) { const int32 KeyIndex = GetIndex(InHandles[Index]); if (KeyIndex != INDEX_NONE) { MoveKey(KeyIndex, InKeyTimes[Index]); } } } /** * Duplicate a number of keys within this channel data * * @param InHandles Array of key handles that should be duplicated * @param OutNewHandles Array view to receive key handles for each duplicated key. Must exactly mathc the size of InHandles. */ void DuplicateKeys(TArrayView InHandles, TArrayView OutNewHandles) { for (int32 Index = 0; Index < InHandles.Num(); ++Index) { const int32 KeyIndex = GetIndex(InHandles[Index]); if (KeyIndex == INDEX_NONE) { // we must add a handle even if the supplied handle does not relate to a key in this channel OutNewHandles[Index] = FKeyHandle::Invalid(); } else { // Do not cache value and time arrays since they can be reallocated during this loop auto KeyCopy = (*Values)[KeyIndex]; int32 NewKeyIndex = AddKey((*Times)[KeyIndex], MoveTemp(KeyCopy)); OutNewHandles[Index] = GetHandle(NewKeyIndex); } } } /** * Delete a number of keys from this channel data * * @param InHandles Array of key handles that should be deleted */ void DeleteKeys(TArrayView InHandles) { for (int32 Index = 0; Index < InHandles.Num(); ++Index) { const int32 KeyIndex = GetIndex(InHandles[Index]); if (KeyIndex != INDEX_NONE) { RemoveKey(KeyIndex); } } } /** * Delete keys before or after a specified time * * @param InTime Delete keys after this time * @param bDeleteKeysBefore Whether to delete keys before the specified time */ void DeleteKeysFrom(FFrameNumber InTime, bool bDeleteKeysBefore) { TArray OutKeyTimes; TArray OutKeyHandles; GetKeys(TRange::All(), &OutKeyTimes, &OutKeyHandles); TArray KeysToRemove; for (int32 Index = 0; Index < OutKeyTimes.Num(); ++Index) { if (bDeleteKeysBefore) { if (OutKeyTimes[Index] < InTime) { KeysToRemove.Add(OutKeyHandles[Index]); } } else { if (OutKeyTimes[Index] > InTime) { KeysToRemove.Add(OutKeyHandles[Index]); } } } DeleteKeys(KeysToRemove); } /** * Remove all the keys from this channel */ void Reset() { if (OwningChannel && OwningChannel->OnKeyDeletedEvent().IsBound()) { TArray Items; for (int32 Index = 0; Index < Times->Num(); ++Index) { const FFrameNumber Time = (*Times)[Index]; Items.Add(FKeyAddOrDeleteEventItem(Index, Time)); } OwningChannel->OnKeyDeletedEvent().Broadcast(OwningChannel, Items); } Times->Reset(); Values->Reset(); if (KeyHandles) { KeyHandles->Reset(); } } private: /** Pointer to an external array of values, to be kept in sync with FMovieSceneChannelData::Times */ TArray* Values; }; /** * Specialization of TMovieSceneChannelData for const value types (limited read-only access to data) */ template struct TMovieSceneChannelData { typedef typename TCallTraits::ParamType ParamType; /** * Constructor that takes a non-owning pointer to an array of times and values, and a key handle map * * @param InTimes A pointer to an array of times that should be operated on by this class. Externally owned. * @param InValues A pointer to an array of values that should be operated on by this class. Externally owned. * @param InKeyHandles A key handle map used for persistent, order independent identification of keys */ TMovieSceneChannelData(const TArray* InTimes, const TArray* InValues) : Times(InTimes), Values(InValues) { check(Times && Values); } /** * Read-only access to this channel's key times. */ FORCEINLINE TArrayView GetTimes() const { return *Times; } /** * Read-only access to this channel's values */ FORCEINLINE TArrayView GetValues() const { return *Values; } /** * Attempt to find a key at a given time and tolerance * * @param InTime The time at which to search * @param InTolerance A tolerance of frame numbers to allow either side of the specified time * @return The index of the key closest to InTime and within InTolerance, INDEX_NONE or Times.Num() */ int32 FindKey(FFrameNumber InTime, FFrameNumber InTolerance = 0) const { int32 MinIndex = 0, MaxIndex = 0; UE::MovieScene::FindRange(*Times, InTime, InTolerance, 1, MinIndex, MaxIndex); return MinIndex; } /** * Find the range of keys that fall around InTime +/- InTolerance up to a maximum * * @param InTime The time around which to search * @param MaxNum A maximum number of times to find, starting with those closest to the predicate time * @param OutMin The earliest index that met the conditions of the search * @param OutMax The latest index that met the conditions of the search * @param InTolerance The tolerance range to search around PredicateTime with */ void FindKeys(FFrameNumber InTime, int32 MaxNum, int32& OutMinIndex, int32& OutMaxIndex, FFrameNumber InTolerance) const { UE::MovieScene::FindRange(*Times, InTime, InTolerance, MaxNum, OutMinIndex, OutMaxIndex); } /** * Compute the total time range of the channel data. * * @return The range of this channel data */ TRange GetTotalRange() const { return Times->Num() ? TRange((*Times)[0], TRangeBound::Inclusive((*Times)[Times->Num()-1])) : TRange::Empty(); } private: /** Pointer to an external array of sorted times. Must be kept in sync with Values. */ const TArray* Times; /** Pointer to an external array of values, to be kept in sync with Times. */ const TArray* Values; };