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

541 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SubTrackEditorMode.h"
#include "EditorModeManager.h"
#include "Engine/Selection.h"
#include "EngineUtils.h"
#include "UnrealClient.h"
#include "EditorViewportClient.h"
#include "IMovieScenePlaybackClient.h"
#include "LevelSequence.h"
#include "MovieScene.h"
#include "SequencerChannelTraits.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "EntitySystem/BuiltInComponentTypes.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include "MovieSceneSection.h"
#include "Sections/MovieSceneSubSection.h"
#include "Systems/MovieSceneTransformOriginSystem.h"
#include "Tracks/IMovieSceneTransformOrigin.h"
#include "Tracks/MovieSceneSubTrack.h"
FName FSubTrackEditorMode::ModeName("EditMode.SubTrackEditMode");
FSubTrackEditorMode::FSubTrackEditorMode()
{
CachedLocation.Reset();
PreviewCoordinateSpaceRotation.Reset();
PreviewLocation.Reset();
}
FSubTrackEditorMode::~FSubTrackEditorMode()
{
}
void FSubTrackEditorMode::Initialize()
{
CachedLocation.Reset();
PreviewCoordinateSpaceRotation.Reset();
PreviewLocation.Reset();
}
bool FSubTrackEditorMode::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale)
{
if(!bIsTracking || AreAnyActorsSelected() || (InDrag.IsNearlyZero() && InRot.IsNearlyZero()))
{
return false;
}
const bool bCtrlDown = InViewport->KeyState(EKeys::LeftControl) || InViewport->KeyState(EKeys::RightControl);
const bool bShiftDown = InViewport->KeyState(EKeys::LeftShift) || InViewport->KeyState(EKeys::RightShift);
const bool bAltDown = InViewport->KeyState(EKeys::LeftAlt) || InViewport->KeyState(EKeys::RightAlt);
const bool bMouseButtonDown = InViewport->KeyState(EKeys::LeftMouseButton);
const bool bAnyModifiers = bAltDown || bCtrlDown || bShiftDown;
const EAxisList::Type CurrentAxis = InViewportClient->GetCurrentWidgetAxis();
if(bMouseButtonDown && !bAnyModifiers && CurrentAxis != EAxisList::None)
{
const FTransform TransformOriginFocusedSequence = GetTransformOriginForSequence(GetFocusedSequenceID());
// Remove parent transform from inputs.
const FTransform LocalRotation = TransformOriginFocusedSequence * FTransform(InRot) * TransformOriginFocusedSequence.Inverse();
const FTransform LocalPosition = TransformOriginFocusedSequence * FTransform(InDrag) * TransformOriginFocusedSequence.Inverse();
// Keep preview space up-to-date.
PreviewCoordinateSpaceRotation.GetValue() *= FTransform(InRot).ToMatrixNoScale().RemoveTranslation();
PreviewLocation.GetValue() += InDrag;
OnOriginValueChanged.Broadcast(LocalPosition.GetLocation(), LocalRotation.Rotator());
return true;
}
return false;
}
bool FSubTrackEditorMode::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport)
{
if (HandleBeginTransform())
{
return true;
}
return FEdMode::StartTracking(InViewportClient, InViewport);
}
bool FSubTrackEditorMode::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport)
{
if (HandleEndTransform())
{
return true;
}
return FEdMode::EndTracking(InViewportClient, InViewport);
}
bool FSubTrackEditorMode::BeginTransform(const FGizmoState& InState)
{
return HandleBeginTransform();
}
bool FSubTrackEditorMode::EndTransform(const FGizmoState& InState)
{
return HandleEndTransform();
}
bool FSubTrackEditorMode::HandleBeginTransform()
{
if (const UMovieSceneSubSection* SubSection = GetSectionToEdit())
{
bIsTracking = true;
PreviewCoordinateSpaceRotation = GetFinalTransformOriginForSubSection(SubSection).ToMatrixNoScale().RemoveTranslation();
if (!PreviewLocation.IsSet())
{
PreviewLocation = GetAverageLocationOfBindingsInSubSection(SubSection);
}
return true;
}
return false;
}
bool FSubTrackEditorMode::HandleEndTransform()
{
if (bIsTracking)
{
bIsTracking = false;
// Only reset the preview rotation. Resetting the preview location here could interfere with multiple rotation edits in a row.
// The location would otherwise change after the user let go of the mouse, and would have to move it to the new location to continue rotating.
PreviewCoordinateSpaceRotation.Reset();
return true;
}
return false;
}
bool FSubTrackEditorMode::UsesTransformWidget() const
{
const UMovieSceneSubSection* SubSection = GetSelectedSection();
if(!AreAnyActorsSelected() && SubSection)
{
return DoesSubSectionHaveTransformOverrides(*SubSection);
}
return FEdMode::UsesTransformWidget();
}
bool FSubTrackEditorMode::UsesTransformWidget(UE::Widget::EWidgetMode CheckMode) const
{
const UMovieSceneSubSection* SubSection = GetSelectedSection();
if(!AreAnyActorsSelected() && SubSection)
{
return DoesSubSectionHaveTransformOverrides(*SubSection);
}
return FEdMode::UsesTransformWidget(CheckMode);
}
TOptional<FMovieSceneSequenceID> FSubTrackEditorMode::GetSequenceIDForSubSection(const UMovieSceneSubSection* InSubSection) const
{
const TSharedPtr<ISequencer> Sequencer = WeakSequencer.Pin();
if(!Sequencer)
{
return TOptional<FMovieSceneSequenceID>();
}
const FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = Sequencer->GetEvaluationTemplate();
UMovieSceneCompiledDataManager* CompiledDataManager = EvaluationTemplate.GetCompiledDataManager();
UMovieSceneSequence* RootSequence = EvaluationTemplate.GetSequence(Sequencer->GetRootTemplateID());
const FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(RootSequence);
const FMovieSceneSequenceHierarchy& Hierarchy = CompiledDataManager->GetHierarchyChecked(DataID);
UE::MovieScene::FSubSequencePath Path;
const TOptional<FMovieSceneSequenceID> ParentSequenceID = GetFocusedSequenceID();
if(!ParentSequenceID)
{
return TOptional<FMovieSceneSequenceID>();
}
Path.Reset(ParentSequenceID.GetValue(), &Hierarchy);
return Path.ResolveChildSequenceID(InSubSection->GetSequenceID());
}
TOptional<FMovieSceneSequenceID> FSubTrackEditorMode::GetFocusedSequenceID() const
{
const TSharedPtr<ISequencer> Sequencer = WeakSequencer.Pin();
if(!Sequencer)
{
return TOptional<FMovieSceneSequenceID>();
}
return Sequencer->GetFocusedTemplateID();
}
FTransform FSubTrackEditorMode::GetFinalTransformOriginForSubSection(const UMovieSceneSubSection* InSubSection) const
{
const TOptional<FMovieSceneSequenceID> ChildSequenceID = GetSequenceIDForSubSection(InSubSection);
return GetTransformOriginForSequence(ChildSequenceID);
}
FTransform FSubTrackEditorMode::GetTransformOriginForSequence(const TOptional<FMovieSceneSequenceID> InSequenceID) const
{
FTransform TransformOrigin = FTransform::Identity;
const TSharedPtr<ISequencer> Sequencer = WeakSequencer.Pin();
if(!Sequencer)
{
return TransformOrigin;
}
const FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = Sequencer->GetEvaluationTemplate();
const IMovieScenePlaybackClient* Client = Sequencer->GetPlaybackClient();
const UObject* InstanceData = Client ? Client->GetInstanceData() : nullptr;
const IMovieSceneTransformOrigin* RawInterface = Cast<const IMovieSceneTransformOrigin>(InstanceData);
const bool bHasInterface = RawInterface || (InstanceData && InstanceData->GetClass()->ImplementsInterface(UMovieSceneTransformOrigin::StaticClass()));
if (bHasInterface)
{
// Retrieve the current origin
TransformOrigin = RawInterface ? RawInterface->GetTransformOrigin() : IMovieSceneTransformOrigin::Execute_BP_GetTransformOrigin(InstanceData);
}
const UMovieSceneEntitySystemLinker* EntityLinker = EvaluationTemplate.GetEntitySystemLinker();
if(!EntityLinker || !InSequenceID)
{
return TransformOrigin;
}
const UMovieSceneTransformOriginSystem* TransformOriginSystem = EntityLinker->FindSystem<UMovieSceneTransformOriginSystem>();
if(!TransformOriginSystem)
{
return TransformOrigin;
}
const TSparseArray<FTransform>& TransformOrigins = TransformOriginSystem->GetTransformOriginsByInstanceID();
const TMap<FMovieSceneSequenceID, UE::MovieScene::FInstanceHandle> SequenceIDToInstanceHandle = TransformOriginSystem->GetSequenceIDToInstanceHandle();
if(SequenceIDToInstanceHandle.Contains(InSequenceID.GetValue()))
{
const UE::MovieScene::FInstanceHandle CurrentHandle = SequenceIDToInstanceHandle[InSequenceID.GetValue()];
if(TransformOrigins.IsValidIndex(CurrentHandle.InstanceID))
{
TransformOrigin = TransformOrigins[CurrentHandle.InstanceID];
}
}
return TransformOrigin;
}
bool FSubTrackEditorMode::AreAnyActorsSelected() const
{
if (Owner)
{
if (USelection* SelectedActors = Owner->GetSelectedActors())
{
return SelectedActors->Num() > 0;
}
}
return false;
}
FVector FSubTrackEditorMode::GetWidgetLocation() const
{
if(const UMovieSceneSubSection* SubSection = GetSectionToEdit())
{
const FVector NewLocation = PreviewLocation.IsSet() ? PreviewLocation.GetValue() : GetAverageLocationOfBindingsInSubSection(SubSection);
if(!CachedLocation.IsSet() || !NewLocation.Equals(CachedLocation.GetValue()))
{
CachedLocation = NewLocation;
// Invalidate hit proxies, otherwise the hit proxy for the widget can be out of sync, and still at the old widget location
GEditor->RedrawLevelEditingViewports(true);
}
return CachedLocation.GetValue();
}
return FEdMode::GetWidgetLocation();
}
FVector FSubTrackEditorMode::GetAverageLocationOfBindingsInSubSection(const UMovieSceneSubSection* SubSection) const
{
FVector TotalPosition = FVector::ZeroVector;
int32 ActorCount = 0;
const UMovieSceneSequence* CurrentSequence = SubSection->GetSequence();
if (!CurrentSequence)
{
return TotalPosition;
}
const UMovieScene* CurrentMovieScene = CurrentSequence->GetMovieScene();
if (!CurrentMovieScene)
{
return TotalPosition;
}
TArray<FMovieSceneBinding> Bindings = CurrentMovieScene->GetBindings();
const TSharedPtr<ISequencer> Sequencer = WeakSequencer.Pin();
if (!Sequencer)
{
return FVector::ZeroVector;
}
const FMovieSceneSequenceID FocusedSequenceID = GetFocusedSequenceID().GetValue();
const FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = Sequencer->GetEvaluationTemplate();
UMovieSceneCompiledDataManager* CompiledDataManager = EvaluationTemplate.GetCompiledDataManager();
UMovieSceneSequence* RootSequence = Sequencer->GetRootMovieSceneSequence();
const FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(RootSequence);
const FMovieSceneSequenceHierarchy Hierarchy = CompiledDataManager->GetHierarchyChecked(DataID);
RecursiveAccumulateBindingPositions(SubSection, TotalPosition, ActorCount, &Hierarchy, FocusedSequenceID, FocusedSequenceID, Sequencer);
if (ActorCount > 0)
{
return TotalPosition / ActorCount;
}
return FVector::ZeroVector;
}
void FSubTrackEditorMode::RecursiveAccumulateBindingPositions(const UMovieSceneSubSection* SubSection, FVector& AccumulatedLocation,
int32& ActorCount, const FMovieSceneSequenceHierarchy* Hierarchy, const FMovieSceneSequenceID FocusedSequenceID, const FMovieSceneSequenceID ParentSequenceID, const TSharedPtr<ISequencer> Sequencer) const
{
const UMovieSceneSequence* CurrentSequence = SubSection->GetSequence();
if (!CurrentSequence)
{
return;
}
const UMovieScene* CurrentMovieScene = CurrentSequence->GetMovieScene();
if (!CurrentMovieScene)
{
return;
}
TArray<FMovieSceneBinding> Bindings = CurrentMovieScene->GetBindings();
UE::MovieScene::FSubSequencePath Path;
Path.Reset(ParentSequenceID, Hierarchy);
const FMovieSceneSequenceID ResolvedSequenceID = Path.ResolveChildSequenceID(SubSection->GetSequenceID());
for (FMovieSceneBinding& Binding : Bindings)
{
FMovieSceneObjectBindingID RelativeBinding = UE::MovieScene::FRelativeObjectBindingID(FocusedSequenceID, ResolvedSequenceID, Binding.GetObjectGuid(), Hierarchy);
for (TWeakObjectPtr<> Object : RelativeBinding.ResolveBoundObjects(FocusedSequenceID, *Sequencer.Get()))
{
if (TStrongObjectPtr<UObject> StrongObject = Object.Pin())
{
if (const AActor* BoundActor = Cast<AActor>(StrongObject.Get()))
{
AccumulatedLocation += BoundActor->GetActorLocation();
ActorCount++;
}
}
}
}
const TArray<UMovieSceneTrack*> Tracks = CurrentMovieScene->GetTracks();
for (UMovieSceneTrack* Track : Tracks)
{
if (Track)
{
if (const UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
{
for (UMovieSceneSection* Section : SubTrack->GetAllSections())
{
const UMovieSceneSubSection* ChildSubSection = Cast<UMovieSceneSubSection>(Section);
RecursiveAccumulateBindingPositions(ChildSubSection, AccumulatedLocation, ActorCount, Hierarchy, FocusedSequenceID, ResolvedSequenceID, Sequencer);
}
}
}
}
}
bool FSubTrackEditorMode::ShouldDrawWidget() const
{
if (GetSectionToEdit())
{
return true;
}
// If the widget is not being drawn, its hit proxies need to be invalidated the next time it is drawn.
// Resetting the cached location will trigger the invalidation in GetWidgetLocation
CachedLocation.Reset();
return false;
}
bool FSubTrackEditorMode::GetPivotForOrbit(FVector& OutPivot) const
{
return FEdMode::GetPivotForOrbit(OutPivot);
}
bool FSubTrackEditorMode::GetCustomDrawingCoordinateSystem(FMatrix& OutMatrix, void* InData)
{
if (GetModeManager()->GetCoordSystem() != COORD_Local)
{
return false;
}
if (const UMovieSceneSubSection* SubSection = GetSectionToEdit())
{
// While manipulating the gizmo, the preview coordinate space is kept up-to-date directly, since the transform origin data is set by a callback,
// and can be out of date, which would cause the gizmo to jitter.
if (PreviewCoordinateSpaceRotation.IsSet())
{
OutMatrix = PreviewCoordinateSpaceRotation.GetValue().RemoveTranslation();
return true;
}
OutMatrix = GetFinalTransformOriginForSubSection(SubSection).ToMatrixNoScale().RemoveTranslation();
return true;
}
return FEdMode::GetCustomDrawingCoordinateSystem(OutMatrix, InData);
}
bool FSubTrackEditorMode::GetCustomInputCoordinateSystem(FMatrix& OutMatrix, void* InData)
{
return FEdMode::GetCustomInputCoordinateSystem(OutMatrix, InData);
}
bool FSubTrackEditorMode::IsCompatibleWith(FEditorModeID OtherModeID) const
{
if(IncompatibleEditorModes.Contains(OtherModeID))
{
return false;
}
return true;
}
void FSubTrackEditorMode::ClearCachedCoordinates()
{
PreviewLocation.Reset();
CachedLocation.Reset();
PreviewCoordinateSpaceRotation.Reset();
}
bool FSubTrackEditorMode::DoesSubSectionHaveTransformOverrides(const UMovieSceneSubSection& SubSection)
{
if (SubSection.IsActive())
{
const EMovieSceneTransformChannel SectionTransformChannels = SubSection.GetMask().GetChannels();
return EnumHasAnyFlags(SectionTransformChannels, EMovieSceneTransformChannel::Translation) || EnumHasAnyFlags(SectionTransformChannels, EMovieSceneTransformChannel::Rotation);
}
return false;
}
UMovieSceneSubSection* FSubTrackEditorMode::GetSelectedSection() const
{
const TSharedPtr<ISequencer> PinnedSequencer = WeakSequencer.Pin();
UMovieSceneSubSection* SelectedSection = nullptr;
if(!PinnedSequencer.IsValid())
{
return SelectedSection;
}
TArray<UMovieSceneSection*> SelectedSections;
PinnedSequencer->GetSelectedSections(SelectedSections);
for(UMovieSceneSection* Section : SelectedSections)
{
if(UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(Section))
{
// Mirror behavior when multiple actors are selected in the level editor, and pick the last selected item that can still be edited.
if(SubSection->IsTransformOriginEditable())
{
SelectedSection = SubSection;
}
}
}
if(SelectedSection)
{
return SelectedSection;
}
TArray<UMovieSceneTrack*> SelectedTracks;
PinnedSequencer->GetSelectedTracks(SelectedTracks);
for(UMovieSceneTrack* Track : SelectedTracks)
{
// Similarly to section selection, pick the last selected track.
if(const UMovieSceneSubTrack* SubTrack = Cast<UMovieSceneSubTrack>(Track))
{
if(SubTrack->GetSectionToKey())
{
UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(SubTrack->GetSectionToKey());
if(SubSection && SubSection->IsTransformOriginEditable())
{
SelectedSection = SubSection;
}
}
else if(SubTrack->GetAllSections().Num())
{
// Since the first section is the section that will be keyed by default, select the first section from the track.
for(UMovieSceneSection* Section : SubTrack->FindAllSections(PinnedSequencer.Get()->GetLocalTime().Time.FrameNumber))
{
if(UMovieSceneSubSection* SubSection = Cast<UMovieSceneSubSection>(Section))
{
if(SubSection->IsTransformOriginEditable())
{
SelectedSection = SubSection;
break;
}
}
}
}
}
}
return SelectedSection;
}
UMovieSceneSubSection* FSubTrackEditorMode::GetSectionToEdit() const
{
UMovieSceneSubSection* SubSectionToReturn = GetSelectedSection();
if (!AreAnyActorsSelected() && SubSectionToReturn && DoesSubSectionHaveTransformOverrides(*SubSectionToReturn))
{
return SubSectionToReturn;
}
return nullptr;
}