Files
UnrealEngine/Engine/Source/Runtime/MovieSceneTracks/Private/Sections/MovieSceneSkeletalAnimationSection.cpp
2025-05-18 13:04:45 +08:00

963 lines
33 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Sections/MovieSceneSkeletalAnimationSection.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "Decorations/IMovieSceneChannelDecoration.h"
#include "Animation/AnimSequence.h"
#include "AnimSequencerInstanceProxy.h"
#include "Logging/MessageLog.h"
#include "MovieScene.h"
#include "UObject/SequencerObjectVersion.h"
#include "MovieSceneTimeHelpers.h"
#include "Tracks/MovieSceneSkeletalAnimationTrack.h"
#include "BoneContainer.h"
#include "MovieSceneTransformTypes.h"
#include "Animation/AnimationPoseData.h"
#include "Animation/AnimSequenceDecompressionContext.h"
#include "Animation/AttributesRuntime.h"
#include "Misc/FrameRate.h"
#include "EntitySystem/BuiltInComponentTypes.h"
#include "MovieSceneTracksComponentTypes.h"
#include "Sections/MovieSceneSectionTimingParameters.h"
#include "Variants/MovieSceneTimeWarpGetter.h"
#include "Systems/MovieSceneSkeletalAnimationSystem.h"
#include "Decorations/MovieSceneLanguagePreviewDecoration.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneSkeletalAnimationSection)
#define LOCTEXT_NAMESPACE "MovieSceneSkeletalAnimationSection"
TAutoConsoleVariable<bool> CVarStartTransformOffsetInBoneSpace(TEXT("Sequencer.StartTransformOffsetInBoneSpace"), true,
TEXT("When true we offset the start offsets for skeletal animation matching in bone space, if false we do it in root space, by default true"));
namespace
{
FName DefaultSlotName( "DefaultSlot" );
float SkeletalDeprecatedMagicNumber = TNumericLimits<float>::Lowest();
}
FMovieSceneSkeletalAnimationParams::FMovieSceneSkeletalAnimationParams()
{
Animation = nullptr;
MirrorDataTable = nullptr;
StartOffset_DEPRECATED = SkeletalDeprecatedMagicNumber;
EndOffset_DEPRECATED = SkeletalDeprecatedMagicNumber;
PlayRate = 1.f;
bReverse = false;
SlotName = DefaultSlotName;
Weight.SetDefault(1.f);
bSkipAnimNotifiers = false;
bForceCustomMode = false;
SwapRootBone = ESwapRootBone::SwapRootBone_None;
bLinearPlaybackWhenScaled = false;
}
FMovieSceneSequenceTransform FMovieSceneSkeletalAnimationParams::MakeTransform(const FFrameRate& OuterFrameRate, const TRange<FFrameNumber>& OuterRange, UAnimSequenceBase* OverrideSequence) const
{
if (!OverrideSequence)
{
OverrideSequence = Animation;
}
if (!OverrideSequence)
{
return FMovieSceneSequenceTransform();
}
const double SequenceLength = OverrideSequence->GetPlayLength();
const FFrameTime AnimationLength = SequenceLength * OuterFrameRate;
const int32 LengthInFrames = AnimationLength.FrameNumber.Value + (int)(AnimationLength.GetSubFrame() + 0.5f) + 1;
const bool bLooping = (UE::MovieScene::DiscreteSize(OuterRange) + StartFrameOffset + EndFrameOffset) > LengthInFrames;
FMovieSceneSectionTimingParametersSeconds TimingParams = {
PlayRate.ShallowCopy(),
StartFrameOffset / OuterFrameRate,
EndFrameOffset / OuterFrameRate,
FirstLoopStartFrameOffset / OuterFrameRate,
bLooping,
!bLooping,
bReverse
};
return TimingParams.MakeTransform(OuterFrameRate, OuterRange, SequenceLength, OverrideSequence->RateScale);
}
double FMovieSceneSkeletalAnimationParams::MapTimeToAnimation(const UMovieSceneSection* InSection, FFrameTime InPosition, FFrameRate InFrameRate, UAnimSequenceBase* OverrideSequence) const
{
const FFrameNumber SectionStartTime = InSection->GetInclusiveStartFrame();
const FFrameNumber SectionEndTime = InSection->GetExclusiveEndFrame();
return MapTimeToAnimation(SectionStartTime, SectionEndTime, InPosition, InFrameRate, OverrideSequence);
}
double FMovieSceneSkeletalAnimationParams::MapTimeToAnimation(FFrameNumber InSectionStartTime, FFrameNumber InSectionEndTime, FFrameTime InPosition, FFrameRate InFrameRate, UAnimSequenceBase* OverrideSequence) const
{
return MakeTransform(InFrameRate, TRange<FFrameNumber>(InSectionStartTime, InSectionEndTime), OverrideSequence).TransformTime(InPosition).AsDecimal();
}
UMovieSceneSkeletalAnimationSection::UMovieSceneSkeletalAnimationSection( const FObjectInitializer& ObjectInitializer )
: Super( ObjectInitializer )
{
AnimSequence_DEPRECATED = nullptr;
Animation_DEPRECATED = nullptr;
StartOffset_DEPRECATED = 0.f;
EndOffset_DEPRECATED = 0.f;
PlayRate_DEPRECATED = 1.f;
bReverse_DEPRECATED = false;
SlotName_DEPRECATED = DefaultSlotName;
#if WITH_EDITORONLY_DATA
bShowSkeleton = false;
#endif
BlendType = EMovieSceneBlendType::Absolute;
EvalOptions.EnableAndSetCompletionMode
(GetLinkerCustomVersion(FSequencerObjectVersion::GUID) < FSequencerObjectVersion::WhenFinishedDefaultsToProjectDefault ?
EMovieSceneCompletionMode::RestoreState :
EMovieSceneCompletionMode::ProjectDefault);
StartLocationOffset = FVector::ZeroVector;
StartRotationOffset = FRotator::ZeroRotator;
bMatchWithPrevious = true;
MatchedBoneName = NAME_None;
MatchedLocationOffset = FVector::ZeroVector;
MatchedRotationOffset = FRotator::ZeroRotator;
bMatchTranslation = true;
bMatchRotationYaw = true;
bMatchRotationRoll = false;
bMatchRotationPitch = false;
bMatchIncludeZHeight = false;
bDebugForceTickPose = false;
MixedAnimationTarget = TInstancedStruct<FMovieSceneMixedAnimationTarget>::Make();
}
UAnimSequenceBase* UMovieSceneSkeletalAnimationSection::GetAnimation() const
{
return Params.Animation;
}
UAnimSequenceBase* UMovieSceneSkeletalAnimationSection::GetPlaybackAnimation() const
{
return UMovieSceneLanguagePreviewDecoration::FindLocalizedAsset(Params.Animation, this);
}
EMovieSceneChannelProxyType UMovieSceneSkeletalAnimationSection::CacheChannelProxy()
{
FMovieSceneChannelProxyData Channels;
if (Params.PlayRate.GetType() == EMovieSceneTimeWarpType::Custom)
{
UMovieSceneTimeWarpGetter* Custom = Params.PlayRate.AsCustom();
if (Custom)
{
Custom->PopulateChannelProxy(Channels, UMovieSceneTimeWarpGetter::EAllowTopLevelChannels::No);
}
}
for (UObject* Decoration : GetDecorations())
{
if (IMovieSceneChannelDecoration* ChannelDecoration = Cast<IMovieSceneChannelDecoration>(Decoration))
{
// Result doesn't matter because we always return EMovieSceneChannelProxyType::Dynamic anyway
ChannelDecoration->PopulateChannelProxy(Channels);
}
}
#if WITH_EDITOR
static FMovieSceneChannelMetaData MetaData("Weight", LOCTEXT("WeightChannelName", "Weight"));
MetaData.bCanCollapseToTrack = false;
Channels.Add(Params.Weight, MetaData, TMovieSceneExternalValue<float>());
#else
Channels.Add(Params.Weight);
#endif
ChannelProxy = MakeShared<FMovieSceneChannelProxy>(MoveTemp(Channels));
return EMovieSceneChannelProxyType::Dynamic;
}
void UMovieSceneSkeletalAnimationSection::DeleteChannels(TArrayView<const FName> ChannelNames)
{
bool bDeletedAny = false;
if (Params.PlayRate.GetType() == EMovieSceneTimeWarpType::Custom && TryModify())
{
if (UMovieSceneTimeWarpGetter* Getter = Params.PlayRate.AsCustom())
{
for (FName ChannelName : ChannelNames)
{
bDeletedAny |= Getter->DeleteChannel(Params.PlayRate, ChannelName);
}
}
}
if (bDeletedAny)
{
ChannelProxy = nullptr;
}
}
TOptional<FFrameTime> UMovieSceneSkeletalAnimationSection::GetOffsetTime() const
{
return TOptional<FFrameTime>(Params.FirstLoopStartFrameOffset);
}
void UMovieSceneSkeletalAnimationSection::MigrateFrameTimes(FFrameRate SourceRate, FFrameRate DestinationRate)
{
if (Params.StartFrameOffset.Value > 0)
{
FFrameNumber NewStartFrameOffset = ConvertFrameTime(FFrameTime(Params.StartFrameOffset), SourceRate, DestinationRate).FloorToFrame();
Params.StartFrameOffset = NewStartFrameOffset;
}
if (Params.EndFrameOffset.Value > 0)
{
FFrameNumber NewEndFrameOffset = ConvertFrameTime(FFrameTime(Params.EndFrameOffset), SourceRate, DestinationRate).FloorToFrame();
Params.EndFrameOffset = NewEndFrameOffset;
}
if (Params.FirstLoopStartFrameOffset.Value > 0)
{
FFrameNumber NewFirstLoopStartFrameOffset = ConvertFrameTime(FFrameTime(Params.FirstLoopStartFrameOffset), SourceRate, DestinationRate).FloorToFrame();
Params.FirstLoopStartFrameOffset = NewFirstLoopStartFrameOffset;
}
}
void UMovieSceneSkeletalAnimationSection::Serialize(FArchive& Ar)
{
Ar.UsingCustomVersion(FSequencerObjectVersion::GUID);
Super::Serialize(Ar);
}
void UMovieSceneSkeletalAnimationSection::PostLoad()
{
if (AnimSequence_DEPRECATED)
{
Params.Animation = AnimSequence_DEPRECATED;
}
if (Animation_DEPRECATED != nullptr)
{
Params.Animation = Animation_DEPRECATED;
}
if (StartOffset_DEPRECATED != 0.f)
{
Params.StartOffset_DEPRECATED = StartOffset_DEPRECATED;
}
if (EndOffset_DEPRECATED != 0.f)
{
Params.EndOffset_DEPRECATED = EndOffset_DEPRECATED;
}
if (PlayRate_DEPRECATED != 1.f)
{
Params.PlayRate = PlayRate_DEPRECATED;
}
if (bReverse_DEPRECATED != false)
{
Params.bReverse = bReverse_DEPRECATED;
}
if (SlotName_DEPRECATED != DefaultSlotName)
{
Params.SlotName = SlotName_DEPRECATED;
}
UMovieScene* MovieScene = GetTypedOuter<UMovieScene>();
if (MovieScene)
{
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
FFrameRate TickResolution = MovieScene->GetTickResolution();
if (Params.StartOffset_DEPRECATED != SkeletalDeprecatedMagicNumber)
{
Params.StartFrameOffset = ConvertFrameTime(FFrameTime::FromDecimal(DisplayRate.AsDecimal() * Params.StartOffset_DEPRECATED), DisplayRate, TickResolution).FrameNumber;
Params.StartOffset_DEPRECATED = SkeletalDeprecatedMagicNumber;
}
if (Params.EndOffset_DEPRECATED != SkeletalDeprecatedMagicNumber)
{
Params.EndFrameOffset = ConvertFrameTime(FFrameTime::FromDecimal(DisplayRate.AsDecimal() * Params.EndOffset_DEPRECATED), DisplayRate, TickResolution).FrameNumber;
Params.EndOffset_DEPRECATED = SkeletalDeprecatedMagicNumber;
}
}
// if version is less than this
if (GetLinkerCustomVersion(FSequencerObjectVersion::GUID) < FSequencerObjectVersion::ConvertEnableRootMotionToForceRootLock)
{
UAnimSequence* AnimSeq = Cast<UAnimSequence>(Params.Animation);
if (AnimSeq && AnimSeq->bEnableRootMotion && !AnimSeq->bForceRootLock)
{
// this is not ideal, but previously single player node was using this flag to whether or not to extract root motion
// with new anim sequencer instance, this would break because we use the instance flag to extract root motion or not
// so instead of setting that flag, we use bForceRootLock flag to asset
// this can have side effect, where users didn't want that to be on to start with
// So we'll notify users to let them know this has to be saved
AnimSeq->bForceRootLock = true;
AnimSeq->MarkPackageDirty();
// warning to users
#if WITH_EDITOR
if (!IsRunningGame())
{
static FName NAME_LoadErrors("LoadErrors");
FMessageLog LoadErrors(NAME_LoadErrors);
TSharedRef<FTokenizedMessage> Message = LoadErrors.Warning();
Message->AddToken(FTextToken::Create(LOCTEXT("RootMotionFixUp1", "The Animation ")));
Message->AddToken(FAssetNameToken::Create(AnimSeq->GetPathName(), FText::FromString(GetNameSafe(AnimSeq))));
Message->AddToken(FTextToken::Create(LOCTEXT("RootMotionFixUp2", "will be set to ForceRootLock on. Please save the animation if you want to keep this change.")));
Message->SetSeverity(EMessageSeverity::Warning);
LoadErrors.Notify();
}
#endif // WITH_EDITOR
UE_LOG(LogMovieScene, Warning, TEXT("%s Animation has set ForceRootLock to be used in Sequencer. If this animation is used in anywhere else using root motion, that will cause conflict."), *AnimSeq->GetName());
}
}
Super::PostLoad();
}
TOptional<TRange<FFrameNumber> > UMovieSceneSkeletalAnimationSection::GetAutoSizeRange() const
{
if (UMovieScene* MovieScene = GetTypedOuter<UMovieScene>())
{
FFrameRate TickResolution = MovieScene->GetTickResolution();
FMovieSceneInverseSequenceTransform InnerToOuterTransform = Params.MakeTransform(TickResolution, GetRange()).Inverse();
FFrameTime AnimationLength = Params.GetSequenceLength() * TickResolution;
int32 IFrameNumber = AnimationLength.FrameNumber.Value + static_cast<int32>(AnimationLength.GetSubFrame() + 0.5f) + 1;
FFrameTime InnerStartTime = Params.StartFrameOffset + Params.FirstLoopStartFrameOffset;
FFrameTime InnerEndTime = IFrameNumber - Params.EndFrameOffset;
const FFrameTime OuterStartTime = InnerToOuterTransform.TryTransformTime(InnerStartTime).Get(InnerStartTime);
const FFrameTime OuterEndTime = InnerToOuterTransform.TryTransformTime(InnerEndTime).Get(InnerEndTime);
return TRange<FFrameNumber>(GetInclusiveStartFrame(), GetInclusiveStartFrame() + (OuterEndTime - OuterStartTime).FrameNumber);
}
return TOptional<TRange<FFrameNumber>>();
}
void UMovieSceneSkeletalAnimationSection::TrimSection(FQualifiedFrameTime TrimTime, bool bTrimLeft, bool bDeleteKeys)
{
SetFlags(RF_Transactional);
if (TryModify())
{
if (bTrimLeft)
{
FFrameRate FrameRate = GetTypedOuter<UMovieScene>()->GetTickResolution();
FMovieSceneSequenceTransform OuterToInnerTransform = Params.MakeTransform(FrameRate, GetRange());
FFrameTime AnimationTimeInSeconds = OuterToInnerTransform.TransformTime(TrimTime.Time);
Params.FirstLoopStartFrameOffset = (AnimationTimeInSeconds.AsDecimal() * FrameRate).FrameNumber;
}
Super::TrimSection(TrimTime, bTrimLeft, bDeleteKeys);
}
}
UMovieSceneSection* UMovieSceneSkeletalAnimationSection::SplitSection(FQualifiedFrameTime SplitTime, bool bDeleteKeys)
{
//handle root motion, only in editor
#if WITH_EDITOR
TOptional<FTransform> RootTransform;
FName BoneName;
UMovieSceneCommonAnimationTrack* Track = GetTypedOuter<UMovieSceneCommonAnimationTrack>();
//if we are doing root motion then get the BoneName
if (Track)
{
int32 BoneIndex = SetBoneIndexForRootMotionCalculations(Track->bBlendFirstChildOfRoot);
FMovieSceneSkeletalAnimRootMotionTrackParams* RootMotionParams = GetRootMotionParams();
if (RootMotionParams->bHaveRootMotion)
{
if (UAnimSequence* AnimSequence = Cast<UAnimSequence>(Params.Animation))
{
const FReferenceSkeleton& RefSkeleton = AnimSequence->GetSkeleton()->GetReferenceSkeleton();
RootTransform = Track->GetRootMotion(SplitTime.Time);
if (RootTransform.IsSet())
{
BoneName = RefSkeleton.GetBoneName(BoneIndex);
}
}
}
}
#endif
//handle FirstLoopStartFrameOffset
const FFrameNumber InitialFirstLoopStartFrameOffset = Params.FirstLoopStartFrameOffset;
FFrameRate FrameRate = GetTypedOuter<UMovieScene>()->GetTickResolution();
FMovieSceneSequenceTransform OuterToInnerTransform = Params.MakeTransform(FrameRate, GetRange());
FFrameTime AnimationTimeInSeconds = OuterToInnerTransform.TransformTime(SplitTime.Time);
const FFrameNumber NewOffset = (AnimationTimeInSeconds.AsDecimal() * FrameRate).FrameNumber;
UMovieSceneSkeletalAnimationSection* NewSection = Cast<UMovieSceneSkeletalAnimationSection>(Super::SplitSection(SplitTime, bDeleteKeys));
if (NewSection != nullptr)
{
NewSection->Params.FirstLoopStartFrameOffset = NewOffset;
#if WITH_EDITOR
if (RootTransform.IsSet())
{
NewSection->bMatchTranslation = NewSection->bMatchIncludeZHeight = true;
NewSection->bMatchRotationYaw = NewSection->bMatchRotationPitch = NewSection->bMatchRotationRoll = true;
NewSection->bMatchWithPrevious = true;
NewSection->MatchedLocationOffset = FVector(0.0, 0.0, 0.0);
NewSection->MatchedRotationOffset = FRotator(0.0, 0.0, 0.0);
if (NewSection->GetRootMotionParams())
{
NewSection->GetRootMotionParams()->bRootMotionsDirty = true;
}
}
#endif
}
// Restore original offset modified by splitting
Params.FirstLoopStartFrameOffset = InitialFirstLoopStartFrameOffset;
return NewSection;
}
void UMovieSceneSkeletalAnimationSection::GetSnapTimes(TArray<FFrameNumber>& OutSnapTimes, bool bGetSectionBorders) const
{
using namespace UE::MovieScene;
Super::GetSnapTimes(OutSnapTimes, bGetSectionBorders);
const UMovieScene* MovieScene = GetTypedOuter<UMovieScene>();
const FFrameNumber StartFrame = GetInclusiveStartFrame();
const FFrameNumber EndFrame = GetExclusiveEndFrame();
if (!MovieScene)
{
return;
}
auto VisitBoundary = [&OutSnapTimes](FFrameTime InTime)
{
OutSnapTimes.Add(InTime.RoundToFrame());
return true;
};
FMovieSceneSequenceTransform OuterToInnerTransform = Params.MakeTransform(MovieScene->GetTickResolution(), GetRange());
if (!OuterToInnerTransform.ExtractBoundariesWithinRange(StartFrame, EndFrame, VisitBoundary))
{
FMovieSceneInverseSequenceTransform InnerToOuterTransform = OuterToInnerTransform.Inverse();
TOptional<FFrameTime> AnimEnd = InnerToOuterTransform.TryTransformTime(FFrameTime::FromDecimal(Params.GetSequenceLength()));
if (AnimEnd && AnimEnd.GetValue() < EndFrame)
{
VisitBoundary(AnimEnd.GetValue());
}
}
}
double UMovieSceneSkeletalAnimationSection::MapTimeToAnimation(FFrameTime InPosition, FFrameRate InFrameRate) const
{
return Params.MapTimeToAnimation(this, InPosition, InFrameRate);
}
float UMovieSceneSkeletalAnimationSection::GetTotalWeightValue(FFrameTime InTime) const
{
float ManualWeight = 1.f;
Params.Weight.Evaluate(InTime, ManualWeight);
return ManualWeight * EvaluateEasing(InTime);
}
void UMovieSceneSkeletalAnimationSection::SetRange(const TRange<FFrameNumber>& NewRange)
{
UMovieSceneSection::SetRange(NewRange);
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
}
void UMovieSceneSkeletalAnimationSection::SetStartFrame(TRangeBound<FFrameNumber> NewStartFrame)
{
UMovieSceneSection::SetStartFrame(NewStartFrame);
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
}
void UMovieSceneSkeletalAnimationSection::SetEndFrame(TRangeBound<FFrameNumber> NewEndFrame)
{
UMovieSceneSection::SetEndFrame(NewEndFrame);
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
}
FMovieSceneTimeWarpVariant* UMovieSceneSkeletalAnimationSection::GetTimeWarp()
{
return &Params.PlayRate;
}
UObject* UMovieSceneSkeletalAnimationSection::GetSourceObject() const
{
return Params.Animation;
}
void UMovieSceneSkeletalAnimationSection::ImportEntityImpl(UMovieSceneEntitySystemLinker* EntityLinker, const FEntityImportParams& InParams, FImportedEntity* OutImportedEntity)
{
using namespace UE::MovieScene;
const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
const FMovieSceneTracksComponentTypes* TrackComponents = FMovieSceneTracksComponentTypes::Get();
const FGuid ObjectBindingID = InParams.GetObjectBindingID();
const bool bIsAdditive = Params.Animation->IsValidAdditive();
if (!ObjectBindingID.IsValid())
{
return;
}
FMovieSceneSkeletalAnimationComponentData ComponentData { this };
OutImportedEntity->AddBuilder(
FEntityBuilder()
.Add(TrackComponents->SkeletalAnimation, ComponentData)
.Add(BuiltInComponents->GenericObjectBinding, ObjectBindingID)
.Add(BuiltInComponents->BoundObjectResolver, UMovieSceneSkeletalAnimationSystem::ResolveSkeletalMeshComponentBinding)
.AddConditional(BuiltInComponents->WeightChannel, &Params.Weight, Params.Weight.HasAnyData())
.AddTagConditional(BuiltInComponents->Tags.AdditiveAnimation, bIsAdditive)
);
}
bool UMovieSceneSkeletalAnimationSection::PopulateEvaluationFieldImpl(const TRange<FFrameNumber>& EffectiveRange, const FMovieSceneEvaluationFieldEntityMetaData& InMetaData, FMovieSceneEntityComponentFieldBuilder* OutFieldBuilder)
{
if (!Params.Animation)
{
return true;
}
return false;
}
#if WITH_EDITOR
bool UMovieSceneSkeletalAnimationSection::Modify(bool bAlwaysMarkDirty)
{
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
return Super::Modify(bAlwaysMarkDirty);
}
void UMovieSceneSkeletalAnimationSection::PreEditChange(FProperty* PropertyAboutToChange)
{
// Store the current play rate so that we can compute the amount to compensate the section end time when the play rate changes
if (Params.PlayRate.GetType() == EMovieSceneTimeWarpType::FixedPlayRate)
{
PreviousPlayRate = Params.PlayRate.AsFixedPlayRateFloat();
}
Super::PreEditChange(PropertyAboutToChange);
}
void UMovieSceneSkeletalAnimationSection::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Adjust the duration automatically if the play rate changes
if (PropertyChangedEvent.Property != nullptr &&
PropertyChangedEvent.Property->GetFName() == TEXT("PlayRate"))
{
if (Params.PlayRate.GetType() == EMovieSceneTimeWarpType::FixedPlayRate)
{
float NewPlayRate = Params.PlayRate.AsFixedPlayRateFloat();
if (NewPlayRate > KINDA_SMALL_NUMBER)
{
float CurrentDuration = UE::MovieScene::DiscreteSize(GetRange());
float NewDuration = CurrentDuration * (PreviousPlayRate / NewPlayRate);
SetEndFrame( GetInclusiveStartFrame() + FMath::FloorToInt(NewDuration) );
PreviousPlayRate = NewPlayRate;
}
}
ChannelProxy = nullptr;
}
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void UMovieSceneSkeletalAnimationSection::PostEditImport()
{
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
Super::PostEditImport();
}
void UMovieSceneSkeletalAnimationSection::PostEditUndo()
{
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
Super::PostEditUndo();
}
#endif
void UMovieSceneSkeletalAnimationSection::GetRootMotion(FFrameTime CurrentTime, UMovieSceneSkeletalAnimationSection::FRootMotionParams& OutRootMotionParams) const
{
if (GetRootMotionParams())
{
if (UMovieSceneCommonAnimationTrack* Track = GetTypedOuter<UMovieSceneCommonAnimationTrack>())
{
OutRootMotionParams.Transform = Track->GetRootMotion(CurrentTime);
OutRootMotionParams.ChildBoneIndex = TempRootBoneIndex.IsSet() ? TempRootBoneIndex.GetValue() : INDEX_NONE;
OutRootMotionParams.bBlendFirstChildOfRoot = Track->bBlendFirstChildOfRoot ;
OutRootMotionParams.PreviousTransform = PreviousTransform;
}
}
}
bool UMovieSceneSkeletalAnimationSection::GetRootMotionVelocity(FFrameTime PreviousTime, FFrameTime CurrentTime, FFrameRate FrameRate,
FTransform& OutVelocity, float& OutWeight) const
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(Params.Animation);
if (AnimSequence)
{
float ManualWeight = 1.f;
Params.Weight.Evaluate(CurrentTime, ManualWeight);
OutWeight = ManualWeight * EvaluateEasing(CurrentTime);
//mz todo we should be able to cache the PreviousTimeSeconds;
//mz todo need to get the starting value.
float PreviousTimeSeconds = static_cast<float>(MapTimeToAnimation(PreviousTime, FrameRate));
float CurrentTimeSeconds = static_cast<float>(MapTimeToAnimation(CurrentTime, FrameRate));
OutVelocity = AnimSequence->ExtractRootMotionFromRange(PreviousTimeSeconds, CurrentTimeSeconds, FAnimExtractContext());
return true;
}
return false;
}
FMovieSceneSkeletalAnimRootMotionTrackParams* UMovieSceneSkeletalAnimationSection::GetRootMotionParams() const
{
UMovieSceneCommonAnimationTrack* Track = GetTypedOuter<UMovieSceneCommonAnimationTrack>();
if (Track)
{
return &Track->RootMotionParams;
}
return nullptr;
}
int32 UMovieSceneSkeletalAnimationSection::SetBoneIndexForRootMotionCalculations(bool bBlendFirstChildOfRoot)
{
if (!bBlendFirstChildOfRoot)
{
TempRootBoneIndex.Reset();
return 0;
}
else if (const UAnimSequence* AnimSequence = Cast<UAnimSequence>(Params.Animation))
{
if (TempRootBoneIndex.IsSet() == false || TempRootBoneIndex.GetValue() == INDEX_NONE)
{
//but if not first find first
int32 RootIndex = INDEX_NONE;
#if WITH_EDITOR
TArray<FName> TrackNames;
AnimSequence->GetDataModelInterface()->GetBoneTrackNames(TrackNames);
const FReferenceSkeleton& RefSkeleton = AnimSequence->GetSkeleton()->GetReferenceSkeleton();
const IAnimationDataModel* DataModel = AnimSequence->GetDataModel();
TArray<FTransform> OutTransforms;
for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
{
const FName BoneName = RefSkeleton.GetBoneName(BoneIndex);
if (DataModel->IsValidBoneTrackName(BoneName))
{
DataModel->GetBoneTrackTransforms(BoneName, OutTransforms);
TOptional<FTransform> LocalPreviousTransform;
for (const FTransform& Transform : OutTransforms)
{
if (Transform.GetLocation().IsNearlyZero() == false
&& LocalPreviousTransform.IsSet() && LocalPreviousTransform.GetValue().GetLocation() != Transform.GetLocation())
{
TempRootBoneIndex = BoneIndex;
break;
}
LocalPreviousTransform = Transform;
}
OutTransforms.Reset();
if (TempRootBoneIndex.IsSet())
{
break;
}
}
}
#else
UAnimSequence::FScopedCompressedAnimSequence CompressedData = AnimSequence->GetCompressedData();
const TArray<FTrackToSkeletonMap>& BoneMappings = CompressedData.Get().CompressedTrackToSkeletonMapTable;
const int32 NumTracks = BoneMappings.Num();
for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex)
{
const FTrackToSkeletonMap& Mapping = BoneMappings[TrackIndex];
// verify if this bone exists in skeleton
const int32 BoneTreeIndex = Mapping.BoneTreeIndex;
if (BoneTreeIndex != INDEX_NONE)
{
int32 ParentIndex = AnimSequence->GetSkeleton()->GetReferenceSkeleton().GetParentIndex(BoneTreeIndex);
if (ParentIndex == INDEX_NONE)
{
RootIndex = TrackIndex;
}
else if (ParentIndex == RootIndex)
{
FTransform Transform;
const int32 NumFrames = AnimSequence->GetNumberOfSampledKeys();
for (int32 Index = 0; Index < NumFrames; ++Index)
{
const double Pos = FMath::Clamp(AnimSequence->GetSamplingFrameRate().AsSeconds(Index), 0.0, AnimSequence->GetPlayLength());
AnimSequence->GetBoneTransform(Transform, FSkeletonPoseBoneIndex(BoneTreeIndex), FAnimExtractContext(Pos), false);
if (Transform.Equals(FTransform::Identity) == false)
{
TempRootBoneIndex = BoneTreeIndex;
break;
}
}
if (TempRootBoneIndex.IsSet())
{
break;
}
}
}
}
#endif
}
}
return TempRootBoneIndex.IsSet() ? TempRootBoneIndex.GetValue() : 0;
}
FTransform UMovieSceneSkeletalAnimationSection::GetRootMotionStartOffset() const
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(Params.Animation);
if (AnimSequence && TempRootBoneIndex.IsSet() && TempRootBoneIndex.GetValue() != 0)
{
UMovieScene* MovieScene = GetTypedOuter<UMovieScene>();
const double StartSeconds = MovieScene ? (MapTimeToAnimation(FFrameNumber(0), MovieScene->GetTickResolution())): 0.0;
return AnimSequence->ExtractRootTrackTransform(FAnimExtractContext(StartSeconds), nullptr);
}
return FTransform::Identity;;
}
bool UMovieSceneSkeletalAnimationSection::GetRootMotionTransform(FAnimationPoseData& AnimationPoseData,FRootMotionTransformParam& InOutParams) const
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(Params.Animation);
FTransform OffsetTransform(FQuat(StartRotationOffset), StartLocationOffset);
FTransform MatchedTransform(FQuat(MatchedRotationOffset), MatchedLocationOffset);
if (AnimSequence)
{
float ManualWeight = 1.f;
Params.Weight.Evaluate(InOutParams.CurrentTime, ManualWeight);
InOutParams.OutWeight = ManualWeight * EvaluateEasing(InOutParams.CurrentTime);
InOutParams.bOutIsAdditive = false;
const double CurrentTimeSeconds = MapTimeToAnimation(InOutParams.CurrentTime, InOutParams.FrameRate);
const double StartSeconds = (MapTimeToAnimation(FFrameNumber(0), InOutParams.FrameRate));
InOutParams.bOutIsAdditive = AnimSequence->GetAdditiveAnimType() != EAdditiveAnimationType::AAT_None;
FTransform StartBoneTransform;
InOutParams.OutRootStartTransform = GetRootMotionStartOffset();
if (TempRootBoneIndex.IsSet() && TempRootBoneIndex.GetValue() != 0)
{
//get the start pose first since we pass out the pose and need the current
FCompactPoseBoneIndex PoseIndex = AnimationPoseData.GetPose().GetBoneContainer().GetCompactPoseIndexFromSkeletonIndex(TempRootBoneIndex.GetValue());
FAnimExtractContext ExtractionContext(StartSeconds);
AnimSequence->GetAnimationPose(AnimationPoseData, ExtractionContext);
StartBoneTransform = AnimationPoseData.GetPose()[FCompactPoseBoneIndex(PoseIndex)];
ExtractionContext.CurrentTime = CurrentTimeSeconds;
AnimSequence->GetAnimationPose(AnimationPoseData, ExtractionContext);
InOutParams.OutPoseTransform = AnimationPoseData.GetPose()[FCompactPoseBoneIndex(PoseIndex)];
}
else //not set then just use root.
{
StartBoneTransform = AnimSequence->ExtractRootTrackTransform(FAnimExtractContext(StartSeconds), nullptr);
InOutParams.OutPoseTransform = AnimSequence->ExtractRootTrackTransform(FAnimExtractContext(CurrentTimeSeconds), nullptr);
}
//note though we don't support mesh space addtive just local additive it will still work the same here for the root so
if (!InOutParams.bOutIsAdditive)
{
const bool bStartTransformOffsetInBoneSpace = CVarStartTransformOffsetInBoneSpace.GetValueOnGameThread();
if (bStartTransformOffsetInBoneSpace)
{
FTransform StartMatchedInRoot = StartBoneTransform * MatchedTransform;
FTransform LocalToRoot = (InOutParams.OutPoseTransform * StartBoneTransform.Inverse());
FTransform OffsetInLocalSpace = LocalToRoot * OffsetTransform;
InOutParams.OutTransform = OffsetInLocalSpace * StartMatchedInRoot;
}
else
{
InOutParams.OutTransform = InOutParams.OutPoseTransform * OffsetTransform * MatchedTransform;
}
InOutParams.OutParentTransform = OffsetTransform.GetRelativeTransformReverse(InOutParams.OutTransform);
}
return true;
}
//for safety always return true for now
InOutParams.OutParentTransform = OffsetTransform * MatchedTransform;
InOutParams.OutTransform = InOutParams.OutParentTransform;
InOutParams.OutPoseTransform = FTransform::Identity;
return true;
}
void UMovieSceneSkeletalAnimationSection::MultiplyOutInverseOnNextClips(FVector PreviousMatchedLocationOffset, FRotator PreviousMatchedRotationOffset)
{
UMovieSceneCommonAnimationTrack* Track = GetTypedOuter<UMovieSceneCommonAnimationTrack>();
if (Track)
{
bool bMultiplyOutInverse = false;
//calculate the diff here....
FTransform Previous(PreviousMatchedRotationOffset.Quaternion(), PreviousMatchedLocationOffset);
FTransform Matched(MatchedRotationOffset.Quaternion(), MatchedLocationOffset);
FTransform Inverse = Previous.GetRelativeTransformReverse(Matched);
for (UMovieSceneSection* Section : Track->AnimationSections)
{
if (bMultiplyOutInverse)
{
UMovieSceneSkeletalAnimationSection* AnimSection = Cast<UMovieSceneSkeletalAnimationSection>(Section);
if (AnimSection)
{
//then for all of the next sections we need to multiply that diff through.
FTransform CurrentMatched(AnimSection->MatchedRotationOffset, AnimSection->MatchedLocationOffset);
FTransform NewMatched = Inverse.GetRelativeTransformReverse(CurrentMatched);
AnimSection->MatchedLocationOffset = NewMatched.GetTranslation();
AnimSection->MatchedRotationOffset = NewMatched.GetRotation().Rotator();
}
break;
}
if (Section == this) //next ones we multiply out
{
bMultiplyOutInverse = true;
}
}
}
}
void UMovieSceneSkeletalAnimationSection::ClearMatchedOffsetTransforms()
{
//need to store the previous since we may need to apply the change we made to the next clips so they don't move
FVector PreviousMatchedLocationOffset = MatchedLocationOffset;
FRotator PreviousMatchedRotationOffset = MatchedRotationOffset;
MatchedLocationOffset = FVector::ZeroVector;
MatchedRotationOffset = FRotator::ZeroRotator;
if (bMatchWithPrevious == false)
{
MultiplyOutInverseOnNextClips(PreviousMatchedLocationOffset, PreviousMatchedRotationOffset);
}
bMatchWithPrevious = true;
MatchedBoneName = NAME_None;
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
}
void UMovieSceneSkeletalAnimationSection::MatchSectionByBoneTransform(USkeletalMeshComponent* SkelMeshComp, FFrameTime CurrentFrame, FFrameRate FrameRate,
const FName& BoneName)
{
MatchedBoneName = BoneName;
UMovieSceneCommonAnimationTrack* Track = GetTypedOuter<UMovieSceneCommonAnimationTrack>();
if (Track)
{
FTransform DiffTransform;
FVector DiffTranslate;
FQuat DiffRotate;
//need to store the previous since we may need to apply the change we made to the next clips so they don't move
FVector PreviousMatchedLocationOffset = MatchedLocationOffset;
FRotator PreviousMatchedRotationOffset = MatchedRotationOffset;
Track->MatchSectionByBoneTransform(bMatchWithPrevious,SkelMeshComp, this, CurrentFrame, FrameRate, BoneName, DiffTransform,DiffTranslate,DiffRotate);
MatchedLocationOffset = bMatchTranslation ? DiffTranslate : FVector::ZeroVector;
MatchedRotationOffset = DiffRotate.Rotator();
if (bMatchWithPrevious == false)
{
MultiplyOutInverseOnNextClips(PreviousMatchedLocationOffset, PreviousMatchedRotationOffset);
}
if (GetRootMotionParams())
{
GetRootMotionParams()->bRootMotionsDirty = true;
}
}
}
void UMovieSceneSkeletalAnimationSection::ToggleMatchTranslation()
{
bMatchTranslation = bMatchTranslation ? false : true;
GetRootMotionParams()->bRootMotionsDirty = true;
}
void UMovieSceneSkeletalAnimationSection::ToggleMatchIncludeZHeight()
{
bMatchIncludeZHeight = bMatchIncludeZHeight ? false : true;
GetRootMotionParams()->bRootMotionsDirty = true;
}
void UMovieSceneSkeletalAnimationSection::ToggleMatchIncludeYawRotation()
{
bMatchRotationYaw = bMatchRotationYaw ? false : true;
GetRootMotionParams()->bRootMotionsDirty = true;
}
void UMovieSceneSkeletalAnimationSection::ToggleMatchIncludePitchRotation()
{
bMatchRotationPitch = bMatchRotationPitch ? false : true;
GetRootMotionParams()->bRootMotionsDirty = true;
}
void UMovieSceneSkeletalAnimationSection::ToggleMatchIncludeRollRotation()
{
bMatchRotationRoll = bMatchRotationRoll ? false : true;
GetRootMotionParams()->bRootMotionsDirty = true;
}
#if WITH_EDITORONLY_DATA
void UMovieSceneSkeletalAnimationSection::ToggleShowSkeleton()
{
bShowSkeleton = bShowSkeleton ? false : true;
}
#endif
#undef LOCTEXT_NAMESPACE