// Copyright Epic Games, Inc. All Rights Reserved. #include "ComponentConstraintChannelInterface.h" #include "Evaluation/MovieSceneEvaluationTemplateInstance.h" #include "MovieSceneToolHelpers.h" #include "Transform/TransformableHandle.h" #include "Transform/TransformConstraint.h" #include "Constraints/MovieSceneConstraintChannelHelper.inl" #include "EntitySystem/MovieSceneDecompositionQuery.h" #include "Evaluation/MovieSceneEvaluationTemplateInstance.h" #include "Sections/MovieSceneConstrainedSection.h" #include "Sections/MovieScene3DTransformSection.h" #include "Systems/MovieScenePropertyInstantiator.h" #include "Tracks/MovieScene3DTransformTrack.h" #include "ConstraintsManager.h" #include "ScopedTransaction.h" UMovieSceneSection* FComponentConstraintChannelInterface::GetHandleSection(const UTransformableHandle* InHandle, const TSharedPtr& InSequencer) { if (!IsValid(InHandle) || !InHandle->IsValid()) { return nullptr; } const UTransformableComponentHandle* ComponentHandle = static_cast(InHandle); static constexpr bool bConstraintSection = false; return GetComponentSection(ComponentHandle, InSequencer, bConstraintSection); } UMovieSceneSection* FComponentConstraintChannelInterface::GetHandleConstraintSection(const UTransformableHandle* InHandle, const TSharedPtr& InSequencer) { if (!IsValid(InHandle) || !InHandle->IsValid()) { return nullptr; } const UTransformableComponentHandle* ComponentHandle = static_cast(InHandle); static constexpr bool bConstraintSection = true; return GetComponentSection(ComponentHandle, InSequencer, bConstraintSection); } UWorld* FComponentConstraintChannelInterface::GetHandleWorld(UTransformableHandle* InHandle) { if (!IsValid(InHandle) || !InHandle->IsValid()) { return nullptr; } const UTransformableComponentHandle* ComponentHandle = static_cast(InHandle); const AActor* Actor = ComponentHandle->Component->GetOwner(); return Actor ? Actor->GetWorld() : nullptr; } bool FComponentConstraintChannelInterface::SmartConstraintKey( UTickableTransformConstraint* InConstraint, const TOptional& InOptActive, const FFrameNumber& InTime, const TSharedPtr& InSequencer) { if (!IsValid(InConstraint->ChildTRSHandle) || !InConstraint->ChildTRSHandle->IsValid()) { return false; } const UTransformableComponentHandle* ComponentHandle = static_cast(InConstraint->ChildTRSHandle); UMovieScene3DTransformSection* ConstraintSection = GetComponentSection(ComponentHandle, InSequencer, true); UMovieScene3DTransformSection* TransformSection = GetComponentSection(ComponentHandle, InSequencer, false); if ((!ConstraintSection) || (!TransformSection)) { return false; } FScopedTransaction Transaction(NSLOCTEXT("Constraints", "KeyConstraintaKehy", "Key Constraint Key")); TransformSection->Modify(); ConstraintSection->Modify(); // set constraint as dynamic InConstraint->bDynamicOffset = true; // add the channel const bool bHadConstraintChannel = ConstraintSection->HasConstraintChannel(InConstraint->ConstraintID); if (!bHadConstraintChannel) { //check if static if so we need to delete it from world, will get added later again if (UConstraintsManager* Manager = InConstraint->GetTypedOuter()) { Manager->RemoveStaticConstraint(InConstraint); } ConstraintSection->AddConstraintChannel(InConstraint); if (InSequencer.IsValid()) { InSequencer->RecreateCurveEditor(); } } // add key if needed if (FConstraintAndActiveChannel* Channel = ConstraintSection->GetConstraintChannel(InConstraint->ConstraintID)) { bool ActiveValueToBeSet = false; //add key if we can and make sure the key we are setting is what we want if (CanAddKey(Channel->ActiveChannel, InTime, ActiveValueToBeSet) && (InOptActive.IsSet() == false || InOptActive.GetValue() == ActiveValueToBeSet)) { const bool bNeedsCompensation = InConstraint->NeedsCompensation(); //new for compensation TGuardValue CompensateGuard(FMovieSceneConstraintChannelHelper::bDoNotCompensate, true); TGuardValue RemoveConstraintGuard(FConstraintsManagerController::bDoNotRemoveConstraint, true); // store the frames to compensate const TArrayView Channels = ComponentHandle->GetDoubleChannels(TransformSection); TArray FramesToCompensate; if (bNeedsCompensation) { FMovieSceneConstraintChannelHelper::GetFramesToCompensate(Channel->ActiveChannel, ActiveValueToBeSet, InTime, Channels, FramesToCompensate); } else { FramesToCompensate.Add(InTime); } FCompensationEvaluator::FEvalParameters EvalParams(InSequencer, FramesToCompensate); EvalParams.bToActive = ActiveValueToBeSet; if (bHadConstraintChannel && ActiveValueToBeSet) { const TMovieSceneChannelData ChannelData = Channel->ActiveChannel.GetData(); if (ChannelData.GetTimes().IsEmpty()) { // this means that the channel already existed but there was no existing key // e.g., when the user deletes keys directly in sequencer without deleting the constraint const FFrameRate TickResolution = InSequencer->GetFocusedTickResolution(); const FFrameTime FrameTime = InSequencer->GetLocalTime().ConvertTo(TickResolution); const FFrameNumber Time = FrameTime.GetFrame(); EvalParams.bKeepCurrent = !FramesToCompensate.IsEmpty() && FramesToCompensate[0] == Time; } } // store child and space transforms for these frames FCompensationEvaluator Evaluator(InConstraint); Evaluator.ComputeLocalTransforms(GetHandleWorld(InConstraint->ChildTRSHandle), EvalParams); TArray& ChildLocals = Evaluator.ChildLocals; // store tangents at this time TArray Tangents; static constexpr int32 ChannelIndex = 0, NumChannels = 9; if (bNeedsCompensation) { EvaluateTangentAtThisTime(ChannelIndex, NumChannels, TransformSection, InTime, Tangents); } const EMovieSceneTransformChannel ChannelsToKey = InConstraint->GetChannelsToKey(); // add child's transform key at Time-1 to keep animation if (bNeedsCompensation) { const FFrameNumber TimeMinusOne(InTime - 1); TArray TransformMinusOne({ChildLocals[0]} ); if (ConstraintSection != TransformSection) { RecomposeTransforms(InSequencer, ComponentHandle->Component.Get(), TransformSection, { TimeMinusOne },TransformMinusOne); } MovieSceneToolHelpers::AddTransformKeys(TransformSection, { TimeMinusOne }, TransformMinusOne, ChannelsToKey); SetTangentsAtThisTime(ChannelIndex, NumChannels, TransformSection, TimeMinusOne, Tangents); } // add active key { TMovieSceneChannelData ChannelData = Channel->ActiveChannel.GetData(); ChannelData.AddKey(InTime, ActiveValueToBeSet); } // compensate { // we need to remove the first transforms as we store NumFrames+1 transforms ChildLocals.RemoveAt(0); if (ConstraintSection != TransformSection) { RecomposeTransforms(InSequencer, ComponentHandle->Component.Get(), TransformSection, FramesToCompensate,ChildLocals); } // add keys MovieSceneToolHelpers::AddTransformKeys(TransformSection, FramesToCompensate, ChildLocals, ChannelsToKey); // set tangents at Time if (bNeedsCompensation) { SetTangentsAtThisTime(ChannelIndex,NumChannels, TransformSection, InTime, Tangents); } } // cleanup next keys that have the same value as they are useless { TArray TimesRemoved; CleanDuplicates(Channel->ActiveChannel, InTime, ActiveValueToBeSet, TimesRemoved); // as compensation has been disabled earlier (FMovieSceneConstraintChannelHelper::bDoNotCompensate set to true), // previous transform compensation keys must be explicitly removed as HandleConstraintKeyDeleted won't do it for (const FFrameNumber& TimeToRemove: TimesRemoved) { const FFrameNumber TimeMinusOne(TimeToRemove - 1); FMovieSceneConstraintChannelHelper::DeleteTransformKeys(Channels, TimeMinusOne); } } // evaluate the constraint, this is needed so the global transform will be set up on the component //Todo do we need to evaluate all constraints? InConstraint->SetActive(true); //will be false InConstraint->Evaluate(); //need to fire this event so the transform values set by the constraint propragate to the transform section //first turn off autokey though EAutoChangeMode AutoChangeMode = InSequencer->GetAutoChangeMode(); if (AutoChangeMode == EAutoChangeMode::AutoKey || AutoChangeMode == EAutoChangeMode::All) { InSequencer->SetAutoChangeMode(EAutoChangeMode::None); }; AActor* Actor = ComponentHandle->Component->GetOwner(); FProperty* TransformProperty = FindFProperty(USceneComponent::StaticClass(), USceneComponent::GetRelativeLocationPropertyName()); FEditPropertyChain PropertyChain; PropertyChain.AddHead(TransformProperty); FCoreUObjectDelegates::OnPreObjectPropertyChanged.Broadcast(Actor, PropertyChain); FPropertyChangedEvent PropertyChangedEvent(TransformProperty, EPropertyChangeType::ValueSet); FCoreUObjectDelegates::OnObjectPropertyChanged.Broadcast(Actor, PropertyChangedEvent); InSequencer->RequestEvaluate(); if (AutoChangeMode == EAutoChangeMode::AutoKey || AutoChangeMode == EAutoChangeMode::All) { InSequencer->SetAutoChangeMode(AutoChangeMode); } return true; } } return false; } void FComponentConstraintChannelInterface::AddHandleTransformKeys( const TSharedPtr& InSequencer, const UTransformableHandle* InHandle, const TArray& InFrames, const TArray& InTransforms, const EMovieSceneTransformChannel& InChannels) { ensure(InTransforms.Num()); if (!IsValid(InHandle) || !InHandle->IsValid()) { return; } const UTransformableComponentHandle* Handle = static_cast(InHandle); AActor* Actor = Handle->Component->GetOwner(); if (!Actor) { return; } const FGuid Guid = InSequencer->GetHandleToObject(Actor, true); if (!Guid.IsValid()) { return; } UMovieScene3DTransformSection* TransformSection = GetComponentSection(Handle, InSequencer, false); if (!TransformSection) { return; } const UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); const UMovieScene3DTransformSection* ConstraintSection = GetComponentSection(Handle, InSequencer, true); if (ConstraintSection && TransformSection != ConstraintSection) { TArray Transforms(InTransforms); RecomposeTransforms(InSequencer, Handle->Component.Get(), TransformSection, InFrames, Transforms); Handle->AddTransformKeys(InFrames, Transforms, InChannels, MovieScene->GetTickResolution(), TransformSection, true); } else { Handle->AddTransformKeys(InFrames, InTransforms, InChannels, MovieScene->GetTickResolution(), TransformSection, true); } } UMovieScene3DTransformSection* FComponentConstraintChannelInterface::GetComponentSection( const UTransformableComponentHandle* InHandle, const TSharedPtr& InSequencer, const bool bIsConstraint) { if (!IsValid(InHandle) || !InHandle->IsValid()) { return nullptr; } AActor* Actor = InHandle->Component->GetOwner(); if (!Actor) { return nullptr; } const FGuid Guid = InSequencer->GetHandleToObject(Actor, true); if (!Guid.IsValid()) { return nullptr; } const FTransform DefaultTransform = InHandle->GetLocalTransform(); if (!bIsConstraint) { return MovieSceneToolHelpers::GetTransformSection(InSequencer.Get(), Guid, DefaultTransform); } const UMovieSceneSequence* MovieSceneSequence = InSequencer ? InSequencer->GetFocusedMovieSceneSequence() : nullptr; UMovieScene* MovieScene = MovieSceneSequence ? MovieSceneSequence->GetMovieScene() : nullptr; if (!MovieScene) { return nullptr; } UMovieScene3DTransformTrack* TransformTrack = MovieScene->FindTrack(Guid); if (!TransformTrack) { MovieScene->Modify(); TransformTrack = MovieScene->AddTrack(Guid); } TransformTrack->Modify(); static constexpr FFrameNumber Frame0; bool bSectionAdded = false; const TArray& AllSections = TransformTrack->GetAllSections(); UMovieScene3DTransformSection* TransformSection = AllSections.IsEmpty() ? Cast(TransformTrack->FindOrAddSection(Frame0, bSectionAdded)) : Cast(AllSections[0]); if (!TransformSection) { return nullptr; } TransformSection->Modify(); if (bSectionAdded) { TransformSection->SetRange(TRange::All()); const FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy(); const TMovieSceneChannelHandle DoubleChannels[] = { SectionChannelProxy.GetChannelByName("Location.X"), SectionChannelProxy.GetChannelByName("Location.Y"), SectionChannelProxy.GetChannelByName("Location.Z"), SectionChannelProxy.GetChannelByName("Rotation.X"), SectionChannelProxy.GetChannelByName("Rotation.Y"), SectionChannelProxy.GetChannelByName("Rotation.Z"), SectionChannelProxy.GetChannelByName("Scale.X"), SectionChannelProxy.GetChannelByName("Scale.Y"), SectionChannelProxy.GetChannelByName("Scale.Z") }; const FVector Location0 = DefaultTransform.GetLocation(); const FRotator Rotation0 = DefaultTransform.GetRotation().Rotator(); const FVector Scale3D0 = DefaultTransform.GetScale3D(); const FTransform::FReal DefaultValues[] = { Location0.X, Location0.Y, Location0.Z, Rotation0.Roll, Rotation0.Pitch, Rotation0.Yaw, Scale3D0.X, Scale3D0.Y, Scale3D0.Z }; for (int32 Index = 0; Index < 9; Index++) { if (FMovieSceneDoubleChannel* Channel = DoubleChannels[Index].Get()) { Channel->SetDefault(DefaultValues[Index]); } } } return TransformSection; } void FComponentConstraintChannelInterface::RecomposeTransforms( const TSharedPtr& InSequencer, USceneComponent* SceneComponent, UMovieSceneSection* Section, const TArray& InFrames, TArray& InOutTransforms) const { using namespace UE::MovieScene; FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = InSequencer->GetEvaluationTemplate(); UMovieSceneEntitySystemLinker* EntityLinker = EvaluationTemplate.GetEntitySystemLinker(); if (!EntityLinker) { return; } UMovieScenePropertyInstantiatorSystem* System = EntityLinker->FindSystem(); if (!System) { return; } TGuardValue DebugVizGuard(GEntityManagerForDebuggingVisualizers, &EntityLinker->EntityManager); TArray ImportedEntityIDs; EvaluationTemplate.FindEntitiesFromOwner(Section, InSequencer->GetFocusedTemplateID(), ImportedEntityIDs); if (ImportedEntityIDs.Num()) { const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); FMovieSceneTracksComponentTypes* TrackComponents = FMovieSceneTracksComponentTypes::Get(); UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); const FFrameRate TickResolution = MovieScene->GetTickResolution(); const EMovieScenePlayerStatus::Type PlaybackStatus = InSequencer->GetPlaybackStatus(); TArray EntityIDs; FDecompositionQuery Query; Query.Object = SceneComponent; Query.bConvertFromSourceEntityIDs = false; // We already pass the children entity IDs FMovieSceneSequenceTransform RootToLocalTransform = InSequencer->GetFocusedMovieSceneSequenceTransform(); FMovieSceneInverseSequenceTransform LocalToRootTransform = RootToLocalTransform.Inverse(); // add keys for (int32 Index = 0; Index < InFrames.Num(); ++Index) { TOptional RootTime = LocalToRootTransform.TryTransformTime(InFrames[Index]); if (!RootTime) { continue; } const FMovieSceneEvaluationRange EvaluationRange = FMovieSceneEvaluationRange(RootTime.GetValue(), TickResolution); const FMovieSceneContext Context = FMovieSceneContext(EvaluationRange, PlaybackStatus).SetHasJumped(true); EvaluationTemplate.EvaluateSynchronousBlocking(Context); if (EntityIDs.IsEmpty()) { // In order to check for the result channels later, we need to look up the children entities that are // bound to the given animated object. Imported entities generally don't have the result channels. FEntityTaskBuilder() .ReadEntityIDs() .Read(BuiltInComponents->ParentEntity) .Read(BuiltInComponents->BoundObject) .FilterAll({ TrackComponents->ComponentTransform.PropertyTag }) .Iterate_PerEntity( &EntityLinker->EntityManager, [SceneComponent, ImportedEntityIDs, &EntityIDs](FMovieSceneEntityID EntityID, FMovieSceneEntityID ParentEntityID, UObject* BoundObject) { if (SceneComponent == BoundObject && ImportedEntityIDs.Contains(ParentEntityID)) { EntityIDs.Add(EntityID); } }); Query.Entities = MakeArrayView(EntityIDs); } FTransform& Transform = InOutTransforms[Index]; const FIntermediate3DTransform CurrentValue(Transform.GetTranslation(), Transform.GetRotation().Rotator(), Transform.GetScale3D()); TRecompositionResult TransformData = System->RecomposeBlendOperational(TrackComponents->ComponentTransform, Query, CurrentValue); double CurrentTransformChannels[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; EMovieSceneTransformChannel ChannelsObtained(EMovieSceneTransformChannel::None); check(EntityIDs.Num() == TransformData.Values.Num()); for (int32 EntityIndex = 0; EntityIndex < TransformData.Values.Num(); ++EntityIndex) { const FMovieSceneEntityID& EntityID = EntityIDs[EntityIndex]; const FIntermediate3DTransform& EntityTransformData = TransformData.Values[EntityIndex]; FComponentMask EntityType = EntityLinker->EntityManager.GetEntityType(EntityID); for (int32 CompositeIndex = 0; CompositeIndex < 9; ++CompositeIndex) { EMovieSceneTransformChannel ChannelMask = (EMovieSceneTransformChannel)(1 << CompositeIndex); if (!EnumHasAnyFlags(ChannelsObtained, ChannelMask) && EntityType.Contains(BuiltInComponents->DoubleResult[CompositeIndex])) { EnumAddFlags(ChannelsObtained, (EMovieSceneTransformChannel)(1 << CompositeIndex)); CurrentTransformChannels[CompositeIndex] = EntityTransformData[CompositeIndex]; } } } Transform = FTransform( FRotator(CurrentTransformChannels[4], CurrentTransformChannels[5], CurrentTransformChannels[3]), // pitch yaw roll FVector(CurrentTransformChannels[0], CurrentTransformChannels[1], CurrentTransformChannels[2]), FVector(CurrentTransformChannels[6], CurrentTransformChannels[7], CurrentTransformChannels[8])); } } } void FComponentConstraintChannelInterface::UnregisterTrack(UMovieSceneTrack* InTrack, UWorld* InWorld) { ITransformConstraintChannelInterface::UnregisterTrack(InTrack, InWorld); UMovieScene3DTransformTrack* TransformTrack = Cast(InTrack); if (!TransformTrack) { return; } const TArray& AllSections = TransformTrack->GetAllSections(); UMovieScene3DTransformSection* Section = AllSections.IsEmpty() ? nullptr : Cast(AllSections[0]); if (!Section) { return; } UnregisterConstraints(Section, InWorld); }