Files
UnrealEngine/Engine/Source/Editor/MovieSceneTools/Private/Constraints/MovieSceneConstraintChannelHelper.cpp
2025-05-18 13:04:45 +08:00

1648 lines
55 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Constraints/MovieSceneConstraintChannelHelper.inl"
#include "ISequencer.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include "LevelSequence.h"
#include "MovieSceneToolHelpers.h"
#include "Transform/TransformableHandle.h"
#include "Transform/TransformConstraint.h"
#include "Transform/TransformConstraintUtil.h"
#include "Algo/Copy.h"
#include "ScopedTransaction.h"
#include "Channels/MovieSceneFloatChannel.h"
#include "Channels/MovieSceneDoubleChannel.h"
#include "LevelEditorViewport.h"
#include "MovieSceneSpawnableAnnotation.h"
#include "Sections/MovieSceneConstrainedSection.h"
#include "MovieSceneToolsModule.h"
#include "Channels/MovieSceneChannelTraits.h"
#include "Constraints/TransformConstraintChannelInterface.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
/*
*
* FCompensationEvaluator
*
*/
FCompensationEvaluator::FCompensationEvaluator(UTickableTransformConstraint* InConstraint)
: Constraint(InConstraint)
, Handle(InConstraint ? InConstraint->ChildTRSHandle : nullptr)
{}
void FCompensationEvaluator::ComputeLocalTransforms(UWorld* InWorld, const FEvalParameters& InEvalParams)
{
if (!InEvalParams.IsValid())
{
return;
}
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
const TArray< ConstraintPtr > Constraints = GetHandleTransformConstraints(InWorld);
if (Constraints.IsEmpty())
{
return;
}
const TArray<ConstraintPtr> ConstraintsMinusThis =
Constraints.FilterByPredicate([this](const ConstraintPtr& InConstraint)
{
return InConstraint != Constraint;
});
// find last active constraint in the list that is different than the on we want to compensate for
auto GetLastActiveConstraint = [ConstraintsMinusThis]()
{
// find last active constraint in the list that is different than the one we want to compensate for
const int32 LastActiveIndex = UE::TransformConstraintUtil::GetLastActiveConstraintIndex(ConstraintsMinusThis);
// if found, return its parent global transform
return LastActiveIndex > INDEX_NONE ? Cast<UTickableTransformConstraint>(ConstraintsMinusThis[LastActiveIndex]) : nullptr;
};
ISequencer* Sequencer = InEvalParams.Sequencer;
UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
const FFrameRate TickResolution = MovieScene->GetTickResolution();
const EMovieScenePlayerStatus::Type PlaybackStatus = Sequencer->GetPlaybackStatus();
const TConstArrayView<FFrameNumber> Frames(InEvalParams.Frames);
const int32 NumFrames = Frames.Num();
const FTransform CurrentLocal = Handle->GetLocalTransform();
const FTransform CurrentGlobal = Handle->GetGlobalTransform();
// resize arrays to num frames + 1 as we also evaluate at InFrames[0]-1
ChildLocals.SetNum(NumFrames + 1);
ChildGlobals.SetNum(NumFrames + 1);
SpaceGlobals.SetNum(NumFrames + 1);
// avoid transacting when evaluating sequencer
TGuardValue<ITransaction*> TransactionGuard(GUndo, nullptr);
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StartBaking(MovieScene);
}
}
// get all constraints to evaluate
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld);
static constexpr bool bSorted = true, bTickHandles = true;
const TArray<ConstraintPtr> AllConstraints = Controller.GetAllConstraints(bSorted);
FMovieSceneInverseSequenceTransform LocalToRootTransform = Sequencer->GetFocusedMovieSceneSequenceTransform().Inverse();
for (int32 Index = 0; Index < NumFrames + 1; ++Index)
{
FFrameNumber FrameNumber = (Index == 0) ? Frames[0] - 1 : Frames[Index - 1];
TOptional<FFrameTime> RootTime = LocalToRootTransform.TryTransformTime(FrameNumber);
if (!RootTime)
{
continue;
}
FrameNumber = RootTime->GetFrame();
// evaluate animation
const FMovieSceneEvaluationRange EvaluationRange = FMovieSceneEvaluationRange(FFrameTime(FrameNumber), TickResolution);
const FMovieSceneContext Context = FMovieSceneContext(EvaluationRange, PlaybackStatus).SetHasJumped(true);
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PreEvaluation(MovieScene, FrameNumber);
}
}
Sequencer->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context);
// evaluate constraints
for (const TWeakObjectPtr<UTickableConstraint>& InConstraint : AllConstraints)
{
if (InConstraint.IsValid())
{
InConstraint->Evaluate(bTickHandles);
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PostEvaluation(MovieScene, FrameNumber);
}
}
FTransform& ChildLocal = ChildLocals[Index];
FTransform& ChildGlobal = ChildGlobals[Index];
FTransform& SpaceGlobal = SpaceGlobals[Index];
if (InEvalParams.bKeepCurrent && Index < 2)
{
ChildGlobal = CurrentGlobal;
}
else
{
// store child transforms
ChildLocal = Handle->GetLocalTransform();
Handle->PreEvaluate();
ChildGlobal = Handle->GetGlobalTransform();
}
const UTickableTransformConstraint* LastConstraint = GetLastActiveConstraint();
// store constraint/parent space global transform
if (InEvalParams.bToActive)
{
// if activating the constraint, store last constraint or parent space at T[0]-1
// and constraint space for all other times
if (Index == 0)
{
if (LastConstraint)
{
SpaceGlobal = LastConstraint->GetParentGlobalTransform();
TOptional<FTransform> Relative =
UE::TransformConstraintUtil::GetConstraintsRelativeTransform(ConstraintsMinusThis, ChildLocal, ChildGlobal);
if (Relative)
{
ChildLocal = *Relative;
}
}
else if (InEvalParams.bKeepCurrent)
{
ChildLocal = CurrentLocal;
}
}
else
{
SpaceGlobal = Constraint->GetParentGlobalTransform();
ChildLocal = UE::TransformConstraintUtil::ComputeRelativeTransform(
ChildLocal, ChildGlobal, SpaceGlobal, Constraint);
}
}
else
{
// if deactivating the constraint, store constraint space at T[0]-1
// and last constraint or parent space for all other times
if (Index == 0)
{
SpaceGlobal = Constraint->GetParentGlobalTransform();
ChildLocal = UE::TransformConstraintUtil::ComputeRelativeTransform(
ChildLocal, ChildGlobal, SpaceGlobal, Constraint);
}
else
{
if (LastConstraint)
{
SpaceGlobal = LastConstraint->GetParentGlobalTransform();
TOptional<FTransform> Relative =
UE::TransformConstraintUtil::GetConstraintsRelativeTransform(ConstraintsMinusThis, ChildLocal, ChildGlobal);
if (Relative)
{
ChildLocal = *Relative;
}
}
}
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StopBaking(MovieScene);
}
}
//get back to where we are at, should also make sure things are active
Sequencer->ForceEvaluate();
}
void FCompensationEvaluator::ComputeLocalTransformsForBaking(UWorld* InWorld, const FEvalParameters& InEvalParams)
{
if (!InEvalParams.IsValid())
{
return;
}
if (!IsValid(Handle) || !Handle->IsValid())
{
return;
}
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
const TArray< ConstraintPtr > Constraints = GetHandleTransformConstraints(InWorld);
const TArray< ConstraintPtr > ConstraintsMinusThis =
Constraints.FilterByPredicate([this](const ConstraintPtr& InConstraint)
{
return InConstraint != Constraint;
});
auto GetLastActiveConstraint = [ConstraintsMinusThis]()
{
// find last active constraint in the list that is different than the one we want to compensate for
const int32 LastActiveIndex = UE::TransformConstraintUtil::GetLastActiveConstraintIndex(ConstraintsMinusThis);
// if found, return its parent global transform
return LastActiveIndex > INDEX_NONE ? Cast<UTickableTransformConstraint>(ConstraintsMinusThis[LastActiveIndex]) : nullptr;
};
ISequencer* Sequencer = InEvalParams.Sequencer;
UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
const FFrameRate TickResolution = MovieScene->GetTickResolution();
const EMovieScenePlayerStatus::Type PlaybackStatus = Sequencer->GetPlaybackStatus();
const TConstArrayView<FFrameNumber> Frames(InEvalParams.Frames);
const int32 NumFrames = Frames.Num();
ChildLocals.SetNum(NumFrames);
ChildGlobals.SetNum(NumFrames);
SpaceGlobals.SetNum(NumFrames);
// get all constraints for evaluation
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld);
static constexpr bool bSorted = true;
const TArray<ConstraintPtr> AllConstraints = Controller.GetAllConstraints(bSorted);
// avoid transacting when evaluating sequencer
TGuardValue<ITransaction*> TransactionGuard(GUndo, nullptr);
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StartBaking(MovieScene);
}
}
FMovieSceneInverseSequenceTransform LocalToRootTransform = Sequencer->GetFocusedMovieSceneSequenceTransform().Inverse();
for (int32 Index = 0; Index < NumFrames; ++Index)
{
TOptional<FFrameTime> RootTime = LocalToRootTransform.TryTransformTime(Frames[Index]);
if (!RootTime)
{
continue;
}
FFrameNumber FrameNumber = RootTime->GetFrame();
// evaluate animation
const FMovieSceneEvaluationRange EvaluationRange = FMovieSceneEvaluationRange(FFrameTime(FrameNumber), TickResolution);
const FMovieSceneContext Context = FMovieSceneContext(EvaluationRange, PlaybackStatus).SetHasJumped(true);
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PreEvaluation(MovieScene, FrameNumber);
}
}
Sequencer->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context);
// evaluate constraints
for (const TWeakObjectPtr<UTickableConstraint>& InConstraint : AllConstraints)
{
if (InConstraint.IsValid())
{
InConstraint->Evaluate(true);
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PostEvaluation(MovieScene, FrameNumber);
}
}
// evaluate ControlRig?
// ControlRig->Evaluate_AnyThread();
FTransform& ChildLocal = ChildLocals[Index];
FTransform& ChildGlobal = ChildGlobals[Index];
FTransform& SpaceGlobal = SpaceGlobals[Index];
// store child transforms
ChildLocal = Handle->GetLocalTransform();
ChildGlobal = Handle->GetGlobalTransform();
// store constraint/parent space global transform
if (const UTickableTransformConstraint* LastConstraint = GetLastActiveConstraint())
{
SpaceGlobal = LastConstraint->GetParentGlobalTransform();
TOptional<FTransform> Relative =
UE::TransformConstraintUtil::GetConstraintsRelativeTransform(ConstraintsMinusThis, ChildLocal, ChildGlobal);
if (Relative)
{
ChildLocal = *Relative;
}
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StopBaking(MovieScene);
}
}
const bool bIsValidAfterBaking = IsValid(Handle) && Handle->IsValid();
if (!bIsValidAfterBaking)
{
// the handle might not be valid after baking due to spawnables or baking out of the sequence boundaries
// so force sequencer evaluation to make sure we're back to normal
Sequencer->ForceEvaluate();
}
}
void FCompensationEvaluator::ComputeLocalTransformsBeforeDeletion(UWorld* InWorld, const FEvalParameters& InEvalParams)
{
if (!InEvalParams.IsValid())
{
return;
}
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
const TArray<ConstraintPtr> Constraints = GetHandleTransformConstraints(InWorld);
const TArray<ConstraintPtr> ConstraintsMinusThis = Constraints.FilterByPredicate(
[this](const ConstraintPtr& InConstraint)
{
return InConstraint.Get() != Constraint;
});
// find last active constraint in the list that is different than the on we want to compensate for
auto GetLastActiveConstraint = [this, ConstraintsMinusThis]()
{
// find last active constraint in the list that is different than the on we want to compensate for
const int32 LastActiveIndex = UE::TransformConstraintUtil::GetLastActiveConstraintIndex(ConstraintsMinusThis);
// if found, return its parent global transform
return LastActiveIndex > INDEX_NONE ? Cast<UTickableTransformConstraint>(ConstraintsMinusThis[LastActiveIndex]) : nullptr;
};
// get all constraints for evaluation
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld);
static constexpr bool bSorted = true;
const TArray<ConstraintPtr> AllConstraints = Controller.GetAllConstraints(bSorted);
ISequencer* Sequencer = InEvalParams.Sequencer;
UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
const FFrameRate TickResolution = MovieScene->GetTickResolution();
const EMovieScenePlayerStatus::Type PlaybackStatus = Sequencer->GetPlaybackStatus();
const TConstArrayView<FFrameNumber> Frames(InEvalParams.Frames);
const int32 NumFrames = Frames.Num();
ChildLocals.SetNum(NumFrames);
ChildGlobals.SetNum(NumFrames);
SpaceGlobals.SetNum(NumFrames);
// avoid transacting when evaluating sequencer
TGuardValue<ITransaction*> TransactionGuard(GUndo, nullptr);
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StartBaking(MovieScene);
}
}
FMovieSceneInverseSequenceTransform LocalToRootTransform = Sequencer->GetFocusedMovieSceneSequenceTransform().Inverse();
for (int32 Index = 0; Index < NumFrames; ++Index)
{
TOptional<FFrameTime> RootTime = LocalToRootTransform.TryTransformTime(Frames[Index]);
if (!RootTime)
{
continue;
}
FFrameNumber FrameNumber = RootTime->GetFrame();
// evaluate animation
const FMovieSceneEvaluationRange EvaluationRange = FMovieSceneEvaluationRange(FFrameTime(FrameNumber), TickResolution);
const FMovieSceneContext Context = FMovieSceneContext(EvaluationRange, PlaybackStatus).SetHasJumped(true);
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PreEvaluation(MovieScene, FrameNumber);
}
}
Sequencer->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context);
// evaluate constraints
for (const TWeakObjectPtr<UTickableConstraint>& InConstraint : AllConstraints)
{
if (InConstraint.IsValid())
{
InConstraint->Evaluate(true);
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PostEvaluation(MovieScene, FrameNumber);
}
}
// evaluate ControlRig?
// ControlRig->Evaluate_AnyThread();
FTransform& ChildLocal = ChildLocals[Index];
FTransform& ChildGlobal = ChildGlobals[Index];
FTransform& SpaceGlobal = SpaceGlobals[Index];
// store child transforms
ChildLocal = Handle->GetLocalTransform();
ChildGlobal = Handle->GetGlobalTransform();
// store constraint/parent space global transform
if (const UTickableTransformConstraint* LastConstraint = GetLastActiveConstraint())
{
SpaceGlobal = LastConstraint->GetParentGlobalTransform();
TOptional<FTransform> Relative =
UE::TransformConstraintUtil::GetConstraintsRelativeTransform(Constraints, ChildLocal, ChildGlobal);
if(Relative)
{
ChildLocal = *Relative;
}
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StopBaking(MovieScene);
}
}
}
void FCompensationEvaluator::ComputeCompensation(UWorld* InWorld, const TSharedPtr<ISequencer>& InSequencer, const FFrameNumber& InTime)
{
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
const TArray<ConstraintPtr> Constraints = GetHandleTransformConstraints(InWorld);
if (Constraints.IsEmpty())
{
return;
}
// find last active constraint in the list that is different than the on we want to compensate for
auto GetLastActiveConstraint = [this, Constraints]()
{
// find last active constraint in the list that is different than the on we want to compensate for
const int32 LastActiveIndex = UE::TransformConstraintUtil::GetLastActiveConstraintIndex(Constraints);
// if found, return its parent global transform
return LastActiveIndex > INDEX_NONE ? Cast<UTickableTransformConstraint>(Constraints[LastActiveIndex]) : nullptr;
};
// get all constraints for evaluation
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld);
static constexpr bool bSorted = true;
const TArray<ConstraintPtr> AllConstraints = Controller.GetAllConstraints(bSorted);
// avoid transacting when evaluating sequencer
TGuardValue<ITransaction*> TransactionGuard(GUndo, nullptr);
UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StartBaking(MovieScene);
}
}
FMovieSceneInverseSequenceTransform LocalToRootTransform = InSequencer->GetFocusedMovieSceneSequenceTransform().Inverse();
auto EvaluateAt = [Handle = this->Handle, InSequencer, &AllConstraints, &BakeHelpers, &LocalToRootTransform](FFrameNumber InFrame)
{
TOptional<FFrameTime> RootTime = LocalToRootTransform.TryTransformTime(InFrame);
if (!RootTime)
{
return;
}
InFrame = RootTime->GetFrame();
UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
const FFrameRate TickResolution = MovieScene->GetTickResolution();
const EMovieScenePlayerStatus::Type PlaybackStatus = InSequencer->GetPlaybackStatus();
const FMovieSceneEvaluationRange EvaluationRange0 = FMovieSceneEvaluationRange(FFrameTime(InFrame), TickResolution);
const FMovieSceneContext Context0 = FMovieSceneContext(EvaluationRange0, PlaybackStatus).SetHasJumped(true);
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PreEvaluation(MovieScene, InFrame);
}
}
InSequencer->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context0);
for (const TWeakObjectPtr<UTickableConstraint>& InConstraint : AllConstraints)
{
if (InConstraint.IsValid())
{
InConstraint->Evaluate(true);
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PostEvaluation(MovieScene, InFrame);
}
}
if (Handle)
{
Handle->PreEvaluate();
}
};
// allocate
ChildLocals.SetNum(1);
ChildGlobals.SetNum(1);
SpaceGlobals.SetNum(1);
// evaluate at InTime and store global
EvaluateAt(InTime);
ChildGlobals[0] = Handle->GetGlobalTransform();
// evaluate at InTime-1 and store local
EvaluateAt(InTime - 1);
ChildLocals[0] = Handle->GetLocalTransform();
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StopBaking(MovieScene);
}
}
// if constraint at T-1 then switch to its space
if (const UTickableTransformConstraint* LastConstraint = GetLastActiveConstraint())
{
SpaceGlobals[0] = LastConstraint->GetParentGlobalTransform();
TOptional<FTransform> Relative =
UE::TransformConstraintUtil::GetConstraintsRelativeTransform(Constraints, ChildLocals[0], ChildGlobals[0]);
if(Relative)
{
ChildLocals[0] = *Relative;
}
}
else // switch to parent space
{
const FTransform ChildLocal = ChildLocals[0];
Handle->SetGlobalTransform(ChildGlobals[0]);
Handle->PreEvaluate();
ChildLocals[0] = Handle->GetLocalTransform();
Handle->SetLocalTransform(ChildLocal);
Handle->PreEvaluate();
}
}
void FCompensationEvaluator::CacheTransforms(UWorld* InWorld, const FEvalParameters& InEvalParams)
{
if (!InEvalParams.IsValid())
{
return;
}
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
// get all constraints for evaluation
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld);
static constexpr bool bSorted = true;
const TArray<ConstraintPtr> AllConstraints = Controller.GetAllConstraints(bSorted);
ISequencer* Sequencer = InEvalParams.Sequencer;
UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
const FFrameRate TickResolution = Sequencer->GetRootMovieSceneSequence()->GetMovieScene()->GetTickResolution();
const EMovieScenePlayerStatus::Type PlaybackStatus = Sequencer->GetPlaybackStatus();
const TConstArrayView<FFrameNumber> Frames(InEvalParams.Frames);
const int32 NumFrames = Frames.Num();
ChildLocals.SetNum(NumFrames);
ChildGlobals.SetNum(NumFrames);
SpaceGlobals.SetNum(NumFrames);
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
FMovieSceneInverseSequenceTransform LocalToRootTransform = Sequencer->GetFocusedMovieSceneSequenceTransform().Inverse();
auto EvaluateAt = [&](FFrameNumber InFrame)
{
TOptional<FFrameTime> RootTime = LocalToRootTransform.TryTransformTime(InFrame);
if (!RootTime)
{
return;
}
const FMovieSceneEvaluationRange EvaluationRange = FMovieSceneEvaluationRange(RootTime.GetValue(), TickResolution);
const FMovieSceneContext Context = FMovieSceneContext(EvaluationRange, PlaybackStatus).SetHasJumped(true);
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PreEvaluation(MovieScene, InFrame);
}
}
Sequencer->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context);
// evaluate constraints
for (const TWeakObjectPtr<UTickableConstraint>& InConstraint : AllConstraints)
{
if (InConstraint.IsValid())
{
InConstraint->Evaluate(true);
}
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->PostEvaluation(MovieScene, InFrame);
}
}
};
// avoid transacting when evaluating sequencer
TGuardValue<ITransaction*> TransactionGuard(GUndo, nullptr);
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StartBaking(MovieScene);
}
}
for (int32 Index = 0; Index < NumFrames; ++Index)
{
// evaluate animation
EvaluateAt(Frames[Index]);
// store transforms
ChildLocals[Index] = Handle->GetLocalTransform();
ChildGlobals[Index] = Handle->GetGlobalTransform();
SpaceGlobals[Index] = Constraint->GetParentGlobalTransform();
}
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
{
if (BakeHelper)
{
BakeHelper->StopBaking(MovieScene);
}
}
}
void FCompensationEvaluator::ComputeCurrentTransforms(UWorld* InWorld)
{
ChildLocals = ChildGlobals = SpaceGlobals = {FTransform::Identity};
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
const TArray< ConstraintPtr > Constraints = GetHandleTransformConstraints(InWorld);
if (Constraints.IsEmpty())
{
return;
}
for (const TWeakObjectPtr<UTickableConstraint>& InConstraint : Constraints)
{
if (InConstraint.IsValid())
{
InConstraint->Evaluate();
}
}
ChildLocals[0] = Handle->GetLocalTransform();
ChildGlobals[0] = Handle->GetGlobalTransform();
auto GetLastActiveConstraint = [Constraints]()
{
// find last active constraint in the list that is different than the one we want to compensate for
const int32 LastActiveIndex = UE::TransformConstraintUtil::GetLastActiveConstraintIndex(Constraints);
// if found, return its parent global transform
return LastActiveIndex > INDEX_NONE ? Cast<UTickableTransformConstraint>(Constraints[LastActiveIndex]) : nullptr;
};
if (const UTickableTransformConstraint* LastConstraint = GetLastActiveConstraint())
{
SpaceGlobals[0] = LastConstraint->GetParentGlobalTransform();
TOptional<FTransform> Relative =
UE::TransformConstraintUtil::GetConstraintsRelativeTransform(Constraints, ChildLocals[0], ChildGlobals[0]);
if (Relative)
{
ChildLocals[0] = *Relative;
}
}
}
const TArray< TWeakObjectPtr<UTickableConstraint> > FCompensationEvaluator::GetHandleTransformConstraints(UWorld* InWorld) const
{
using ConstraintPtr = TWeakObjectPtr<UTickableConstraint>;
if (Handle)
{
// get sorted transform constraints
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld);
static constexpr bool bSorted = true;
const TArray< ConstraintPtr > Constraints = Controller.GetParentConstraints(Handle->GetHash(), bSorted);
return Constraints.FilterByPredicate([](const ConstraintPtr& InConstraint)
{
return IsValid(InConstraint.Get()) && InConstraint.Get()->IsA<UTickableTransformConstraint>();
});
}
static const TArray< ConstraintPtr > DummyArray;
return DummyArray;
}
bool FMovieSceneConstraintChannelHelper::bDoNotCompensate = false;
/*
*
* Constraint Channel Helpers
*
*/
void FMovieSceneConstraintChannelHelper::HandleConstraintRemoved(
UTickableConstraint* InConstraint,
const FMovieSceneConstraintChannel* InConstraintChannel,
const TSharedPtr<ISequencer>& InSequencer,
UMovieSceneSection* InSection)
{
UTickableTransformConstraint* Constraint = Cast<UTickableTransformConstraint>(InConstraint);
if (!Constraint || !Constraint->NeedsCompensation() || !InConstraintChannel || !InSection)
{
return;
}
InSection->Modify();
TGuardValue<bool> CompensateGuard(bDoNotCompensate, true);
if (const UTransformableHandle* ControlHandle =Constraint->ChildTRSHandle)
{
const TArrayView<const FFrameNumber> Times = InConstraintChannel->GetData().GetTimes();
if (Times.IsEmpty())
{
return;
}
// get transform channels
const TArrayView<FMovieSceneFloatChannel*> FloatTransformChannels = ControlHandle->GetFloatChannels(InSection);
const TArrayView<FMovieSceneDoubleChannel*> DoubleTransformChannels = ControlHandle->GetDoubleChannels(InSection);
// get frames after this time
TArray<FFrameNumber> FramesToCompensate;
if (FloatTransformChannels.Num() > 0)
{
GetFramesWithinActiveState(*InConstraintChannel, FloatTransformChannels, FramesToCompensate);
}
else
{
GetFramesWithinActiveState(*InConstraintChannel, DoubleTransformChannels, FramesToCompensate);
}
// do the compensation
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
FCompensationEvaluator Evaluator(Constraint);
const FCompensationEvaluator::FEvalParameters EvalParams(InSequencer, FramesToCompensate);
Evaluator.ComputeLocalTransformsBeforeDeletion(World, EvalParams);
const TArray<FTransform>& ChildLocals = Evaluator.ChildLocals;
const EMovieSceneTransformChannel ChannelsToKey = Constraint->GetChannelsToKey();
const FFrameRate TickResolution = InSequencer->GetFocusedTickResolution();
ControlHandle->AddTransformKeys(FramesToCompensate,
ChildLocals, ChannelsToKey, TickResolution, InSection);
// clean double keys
if (IMovieSceneConstrainedSection* Section = Cast<IMovieSceneConstrainedSection>(InSection))
{
// get constraints acting on the same child that is different that InConstraint
const TArray<FConstraintAndActiveChannel>& ConstraintChannels = Section->GetConstraintsChannels();
const TArray<FConstraintAndActiveChannel> OtherConstraints = ConstraintChannels.FilterByPredicate([Constraint](const FConstraintAndActiveChannel& Channel)
{
if (const UTickableTransformConstraint* TransformConstraint = Cast< UTickableTransformConstraint>(Channel.GetConstraint()))
{
return TransformConstraint->GetTargetHash() == Constraint->GetTargetHash() && TransformConstraint != Constraint;
}
return false;
});
// disable extra compensation when removing keys
if (OtherConstraints.IsEmpty())
{
// this was the only constraint so we can remove its double keys
const EMovieSceneKeyInterpolation KeyType = InSequencer->GetKeyInterpolation();
for (const FFrameNumber& Time: Times)
{
const FFrameNumber TimeMinusOne = Time - 1;
if (FloatTransformChannels.Num() > 0)
{
DeleteTransformKeys(FloatTransformChannels, TimeMinusOne);
//we also set the tangent at the break to the default type
ChangeKeyInterpolation(FloatTransformChannels, Time, KeyType);
}
else if (DoubleTransformChannels.Num() > 0)
{
DeleteTransformKeys(DoubleTransformChannels, TimeMinusOne);
//we also set the tangent at the break to the default type
ChangeKeyInterpolation(DoubleTransformChannels, Time, KeyType);
}
}
}
}
}
}
void FMovieSceneConstraintChannelHelper::HandleConstraintKeyDeleted(
UTickableTransformConstraint* InConstraint,
const FMovieSceneConstraintChannel* InConstraintChannel,
const TSharedPtr<ISequencer>& InSequencer,
UMovieSceneSection* InSection,
const FFrameNumber& InTime)
{
if (bDoNotCompensate == true || IsInGameThread() == false) //this may happen in an non game thread via a parallel for when we are deleting many channels
{
return;
}
if (!InConstraint || !InConstraint->NeedsCompensation())
{
return;
}
const FFrameNumber TimeMinusOne(InTime - 1);
bool CurrentValue = false, PreviousValue = false;
InConstraintChannel->Evaluate(TimeMinusOne, PreviousValue);
InConstraintChannel->Evaluate(InTime, CurrentValue);
if (CurrentValue == PreviousValue)
{
const int32 NumKeys = InConstraintChannel->GetNumKeys();
if (NumKeys > 1)
{
return;
}
}
TGuardValue<bool> CompensateGuard(bDoNotCompensate, true);
if (const UTransformableHandle* ControlHandle = InConstraint->ChildTRSHandle)
{
// get transform channels
const TArrayView<FMovieSceneFloatChannel*> FloatTransformChannels = ControlHandle->GetFloatChannels(InSection);
const TArrayView<FMovieSceneDoubleChannel*> DoubleTransformChannels = ControlHandle->GetDoubleChannels(InSection);
// get frames after this time
TArray<FFrameNumber> FramesToCompensate;
if (FloatTransformChannels.Num() > 0)
{
GetFramesAfter(*InConstraintChannel, InTime, FloatTransformChannels, FramesToCompensate);
}
else
{
GetFramesAfter(*InConstraintChannel, InTime, DoubleTransformChannels, FramesToCompensate);
}
// do the compensation
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
FCompensationEvaluator Evaluator(InConstraint);
FCompensationEvaluator::FEvalParameters EvalParams(InSequencer, FramesToCompensate);
EvalParams.bToActive = PreviousValue;
Evaluator.ComputeLocalTransforms(World, EvalParams);
TArray<FTransform>& ChildLocals = Evaluator.ChildLocals;
//turn off constraint, if we delete the key it may not evaluate to false
InConstraint->SetActive(false);
if (ChildLocals.Num() < 2)
{
return;
}
ChildLocals.RemoveAt(0);
const EMovieSceneTransformChannel ChannelsToKey = InConstraint->GetChannelsToKey();
const FFrameRate TickResolution = InSequencer->GetFocusedTickResolution();
ControlHandle->AddTransformKeys(FramesToCompensate,
ChildLocals, ChannelsToKey, TickResolution, InSection);
// now delete any extra TimeMinusOne
if (FloatTransformChannels.Num() > 0)
{
FMovieSceneConstraintChannelHelper::DeleteTransformKeys(FloatTransformChannels, TimeMinusOne);
}
else
{
FMovieSceneConstraintChannelHelper::DeleteTransformKeys(DoubleTransformChannels, TimeMinusOne);
}
}
}
void FMovieSceneConstraintChannelHelper::HandleConstraintKeyMoved(
const UTickableTransformConstraint* InConstraint,
const FMovieSceneConstraintChannel* InConstraintChannel,
UMovieSceneSection* InSection,
const FFrameNumber& InCurrentFrame, const FFrameNumber& InNextFrame)
{
const FFrameNumber Delta = InNextFrame - InCurrentFrame;
if (Delta == 0)
{
return;
}
if (!InConstraint || !InConstraintChannel || !InSection)
{
return;
}
if (const UTransformableHandle* ControlHandle = InConstraint->ChildTRSHandle)
{
// get transform channels
const TArrayView<FMovieSceneFloatChannel*> FloatTransformChannels = ControlHandle->GetFloatChannels(InSection);
const TArrayView<FMovieSceneDoubleChannel*> DoubleTransformChannels = ControlHandle->GetDoubleChannels(InSection);
// move them
if (FloatTransformChannels.Num() > 0)
{
FMovieSceneConstraintChannelHelper::MoveTransformKeys(FloatTransformChannels, InCurrentFrame, InNextFrame);
}
else
{
FMovieSceneConstraintChannelHelper::MoveTransformKeys(DoubleTransformChannels, InCurrentFrame, InNextFrame);
}
}
}
namespace UE::Constraints::Private
{
void ShowSpawnableWarning(const FText& InNotification)
{
// output log notification
UE_LOG(LogTemp, Warning, TEXT("%s"), *InNotification.ToString());
// editor notification
FNotificationInfo Info(InNotification);
Info.Image = FAppStyle::GetBrush(TEXT("MessageLog.Warning"));
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info);
}
static void ShowSpawnableDiscrepancyWarning(
const TObjectPtr<UTransformableHandle>& InSpawnableHandle, const TObjectPtr<UTransformableHandle>& InNonSpawnableHandle)
{
if (!IsValid(InSpawnableHandle) || !IsValid(InNonSpawnableHandle))
{
return;
}
FFormatNamedArguments Args;
Args.Add(TEXT("Spawnable"), FText::FromString(InSpawnableHandle->GetLabel()));
Args.Add(TEXT("NonSpawnable"), FText::FromString(InNonSpawnableHandle->GetLabel()));
const FText Notification = FText::Format(
NSLOCTEXT("FMovieSceneConstraintChannelHelper", "ChildParentSpawnableDiscrepancy",
"Object '{Spawnable}' is spawnable whereas '{NonSpawnable}' is not.\n"
"Consider making '{NonSpawnable}' spawnable to avoid a future undefined state of this constraint."), Args);
ShowSpawnableWarning(Notification);
}
static void ShowNonSpawnableWarning(
const TObjectPtr<UTransformableHandle>& InNonSpawnableHandle0, const TObjectPtr<UTransformableHandle>& InNonSpawnableHandle1)
{
if (!IsValid(InNonSpawnableHandle0) || !IsValid(InNonSpawnableHandle1))
{
return;
}
FFormatNamedArguments Args;
Args.Add(TEXT("NonSpawnable0"), FText::FromString(InNonSpawnableHandle0->GetLabel()));
Args.Add(TEXT("NonSpawnable1"), FText::FromString(InNonSpawnableHandle1->GetLabel()));
const FText Notification = FText::Format(
NSLOCTEXT("FMovieSceneConstraintChannelHelper", "ChildParentNonSpawnable",
"Objects '{NonSpawnable0}' and '{NonSpawnable1}' are not spawnable.\n"
"Consider making them spawnable if you want them, as well as this constraint, to be fully integrated to your level sequence."), Args);
ShowSpawnableWarning(Notification);
}
}
bool FMovieSceneConstraintChannelHelper::AddConstraintToSequencer(
const TSharedPtr<ISequencer>& InSequencer,
UTickableTransformConstraint* InConstraint)
{
if (!InSequencer.IsValid() || !InSequencer->GetFocusedMovieSceneSequence())
{
return false;
}
ITransformConstraintChannelInterface* Interface = InConstraint ? GetHandleInterface(InConstraint->ChildTRSHandle) : nullptr;
if (!Interface)
{
return false;
}
const bool bIsChildSpawnable = IsHandleSpawnable(InSequencer, InConstraint->ChildTRSHandle);
const bool bIsParentSpawnable = IsHandleSpawnable(InSequencer, InConstraint->ParentTRSHandle);
// create bindings before smart keying so added to spawn copies
CreateBindingIDForHandle(InSequencer, InConstraint->ChildTRSHandle);
CreateBindingIDForHandle(InSequencer, InConstraint->ParentTRSHandle);
// adding the child to sequencer can trigger that same function so the constraint might already be added
const bool IsOuterASection = !!Cast<IMovieSceneConstrainedSection>(InConstraint->GetOuter());
if (IsOuterASection)
{
return true;
}
// notify of spawnable discrepancy
if (bIsChildSpawnable != bIsParentSpawnable)
{
const TObjectPtr<UTransformableHandle>& SpawnableHandle = bIsChildSpawnable ? InConstraint->ChildTRSHandle : InConstraint->ParentTRSHandle;
const TObjectPtr<UTransformableHandle>& NonSpawnableHandle = bIsChildSpawnable ? InConstraint->ParentTRSHandle : InConstraint->ChildTRSHandle;
UE::Constraints::Private::ShowSpawnableDiscrepancyWarning(SpawnableHandle, NonSpawnableHandle);
}
else if (!bIsChildSpawnable && !bIsParentSpawnable)
{
const FMovieSceneObjectBindingID& ChildBindingID = InConstraint->ChildTRSHandle->ConstraintBindingID;
const FMovieSceneObjectBindingID& ParentBindingID = InConstraint->ParentTRSHandle->ConstraintBindingID;
if (ChildBindingID.IsValid() && ParentBindingID.IsValid())
{
UE::Constraints::Private::ShowNonSpawnableWarning(InConstraint->ChildTRSHandle, InConstraint->ParentTRSHandle);
}
}
const FFrameRate TickResolution = InSequencer->GetFocusedTickResolution();
const FFrameTime FrameTime = InSequencer->GetLocalTime().ConvertTo(TickResolution);
const FFrameNumber Time = FrameTime.GetFrame();
return Interface->SmartConstraintKey(InConstraint, TOptional<bool>(), Time, InSequencer);
}
bool FMovieSceneConstraintChannelHelper::SmartConstraintKey(
const TSharedPtr<ISequencer>& InSequencer,
UTickableTransformConstraint* InConstraint,
const TOptional<bool>& InOptActive,
const TOptional<FFrameNumber>& InOptFrameTime)
{
if (!InSequencer.IsValid() || !InSequencer->GetFocusedMovieSceneSequence())
{
return false;
}
ITransformConstraintChannelInterface* Interface = GetHandleInterface(InConstraint->ChildTRSHandle);
if (!Interface)
{
return false;
}
FFrameNumber Time;
if (InOptFrameTime.IsSet())
{
Time = InOptFrameTime.GetValue();
}
else
{
const FFrameRate TickResolution = InSequencer->GetFocusedTickResolution();
const FFrameTime FrameTime = InSequencer->GetLocalTime().ConvertTo(TickResolution);
Time = FrameTime.GetFrame();
}
//create bindings before smart keying so added to spawn copies
CreateBindingIDForHandle(InSequencer, InConstraint->ChildTRSHandle);
CreateBindingIDForHandle(InSequencer, InConstraint->ParentTRSHandle);
const bool bSucceeded = Interface->SmartConstraintKey(InConstraint, InOptActive, Time, InSequencer);
return bSucceeded;
}
void FMovieSceneConstraintChannelHelper::Compensate(
const TSharedPtr<ISequencer>& InSequencer,
const UTickableTransformConstraint* InConstraint,
const TOptional<FFrameNumber>& InOptTime,
const bool bCompPreviousTick)
{
if (!InSequencer.IsValid() || !InSequencer->GetFocusedMovieSceneSequence())
{
return;
}
const TObjectPtr<UTransformableHandle>& Handle = InConstraint->ChildTRSHandle;
ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle);
if (!Interface)
{
return;
}
IMovieSceneConstrainedSection* Section = Cast<IMovieSceneConstrainedSection>(Interface->GetHandleConstraintSection(Handle, InSequencer));
const UWorld* World = Interface->GetHandleWorld(Handle);
if (!Section || !IsValid(World))
{
return;
}
CompensateIfNeeded(InSequencer, Section, InOptTime, bCompPreviousTick, Handle->GetHash());
}
void FMovieSceneConstraintChannelHelper::CompensateIfNeeded(
const TSharedPtr<ISequencer>& InSequencer,
IMovieSceneConstrainedSection* ConstraintSection,
const TOptional<FFrameNumber>& OptionalTime,
const bool bCompPreviousTick,
const int32 InChildHash)
{
if (bDoNotCompensate)
{
return;
}
TGuardValue<bool> CompensateGuard(bDoNotCompensate, true);
// Frames to compensate
TArray<FFrameNumber> OptionalTimeArray;
if (OptionalTime.IsSet())
{
OptionalTimeArray.Add(OptionalTime.GetValue());
}
auto GetConstraintTimesToCompensate = [&OptionalTimeArray](const FConstraintAndActiveChannel& Channel)->TArrayView<const FFrameNumber>
{
if (OptionalTimeArray.IsEmpty())
{
return Channel.ActiveChannel.GetData().GetTimes();
}
return OptionalTimeArray;
};
// gather all transform constraints' channels for
const TArray<FConstraintAndActiveChannel>& ConstraintChannels = ConstraintSection->GetConstraintsChannels();
TArray<FConstraintAndActiveChannel> TransformConstraintsChannels;
Algo::CopyIf(ConstraintChannels, TransformConstraintsChannels,
[InChildHash](const FConstraintAndActiveChannel& InChannel)
{
if (!InChannel.GetConstraint().Get())
{
return false;
}
if ((InChildHash != INDEX_NONE) && (InChannel.GetConstraint()->GetTargetHash() != InChildHash))
{
return false;
}
const UTickableTransformConstraint* Constraint = Cast<UTickableTransformConstraint>(InChannel.GetConstraint().Get());
//if no InChildHash specified(== INDEX_NONE) then do all!
return Constraint && (InChildHash == INDEX_NONE || Constraint->GetTargetHash() == InChildHash) && Constraint->NeedsCompensation();
}
);
// we only need to treat one single constraint per child as FCompensationEvaluator::ComputeCompensation will
// compensate within the last active constraint's space
using CompensationData = TPair< UTickableTransformConstraint*, TArray<FFrameNumber> >;
TArray< CompensationData > ToCompensate;
// store constraints and times where compensation is needed
for (const FConstraintAndActiveChannel& Channel : TransformConstraintsChannels)
{
const TArrayView<const FFrameNumber> FramesToCompensate = GetConstraintTimesToCompensate(Channel);
for (const FFrameNumber& Time : FramesToCompensate)
{
const FFrameNumber TimeMinusOne(Time - 1);
bool CurrentValue = false, PreviousValue = false;
Channel.ActiveChannel.Evaluate(TimeMinusOne, PreviousValue);
Channel.ActiveChannel.Evaluate(Time, CurrentValue);
if (CurrentValue != PreviousValue) //if they are the same no need to do anything
{
UTickableTransformConstraint* Constraint = Cast<UTickableTransformConstraint>(Channel.GetConstraint().Get());
// is the child already in that array?
int32 DataIndex = ToCompensate.IndexOfByPredicate([Constraint](const CompensationData& InData)
{
return InData.Key->GetTargetHash() == Constraint->GetTargetHash();
});
// if not, add the constraint
if (DataIndex == INDEX_NONE)
{
DataIndex = ToCompensate.Emplace(Constraint, TArray<FFrameNumber>() );
}
// store the time it needs to be compensated at
TArray<FFrameNumber>& Times = ToCompensate[DataIndex].Value;
Times.AddUnique(Time);
}
}
}
// compensate
bool bNeedsEvaluation = false;
for (const CompensationData& Data: ToCompensate)
{
UTickableTransformConstraint* Constraint = Data.Key;
const TObjectPtr<UTransformableHandle>& Handle = Constraint->ChildTRSHandle;
if (ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle))
{
UWorld* World = Interface->GetHandleWorld(Handle);
FCompensationEvaluator Evaluator(Constraint);
const EMovieSceneTransformChannel ChannelsToKey = Constraint->GetChannelsToKey();
for (const FFrameNumber& Time : Data.Value)
{
const FFrameNumber EvalTime = bCompPreviousTick ? Time : Time - 1;
const FFrameNumber SetTime = bCompPreviousTick ? Time - 1 : Time;
// compute transform to set
// if switching from active to inactive then we must add a key at T-1 in the constraint space
// if switching from inactive to active then we must add a key at T-1 in the previous constraint or parent space
Evaluator.ComputeCompensation(World, InSequencer, EvalTime);
const TArray<FTransform>& LocalTransforms = Evaluator.ChildLocals;
Interface->AddHandleTransformKeys(InSequencer, Handle, { SetTime }, LocalTransforms, ChannelsToKey);
bNeedsEvaluation = true;
}
}
}
if (bNeedsEvaluation)
{
InSequencer->ForceEvaluate();
}
}
FConstraintSections FMovieSceneConstraintChannelHelper::GetConstraintSectionAndChannel(
const UTickableTransformConstraint* InConstraint,
const TSharedPtr<ISequencer>& InSequencer)
{
FConstraintSections ReturnValue;
if (InSequencer.IsValid() == false)
{
return ReturnValue;
}
const TObjectPtr<UTransformableHandle>& ChildHandle = InConstraint->ChildTRSHandle;
const FConstraintChannelInterfaceRegistry& InterfaceRegistry = FConstraintChannelInterfaceRegistry::Get();
ReturnValue.Interface = InterfaceRegistry.FindConstraintChannelInterface(ChildHandle->GetClass());
if (!ReturnValue.Interface)
{
return ReturnValue;
}
//get the section to be used later to delete the extra transform keys at the frame -1 times, abort if not there for some reason
ReturnValue.ConstraintSection = ReturnValue.Interface->GetHandleConstraintSection(ChildHandle, InSequencer);
ReturnValue.ChildTransformSection = ReturnValue.Interface->GetHandleSection(ChildHandle, InSequencer);
ITransformConstraintChannelInterface* ParentInterface = InterfaceRegistry.FindConstraintChannelInterface(InConstraint->ParentTRSHandle->GetClass());
if (ParentInterface)
{
ReturnValue.ParentTransformSection = ParentInterface->GetHandleSection(InConstraint->ParentTRSHandle, InSequencer);
}
IMovieSceneConstrainedSection* ConstrainedSection = Cast<IMovieSceneConstrainedSection>(ReturnValue.ConstraintSection);
if (ConstrainedSection == nullptr )
{
return ReturnValue;
}
ReturnValue.ActiveChannel = ConstrainedSection->GetConstraintChannel(InConstraint->ConstraintID);
return ReturnValue;
}
void FMovieSceneConstraintChannelHelper::GetTransformFramesForConstraintHandles(
const UTickableTransformConstraint* InConstraint,
const TSharedPtr<ISequencer>& InSequencer,
const FFrameNumber& StartFrame,
const FFrameNumber& EndFrame,
TArray<FFrameNumber>& OutFramesToBake)
{
if ((InConstraint == nullptr) || (InConstraint->ChildTRSHandle == nullptr) || (InConstraint->ParentTRSHandle == nullptr))
{
return;
}
FConstraintSections ConstraintSections = FMovieSceneConstraintChannelHelper::GetConstraintSectionAndChannel(
InConstraint, InSequencer);
if (ConstraintSections.ChildTransformSection)
{
const TArrayView<FMovieSceneFloatChannel*> FloatTransformChannels = InConstraint->ChildTRSHandle->GetFloatChannels(ConstraintSections.ChildTransformSection);
TArray<FFrameNumber> TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes(
FloatTransformChannels, StartFrame, EndFrame);
//add transforms keys to bake
{
for (FFrameNumber& Frame : TransformFrameTimes)
{
OutFramesToBake.Add(Frame);
}
}
const TArrayView<FMovieSceneDoubleChannel*> DoubleTransformChannels = InConstraint->ChildTRSHandle->GetDoubleChannels(ConstraintSections.ChildTransformSection);
TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes(
DoubleTransformChannels, StartFrame, EndFrame);
//add transforms keys to bake
{
for (FFrameNumber& Frame : TransformFrameTimes)
{
OutFramesToBake.Add(Frame);
}
}
}
if (ConstraintSections.ParentTransformSection)
{
const TArrayView<FMovieSceneFloatChannel*> FloatTransformChannels = InConstraint->ParentTRSHandle->GetFloatChannels(ConstraintSections.ParentTransformSection);
TArray<FFrameNumber> TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes(
FloatTransformChannels, StartFrame, EndFrame);
//add transforms keys to bake
{
for (FFrameNumber& Frame : TransformFrameTimes)
{
OutFramesToBake.Add(Frame);
}
}
const TArrayView<FMovieSceneDoubleChannel*> DoubleTransformChannels = InConstraint->ParentTRSHandle->GetDoubleChannels(ConstraintSections.ParentTransformSection);
TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes(
DoubleTransformChannels, StartFrame, EndFrame);
//add transforms keys to bake
{
for (FFrameNumber& Frame : TransformFrameTimes)
{
OutFramesToBake.Add(Frame);
}
}
}
}
ITransformConstraintChannelInterface* FMovieSceneConstraintChannelHelper::GetHandleInterface(const UTransformableHandle* InHandle)
{
if (!IsValid(InHandle) || !InHandle->IsValid())
{
return nullptr;
}
const FConstraintChannelInterfaceRegistry& InterfaceRegistry = FConstraintChannelInterfaceRegistry::Get();
return InterfaceRegistry.FindConstraintChannelInterface(InHandle->GetClass());
}
bool FMovieSceneConstraintChannelHelper::IsHandleSpawnable(const TSharedPtr<ISequencer>& InSequencer, const UTransformableHandle* InHandle)
{
if (!InHandle || !InSequencer.IsValid())
{
return false;
}
if (USceneComponent* SceneComponent = Cast<USceneComponent>(InHandle->GetTarget().Get()))
{
if (AActor* Actor = SceneComponent->GetTypedOuter<AActor>())
{
const TOptional<FMovieSceneSpawnableAnnotation> Spawnable = FMovieSceneSpawnableAnnotation::Find(Actor);
return Spawnable.IsSet();
}
}
return false;
}
void FMovieSceneConstraintChannelHelper::CreateBindingIDForHandle(const TSharedPtr<ISequencer>& InSequencer, UTransformableHandle* InHandle)
{
if (InHandle == nullptr || InSequencer.IsValid() == false)
{
return;
}
// make sure object is in sequencer or binding id will be empty we won't resolve the binding
static constexpr bool bCreateHandleIfMissing = true;
if (USceneComponent* SceneComponent = Cast<USceneComponent>(InHandle->GetTarget().Get()))
{
if (AActor* Actor = SceneComponent->GetTypedOuter<AActor>())
{
TOptional<FMovieSceneSpawnableAnnotation> Spawnable = FMovieSceneSpawnableAnnotation::Find(Actor);
if (Spawnable.IsSet())
{
// Check whether the spawnable is underneath the current sequence, if so, we can remap it to a local sequence ID
InHandle->ConstraintBindingID = UE::MovieScene::FRelativeObjectBindingID(InSequencer->GetFocusedTemplateID(), Spawnable->SequenceID, Spawnable->ObjectBindingID,
*(InSequencer.Get()));
}
else
{
const FGuid Guid = InSequencer->GetHandleToObject(Actor, bCreateHandleIfMissing);
InHandle->ConstraintBindingID = UE::MovieScene::FRelativeObjectBindingID(Guid);
}
// in the context of actors with multiple scene components (such as BPs with multiple skeletal meshes, for example)
// the ID must be the SceneComponent handle instead of the Actor handle.
// this will also ensure that the binding for the component is created, if this has not yet been done.
if (InHandle->ConstraintBindingID.IsValid() && SceneComponent != Actor->GetRootComponent())
{
const FGuid ComponentHandle = InSequencer->GetHandleToObject(SceneComponent, bCreateHandleIfMissing);
if (ComponentHandle.IsValid())
{
InHandle->ConstraintBindingID = UE::MovieScene::FRelativeObjectBindingID(ComponentHandle);
}
}
}
}
}
void FMovieSceneConstraintChannelHelper::HandleConstraintPropertyChanged(
UTickableTransformConstraint* InConstraint,
const FMovieSceneConstraintChannel& InActiveChannel,
const FPropertyChangedEvent& InPropertyChangedEvent,
const TSharedPtr<ISequencer>& InSequencer,
UMovieSceneSection* InSection)
{
if (!InConstraint || !InSection || !InSequencer.IsValid())
{
return;
}
const FName PropertyName = InPropertyChangedEvent.GetPropertyName();
if (PropertyName == UTickableParentConstraint::GetScalingPropertyName())
{
return CompensateScale(Cast<UTickableParentConstraint>(InConstraint), InActiveChannel, InSequencer, InSection);
}
auto IsOffsetProperty = [](const FName InPropertyName)
{
return InPropertyName == GET_MEMBER_NAME_CHECKED(UTickableTranslationConstraint, OffsetTranslation) ||
InPropertyName == GET_MEMBER_NAME_CHECKED(UTickableRotationConstraint, OffsetRotation) ||
InPropertyName == GET_MEMBER_NAME_CHECKED(UTickableScaleConstraint, OffsetScale) ||
InPropertyName == GET_MEMBER_NAME_CHECKED(UTickableParentConstraint, OffsetTransform);
};
if (IsOffsetProperty(PropertyName) || IsOffsetProperty(InPropertyChangedEvent.GetMemberPropertyName()))
{
return HandleOffsetChanged(InConstraint, InActiveChannel, InSequencer);
}
}
void FMovieSceneConstraintChannelHelper::CompensateScale(
UTickableParentConstraint* InParentConstraint,
const FMovieSceneConstraintChannel& InActiveChannel,
const TSharedPtr<ISequencer>& InSequencer,
UMovieSceneSection* InSection)
{
if (!InParentConstraint)
{
return;
}
TObjectPtr<UTransformableHandle> Handle = InParentConstraint->ChildTRSHandle;
ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle);
if (!Interface)
{
return;
}
const TArrayView<const FFrameNumber> Times = InActiveChannel.GetTimes();
if (Times.IsEmpty())
{
return;
}
// get transform channels
const TArrayView<FMovieSceneFloatChannel*> FloatTransformChannels = Handle->GetFloatChannels(InSection);
const TArrayView<FMovieSceneDoubleChannel*> DoubleTransformChannels = Handle->GetDoubleChannels(InSection);
// get frames after this time
TArray<FFrameNumber> ActiveTimes;
if (!FloatTransformChannels.IsEmpty())
{
GetFramesWithinActiveState(InActiveChannel, FloatTransformChannels, ActiveTimes);
}
else
{
GetFramesWithinActiveState(InActiveChannel, DoubleTransformChannels, ActiveTimes);
}
if (ActiveTimes.IsEmpty())
{
return;
}
const bool bRefScalingValue = InParentConstraint->IsScalingEnabled();
// if scaling has been enabled (bRefScalingValue == true), it means that it was not before the property has changed so
// the current scale channels values represent the local scale values of the handle
// if scaling has been disabled (bRefScalingValue == false), it means that it was before the property has changed so
// the current scale channels values represent the offset in the constraint space
InParentConstraint->SetScaling(!bRefScalingValue);
TGuardValue<bool> CompensateGuard(bDoNotCompensate, true);
InSection->Modify();
FCompensationEvaluator Evaluator(InParentConstraint);
const FCompensationEvaluator::FEvalParameters EvalParams(InSequencer, ActiveTimes);
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
Evaluator.CacheTransforms(World, EvalParams);
TArray<FTransform>& ChildLocals = Evaluator.ChildLocals;
const TArray<FTransform>& ChildGlobals = Evaluator.ChildGlobals;
const TArray<FTransform>& SpaceGlobals = Evaluator.SpaceGlobals;
const int32 NumTransforms = ChildLocals.Num();
if (bRefScalingValue)
{
// local scale values have to be switched to the constraint space to represent the offset
for (int Index = 0; Index < NumTransforms; Index++)
{
FTransform& ChildLocal = ChildLocals[Index];
const FTransform Offset = ChildGlobals[Index].GetRelativeTransform(SpaceGlobals[Index]);
ChildLocal.SetScale3D(Offset.GetScale3D());
}
}
// else ChildLocals already represents the data that needs to be keyed as it is the result of
// the constraint evaluation so it just needs to be keyed
// add keys
Interface->AddHandleTransformKeys(InSequencer, Handle, ActiveTimes, ChildLocals, EMovieSceneTransformChannel::Scale);
// reset scaling to reference value
InParentConstraint->SetScaling(bRefScalingValue);
}
void FMovieSceneConstraintChannelHelper::HandleOffsetChanged(
UTickableTransformConstraint* InConstraint,
const FMovieSceneConstraintChannel& InActiveChannel,
const TSharedPtr<ISequencer>& InSequencer)
{
if (!InConstraint || !InSequencer.IsValid())
{
return;
}
TObjectPtr<UTransformableHandle> Handle = InConstraint->ChildTRSHandle;
ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle);
if (!Interface)
{
return;
}
const TArrayView<const FFrameNumber> Times = InActiveChannel.GetTimes();
if (Times.IsEmpty())
{
return;
}
const FFrameRate TickResolution = InSequencer->GetFocusedTickResolution();
const FFrameTime FrameTime = InSequencer->GetLocalTime().ConvertTo(TickResolution);
const FFrameNumber Time = FrameTime.GetFrame();
bool bIsActive = false; InActiveChannel.Evaluate(Time, bIsActive);
if (bIsActive)
{
const EMovieSceneTransformChannel Channels = InConstraint->GetChannelsToKey();
// compute the current local value
FCompensationEvaluator Evaluator(InConstraint);
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
Evaluator.ComputeCurrentTransforms(World);
// update key
Interface->AddHandleTransformKeys(InSequencer, Handle, {Time}, {Evaluator.ChildLocals[0]}, Channels);
// force evaluation so that new local values are evaluated before the constraint
InSequencer->ForceEvaluate();
}
}