// 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; const TArray< ConstraintPtr > Constraints = GetHandleTransformConstraints(InWorld); if (Constraints.IsEmpty()) { return; } const TArray 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(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 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 TransactionGuard(GUndo, nullptr); const TArray& 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 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 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& 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 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 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; 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(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 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 AllConstraints = Controller.GetAllConstraints(bSorted); // avoid transacting when evaluating sequencer TGuardValue TransactionGuard(GUndo, nullptr); const TArray& 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 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& 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 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; const TArray Constraints = GetHandleTransformConstraints(InWorld); const TArray 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(ConstraintsMinusThis[LastActiveIndex]) : nullptr; }; // get all constraints for evaluation const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld); static constexpr bool bSorted = true; const TArray 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 Frames(InEvalParams.Frames); const int32 NumFrames = Frames.Num(); ChildLocals.SetNum(NumFrames); ChildGlobals.SetNum(NumFrames); SpaceGlobals.SetNum(NumFrames); // avoid transacting when evaluating sequencer TGuardValue TransactionGuard(GUndo, nullptr); const TArray& 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 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& 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 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& InSequencer, const FFrameNumber& InTime) { using ConstraintPtr = TWeakObjectPtr; const TArray 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(Constraints[LastActiveIndex]) : nullptr; }; // get all constraints for evaluation const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld); static constexpr bool bSorted = true; const TArray AllConstraints = Controller.GetAllConstraints(bSorted); // avoid transacting when evaluating sequencer TGuardValue TransactionGuard(GUndo, nullptr); UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); const TArray& 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 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& 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 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; // get all constraints for evaluation const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(InWorld); static constexpr bool bSorted = true; const TArray 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 Frames(InEvalParams.Frames); const int32 NumFrames = Frames.Num(); ChildLocals.SetNum(NumFrames); ChildGlobals.SetNum(NumFrames); SpaceGlobals.SetNum(NumFrames); const TArray& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers(); FMovieSceneInverseSequenceTransform LocalToRootTransform = Sequencer->GetFocusedMovieSceneSequenceTransform().Inverse(); auto EvaluateAt = [&](FFrameNumber InFrame) { TOptional 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& InConstraint : AllConstraints) { if (InConstraint.IsValid()) { InConstraint->Evaluate(true); } } for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->PostEvaluation(MovieScene, InFrame); } } }; // avoid transacting when evaluating sequencer TGuardValue 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; const TArray< ConstraintPtr > Constraints = GetHandleTransformConstraints(InWorld); if (Constraints.IsEmpty()) { return; } for (const TWeakObjectPtr& 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(Constraints[LastActiveIndex]) : nullptr; }; if (const UTickableTransformConstraint* LastConstraint = GetLastActiveConstraint()) { SpaceGlobals[0] = LastConstraint->GetParentGlobalTransform(); TOptional Relative = UE::TransformConstraintUtil::GetConstraintsRelativeTransform(Constraints, ChildLocals[0], ChildGlobals[0]); if (Relative) { ChildLocals[0] = *Relative; } } } const TArray< TWeakObjectPtr > FCompensationEvaluator::GetHandleTransformConstraints(UWorld* InWorld) const { using ConstraintPtr = TWeakObjectPtr; 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(); }); } static const TArray< ConstraintPtr > DummyArray; return DummyArray; } bool FMovieSceneConstraintChannelHelper::bDoNotCompensate = false; /* * * Constraint Channel Helpers * */ void FMovieSceneConstraintChannelHelper::HandleConstraintRemoved( UTickableConstraint* InConstraint, const FMovieSceneConstraintChannel* InConstraintChannel, const TSharedPtr& InSequencer, UMovieSceneSection* InSection) { UTickableTransformConstraint* Constraint = Cast(InConstraint); if (!Constraint || !Constraint->NeedsCompensation() || !InConstraintChannel || !InSection) { return; } InSection->Modify(); TGuardValue CompensateGuard(bDoNotCompensate, true); if (const UTransformableHandle* ControlHandle =Constraint->ChildTRSHandle) { const TArrayView Times = InConstraintChannel->GetData().GetTimes(); if (Times.IsEmpty()) { return; } // get transform channels const TArrayView FloatTransformChannels = ControlHandle->GetFloatChannels(InSection); const TArrayView DoubleTransformChannels = ControlHandle->GetDoubleChannels(InSection); // get frames after this time TArray 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& 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(InSection)) { // get constraints acting on the same child that is different that InConstraint const TArray& ConstraintChannels = Section->GetConstraintsChannels(); const TArray 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& 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 CompensateGuard(bDoNotCompensate, true); if (const UTransformableHandle* ControlHandle = InConstraint->ChildTRSHandle) { // get transform channels const TArrayView FloatTransformChannels = ControlHandle->GetFloatChannels(InSection); const TArrayView DoubleTransformChannels = ControlHandle->GetDoubleChannels(InSection); // get frames after this time TArray 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& 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 FloatTransformChannels = ControlHandle->GetFloatChannels(InSection); const TArrayView 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& InSpawnableHandle, const TObjectPtr& 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& InNonSpawnableHandle0, const TObjectPtr& 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& 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(InConstraint->GetOuter()); if (IsOuterASection) { return true; } // notify of spawnable discrepancy if (bIsChildSpawnable != bIsParentSpawnable) { const TObjectPtr& SpawnableHandle = bIsChildSpawnable ? InConstraint->ChildTRSHandle : InConstraint->ParentTRSHandle; const TObjectPtr& 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(), Time, InSequencer); } bool FMovieSceneConstraintChannelHelper::SmartConstraintKey( const TSharedPtr& InSequencer, UTickableTransformConstraint* InConstraint, const TOptional& InOptActive, const TOptional& 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& InSequencer, const UTickableTransformConstraint* InConstraint, const TOptional& InOptTime, const bool bCompPreviousTick) { if (!InSequencer.IsValid() || !InSequencer->GetFocusedMovieSceneSequence()) { return; } const TObjectPtr& Handle = InConstraint->ChildTRSHandle; ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle); if (!Interface) { return; } IMovieSceneConstrainedSection* Section = Cast(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& InSequencer, IMovieSceneConstrainedSection* ConstraintSection, const TOptional& OptionalTime, const bool bCompPreviousTick, const int32 InChildHash) { if (bDoNotCompensate) { return; } TGuardValue CompensateGuard(bDoNotCompensate, true); // Frames to compensate TArray OptionalTimeArray; if (OptionalTime.IsSet()) { OptionalTimeArray.Add(OptionalTime.GetValue()); } auto GetConstraintTimesToCompensate = [&OptionalTimeArray](const FConstraintAndActiveChannel& Channel)->TArrayView { if (OptionalTimeArray.IsEmpty()) { return Channel.ActiveChannel.GetData().GetTimes(); } return OptionalTimeArray; }; // gather all transform constraints' channels for const TArray& ConstraintChannels = ConstraintSection->GetConstraintsChannels(); TArray 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(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 >; TArray< CompensationData > ToCompensate; // store constraints and times where compensation is needed for (const FConstraintAndActiveChannel& Channel : TransformConstraintsChannels) { const TArrayView 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(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() ); } // store the time it needs to be compensated at TArray& Times = ToCompensate[DataIndex].Value; Times.AddUnique(Time); } } } // compensate bool bNeedsEvaluation = false; for (const CompensationData& Data: ToCompensate) { UTickableTransformConstraint* Constraint = Data.Key; const TObjectPtr& 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& LocalTransforms = Evaluator.ChildLocals; Interface->AddHandleTransformKeys(InSequencer, Handle, { SetTime }, LocalTransforms, ChannelsToKey); bNeedsEvaluation = true; } } } if (bNeedsEvaluation) { InSequencer->ForceEvaluate(); } } FConstraintSections FMovieSceneConstraintChannelHelper::GetConstraintSectionAndChannel( const UTickableTransformConstraint* InConstraint, const TSharedPtr& InSequencer) { FConstraintSections ReturnValue; if (InSequencer.IsValid() == false) { return ReturnValue; } const TObjectPtr& 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(ReturnValue.ConstraintSection); if (ConstrainedSection == nullptr ) { return ReturnValue; } ReturnValue.ActiveChannel = ConstrainedSection->GetConstraintChannel(InConstraint->ConstraintID); return ReturnValue; } void FMovieSceneConstraintChannelHelper::GetTransformFramesForConstraintHandles( const UTickableTransformConstraint* InConstraint, const TSharedPtr& InSequencer, const FFrameNumber& StartFrame, const FFrameNumber& EndFrame, TArray& OutFramesToBake) { if ((InConstraint == nullptr) || (InConstraint->ChildTRSHandle == nullptr) || (InConstraint->ParentTRSHandle == nullptr)) { return; } FConstraintSections ConstraintSections = FMovieSceneConstraintChannelHelper::GetConstraintSectionAndChannel( InConstraint, InSequencer); if (ConstraintSections.ChildTransformSection) { const TArrayView FloatTransformChannels = InConstraint->ChildTRSHandle->GetFloatChannels(ConstraintSections.ChildTransformSection); TArray TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes( FloatTransformChannels, StartFrame, EndFrame); //add transforms keys to bake { for (FFrameNumber& Frame : TransformFrameTimes) { OutFramesToBake.Add(Frame); } } const TArrayView 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 FloatTransformChannels = InConstraint->ParentTRSHandle->GetFloatChannels(ConstraintSections.ParentTransformSection); TArray TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes( FloatTransformChannels, StartFrame, EndFrame); //add transforms keys to bake { for (FFrameNumber& Frame : TransformFrameTimes) { OutFramesToBake.Add(Frame); } } const TArrayView 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& InSequencer, const UTransformableHandle* InHandle) { if (!InHandle || !InSequencer.IsValid()) { return false; } if (USceneComponent* SceneComponent = Cast(InHandle->GetTarget().Get())) { if (AActor* Actor = SceneComponent->GetTypedOuter()) { const TOptional Spawnable = FMovieSceneSpawnableAnnotation::Find(Actor); return Spawnable.IsSet(); } } return false; } void FMovieSceneConstraintChannelHelper::CreateBindingIDForHandle(const TSharedPtr& 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(InHandle->GetTarget().Get())) { if (AActor* Actor = SceneComponent->GetTypedOuter()) { TOptional 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& InSequencer, UMovieSceneSection* InSection) { if (!InConstraint || !InSection || !InSequencer.IsValid()) { return; } const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); if (PropertyName == UTickableParentConstraint::GetScalingPropertyName()) { return CompensateScale(Cast(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& InSequencer, UMovieSceneSection* InSection) { if (!InParentConstraint) { return; } TObjectPtr Handle = InParentConstraint->ChildTRSHandle; ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle); if (!Interface) { return; } const TArrayView Times = InActiveChannel.GetTimes(); if (Times.IsEmpty()) { return; } // get transform channels const TArrayView FloatTransformChannels = Handle->GetFloatChannels(InSection); const TArrayView DoubleTransformChannels = Handle->GetDoubleChannels(InSection); // get frames after this time TArray 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 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& ChildLocals = Evaluator.ChildLocals; const TArray& ChildGlobals = Evaluator.ChildGlobals; const TArray& 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& InSequencer) { if (!InConstraint || !InSequencer.IsValid()) { return; } TObjectPtr Handle = InConstraint->ChildTRSHandle; ITransformConstraintChannelInterface* Interface = GetHandleInterface(Handle); if (!Interface) { return; } const TArrayView 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(); } }