Files
2025-05-18 13:04:45 +08:00

413 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryCollectionTrackEditor.h"
#include "Rendering/DrawElements.h"
#include "Widgets/SBoxPanel.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "GameFramework/Actor.h"
#include "Modules/ModuleManager.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBox.h"
#include "SequencerSectionPainter.h"
#include "Editor/UnrealEdEngine.h"
#include "UnrealEdGlobals.h"
#include "MovieSceneGeometryCollectionTrack.h"
#include "MovieSceneGeometryCollectionSection.h"
#include "ContentBrowserModule.h"
#include "MVVM/Views/ViewUtilities.h"
#include "ISectionLayoutBuilder.h"
#include "Styling/AppStyle.h"
#include "MovieSceneTimeHelpers.h"
#include "Fonts/FontMeasure.h"
#include "SequencerTimeSliderController.h"
#include "Misc/MessageDialog.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "GeometryCollection/GeometryCollectionComponent.h"
#include "Styling/SlateIconFinder.h"
#include "LevelSequence.h"
#include "TimeToPixel.h"
namespace GeometryCollectionEditorConstants
{
// @todo Sequencer Allow this to be customizable
const uint32 AnimationTrackHeight = 28;
}
#define LOCTEXT_NAMESPACE "FGeometryCollectionTrackEditor"
static UGeometryCollectionComponent* AcquireGeometryCollectionFromObjectGuid(const FGuid& Guid, TSharedPtr<ISequencer> SequencerPtr)
{
UObject* BoundObject = SequencerPtr.IsValid() ? SequencerPtr->FindSpawnedObjectOrTemplate(Guid) : nullptr;
if (AActor* Actor = Cast<AActor>(BoundObject))
{
for (UActorComponent* Component : Actor->GetComponents())
{
if (UGeometryCollectionComponent* GeometryMeshComp = Cast<UGeometryCollectionComponent>(Component))
{
return GeometryMeshComp;
}
}
}
else if(UGeometryCollectionComponent* GeometryMeshComp = Cast<UGeometryCollectionComponent>(BoundObject))
{
return GeometryMeshComp;
}
return nullptr;
}
FGeometryCollectionTrackSection::FGeometryCollectionTrackSection( UMovieSceneSection& InSection, TWeakPtr<ISequencer> InSequencer)
: Section(*CastChecked<UMovieSceneGeometryCollectionSection>(&InSection))
, Sequencer(InSequencer)
, InitialStartOffsetDuringResize(0)
, InitialStartTimeDuringResize(0)
{ }
UMovieSceneSection* FGeometryCollectionTrackSection::GetSectionObject()
{
return &Section;
}
FText FGeometryCollectionTrackSection::GetSectionTitle() const
{
if (Section.Params.GeometryCollectionCache.ResolveObject() != nullptr )
{
UGeometryCollectionCache* GeometryCollectionCache = Cast<UGeometryCollectionCache>(Section.Params.GeometryCollectionCache.ResolveObject());
if (GeometryCollectionCache)
{
return FText::FromString(GeometryCollectionCache->GetName());
}
}
return LOCTEXT("NoGeometryCollectionSection", "No GeometryCollection");
}
float FGeometryCollectionTrackSection::GetSectionHeight() const
{
return (float)GeometryCollectionEditorConstants::AnimationTrackHeight;
}
int32 FGeometryCollectionTrackSection::OnPaintSection( FSequencerSectionPainter& Painter ) const
{
using namespace UE::Sequencer;
const ESlateDrawEffect DrawEffects = Painter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
const FTimeToPixel& TimeToPixelConverter = Painter.GetTimeConverter();
int32 LayerId = Painter.PaintSectionBackground();
static const FSlateBrush* GenericDivider = FAppStyle::GetBrush("Sequencer.GenericDivider");
if (!Section.HasStartFrame() || !Section.HasEndFrame())
{
return LayerId;
}
FFrameRate TickResolution = TimeToPixelConverter.GetTickResolution();
// Add lines where the animation starts and ends/loops
float AnimPlayRate = FMath::IsNearlyZero(Section.Params.PlayRate) ? 1.0f : Section.Params.PlayRate;
float SeqLength = Section.Params.GetSequenceLength() - TickResolution.AsSeconds(Section.Params.StartFrameOffset + Section.Params.EndFrameOffset) / AnimPlayRate;
if (!FMath::IsNearlyZero(SeqLength, KINDA_SMALL_NUMBER) && SeqLength > 0)
{
float MaxOffset = Section.GetRange().Size<FFrameTime>() / TickResolution;
float OffsetTime = SeqLength;
float StartTime = Section.GetInclusiveStartFrame() / TickResolution;
while (OffsetTime < MaxOffset)
{
float OffsetPixel = TimeToPixelConverter.SecondsToPixel(StartTime + OffsetTime) - TimeToPixelConverter.SecondsToPixel(StartTime);
FSlateDrawElement::MakeBox(
Painter.DrawElements,
LayerId,
Painter.SectionGeometry.MakeChild(
FVector2D(2.f, Painter.SectionGeometry.Size.Y-2.f),
FSlateLayoutTransform(FVector2D(OffsetPixel, 1.f))
).ToPaintGeometry(),
GenericDivider,
DrawEffects
);
OffsetTime += SeqLength;
}
}
TSharedPtr<ISequencer> SequencerPtr = Sequencer.Pin();
if (Painter.bIsSelected && SequencerPtr.IsValid())
{
FFrameTime CurrentTime = SequencerPtr->GetLocalTime().Time;
if (Section.GetRange().Contains(CurrentTime.FrameNumber) && Section.Params.GeometryCollectionCache.ResolveObject() != nullptr)
{
const float Time = TimeToPixelConverter.FrameToPixel(CurrentTime);
UGeometryCollectionComponent* GeometryCollection = Cast<UGeometryCollectionComponent>(Section.Params.GeometryCollectionCache.ResolveObject());
// Draw the current time next to the scrub handle
const float AnimTime = Section.MapTimeToAnimation(CurrentTime, TickResolution);
int32 FrameTime = 0; // GeometryCollection->GetFrameAtTime(AnimTime);
FString FrameString = FString::FromInt(FrameTime);
const FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Bold", 10);
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont);
// Flip the text position if getting near the end of the view range
static const float TextOffsetPx = 10.f;
bool bDrawLeft = (Painter.SectionGeometry.Size.X - Time) < (TextSize.X + 22.f) - TextOffsetPx;
float TextPosition = bDrawLeft ? Time - TextSize.X - TextOffsetPx : Time + TextOffsetPx;
//handle mirrored labels
const float MajorTickHeight = 9.0f;
FVector2D TextOffset(TextPosition, Painter.SectionGeometry.Size.Y - (MajorTickHeight + TextSize.Y));
const FLinearColor DrawColor = FAppStyle::GetSlateColor("SelectionColor").GetColor(FWidgetStyle());
const FVector2D BoxPadding = FVector2D(4.0f, 2.0f);
// draw time string
FSlateDrawElement::MakeBox(
Painter.DrawElements,
LayerId + 5,
Painter.SectionGeometry.ToPaintGeometry(TextSize + 2.0f * BoxPadding, FSlateLayoutTransform(TextOffset - BoxPadding)),
FAppStyle::GetBrush("WhiteBrush"),
ESlateDrawEffect::None,
FLinearColor::Black.CopyWithNewOpacity(0.5f)
);
FSlateDrawElement::MakeText(
Painter.DrawElements,
LayerId + 6,
Painter.SectionGeometry.ToPaintGeometry(TextSize, FSlateLayoutTransform(TextOffset)),
FrameString,
SmallLayoutFont,
DrawEffects,
DrawColor
);
}
}
return LayerId;
}
void FGeometryCollectionTrackSection::BeginResizeSection()
{
InitialStartOffsetDuringResize = Section.Params.StartFrameOffset;
InitialStartTimeDuringResize = Section.HasStartFrame() ? Section.GetInclusiveStartFrame() : 0;
}
void FGeometryCollectionTrackSection::ResizeSection(ESequencerSectionResizeMode ResizeMode, FFrameNumber ResizeTime)
{
// Adjust the start offset when resizing from the beginning
if (ResizeMode == SSRM_LeadingEdge)
{
FFrameRate FrameRate = Section.GetTypedOuter<UMovieScene>()->GetTickResolution();
FFrameNumber StartOffset = FrameRate.AsFrameNumber((ResizeTime - InitialStartTimeDuringResize) / FrameRate * Section.Params.PlayRate);
StartOffset += InitialStartOffsetDuringResize;
// Ensure start offset is not less than 0 and adjust ResizeTime
if (StartOffset < 0)
{
ResizeTime = ResizeTime - StartOffset;
StartOffset = FFrameNumber(0);
}
Section.Params.StartFrameOffset = StartOffset;
}
ISequencerSection::ResizeSection(ResizeMode, ResizeTime);
}
void FGeometryCollectionTrackSection::BeginSlipSection()
{
BeginResizeSection();
}
void FGeometryCollectionTrackSection::SlipSection(FFrameNumber SlipTime)
{
FFrameRate FrameRate = Section.GetTypedOuter<UMovieScene>()->GetTickResolution();
FFrameNumber StartOffset = FrameRate.AsFrameNumber((SlipTime - InitialStartTimeDuringResize) / FrameRate * Section.Params.PlayRate);
StartOffset += InitialStartOffsetDuringResize;
// Ensure start offset is not less than 0 and adjust ResizeTime
if (StartOffset < 0)
{
SlipTime = SlipTime - StartOffset;
StartOffset = FFrameNumber(0);
}
Section.Params.StartFrameOffset = StartOffset;
ISequencerSection::SlipSection(SlipTime);
}
void FGeometryCollectionTrackSection::BeginDilateSection()
{
Section.PreviousPlayRate = Section.Params.PlayRate; //make sure to cache the play rate
}
void FGeometryCollectionTrackSection::DilateSection(const TRange<FFrameNumber>& NewRange, float DilationFactor)
{
Section.Params.PlayRate = Section.PreviousPlayRate / DilationFactor;
Section.SetRange(NewRange);
}
FGeometryCollectionTrackEditor::FGeometryCollectionTrackEditor( TSharedRef<ISequencer> InSequencer )
: FMovieSceneTrackEditor( InSequencer )
{ }
TSharedRef<ISequencerTrackEditor> FGeometryCollectionTrackEditor::CreateTrackEditor( TSharedRef<ISequencer> InSequencer )
{
return MakeShareable( new FGeometryCollectionTrackEditor( InSequencer ) );
}
bool FGeometryCollectionTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const
{
ETrackSupport TrackSupported = InSequence ? InSequence->IsTrackSupported(UMovieSceneGeometryCollectionTrack::StaticClass()) : ETrackSupport::Default;
if (TrackSupported == ETrackSupport::NotSupported)
{
return false;
}
return (InSequence && InSequence->IsA(ULevelSequence::StaticClass())) || TrackSupported == ETrackSupport::Supported;
}
bool FGeometryCollectionTrackEditor::SupportsType( TSubclassOf<UMovieSceneTrack> Type ) const
{
return Type == UMovieSceneGeometryCollectionTrack::StaticClass();
}
TSharedRef<ISequencerSection> FGeometryCollectionTrackEditor::MakeSectionInterface( UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding )
{
check( SupportsType( SectionObject.GetOuter()->GetClass() ) );
return MakeShareable( new FGeometryCollectionTrackSection(SectionObject, GetSequencer()) );
}
FText FGeometryCollectionTrackEditor::GetDisplayName() const
{
return LOCTEXT("GeometryCollectionTrackEditor_DisplayName", "Geometry Collection");
}
void FGeometryCollectionTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray<FGuid>& ObjectBindings, const UClass* ObjectClass)
{
if (ObjectClass->IsChildOf(UGeometryCollectionComponent::StaticClass()))
{
for (const FGuid& ObjectBinding : ObjectBindings)
{
UGeometryCollectionComponent* GeomMeshComp = AcquireGeometryCollectionFromObjectGuid(ObjectBinding, GetSequencer());
if (GeomMeshComp)
{
UMovieSceneTrack* Track = nullptr;
MenuBuilder.AddMenuEntry(
NSLOCTEXT("Sequencer", "AddGeometryCollection", "Geometry Collection"),
NSLOCTEXT("Sequencer", "AddGeometryCollectionTooltip", "Adds a Geometry Collection track."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &FGeometryCollectionTrackEditor::BuildGeometryCollectionTrack, ObjectBinding, GeomMeshComp, Track)
)
);
}
}
}
}
void FGeometryCollectionTrackEditor::BuildGeometryCollectionTrack(FGuid ObjectBinding, UGeometryCollectionComponent *GeometryCollectionComponent, UMovieSceneTrack* Track)
{
TSharedPtr<ISequencer> SequencerPtr = GetSequencer();
if (SequencerPtr.IsValid() && ObjectBinding.IsValid())
{
UObject* Object = SequencerPtr->FindSpawnedObjectOrTemplate(ObjectBinding);
if (Object)
{
const FScopedTransaction Transaction(LOCTEXT("AddGeometryCollection_Transaction", "Add Geometry Collection"));
AnimatablePropertyChanged(FOnKeyProperty::CreateRaw(this, &FGeometryCollectionTrackEditor::AddKeyInternal, Object, GeometryCollectionComponent, Track));
}
}
}
FKeyPropertyResult FGeometryCollectionTrackEditor::AddKeyInternal( FFrameNumber KeyTime, UObject* Object, UGeometryCollectionComponent* GeometryCollectionComponent, UMovieSceneTrack* Track)
{
FKeyPropertyResult KeyPropertyResult;
FFindOrCreateHandleResult HandleResult = FindOrCreateHandleToObject( Object );
FGuid ObjectHandle = HandleResult.Handle;
KeyPropertyResult.bHandleCreated |= HandleResult.bWasCreated;
if (ObjectHandle.IsValid())
{
if (!Track)
{
Track = AddTrack(GetSequencer()->GetFocusedMovieSceneSequence()->GetMovieScene(), ObjectHandle, UMovieSceneGeometryCollectionTrack::StaticClass(), NAME_None);
KeyPropertyResult.bTrackCreated = true;
}
if (ensure(Track))
{
Track->Modify();
UMovieSceneSection* NewSection = Cast<UMovieSceneGeometryCollectionTrack>(Track)->AddNewAnimation( KeyTime, GeometryCollectionComponent );
KeyPropertyResult.bTrackModified = true;
KeyPropertyResult.SectionsCreated.Add(NewSection);
GetSequencer()->EmptySelection();
GetSequencer()->SelectSection(NewSection);
GetSequencer()->ThrobSectionSelection();
}
}
return KeyPropertyResult;
}
TSharedPtr<SWidget> FGeometryCollectionTrackEditor::BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params)
{
UGeometryCollectionComponent* GeomMeshComp = AcquireGeometryCollectionFromObjectGuid(ObjectBinding, GetSequencer());
if (GeomMeshComp)
{
TWeakPtr<ISequencer> WeakSequencer = GetSequencer();
auto SubMenuCallback = [this, ObjectBinding, Track, GeomMeshComp]() -> TSharedRef<SWidget>
{
FMenuBuilder MenuBuilder(true, nullptr);
BuildGeometryCollectionTrack(ObjectBinding, GeomMeshComp, Track);
return MenuBuilder.MakeWidget();
};
return UE::Sequencer::MakeAddButton(LOCTEXT("GeomCollectionText", "Geometry Collection"), FOnGetContent::CreateLambda(SubMenuCallback), Params.ViewModel);
}
else
{
return TSharedPtr<SWidget>();
}
}
const FSlateBrush* FGeometryCollectionTrackEditor::GetIconBrush() const
{
return FSlateIconFinder::FindIconForClass(UGeometryCollectionComponent::StaticClass()).GetIcon();
}
#undef LOCTEXT_NAMESPACE