844 lines
31 KiB
C++
844 lines
31 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Sections/MovieSceneSubSection.h"
|
|
#include "Tracks/MovieSceneSubTrack.h"
|
|
#include "MovieScene.h"
|
|
#include "MovieSceneClock.h"
|
|
#include "MovieSceneTrack.h"
|
|
#include "MovieSceneSequence.h"
|
|
#include "MovieSceneTimeHelpers.h"
|
|
#include "MovieSceneTracksComponentTypes.h"
|
|
#include "Channels/MovieSceneChannelProxy.h"
|
|
#include "EntitySystem/BuiltInComponentTypes.h"
|
|
#include "Evaluation/MovieSceneEvaluationTemplate.h"
|
|
#include "Misc/AxisDisplayInfo.h"
|
|
#include "Misc/FrameRate.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "MovieSceneTransformTypes.h"
|
|
#include "Evaluation/MovieSceneRootOverridePath.h"
|
|
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
|
|
#include "EntitySystem/MovieSceneInstanceRegistry.h"
|
|
#include "EntitySystem/IMovieSceneEntityProvider.h"
|
|
#include "Channels/MovieSceneChannelProxy.h"
|
|
#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h"
|
|
#include "Tracks/MovieSceneTimeWarpTrack.h"
|
|
#include "Sections/MovieSceneSectionTimingParameters.h"
|
|
#include "Variants/MovieSceneTimeWarpGetter.h"
|
|
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneSubSection)
|
|
|
|
float DeprecatedMagicNumber = TNumericLimits<float>::Lowest();
|
|
|
|
#if WITH_EDITOR
|
|
|
|
namespace
|
|
{
|
|
FIntVector4 ReverseSwizzleFunc(const FIntVector4& InSwizzle)
|
|
{
|
|
FIntVector4 ReverseSwizzle;
|
|
for (int32 i = 0; i < 4; ++i)
|
|
{
|
|
int32 Index = 0;
|
|
for (int32 j = 0; j < 4; ++j)
|
|
{
|
|
if (InSwizzle[j] == i)
|
|
{
|
|
Index = j;
|
|
break;
|
|
}
|
|
}
|
|
ReverseSwizzle[i] = Index;
|
|
}
|
|
|
|
return ReverseSwizzle;
|
|
};
|
|
} // empty namespace
|
|
|
|
struct FSubSectionEditorData
|
|
{
|
|
FText LocationGroup = NSLOCTEXT("MovieSceneSubSection", "Origin Override Location", "Origin Override Location");
|
|
FText RotationGroup = NSLOCTEXT("MovieSceneSubSection", "Origin Override Rotation", "Origin Override Rotation");
|
|
|
|
FSubSectionEditorData(EMovieSceneTransformChannel Mask, UMovieSceneSubSection* SubSection)
|
|
{
|
|
const EAxisList::Type XAxis = EAxisList::Forward;
|
|
const EAxisList::Type YAxis = EAxisList::Left;
|
|
const EAxisList::Type ZAxis = EAxisList::Up;
|
|
|
|
const FIntVector4 Swizzle = AxisDisplayInfo::GetTransformAxisSwizzle();
|
|
const FIntVector4 ReverseSwizzle = ReverseSwizzleFunc(Swizzle);
|
|
const int32 TranslationOrderOffset = 0;
|
|
const int32 RotationOrderOffset = TranslationOrderOffset + 3;
|
|
|
|
MetaData[0].SetIdentifiers("Override.Location.X", AxisDisplayInfo::GetAxisDisplayName(XAxis), LocationGroup);
|
|
MetaData[0].SubPropertyPath = TEXT("Location.X");
|
|
MetaData[0].SortOrder = TranslationOrderOffset + ReverseSwizzle[0];
|
|
MetaData[0].bEnabled = EnumHasAllFlags(Mask, EMovieSceneTransformChannel::TranslationX);
|
|
MetaData[0].Color = AxisDisplayInfo::GetAxisColor(XAxis);
|
|
MetaData[0].bCanCollapseToTrack = false;
|
|
|
|
MetaData[1].SetIdentifiers("Override.Location.Y", AxisDisplayInfo::GetAxisDisplayName(YAxis), LocationGroup);
|
|
MetaData[1].SubPropertyPath = TEXT("Location.Y");
|
|
MetaData[1].SortOrder = TranslationOrderOffset + ReverseSwizzle[1];
|
|
MetaData[1].bEnabled = EnumHasAllFlags(Mask, EMovieSceneTransformChannel::TranslationY);
|
|
MetaData[1].Color = AxisDisplayInfo::GetAxisColor(YAxis);
|
|
MetaData[1].bCanCollapseToTrack = false;
|
|
MetaData[1].bInvertValue = AxisDisplayInfo::GetAxisDisplayCoordinateSystem() == EAxisList::LeftUpForward;
|
|
|
|
MetaData[2].SetIdentifiers("Override.Location.Z", AxisDisplayInfo::GetAxisDisplayName(ZAxis), LocationGroup);
|
|
MetaData[2].SubPropertyPath = TEXT("Location.Z");
|
|
MetaData[2].SortOrder = TranslationOrderOffset + ReverseSwizzle[2];
|
|
MetaData[2].bEnabled = EnumHasAllFlags(Mask, EMovieSceneTransformChannel::TranslationZ);
|
|
MetaData[2].Color = AxisDisplayInfo::GetAxisColor(ZAxis);
|
|
MetaData[2].bCanCollapseToTrack = false;
|
|
|
|
MetaData[3].SetIdentifiers("Override.Rotation.X", NSLOCTEXT("MovieSceneSubSection", "RotationX", "Roll"), RotationGroup);
|
|
MetaData[3].SubPropertyPath = TEXT("Rotation.X");
|
|
MetaData[3].SortOrder = RotationOrderOffset + 0;
|
|
MetaData[3].bEnabled = EnumHasAllFlags(Mask, EMovieSceneTransformChannel::RotationX);
|
|
MetaData[3].Color = AxisDisplayInfo::GetAxisColor(EAxisList::X);
|
|
MetaData[3].bCanCollapseToTrack = false;
|
|
|
|
MetaData[4].SetIdentifiers("Override.Rotation.Y", NSLOCTEXT("MovieSceneSubSection", "RotationY", "Pitch"), RotationGroup);
|
|
MetaData[4].SubPropertyPath = TEXT("Rotation.Y");
|
|
MetaData[4].SortOrder = RotationOrderOffset + 1;
|
|
MetaData[4].bEnabled = EnumHasAllFlags(Mask, EMovieSceneTransformChannel::RotationY);
|
|
MetaData[4].Color = AxisDisplayInfo::GetAxisColor(EAxisList::Y);
|
|
MetaData[4].bCanCollapseToTrack = false;
|
|
|
|
MetaData[5].SetIdentifiers("Override.Rotation.Z", NSLOCTEXT("MovieSceneSubSection", "RotationZ", "Yaw"), RotationGroup);
|
|
MetaData[5].SubPropertyPath = TEXT("Rotation.Z");
|
|
MetaData[5].SortOrder = RotationOrderOffset + 2;
|
|
MetaData[5].bEnabled = EnumHasAllFlags(Mask, EMovieSceneTransformChannel::RotationZ);
|
|
MetaData[5].Color = AxisDisplayInfo::GetAxisColor(EAxisList::Z);
|
|
MetaData[5].bCanCollapseToTrack = false;
|
|
|
|
ExternalValues[0].OnGetExternalValue = [SubSection](UObject& InObject, FTrackInstancePropertyBindings* Bindings) { return GetValue(SubSection, 0); };
|
|
ExternalValues[1].OnGetExternalValue = [SubSection](UObject& InObject, FTrackInstancePropertyBindings* Bindings) { return GetValue(SubSection, 1); };
|
|
ExternalValues[2].OnGetExternalValue = [SubSection](UObject& InObject, FTrackInstancePropertyBindings* Bindings) { return GetValue(SubSection, 2); };
|
|
ExternalValues[3].OnGetExternalValue = [SubSection](UObject& InObject, FTrackInstancePropertyBindings* Bindings) { return GetValue(SubSection, 3); };
|
|
ExternalValues[4].OnGetExternalValue = [SubSection](UObject& InObject, FTrackInstancePropertyBindings* Bindings) { return GetValue(SubSection, 4); };
|
|
ExternalValues[5].OnGetExternalValue = [SubSection](UObject& InObject, FTrackInstancePropertyBindings* Bindings) { return GetValue(SubSection, 5); };
|
|
|
|
}
|
|
|
|
static TOptional<double> GetValue(UMovieSceneSubSection* SubSection, int32 ChannelIndex)
|
|
{
|
|
if(!SubSection)
|
|
{
|
|
return TOptional<double>();
|
|
}
|
|
|
|
switch (ChannelIndex)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
return SubSection->GetKeyPreviewPosition().IsSet() ? SubSection->GetKeyPreviewPosition().GetValue()[ChannelIndex] : TOptional<double>();
|
|
case 3:
|
|
return SubSection->GetKeyPreviewRotation().IsSet() ? SubSection->GetKeyPreviewRotation().GetValue().Roll: TOptional<double>();
|
|
case 4:
|
|
return SubSection->GetKeyPreviewRotation().IsSet() ? SubSection->GetKeyPreviewRotation().GetValue().Pitch: TOptional<double>();
|
|
case 5:
|
|
return SubSection->GetKeyPreviewRotation().IsSet() ? SubSection->GetKeyPreviewRotation().GetValue().Yaw: TOptional<double>();
|
|
default:
|
|
return TOptional<double>();
|
|
}
|
|
}
|
|
|
|
FMovieSceneChannelMetaData MetaData[6];
|
|
TMovieSceneExternalValue<double> ExternalValues[6];
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
/* UMovieSceneSubSection structors
|
|
*****************************************************************************/
|
|
|
|
UMovieSceneSubSection::UMovieSceneSubSection(const FObjectInitializer& ObjInitializer)
|
|
: Super(ObjInitializer)
|
|
, StartOffset_DEPRECATED(DeprecatedMagicNumber)
|
|
, TimeScale_DEPRECATED(DeprecatedMagicNumber)
|
|
, PrerollTime_DEPRECATED(DeprecatedMagicNumber)
|
|
{
|
|
NetworkMask = (uint8)(EMovieSceneServerClientMask::Server | EMovieSceneServerClientMask::Client);
|
|
|
|
SetBlendType(EMovieSceneBlendType::Absolute);
|
|
|
|
OriginOverrideMask = EMovieSceneTransformChannel::None;
|
|
|
|
#if WITH_EDITOR
|
|
ResetKeyPreviewRotationAndLocation();
|
|
#endif
|
|
}
|
|
|
|
void UMovieSceneSubSection::DeleteChannels(TArrayView<const FName> ChannelNames)
|
|
{
|
|
bool bDeletedAny = false;
|
|
|
|
if (Parameters.TimeScale.GetType() == EMovieSceneTimeWarpType::Custom && TryModify())
|
|
{
|
|
if (UMovieSceneTimeWarpGetter* Getter = Parameters.TimeScale.AsCustom())
|
|
{
|
|
for (FName ChannelName : ChannelNames)
|
|
{
|
|
bDeletedAny |= Getter->DeleteChannel(Parameters.TimeScale, ChannelName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bDeletedAny)
|
|
{
|
|
ChannelProxy = nullptr;
|
|
}
|
|
}
|
|
|
|
EMovieSceneChannelProxyType UMovieSceneSubSection::CacheChannelProxy()
|
|
{
|
|
FMovieSceneChannelProxyData Channels;
|
|
|
|
if (Parameters.TimeScale.GetType() == EMovieSceneTimeWarpType::Custom)
|
|
{
|
|
if (UMovieSceneTimeWarpGetter* Curve = Parameters.TimeScale.AsCustom())
|
|
{
|
|
Curve->PopulateChannelProxy(Channels, UMovieSceneTimeWarpGetter::EAllowTopLevelChannels::No);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
FSubSectionEditorData EditorData(OriginOverrideMask.GetChannels(), this);
|
|
|
|
Channels.Add(Translation[0], EditorData.MetaData[0], EditorData.ExternalValues[0]);
|
|
Channels.Add(Translation[1], EditorData.MetaData[1], EditorData.ExternalValues[1]);
|
|
Channels.Add(Translation[2], EditorData.MetaData[2], EditorData.ExternalValues[2]);
|
|
Channels.Add(Rotation[0], EditorData.MetaData[3], EditorData.ExternalValues[3]);
|
|
Channels.Add(Rotation[1], EditorData.MetaData[4], EditorData.ExternalValues[4]);
|
|
Channels.Add(Rotation[2], EditorData.MetaData[5], EditorData.ExternalValues[5]);
|
|
|
|
#else
|
|
|
|
Channels.Add(Translation[0]);
|
|
Channels.Add(Translation[1]);
|
|
Channels.Add(Translation[2]);
|
|
Channels.Add(Rotation[0]);
|
|
Channels.Add(Rotation[1]);
|
|
Channels.Add(Rotation[2]);
|
|
|
|
#endif
|
|
|
|
|
|
ChannelProxy = MakeShared<FMovieSceneChannelProxy>(MoveTemp(Channels));
|
|
return EMovieSceneChannelProxyType::Dynamic;
|
|
}
|
|
|
|
|
|
FMovieSceneSequenceTransform UMovieSceneSubSection::OuterToInnerTransform_NoInnerTimeWarp() const
|
|
{
|
|
UMovieSceneSequence* SequencePtr = GetSequence();
|
|
if (!SequencePtr)
|
|
{
|
|
return FMovieSceneSequenceTransform();
|
|
}
|
|
|
|
UMovieScene* MovieScenePtr = SequencePtr->GetMovieScene();
|
|
|
|
TRange<FFrameNumber> SubRange = GetRange();
|
|
if (!MovieScenePtr || SubRange.GetLowerBound().IsOpen())
|
|
{
|
|
return FMovieSceneSequenceTransform();
|
|
}
|
|
|
|
const FFrameRate InnerFrameRate = MovieScenePtr->GetTickResolution();
|
|
const FFrameRate OuterFrameRate = GetTypedOuter<UMovieScene>()->GetTickResolution();
|
|
|
|
const TRange<FFrameNumber> MovieScenePlaybackRange = GetValidatedInnerPlaybackRange(Parameters, *MovieScenePtr);
|
|
|
|
FMovieSceneSectionTimingParametersFrames TimingParams = {
|
|
Parameters.TimeScale.ShallowCopy(),
|
|
Parameters.StartFrameOffset,
|
|
Parameters.EndFrameOffset,
|
|
Parameters.FirstLoopStartFrameOffset,
|
|
Parameters.bCanLoop,
|
|
false, // do not clamp sub-sections by default
|
|
false
|
|
};
|
|
|
|
// determine if we need to generate a musical transform or a standard transform...
|
|
FMovieSceneSequenceTransform ClockResult;
|
|
if (MovieScenePtr->GetCustomClock() && MovieScenePtr->GetCustomClock()->MakeSubSequenceTransform(TimingParams, this, ClockResult))
|
|
{
|
|
return ClockResult;
|
|
}
|
|
|
|
return TimingParams.MakeTransform(OuterFrameRate, SubRange, InnerFrameRate, MovieScenePtr->GetPlaybackRange());
|
|
}
|
|
|
|
FMovieSceneSequenceTransform UMovieSceneSubSection::OuterToInnerTransform() const
|
|
{
|
|
FMovieSceneSequenceTransform OuterToInner = OuterToInnerTransform_NoInnerTimeWarp();
|
|
AppendInnerTimeWarpTransform(OuterToInner);
|
|
return OuterToInner;
|
|
}
|
|
|
|
void UMovieSceneSubSection::AppendInnerTimeWarpTransform(FMovieSceneSequenceTransform& OutTransform) const
|
|
{
|
|
UMovieSceneSequence* Sequence = GetSequence();
|
|
UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;
|
|
|
|
if (!MovieScene)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Look for any time warp tracks inside the sub sequence
|
|
for (UMovieSceneTrack* Track : MovieScene->GetTracks())
|
|
{
|
|
UMovieSceneTimeWarpTrack* TimeWarpTrack = Cast<UMovieSceneTimeWarpTrack>(Track);
|
|
if (TimeWarpTrack && !TimeWarpTrack->IsEvalDisabled())
|
|
{
|
|
FMovieSceneNestedSequenceTransform TimeWarpTransform = TimeWarpTrack->GenerateTransform();
|
|
|
|
if (!TimeWarpTransform.IsIdentity())
|
|
{
|
|
if (TimeWarpTransform.IsLinear() && OutTransform.IsLinear())
|
|
{
|
|
OutTransform = FMovieSceneSequenceTransform(OutTransform.AsLinear() * TimeWarpTransform.AsLinear());
|
|
}
|
|
else
|
|
{
|
|
OutTransform.NestedTransforms.Add(TimeWarpTransform);
|
|
}
|
|
}
|
|
|
|
// Only 1 timewarp track supported
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UMovieSceneSubSection::GetValidatedInnerPlaybackRange(TRange<FFrameNumber>& OutInnerPlaybackRange) const
|
|
{
|
|
UMovieSceneSequence* SequencePtr = GetSequence();
|
|
if (SequencePtr != nullptr)
|
|
{
|
|
UMovieScene* MovieScenePtr = SequencePtr->GetMovieScene();
|
|
if (MovieScenePtr != nullptr)
|
|
{
|
|
OutInnerPlaybackRange = GetValidatedInnerPlaybackRange(Parameters, *MovieScenePtr);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FMovieSceneSubSectionOriginOverrideMask UMovieSceneSubSection::GetMask() const
|
|
{
|
|
return OriginOverrideMask;
|
|
}
|
|
|
|
void UMovieSceneSubSection::SetMask(EMovieSceneTransformChannel NewMask)
|
|
{
|
|
OriginOverrideMask = NewMask;
|
|
|
|
ChannelProxy = nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void UMovieSceneSubSection::SetKeyPreviewPosition(TOptional<FVector> InPosition)
|
|
{
|
|
if(InPosition.IsSet())
|
|
{
|
|
KeyPreviewPosition = InPosition.GetValue();
|
|
}
|
|
|
|
}
|
|
|
|
void UMovieSceneSubSection::SetKeyPreviewRotation(TOptional<FRotator> InRotation)
|
|
{
|
|
if(InRotation.IsSet())
|
|
{
|
|
KeyPreviewRotation = InRotation.GetValue();
|
|
}
|
|
}
|
|
|
|
void UMovieSceneSubSection::ResetKeyPreviewRotationAndLocation()
|
|
{
|
|
KeyPreviewPosition.Reset();
|
|
KeyPreviewRotation.Reset();
|
|
}
|
|
|
|
#endif
|
|
|
|
TRange<FFrameNumber> UMovieSceneSubSection::GetValidatedInnerPlaybackRange(const FMovieSceneSectionParameters& SubSectionParameters, const UMovieScene& InnerMovieScene)
|
|
{
|
|
const TRange<FFrameNumber> InnerPlaybackRange = InnerMovieScene.GetPlaybackRange();
|
|
TRangeBound<FFrameNumber> ValidatedLowerBound = InnerPlaybackRange.GetLowerBound();
|
|
TRangeBound<FFrameNumber> ValidatedUpperBound = InnerPlaybackRange.GetUpperBound();
|
|
if (ValidatedLowerBound.IsClosed() && ValidatedUpperBound.IsClosed())
|
|
{
|
|
const FFrameRate TickResolution = InnerMovieScene.GetTickResolution();
|
|
const FFrameRate DisplayRate = InnerMovieScene.GetDisplayRate();
|
|
const FFrameNumber OneFrameInTicks = FFrameRate::TransformTime(FFrameTime(1), DisplayRate, TickResolution).FloorToFrame();
|
|
|
|
ValidatedLowerBound.SetValue(ValidatedLowerBound.GetValue() + SubSectionParameters.StartFrameOffset);
|
|
ValidatedUpperBound.SetValue(FMath::Max(ValidatedUpperBound.GetValue() - SubSectionParameters.EndFrameOffset, ValidatedLowerBound.GetValue() + OneFrameInTicks));
|
|
return TRange<FFrameNumber>(ValidatedLowerBound, ValidatedUpperBound);
|
|
}
|
|
return InnerPlaybackRange;
|
|
}
|
|
|
|
FString UMovieSceneSubSection::GetPathNameInMovieScene() const
|
|
{
|
|
UMovieScene* OuterMovieScene = GetTypedOuter<UMovieScene>();
|
|
check(OuterMovieScene);
|
|
return GetPathName(OuterMovieScene);
|
|
}
|
|
|
|
FMovieSceneSequenceID UMovieSceneSubSection::GetSequenceID() const
|
|
{
|
|
FString FullPath = GetPathNameInMovieScene();
|
|
if (SubSequence)
|
|
{
|
|
FullPath += TEXT(" / ");
|
|
FullPath += SubSequence->GetPathName();
|
|
}
|
|
|
|
return FMovieSceneSequenceID(FCrc::Strihash_DEPRECATED(*FullPath));
|
|
}
|
|
|
|
void UMovieSceneSubSection::PostLoad()
|
|
{
|
|
FFrameRate LegacyFrameRate = GetLegacyConversionFrameRate();
|
|
|
|
TOptional<double> StartOffsetToUpgrade;
|
|
if (StartOffset_DEPRECATED != DeprecatedMagicNumber)
|
|
{
|
|
StartOffsetToUpgrade = StartOffset_DEPRECATED;
|
|
|
|
StartOffset_DEPRECATED = DeprecatedMagicNumber;
|
|
}
|
|
else if (Parameters.StartOffset_DEPRECATED != 0.f)
|
|
{
|
|
StartOffsetToUpgrade = Parameters.StartOffset_DEPRECATED;
|
|
}
|
|
|
|
if (StartOffsetToUpgrade.IsSet())
|
|
{
|
|
FFrameNumber StartFrame = UpgradeLegacyMovieSceneTime(this, LegacyFrameRate, StartOffsetToUpgrade.GetValue());
|
|
Parameters.StartFrameOffset = StartFrame;
|
|
}
|
|
|
|
if (TimeScale_DEPRECATED != DeprecatedMagicNumber)
|
|
{
|
|
Parameters.TimeScale = TimeScale_DEPRECATED;
|
|
|
|
TimeScale_DEPRECATED = DeprecatedMagicNumber;
|
|
}
|
|
|
|
if (PrerollTime_DEPRECATED != DeprecatedMagicNumber)
|
|
{
|
|
Parameters.PrerollTime_DEPRECATED = PrerollTime_DEPRECATED;
|
|
|
|
PrerollTime_DEPRECATED = DeprecatedMagicNumber;
|
|
}
|
|
|
|
// Pre and post roll is now supported generically
|
|
if (Parameters.PrerollTime_DEPRECATED > 0.f)
|
|
{
|
|
FFrameNumber ClampedPreRollFrames = UpgradeLegacyMovieSceneTime(this, LegacyFrameRate, Parameters.PrerollTime_DEPRECATED);
|
|
SetPreRollFrames(ClampedPreRollFrames.Value);
|
|
}
|
|
|
|
if (Parameters.PostrollTime_DEPRECATED > 0.f)
|
|
{
|
|
FFrameNumber ClampedPostRollFrames = UpgradeLegacyMovieSceneTime(this, LegacyFrameRate, Parameters.PostrollTime_DEPRECATED);
|
|
SetPreRollFrames(ClampedPostRollFrames.Value);
|
|
}
|
|
|
|
Super::PostLoad();
|
|
}
|
|
|
|
bool UMovieSceneSubSection::PopulateEvaluationFieldImpl(const TRange<FFrameNumber>& EffectiveRange, const FMovieSceneEvaluationFieldEntityMetaData& InMetaData, FMovieSceneEntityComponentFieldBuilder* OutFieldBuilder)
|
|
{
|
|
if (SubSequence)
|
|
{
|
|
const int32 EntityIndex = OutFieldBuilder->FindOrAddEntity(this, 0);
|
|
const int32 MetaDataIndex = OutFieldBuilder->AddMetaData(InMetaData);
|
|
OutFieldBuilder->AddPersistentEntity(EffectiveRange, EntityIndex, MetaDataIndex);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UMovieSceneSubSection::ImportEntityImpl(UMovieSceneEntitySystemLinker* EntityLinker, const FEntityImportParams& Params, FImportedEntity* OutImportedEntity)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
OutImportedEntity->AddBuilder(
|
|
FEntityBuilder().AddTag(FBuiltInComponentTypes::Get()->Tags.Root)
|
|
);
|
|
|
|
BuildDefaultSubSectionComponents(EntityLinker, Params, OutImportedEntity);
|
|
}
|
|
|
|
void UMovieSceneSubSection::SetSequence(UMovieSceneSequence* Sequence)
|
|
{
|
|
if (!TryModify())
|
|
{
|
|
return;
|
|
}
|
|
|
|
SubSequence = Sequence;
|
|
|
|
#if WITH_EDITOR
|
|
OnSequenceChangedDelegate.ExecuteIfBound(SubSequence);
|
|
#endif
|
|
}
|
|
|
|
UMovieSceneSequence* UMovieSceneSubSection::GetSequence() const
|
|
{
|
|
return SubSequence;
|
|
}
|
|
|
|
FMovieSceneTimeWarpVariant* UMovieSceneSubSection::GetTimeWarp()
|
|
{
|
|
return &Parameters.TimeScale;
|
|
}
|
|
|
|
UObject* UMovieSceneSubSection::GetSourceObject() const
|
|
{
|
|
return GetSequence();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void UMovieSceneSubSection::PreEditChange(FProperty* PropertyAboutToChange)
|
|
{
|
|
if (PropertyAboutToChange && PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(UMovieSceneSubSection, SubSequence))
|
|
{
|
|
// Store the current subsequence in case it needs to be restored in PostEditChangeProperty because the new value would introduce a circular dependency
|
|
PreviousSubSequence = SubSequence;
|
|
}
|
|
|
|
return Super::PreEditChange(PropertyAboutToChange);
|
|
}
|
|
|
|
void UMovieSceneSubSection::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UMovieSceneSubSection, SubSequence))
|
|
{
|
|
// Check whether the subsequence that was just set has tracks that contain the sequence that this subsection is in.
|
|
UMovieScene* SubSequenceMovieScene = SubSequence ? SubSequence->GetMovieScene() : nullptr;
|
|
|
|
UMovieSceneSubTrack* TrackOuter = Cast<UMovieSceneSubTrack>(GetOuter());
|
|
|
|
if (SubSequenceMovieScene && TrackOuter)
|
|
{
|
|
if (UMovieSceneSequence* CurrentSequence = TrackOuter->GetTypedOuter<UMovieSceneSequence>())
|
|
{
|
|
TArray<UMovieSceneSubTrack*> SubTracks;
|
|
|
|
for (UMovieSceneTrack* Track : SubSequenceMovieScene->GetTracks())
|
|
{
|
|
if (UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
|
|
{
|
|
SubTracks.Add(SubTrack);
|
|
}
|
|
}
|
|
|
|
for (const FMovieSceneBinding& Binding : SubSequenceMovieScene->GetBindings())
|
|
{
|
|
for (UMovieSceneTrack* Track : SubSequenceMovieScene->FindTracks(UMovieSceneSubTrack::StaticClass(), Binding.GetObjectGuid()))
|
|
{
|
|
if (UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
|
|
{
|
|
SubTracks.Add(SubTrack);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UMovieSceneSubTrack* SubTrack : SubTracks)
|
|
{
|
|
if ( SubTrack->ContainsSequence(*CurrentSequence, true))
|
|
{
|
|
UE_LOG(LogMovieScene, Error, TEXT("Invalid level sequence %s. It is already contained by: %s."), *SubSequence->GetDisplayName().ToString(), *CurrentSequence->GetDisplayName().ToString());
|
|
|
|
// Restore to the previous sub sequence because there was a circular dependency
|
|
SubSequence = PreviousSubSequence;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PreviousSubSequence = nullptr;
|
|
}
|
|
|
|
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FMovieSceneSectionParameters, TimeScale))
|
|
{
|
|
ChannelProxy = nullptr;
|
|
}
|
|
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
// recreate runtime instance when sequence is changed
|
|
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UMovieSceneSubSection, SubSequence))
|
|
{
|
|
OnSequenceChangedDelegate.ExecuteIfBound(SubSequence);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TOptional<TRange<FFrameNumber> > UMovieSceneSubSection::GetAutoSizeRange() const
|
|
{
|
|
UMovieScene* MovieScene = SubSequence ? SubSequence->GetMovieScene() : nullptr;
|
|
if (MovieScene)
|
|
{
|
|
// We probably want to just auto-size the section to the sub-sequence's scaled playback range... if this section
|
|
// is looping, however, it's hard to know what we want to do. Let's just size it to one loop.
|
|
const FMovieSceneInverseSequenceTransform InnerToOuter = OuterToInnerTransform().Inverse();
|
|
const TRange<FFrameNumber> InnerPlaybackRange = UMovieSceneSubSection::GetValidatedInnerPlaybackRange(Parameters, *MovieScene);
|
|
|
|
const FFrameTime IncAutoStartTime = InnerToOuter.TryTransformTime(UE::MovieScene::DiscreteInclusiveLower(InnerPlaybackRange)).Get(InnerPlaybackRange.GetLowerBoundValue());
|
|
const FFrameTime ExcAutoEndTime = InnerToOuter.TryTransformTime(UE::MovieScene::DiscreteExclusiveUpper(InnerPlaybackRange)).Get(InnerPlaybackRange.GetUpperBoundValue());
|
|
|
|
return TRange<FFrameNumber>(GetInclusiveStartFrame(), GetInclusiveStartFrame() + (ExcAutoEndTime.RoundToFrame() - IncAutoStartTime.RoundToFrame()));
|
|
}
|
|
|
|
return Super::GetAutoSizeRange();
|
|
}
|
|
|
|
void UMovieSceneSubSection::TrimSection( FQualifiedFrameTime TrimTime, bool bTrimLeft, bool bDeleteKeys)
|
|
{
|
|
TRange<FFrameNumber> InitialRange = GetRange();
|
|
if ( !InitialRange.Contains( TrimTime.Time.GetFrame() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetFlags(RF_Transactional);
|
|
if (!TryModify())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If trimming off the left, set the offset of the shot
|
|
if (bTrimLeft && InitialRange.GetLowerBound().IsClosed() && GetSequence())
|
|
{
|
|
// Sections need their offsets calculated in their local resolution. Different sequences can have different tick resolutions
|
|
// so we need to transform from the parent resolution to the local one before splitting them.
|
|
UMovieScene* LocalMovieScene = GetSequence()->GetMovieScene();
|
|
const FFrameRate LocalTickResolution = LocalMovieScene->GetTickResolution();
|
|
const FFrameTime LocalTickResolutionTrimTime = FFrameRate::TransformTime(TrimTime.Time, TrimTime.Rate, LocalTickResolution);
|
|
|
|
// The new first loop start offset is where the trim time fell inside the sub-sequence (this time is already
|
|
// normalized in the case of looping sub-sequences).
|
|
const FMovieSceneSequenceTransform OuterToInner(OuterToInnerTransform());
|
|
const FFrameTime LocalTrimTime = OuterToInner.TransformTime(LocalTickResolutionTrimTime);
|
|
// LocalTrimTime is now in the inner sequence timespace, but StartFrameOffset is an offset from the inner sequence's own
|
|
// playback start time, so we need to account for that.
|
|
TRange<FFrameNumber> LocalPlaybackRange = LocalMovieScene->GetPlaybackRange();
|
|
const FFrameNumber LocalPlaybackStart = LocalPlaybackRange.HasLowerBound() ? LocalPlaybackRange.GetLowerBoundValue() : FFrameNumber(0);
|
|
FFrameNumber NewStartOffset = LocalTrimTime.FrameNumber - LocalPlaybackStart;
|
|
|
|
// Make sure we don't have negative offsets (this shouldn't happen, though).
|
|
NewStartOffset = FMath::Max(FFrameNumber(0), NewStartOffset);
|
|
|
|
const bool bCanLoop = Parameters.bCanLoop;
|
|
if (!bCanLoop)
|
|
{
|
|
Parameters.StartFrameOffset = NewStartOffset;
|
|
}
|
|
else
|
|
{
|
|
Parameters.FirstLoopStartFrameOffset = NewStartOffset;
|
|
}
|
|
}
|
|
|
|
// Actually trim the section range!
|
|
UMovieSceneSection::TrimSection(TrimTime, bTrimLeft, bDeleteKeys);
|
|
}
|
|
|
|
void UMovieSceneSubSection::GetSnapTimes(TArray<FFrameNumber>& OutSnapTimes, bool bGetSectionBorders) const
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
Super::GetSnapTimes(OutSnapTimes, bGetSectionBorders);
|
|
|
|
const FFrameNumber StartFrame = GetInclusiveStartFrame();
|
|
const FFrameNumber EndFrame = GetExclusiveEndFrame();
|
|
|
|
UMovieSceneSequence* Sequence = GetSequence();
|
|
UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;
|
|
if (!MovieScene)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto VisitBoundary = [&OutSnapTimes](FFrameTime InTime)
|
|
{
|
|
OutSnapTimes.Add(InTime.RoundToFrame());
|
|
return true;
|
|
};
|
|
|
|
FMovieSceneSequenceTransform OuterToInner = OuterToInnerTransform();
|
|
|
|
if (!OuterToInner.ExtractBoundariesWithinRange(StartFrame, EndFrame, VisitBoundary))
|
|
{
|
|
FMovieSceneInverseSequenceTransform InnerToOuterTransform = OuterToInner.Inverse();
|
|
|
|
TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
|
|
|
|
TOptional<FFrameTime> SequenceStart = InnerToOuterTransform.TryTransformTime(PlaybackRange.GetLowerBoundValue());
|
|
TOptional<FFrameTime> SequenceEnd = InnerToOuterTransform.TryTransformTime(PlaybackRange.GetUpperBoundValue());
|
|
|
|
if (SequenceStart && SequenceStart.GetValue() >= StartFrame && SequenceStart.GetValue() < EndFrame)
|
|
{
|
|
VisitBoundary(SequenceStart.GetValue());
|
|
}
|
|
|
|
if (SequenceEnd && SequenceEnd.GetValue() >= StartFrame && SequenceEnd.GetValue() < EndFrame)
|
|
{
|
|
VisitBoundary(SequenceEnd.GetValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMovieSceneSubSection::MigrateFrameTimes(FFrameRate SourceRate, FFrameRate DestinationRate)
|
|
{
|
|
MigrateFrameTimes(UE::MovieScene::FFrameRateRetiming(SourceRate, DestinationRate));
|
|
}
|
|
|
|
void UMovieSceneSubSection::MigrateFrameTimes(const UE::MovieScene::IRetimingInterface& Retimer)
|
|
{
|
|
if (Parameters.StartFrameOffset.Value > 0)
|
|
{
|
|
Parameters.StartFrameOffset = Retimer.RemapTime(Parameters.StartFrameOffset);
|
|
}
|
|
if (Parameters.EndFrameOffset.Value > 0)
|
|
{
|
|
Parameters.EndFrameOffset = Retimer.RemapTime(Parameters.EndFrameOffset);
|
|
}
|
|
if (Parameters.FirstLoopStartFrameOffset.Value > 0)
|
|
{
|
|
Parameters.FirstLoopStartFrameOffset = Retimer.RemapTime(Parameters.FirstLoopStartFrameOffset);
|
|
}
|
|
}
|
|
|
|
FMovieSceneSubSequenceData UMovieSceneSubSection::GenerateSubSequenceData(const FSubSequenceInstanceDataParams& Params) const
|
|
{
|
|
return FMovieSceneSubSequenceData(*this);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
bool UMovieSceneSubSection::IsTransformOriginEditable() const
|
|
{
|
|
const EMovieSceneTransformChannel SectionTransformChannels = OriginOverrideMask.GetChannels();
|
|
|
|
const bool bChannelsActive = EnumHasAnyFlags(SectionTransformChannels, EMovieSceneTransformChannel::Translation) || EnumHasAnyFlags(SectionTransformChannels, EMovieSceneTransformChannel::Rotation);
|
|
|
|
return IsActive() && !IsLocked() && bChannelsActive;
|
|
}
|
|
#endif
|
|
|
|
FFrameNumber UMovieSceneSubSection::MapTimeToSectionFrame(FFrameTime InPosition) const
|
|
{
|
|
FFrameNumber LocalPosition = ((InPosition - Parameters.StartFrameOffset) * OuterToInnerTransform()).GetFrame();
|
|
return LocalPosition;
|
|
}
|
|
|
|
bool UMovieSceneSubSection::HasAnyChannelData() const
|
|
{
|
|
bool bHasAnyData = false;
|
|
|
|
bHasAnyData |= Translation[0].HasAnyData();
|
|
bHasAnyData |= Translation[1].HasAnyData();
|
|
bHasAnyData |= Translation[2].HasAnyData();
|
|
bHasAnyData |= Rotation[0].HasAnyData();
|
|
bHasAnyData |= Rotation[1].HasAnyData();
|
|
bHasAnyData |= Rotation[2].HasAnyData();
|
|
|
|
return bHasAnyData;
|
|
}
|
|
|
|
void UMovieSceneSubSection::BuildDefaultSubSectionComponents(UMovieSceneEntitySystemLinker* EntityLinker, const UE::MovieScene::FEntityImportParams& Params, UE::MovieScene::FImportedEntity* OutImportedEntity) const
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
FBuiltInComponentTypes* Components = FBuiltInComponentTypes::Get();
|
|
|
|
const bool bHasEasing = Easing.GetEaseInDuration() > 0 || Easing.GetEaseOutDuration() > 0;
|
|
|
|
|
|
// When interrogating, the sequence ID is known, and set directly, so default to using this value.
|
|
FMovieSceneSequenceID ResolvedSequenceID = MovieSceneSequenceID::Invalid;
|
|
|
|
const TEntitySystemLinkerExtensionID<IInterrogationExtension> ID = IInterrogationExtension::GetExtensionID();
|
|
|
|
const IInterrogationExtension* Interrogation = EntityLinker->FindExtension<IInterrogationExtension>(ID);
|
|
|
|
if (Interrogation)
|
|
{
|
|
const FMovieSceneSequenceHierarchy* Hierarchy = Interrogation->GetHierarchy();
|
|
const FSubSequencePath PathToRoot = FSubSequencePath(Params.Sequence.SequenceID, Hierarchy);
|
|
ResolvedSequenceID = PathToRoot.ResolveChildSequenceID(this->GetSequenceID());
|
|
}
|
|
// During normal evaluation (i.e. not interrogating) the instance registry will have its instance populated, and the sequence ID can be resolved this way.
|
|
else if (EntityLinker->GetInstanceRegistry()->IsHandleValid(Params.Sequence.InstanceHandle))
|
|
{
|
|
const FSubSequencePath PathToRoot = EntityLinker->GetInstanceRegistry()->GetInstance(Params.Sequence.InstanceHandle).GetSubSequencePath();
|
|
ResolvedSequenceID = PathToRoot.ResolveChildSequenceID(this->GetSequenceID());
|
|
}
|
|
|
|
EMovieSceneTransformChannel Channels = OriginOverrideMask.GetChannels();
|
|
|
|
const bool ActiveChannelsMask[] = {
|
|
EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::TranslationX) && Translation[0].HasAnyData(),
|
|
EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::TranslationY) && Translation[1].HasAnyData(),
|
|
EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::TranslationZ) && Translation[2].HasAnyData(),
|
|
EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::RotationX) && Rotation[0].HasAnyData(),
|
|
EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::RotationY) && Rotation[1].HasAnyData(),
|
|
EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::RotationZ) && Rotation[2].HasAnyData(),
|
|
};
|
|
|
|
bool bKeyPreviewPositionIsSet = false;
|
|
bool bKeyPreviewRotationIsSet = false;
|
|
|
|
#if WITH_EDITOR
|
|
bKeyPreviewPositionIsSet = KeyPreviewPosition.IsSet();
|
|
bKeyPreviewRotationIsSet = KeyPreviewRotation.IsSet();
|
|
#endif
|
|
|
|
OutImportedEntity->AddBuilder(
|
|
FEntityBuilder()
|
|
.Add(Components->SequenceID, ResolvedSequenceID)
|
|
.AddTag(Components->Tags.SubInstance)
|
|
.AddConditional(Components->HierarchicalEasingProvider, ResolvedSequenceID, bHasEasing)
|
|
.AddConditional(Components->DoubleChannel[0], &Translation[0], ActiveChannelsMask[0] && !bKeyPreviewPositionIsSet)
|
|
.AddConditional(Components->DoubleChannel[1], &Translation[1], ActiveChannelsMask[1] && !bKeyPreviewPositionIsSet)
|
|
.AddConditional(Components->DoubleChannel[2], &Translation[2], ActiveChannelsMask[2] && !bKeyPreviewPositionIsSet)
|
|
.AddConditional(Components->DoubleChannel[3], &Rotation[0], ActiveChannelsMask[3] && !bKeyPreviewRotationIsSet)
|
|
.AddConditional(Components->DoubleChannel[4], &Rotation[1], ActiveChannelsMask[4] && !bKeyPreviewRotationIsSet)
|
|
.AddConditional(Components->DoubleChannel[5], &Rotation[2], ActiveChannelsMask[5] && !bKeyPreviewRotationIsSet)
|
|
);
|
|
|
|
// Build Key preview entity data. Since the channel data is not written when we have preview data, this data will be used in the transform origin system.
|
|
#if WITH_EDITOR
|
|
OutImportedEntity->AddBuilder(
|
|
FEntityBuilder()
|
|
.AddConditional(Components->DoubleResult[0], KeyPreviewPosition.IsSet() ? KeyPreviewPosition.GetValue().X : 0.0f, EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::TranslationX) && KeyPreviewPosition.IsSet())
|
|
.AddConditional(Components->DoubleResult[1], KeyPreviewPosition.IsSet() ? KeyPreviewPosition.GetValue().Y : 0.0f, EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::TranslationY) && KeyPreviewPosition.IsSet())
|
|
.AddConditional(Components->DoubleResult[2], KeyPreviewPosition.IsSet() ? KeyPreviewPosition.GetValue().Z : 0.0f, EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::TranslationZ) && KeyPreviewPosition.IsSet())
|
|
.AddConditional(Components->DoubleResult[3], KeyPreviewPosition.IsSet() ? KeyPreviewRotation.GetValue().Roll : 0.0f, EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::RotationX) && KeyPreviewRotation.IsSet())
|
|
.AddConditional(Components->DoubleResult[4], KeyPreviewPosition.IsSet() ? KeyPreviewRotation.GetValue().Pitch : 0.0f, EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::RotationY) && KeyPreviewRotation.IsSet())
|
|
.AddConditional(Components->DoubleResult[5], KeyPreviewPosition.IsSet() ? KeyPreviewRotation.GetValue().Yaw : 0.0f, EnumHasAnyFlags(Channels, EMovieSceneTransformChannel::RotationX) && KeyPreviewRotation.IsSet())
|
|
);
|
|
#endif
|
|
|
|
}
|
|
|
|
|