// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Evaluation/Blending/MovieSceneBlendType.h" #include "Evaluation/Blending/BlendableTokenStack.h" #include "EulerTransform.h" /**~ * Generic multi-channel blending support for sequencer. * Works in conjunction with TMovieSceneBlendingActuator where Type has been set up with the correct traits to allow multi-channel blending: * Example: * * // My struct contains 3 floats, so is represented as a TMultiChannelValue * struct FMyStruct { float X, Y, Z; ... }; * * namespace MovieScene * { * // Marshall my struct into a multi-channel value * inline void MultiChannelFromData(FMyStruct In, TMultiChannelValue& Out) * { * Out = { In.X, In.Y, In.Z }; * } * // Marshall my struct out of a static float array * inline void ResolveChannelsToData(float (&Channels)[3], FMyStruct& Out) * { * Out = FMyStruct(Channels[0], Channels[1], Channels[2]); * } * } * * // Access a unique runtime type identifier for FMyStruct. Implemented in cpp to ensure there is only ever 1. This is required to support actuators operating on FMyStructs. * template<> MYMODULE_API FMovieSceneAnimTypeID GetBlendingDataType(); * * // Inform the blending code to use a maksed blendable of 3 floats for my struct * template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; * * // To inject a blendable token into the accumulator for an FMyStruct: * UE::MovieScene::TMultiChannelValue AnimatedData; * * // Set the X and Z channels * AnimatedData.Set(0, 100.f); * AnimatedData.Set(2, 250.f); * * // Ensure the accumulator knows how to apply an FMyStruct (usually this will be on a property, and happen through EnsureActuator<>) * FMovieSceneBlendingActuatorID ActuatorTypeID = FMyActuatorType::GetActuatorTypeID(); * if (!ExecutionTokens.GetAccumulator().FindActuator(ActuatorTypeID)) * { * ExecutionTokens.GetAccumulator().DefineActuator(ActuatorTypeID, MakeShared()); * } * * // Add the blendable to the accumulator * float Weight = EvaluateEasing(Context.GetTime()); * // Constructing a TBlendableToken from any type other than TMultiChannelValue requires a supporting BlendValue function * ExecutionTokens.BlendToken(ActuatorTypeID, TBlendableToken(AnimatedData, EMovieSceneBlendType::Absolute, Weight)); */ namespace UE { namespace MovieScene { /** * Generic value type that supports a specific number of channels, optionally masked out. * Used for blending types that can be represented as a contiguous set of numeric values. * Relative, Weighted and Additive blending occurs on a per-channel basis */ template struct TMultiChannelValue { static_assert(N > 0 && N <= 32, "Cannot store more than 32 channels in this type."); /** Default Constructor */ TMultiChannelValue() : Mask(0) {} /** Construction from a set of values. List size must match the number of channels. */ template TMultiChannelValue(std::initializer_list InitList) : Mask((1 << N) - 1) { check(InitList.size() == N); T* CurrChannel = &Channels[0]; for (auto Val : InitList) { (*CurrChannel++) = Val; } } /** Array indexing operator - returns a channel value */ T operator[](uint8 Index) const { checkf(Index < N && IsSet(Index), TEXT("Attempting to access an invalid channel.")); return Channels[Index]; } /** Access a value with a default */ T Get(uint8 Index, T Default) const { return IsSet(Index) ? Channels[Index] : Default; } /** Check if this value is empty */ bool IsEmpty() const { return Mask == 0; } /** Check if every channel in this value is valid */ bool IsFull() const { static const uint32 FullChannelMask = N == 32 ? 0xFFFFFFFF : (1 << N) - 1; return Mask == FullChannelMask; } /** Check whether the specified channel index is enabled */ bool IsSet(uint8 Index) const { check(Index < N); return (Mask & (1 << Index)) != 0; } /** Enable and apply a value to the specified channel */ void Set(uint8 Index, T Value) { check(Index < N); Channels[Index] = Value; Mask |= (1 << Index); } /** Increment the channel at the specified index by the specified amount */ void Increment(uint8 Index, T Value) { check(Index < N); Channels[Index] = IsSet(Index) ? Channels[Index] + Value : Value; Mask |= (1 << Index); } /** Do weighted blend with current value, this is an override*/ void WeightedBlend(uint8 Index, T Value, float Weight) { check(Index < N); Channels[Index] = IsSet(Index) ? ((1.0 - Weight)*Channels[Index]) + (Value * Weight) : Value; Mask |= (1 << Index); } private: /** Channel data */ T Channels[N]; /** Mask signifying which indices within Channels are valid */ uint32 Mask; }; /** Declaration of a function used to generate multi channel data from a source type. Overload in the MovieScene namespace for type-specific functionality. */ template void MultiChannelFromData(SourceData InSourceData, TMultiChannelValue& OutChannelData) { static_assert(std::is_same_v, "MultiChannelFromData must be implemented to blend SourceData with multi-channel data."); } /** * Declaration of a function used to popupate a specific type with generic channel data after blending has occurred. Overload in the MovieScene namespace for type-specific functionality. * For example, to support multi channel blending */ template void ResolveChannelsToData(const TMultiChannelValue& OutChannelData, SourceData& OutData) { static_assert(std::is_same_v, "ResolveChannelsToData must be implemented to blend SourceData with multi-channel data."); } /** Working data type used to blend multi-channel values */ template struct TMaskedBlendable { TMaskedBlendable() { // All weights start at 0 FMemory::Memzero(&AbsoluteWeights, sizeof(AbsoluteWeights)); } /** Per-channel absolute values to apply, pre-multiplied by their weight */ TMultiChannelValue Absolute; /** Cumulative absolute weights for each channel */ float AbsoluteWeights[N]; /** Per-channel additive values to apply, pre-multiplied by their weight */ TMultiChannelValue Additive; /** Per-channel override values to apply, when set this will override the default absolute/additive set */ TMultiChannelValue Override; /** Cached initial value for this blendable in multi-channel form */ TOptional> InitialValue; /** Resolve this structure's data into a final value to pass to the actuator */ template ActualDataType Resolve(TMovieSceneInitialValueStore& InitialValueStore) { TOptional> CurrentValue; TMultiChannelValue Result; // Iterate through each channel for (uint8 Channel = 0; Channel < N; ++Channel) { // Any animated channels with a weight of 0 should match the object's *initial* position. Exclusively additive channels are based implicitly off the initial value if (Override.IsSet(Channel)) //if override then we override { Result.Set(Channel, Override[Channel]); } else { const bool bUseInitialValue = (Absolute.IsSet(Channel) && AbsoluteWeights[Channel] == 0.f) || (!Absolute.IsSet(Channel) && Additive.IsSet(Channel)); if (bUseInitialValue) { if (!InitialValue.IsSet()) { InitialValue.Emplace(); MultiChannelFromData(InitialValueStore.GetInitialValue(), InitialValue.GetValue()); } Result.Set(Channel, InitialValue.GetValue()[Channel]); } else if (Absolute.IsSet(Channel)) { // If it has a non-zero weight, divide by it, and apply the absolute total to the result Result.Set(Channel, Absolute[Channel] / AbsoluteWeights[Channel]); } // If it has any additive values in the channel, add those on if (Additive.IsSet(Channel)) { Result.Increment(Channel, Additive[Channel]); } } // If the channel has not been animated at all, set it to the *current* value if (!Result.IsSet(Channel)) { if (!CurrentValue.IsSet()) { CurrentValue.Emplace(); MultiChannelFromData(InitialValueStore.RetrieveCurrentValue(), CurrentValue.GetValue()); } Result.Set(Channel, CurrentValue.GetValue()[Channel]); } } ensureMsgf(Result.IsFull(), TEXT("Attempting to apply a compound data type with some channels uninitialized.")); // Resolve the final channel data into the correct data type for the actuator ActualDataType FinalResult; ResolveChannelsToData(Result, FinalResult); return FinalResult; } }; template void BlendValue(TMaskedBlendable& OutBlend, InputType InValue, int32 ChannelIndex, float Weight, EMovieSceneBlendType BlendType, int32 BlendingOrder, TMovieSceneInitialValueStore& InitialValueStore) { if (BlendType == EMovieSceneBlendType::Absolute || BlendType == EMovieSceneBlendType::Relative) { if (BlendType == EMovieSceneBlendType::Relative) { // If it's relative, we need to add our value onto the channel's initial value if (!OutBlend.InitialValue.IsSet()) { OutBlend.InitialValue.Emplace(); MultiChannelFromData(InitialValueStore.GetInitialValue(), OutBlend.InitialValue.GetValue()); } OutBlend.Absolute.Increment(ChannelIndex, (OutBlend.InitialValue.GetValue()[ChannelIndex] + InValue) * Weight); } else { // Coerce to the output type before applying a weight to ensure that we're operating with the working data type throughout. // This allows us to blend integers as doubles without losing precision at extreme numeric ranges OutBlend.Absolute.Increment(ChannelIndex, OutputType(InValue) * Weight); } // Accumulate total weights OutBlend.AbsoluteWeights[ChannelIndex] += Weight; } else if (BlendingOrder != INDEX_NONE) { //If there is a blending order set we use the additive to contain //the full blended value consiting of absolute + overrides + additives //This will already be sorted so we just need to accumulate the values if (OutBlend.Override.IsSet(ChannelIndex) == false) { if (OutBlend.Absolute.IsSet(ChannelIndex)) { // If it has a non-zero weight, divide by it, and apply the absolute total to the result OutputType AbsoluteWeight = (OutBlend.AbsoluteWeights[ChannelIndex] != 0.f) ? OutBlend.AbsoluteWeights[ChannelIndex] : 1.0; OutBlend.Override.Set(ChannelIndex, OutBlend.Absolute[ChannelIndex] / AbsoluteWeight); } } if (BlendType == EMovieSceneBlendType::Additive) { OutBlend.Override.Increment(ChannelIndex, OutputType(InValue) * Weight); } else if (BlendType == EMovieSceneBlendType::Override) { OutBlend.Override.WeightedBlend(ChannelIndex, OutputType(InValue), Weight); } } else if (BlendType == EMovieSceneBlendType::Additive) { // Additive animation just increments the additive channel // Coerce to the output type before applying a weight to ensure that we're operating with the working data type throughout. // This allows us to blend integers as doubles without losing precision at extreme numeric ranges OutBlend.Additive.Increment(ChannelIndex, OutputType(InValue) * Weight); } } template void BlendValue(TMaskedBlendable& OutBlend, InputType InValue, float Weight, EMovieSceneBlendType BlendType, int32 BlendingOrder, TMovieSceneInitialValueStore& InitialValueStore) { BlendValue(OutBlend, InValue, 0, Weight, BlendType, BlendingOrder, InitialValueStore); } template void BlendValue(TMaskedBlendable& OutBlend, const TMultiChannelValue& InValue, float Weight, EMovieSceneBlendType BlendType, int32 BlendingOrder, TMovieSceneInitialValueStore& InitialValueStore) { for (int32 Index = 0; Index < ChannelSize; ++Index) { if (InValue.IsSet(Index)) { BlendValue(OutBlend, InValue[Index], Index, Weight, BlendType, BlendingOrder, InitialValueStore); } } } inline void MultiChannelFromData(int32 In, TMultiChannelValue& Out) { Out = { In }; } inline void MultiChannelFromData(float In, TMultiChannelValue& Out) { Out = { In }; } inline void MultiChannelFromData(FVector2D In, TMultiChannelValue& Out) { Out = { In.X, In.Y }; } inline void MultiChannelFromData(FVector In, TMultiChannelValue& Out) { Out = { In.X, In.Y, In.Z }; } inline void MultiChannelFromData(const FVector4& In, TMultiChannelValue& Out) { Out = { In.X, In.Y, In.Z, In.W }; } inline void MultiChannelFromData(const FTransform& In, TMultiChannelValue& Out) { FVector Translation = In.GetTranslation(); FVector Rotation = In.GetRotation().Rotator().Euler(); FVector Scale = In.GetScale3D(); Out = { Translation.X, Translation.Y, Translation.Z, Rotation.X, Rotation.Y, Rotation.Z, Scale.X, Scale.Y, Scale.Z }; } inline void MultiChannelFromData(const FEulerTransform& In, TMultiChannelValue& Out) { FVector Translation = In.Location; FVector Scale = In.Scale; FRotator3d Rotation = FRotator3d(In.Rotation); Out = { Translation.X, Translation.Y, Translation.Z, Rotation.Roll, Rotation.Pitch, Rotation.Yaw, Scale.X, Scale.Y, Scale.Z }; } inline void MultiChannelFromData(const FLinearColor& In, TMultiChannelValue& Out) { Out = { In.R, In.G, In.B, In.A }; } inline void ResolveChannelsToData(const TMultiChannelValue& In, int32& Out) { Out = In[0]; } inline void ResolveChannelsToData(const TMultiChannelValue& In, float& Out) { Out = In[0]; } inline void ResolveChannelsToData(const TMultiChannelValue& In, FVector2D& Out) { Out = FVector2D(In[0], In[1]); } inline void ResolveChannelsToData(const TMultiChannelValue& In, FVector& Out) { Out = FVector(In[0], In[1], In[2]); } inline void ResolveChannelsToData(const TMultiChannelValue& In, FVector4& Out) { Out = FVector4(In[0], In[1], In[2], In[3]); } inline void ResolveChannelsToData(const TMultiChannelValue& In, FTransform& Out) { Out = FTransform( FRotator::MakeFromEuler(FVector(In[3], In[4], In[5])), FVector(In[0], In[1], In[2]), FVector(In[6], In[7], In[8]) ); } inline void ResolveChannelsToData(const TMultiChannelValue& In, FEulerTransform& Out) { Out = FEulerTransform( FVector(In[0], In[1], In[2]), FRotator::MakeFromEuler(FVector(In[3], In[4], In[5])), FVector(In[6], In[7], In[8]) ); } inline void ResolveChannelsToData(const TMultiChannelValue& In, FLinearColor& Out) { Out = FLinearColor(In[0], In[1], In[2], In[3]); } } // namespace MovieScene } // namespace UE /** Define runtime type identifiers for the built-in C++ data types */ template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); template<> MOVIESCENE_API FMovieSceneAnimTypeID GetBlendingDataType(); /** Define working data types for blending calculations */ template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits{ typedef UE::MovieScene::TMaskedBlendable WorkingDataType; }; template<> struct TBlendableTokenTraits { typedef UE::MovieScene::TMaskedBlendable WorkingDataType; };